This commit is contained in:
Shauren
2016-02-08 20:57:52 +01:00
parent f5ccb7b474
commit 43642630c7
36 changed files with 5247 additions and 2802 deletions

View File

@@ -1,7 +1,7 @@
set(HEADER_FILES
src/CascCommon.h
src/CascLib.h
src/CascMndxRoot.h
src/CascMndx.h
src/CascPort.h
src/common/Common.h
src/common/FileStream.h
@@ -13,19 +13,26 @@ set(HEADER_FILES
set(SRC_FILES
src/common/Common.cpp
src/common/Directory.cpp
src/common/DumpContext.cpp
src/common/DynamicArray.cpp
src/common/FileStream.cpp
src/common/ListFile.cpp
src/common/Map.cpp
src/common/RootHandler.cpp
src/jenkins/lookup3.c
src/CascBuildCfg.cpp
src/CascCommon.cpp
src/CascDecompress.cpp
src/CascDecrypt.cpp
src/CascDumpData.cpp
src/CascFiles.cpp
src/CascFindFile.cpp
src/CascMndxRoot.cpp
src/CascOpenFile.cpp
src/CascOpenStorage.cpp
src/CascReadFile.cpp
src/CascRootFile_Diablo3.cpp
src/CascRootFile_Mndx.cpp
src/CascRootFile_Ovr.cpp
src/CascRootFile_WoW6.cpp
)
set(TOMCRYPT_FILES

File diff suppressed because it is too large Load Diff

View File

@@ -65,3 +65,26 @@ ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes)
return Value;
}
void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes)
{
ValueAsBytes[0] = (Value >> 0x18) & 0xFF;
ValueAsBytes[1] = (Value >> 0x10) & 0xFF;
ValueAsBytes[2] = (Value >> 0x08) & 0xFF;
ValueAsBytes[3] = (Value >> 0x00) & 0xFF;
}
//-----------------------------------------------------------------------------
// Common fre routine of a CASC blob
void FreeCascBlob(PQUERY_KEY pBlob)
{
if(pBlob != NULL)
{
if(pBlob->pbData != NULL)
CASC_FREE(pBlob->pbData);
pBlob->pbData = NULL;
pBlob->cbData = 0;
}
}

View File

@@ -23,9 +23,13 @@
#include "CascPort.h"
#include "common/Common.h"
#include "common/DynamicArray.h"
#include "common/Map.h"
#include "common/FileStream.h"
#include "common/Directory.h"
#include "common/ListFile.h"
#include "common/DumpContext.h"
#include "common/RootHandler.h"
// Headers from LibTomCrypt
#include "libtomcrypt/src/headers/tomcrypt.h"
@@ -36,16 +40,17 @@
//-----------------------------------------------------------------------------
// CascLib private defines
#define CASC_GAME_HOTS 0x00010000 // Heroes of the Storm
#define CASC_GAME_WOW6 0x00020000 // World of Warcraft - Warlords of Draenor
#define CASC_GAME_DIABLO3 0x00030000 // Diablo 3 Since PTR 2.2.0
#define CASC_GAME_MASK 0xFFFF0000 // Mask for getting game ID
#define CASC_GAME_HOTS 0x00010000 // Heroes of the Storm
#define CASC_GAME_WOW6 0x00020000 // World of Warcraft - Warlords of Draenor
#define CASC_GAME_DIABLO3 0x00030000 // Diablo 3 since PTR 2.2.0
#define CASC_GAME_OVERWATCH 0x00040000 // Overwatch since PTR 24919
#define CASC_GAME_STARCRAFT2 0x00050000 // Starcraft II - Legacy of the Void, since build 38996
#define CASC_GAME_MASK 0xFFFF0000 // Mask for getting game ID
#define CASC_INDEX_COUNT 0x10
#define CASC_FILE_KEY_SIZE 0x09 // Size of the file key
#define CASC_MAX_DATA_FILES 0x100
#define CASC_MAX_MAR_FILES 3 // Maximum of 3 MAR files are supported
#define CASC_MNDX_SIGNATURE 0x58444E4D // 'MNDX'
#define CASC_EXTRA_FILES 0x20 // Number of extra entries to be reserved for additionally inserted files
#define CASC_SEARCH_HAVE_NAME 0x0001 // Indicated that previous search found a name
@@ -71,41 +76,28 @@
#define CASC_PACKAGE_BUFFER 0x1000
//-----------------------------------------------------------------------------
// 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;
#ifndef _maxchars
#define _maxchars(buff) ((sizeof(buff) / sizeof(buff[0])) - 1)
#endif
//-----------------------------------------------------------------------------
// In-memory structures
// See http://pxr.dk/wowdev/wiki/index.php?title=CASC for more information
class TMndxFindResult;
struct TFileStream;
struct _MAR_FILE;
typedef enum _CBLD_TYPE
{
CascBuildNone = 0, // No build type found
CascBuildInfo, // .build.info
CascBuildDb, // .build.db (older storages)
} CBLD_TYPE, *PCBLD_TYPE;
typedef struct _ENCODING_KEY
{
BYTE Value[MD5_HASH_SIZE]; // MD5 of the file
} ENCODING_KEY, *PENCODING_KEY;
typedef struct _CASC_INDEX_ENTRY
{
@@ -140,25 +132,34 @@ typedef struct _CASC_FILE_FRAME
BYTE md5[MD5_HASH_SIZE]; // MD5 hash of the file sector
} CASC_FILE_FRAME, *PCASC_FILE_FRAME;
// The encoding file is in the form of:
// * File header
// * String block #1
// * Table A header
// * Table A entries
// * Table B header
// * Table B entries
// * String block #2
// http://pxr.dk/wowdev/wiki/index.php?title=CASC#Key_CASC_Files
typedef struct _CASC_ENCODING_HEADER
{
BYTE Magic[2]; // "EN"
BYTE field_2;
BYTE field_3;
BYTE field_4;
BYTE field_5[2];
BYTE field_7[2];
BYTE NumSegments[4]; // Number of entries (big endian)
BYTE field_D[4];
BYTE Version; // Expected to be 1 by CascLib
BYTE ChecksumSizeA; // The length of the checksums in Encoding Table
BYTE ChecksumSizeB; // The length of the checksums in Encoding Layout Table
BYTE Flags_TableA[2]; // Flags for Encoding Table
BYTE Flags_TableB[2]; // Flags for Encoding Layout Table
BYTE Entries_TableA[4]; // Number of segments in Encoding Table (big endian)
BYTE Entries_TableB[4]; // Number of segments in Encoding Layout Table (big endian)
BYTE field_11;
BYTE SegmentsPos[4]; // Offset of encoding segments
BYTE Size_StringTable1[4]; // Size of the string block #1
} CASC_ENCODING_HEADER, *PCASC_ENCODING_HEADER;
typedef struct _CASC_ENCODING_ENTRY
{
USHORT KeyCount; // Number of subitems
BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
USHORT KeyCount; // Number of index keys
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
@@ -166,87 +167,21 @@ typedef struct _CASC_ENCODING_ENTRY
// Followed by the index keys (number of items = KeyCount)
} CASC_ENCODING_ENTRY, *PCASC_ENCODING_ENTRY;
typedef struct _CASC_ROOT_LOCALE_BLOCK
// A version of CASC_ENCODING_ENTRY with one index key
typedef struct _CASC_ENCODING_ENTRY_1
{
DWORD NumberOfFiles; // Number of entries
DWORD Flags;
DWORD FileLocales; // File locales (CASC_LOCALE_XXX)
USHORT KeyCount; // Number of index keys
BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key
BYTE IndexKey[MD5_HASH_SIZE]; // File index key
// Followed by a block of 32-bit integers (count: NumberOfFiles)
// Followed by the MD5 and file name hash (count: NumberOfFiles)
} CASC_ENCODING_ENTRY_1, *PCASC_ENCODING_ENTRY_1;
} CASC_ROOT_LOCALE_BLOCK, *PCASC_ROOT_LOCALE_BLOCK;
#define GET_INDEX_KEY(pEncodingEntry) (pEncodingEntry->EncodingKey + MD5_HASH_SIZE)
#define FAKE_ENCODING_ENTRY_SIZE (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE)
// 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; // Uncompressed file size, in bytes
} 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
{
bool bRootFileLoaded; // true if the root info file was properly loaded
BYTE RootFileName[MD5_HASH_SIZE]; // Name (aka MD5) of the root file
DWORD HeaderVersion; // Must be <= 2
DWORD FormatVersion;
DWORD field_1C;
DWORD field_20;
DWORD MarInfoOffset; // Offset of the first MAR entry info
DWORD MarInfoCount; // Number of the MAR info entries
DWORD MarInfoSize; // Size of the MAR info entry
DWORD MndxEntriesOffset;
DWORD MndxEntriesTotal; // Total number of MNDX root entries
DWORD MndxEntriesValid; // Number of valid MNDX root entries
DWORD MndxEntrySize; // Size of one MNDX root entry
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_ROOT_ENTRY_MNDX pMndxEntries;
PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
} CASC_MNDX_INFO, *PCASC_MNDX_INFO;
typedef struct _CASC_PACKAGE
{
char * szFileName; // Pointer to file name
size_t nLength; // Length of the file name
} CASC_PACKAGE, *PCASC_PACKAGE;
typedef struct _CASC_PACKAGES
{
char * szNameBuffer; // Pointer to the buffer for file names
size_t NameEntries; // Number of name entries in Names
size_t NameBufferUsed; // Number of bytes used in the name buffer
size_t NameBufferMax; // Total size of the name buffer
CASC_PACKAGE Packages[1]; // List of packages
} CASC_PACKAGES, *PCASC_PACKAGES;
//-----------------------------------------------------------------------------
// Structures for CASC storage and CASC file
typedef struct _TCascStorage
{
@@ -254,6 +189,7 @@ typedef struct _TCascStorage
const TCHAR * szIndexFormat; // Format of the index file name
TCHAR * szRootPath; // This is the game directory
TCHAR * szDataPath; // This is the directory where data files are
TCHAR * szBuildFile; // Build file name (.build.info or .build.db)
TCHAR * szIndexPath; // This is the directory where index files are
TCHAR * szUrlPath; // URL to the Blizzard servers
DWORD dwRefCount; // Number of references
@@ -262,40 +198,29 @@ typedef struct _TCascStorage
DWORD dwFileBeginDelta; // This is number of bytes to shift back from archive offset (from index entry) to actual begin of file data
DWORD dwDefaultLocale; // Default locale, read from ".build.info"
CBLD_TYPE BuildFileType; // Type of the build file
QUERY_KEY CdnConfigKey;
QUERY_KEY CdnBuildKey;
PQUERY_KEY pArchiveArray; // Array of the archives
QUERY_KEY ArchiveGroup; // Name of the group archive file
DWORD ArchiveCount; // Number of archives in the array
PQUERY_KEY pPatchArchiveArray; // Array of the patch archives
QUERY_KEY PatchArchiveGroup; // Name of the patch group archive file
DWORD PatchArchiveCount; // Number of patch archives in the array
QUERY_KEY ArchivesGroup; // Key array of the "archive-group"
QUERY_KEY ArchivesKey; // Key array of the "archives"
QUERY_KEY PatchArchivesKey; // Key array of the "patch-archives"
QUERY_KEY RootKey;
QUERY_KEY PatchKey;
QUERY_KEY DownloadKey;
QUERY_KEY InstallKey;
PQUERY_KEY pEncodingKeys;
QUERY_KEY EncodingKey;
QUERY_KEY EncodingEKey;
DWORD EncodingKeys;
TFileStream * DataFileArray[CASC_MAX_DATA_FILES]; // Data file handles
CASC_MAPPING_TABLE KeyMapping[CASC_INDEX_COUNT]; // Key mapping
PCASC_MAP pIndexEntryMap; // Map of index entries
PCASC_ENCODING_HEADER pEncodingHeader; // The encoding file
PCASC_ENCODING_ENTRY * ppEncodingEntries; // Map of encoding entries
size_t nEncodingEntries;
QUERY_KEY EncodingFile; // Content of the ENCODING file
PCASC_MAP pEncodingMap; // Map of encoding entries
DYNAMIC_ARRAY ExtraEntries; // Extra encoding 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
TRootHandler * pRootHandler; // Common handler for various ROOT file formats
} TCascStorage;
@@ -336,16 +261,19 @@ typedef struct _TCascFile
typedef struct _TCascSearch
{
TCascStorage * hs; // Pointer to the storage handle
const char * szClassName;
TCHAR * szListFile;
const char * szClassName; // Contains "TCascSearch"
TCHAR * szListFile; // Name of the listfile
void * pCache; // Listfile cache
TMndxFindResult * pStruct1C; // Search structure for MNDX info
char * szMask;
char * szMask; // Search mask
char szFileName[MAX_PATH]; // Buffer for the file name
char szNormName[MAX_PATH]; // Buffer for normalized file name
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
// Provider-specific data
void * pRootContext; // Root-specific search context
size_t IndexLevel1; // Root-specific search context
size_t IndexLevel2; // Root-specific search context
DWORD dwState; // Pointer to the search state (0 = listfile, 1 = nameless, 2 = done)
BYTE BitArray[1]; // Bit array of encoding keys. Set for each entry that has already been reported
} TCascSearch;
@@ -384,10 +312,15 @@ DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes);
DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes);
ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes);
void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes);
void FreeCascBlob(PQUERY_KEY pQueryKey);
//-----------------------------------------------------------------------------
// Build configuration reading
// Text file parsing (CascFiles.cpp)
int LoadBuildInfo(TCascStorage * hs);
int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory);
int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, PQUERY_KEY pEncodingKey, char * szFileName, size_t nMaxChars);
//-----------------------------------------------------------------------------
// Internal file functions
@@ -395,11 +328,20 @@ int LoadBuildInfo(TCascStorage * hs);
TCascStorage * IsValidStorageHandle(HANDLE hStorage);
TCascFile * IsValidFileHandle(HANDLE hFile);
PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex);
PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex);
PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, PDWORD PtrIndex);
PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey);
int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer);
int CascDecompress(LPBYTE pvOutBuffer, PDWORD pcbOutBuffer, LPBYTE pvInBuffer, DWORD cbInBuffer);
int CascDecrypt (LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex);
int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer);
//-----------------------------------------------------------------------------
// Support for ROOT file
int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask);
//-----------------------------------------------------------------------------
// Dumping CASC data structures
@@ -409,8 +351,7 @@ void CascDumpSparseArray(const char * szFileName, void * pvSparseArray);
void CascDumpNameFragTable(const char * szFileName, void * pvMarFile);
void CascDumpFileNames(const char * szFileName, void * pvMarFile);
void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs);
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 CascDumpEncodingEntry(TCascStorage * hs, TDumpContext * dc, PCASC_ENCODING_ENTRY pEncodingEntry, int nDumpLevel);
void CascDumpFile(const char * szFileName, HANDLE hFile);
#endif // _DEBUG

View File

@@ -13,9 +13,9 @@
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
// Public functions
static int Decompress_ZLIB(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
{
z_stream z; // Stream information for zlib
int nResult;
@@ -44,40 +44,3 @@ static int Decompress_ZLIB(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInB
// Return an error code
return (nResult == Z_OK || nResult == Z_STREAM_END) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
}
//-----------------------------------------------------------------------------
// Public functions
int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer)
{
LPBYTE pbOutBuffer = (LPBYTE)pvOutBuffer;
LPBYTE pbInBuffer = (LPBYTE)pvInBuffer;
DWORD cbOutBuffer = *pcbOutBuffer; // Current size of the output buffer
BYTE uCompression; // Decompressions applied to the data
// Verify buffer sizes
if(cbInBuffer <= 1)
return 0;
// Get applied compression types and decrement data length
uCompression = *pbInBuffer++;
cbInBuffer--;
// Perform the decompressions
switch(uCompression)
{
case 'N': // Uncompressed
assert(cbOutBuffer == cbInBuffer);
memcpy(pbOutBuffer, pbInBuffer, cbInBuffer);
*pcbOutBuffer = cbOutBuffer;
return ERROR_SUCCESS;
case 'Z': // ZLIB
return Decompress_ZLIB(pbOutBuffer, pcbOutBuffer, pbInBuffer, cbInBuffer);
}
assert(false);
return ERROR_NOT_SUPPORTED;
}

View File

@@ -0,0 +1,300 @@
/*****************************************************************************/
/* CascDecrypt.cpp Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Decryption functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 31.10.15 1.00 Lad The first version of CascDecrypt.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local structures
typedef struct _CASC_ENCRYPTION_KEY
{
ULONGLONG KeyName; // "Name" of the key
BYTE Key[0x10]; // The key itself
} CASC_ENCRYPTION_KEY, *PCASC_ENCRYPTION_KEY;
typedef struct _CASC_SALSA20
{
DWORD Key[0x10];
DWORD dwRounds;
} CASC_SALSA20, *PCASC_SALSA20;
//-----------------------------------------------------------------------------
// Local variables
//keyName FB680CB6A8BF81F3 key 62D90EFA7F36D71C398AE2F1FE37BDB9 keyNameSize 8 keySize 16
//keyName 402CD9D8D6BFED98 key AEB0EADEA47612FE6C041A03958DF241 keyNameSize 8 keySize 16
//keyName 87AEBBC9C4E6B601 key 685E86C6063DFDA6C9E85298076B3D42 keyNameSize 8 keySize 16
//keyName A19C4F859F6EFA54 key 0196CB6F5ECBAD7CB5283891B9712B4B keyNameSize 8 keySize 16
//keyName 11A9203C9881710A key 2E2CB8C397C2F24ED0B5E452F18DC267 keyNameSize 8 keySize 16
//keyName DBD3371554F60306 key 34E397ACE6DD30EEFDC98A2AB093CD3C keyNameSize 8 keySize 16
//keyName DEE3A0521EFF6F03 key AD740CE3FFFF9231468126985708E1B9 keyNameSize 8 keySize 16
//keyName 8C9106108AA84F07 key 53D859DDA2635A38DC32E72B11B32F29 keyNameSize 8 keySize 16
static CASC_ENCRYPTION_KEY CascKeys[] =
{
{0xFB680CB6A8BF81F3ULL, {0x62, 0xD9, 0x0E, 0xFA, 0x7F, 0x36, 0xD7, 0x1C, 0x39, 0x8A, 0xE2, 0xF1, 0xFE, 0x37, 0xBD, 0xB9}},
{0x402CD9D8D6BFED98ULL, {0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41}},
{0x87AEBBC9C4E6B601ULL, {0x68, 0x5E, 0x86, 0xC6, 0x06, 0x3D, 0xFD, 0xA6, 0xC9, 0xE8, 0x52, 0x98, 0x07, 0x6B, 0x3D, 0x42}},
{0xA19C4F859F6EFA54ULL, {0x01, 0x96, 0xCB, 0x6F, 0x5E, 0xCB, 0xAD, 0x7C, 0xB5, 0x28, 0x38, 0x91, 0xB9, 0x71, 0x2B, 0x4B}},
{0x11A9203C9881710AULL, {0x2E, 0x2C, 0xB8, 0xC3, 0x97, 0xC2, 0xF2, 0x4E, 0xD0, 0xB5, 0xE4, 0x52, 0xF1, 0x8D, 0xC2, 0x67}},
{0xDBD3371554F60306ULL, {0x34, 0xE3, 0x97, 0xAC, 0xE6, 0xDD, 0x30, 0xEE, 0xFD, 0xC9, 0x8A, 0x2A, 0xB0, 0x93, 0xCD, 0x3C}},
{0xDEE3A0521EFF6F03ULL, {0xAD, 0x74, 0x0C, 0xE3, 0xFF, 0xFF, 0x92, 0x31, 0x46, 0x81, 0x26, 0x98, 0x57, 0x08, 0xE1, 0xB9}},
{0x8C9106108AA84F07ULL, {0x53, 0xD8, 0x59, 0xDD, 0xA2, 0x63, 0x5A, 0x38, 0xDC, 0x32, 0xE7, 0x2B, 0x11, 0xB3, 0x2F, 0x29}},
{0x49166D358A34D815ULL, {0x66, 0x78, 0x68, 0xCD, 0x94, 0xEA, 0x01, 0x35, 0xB9, 0xB1, 0x6C, 0x93, 0xB1, 0x12, 0x4A, 0xBA}},
{0, {0}}
};
static const char * szKeyConstant16 = "expand 16-byte k";
static const char * szKeyConstant32 = "expand 32-byte k";
//-----------------------------------------------------------------------------
// Local functions
static DWORD Rol32(DWORD dwValue, DWORD dwRolCount)
{
return (dwValue << dwRolCount) | (dwValue >> (32 - dwRolCount));
}
static LPBYTE FindCascKey(ULONGLONG KeyName)
{
// Search the known keys
for(size_t i = 0; CascKeys[i].KeyName != 0; i++)
{
if(CascKeys[i].KeyName == KeyName)
return CascKeys[i].Key;
}
// Key not found
return NULL;
}
static void Initialize(PCASC_SALSA20 pState, LPBYTE pbKey, DWORD cbKeyLength, LPBYTE pbVector)
{
const char * szConstants = (cbKeyLength == 32) ? szKeyConstant32 : szKeyConstant16;
DWORD KeyIndex = cbKeyLength - 0x10;
memset(pState, 0, sizeof(CASC_SALSA20));
pState->Key[0] = *(PDWORD)(szConstants + 0x00);
pState->Key[1] = *(PDWORD)(pbKey + 0x00);
pState->Key[2] = *(PDWORD)(pbKey + 0x04);
pState->Key[3] = *(PDWORD)(pbKey + 0x08);
pState->Key[4] = *(PDWORD)(pbKey + 0x0C);
pState->Key[5] = *(PDWORD)(szConstants + 0x04);
pState->Key[6] = *(PDWORD)(pbVector + 0x00);
pState->Key[7] = *(PDWORD)(pbVector + 0x04);
pState->Key[8] = 0;
pState->Key[9] = 0;
pState->Key[10] = *(PDWORD)(szConstants + 0x08);
pState->Key[11] = *(PDWORD)(pbKey + KeyIndex + 0x00);
pState->Key[12] = *(PDWORD)(pbKey + KeyIndex + 0x04);
pState->Key[13] = *(PDWORD)(pbKey + KeyIndex + 0x08);
pState->Key[14] = *(PDWORD)(pbKey + KeyIndex + 0x0C);
pState->Key[15] = *(PDWORD)(szConstants + 0x0C);
pState->dwRounds = 20;
}
static int Decrypt(PCASC_SALSA20 pState, LPBYTE pbOutBuffer, LPBYTE pbInBuffer, size_t cbInBuffer)
{
LPBYTE pbXorValue;
DWORD KeyMirror[0x10];
DWORD XorValue[0x10];
DWORD BlockSize;
DWORD i;
// Repeat until we have data to read
while(cbInBuffer > 0)
{
// Create the copy of the key
memcpy(KeyMirror, pState->Key, sizeof(KeyMirror));
// Shuffle the key
for(i = 0; i < pState->dwRounds; i += 2)
{
KeyMirror[0x04] ^= Rol32((KeyMirror[0x00] + KeyMirror[0x0C]), 0x07);
KeyMirror[0x08] ^= Rol32((KeyMirror[0x04] + KeyMirror[0x00]), 0x09);
KeyMirror[0x0C] ^= Rol32((KeyMirror[0x08] + KeyMirror[0x04]), 0x0D);
KeyMirror[0x00] ^= Rol32((KeyMirror[0x0C] + KeyMirror[0x08]), 0x12);
KeyMirror[0x09] ^= Rol32((KeyMirror[0x05] + KeyMirror[0x01]), 0x07);
KeyMirror[0x0D] ^= Rol32((KeyMirror[0x09] + KeyMirror[0x05]), 0x09);
KeyMirror[0x01] ^= Rol32((KeyMirror[0x0D] + KeyMirror[0x09]), 0x0D);
KeyMirror[0x05] ^= Rol32((KeyMirror[0x01] + KeyMirror[0x0D]), 0x12);
KeyMirror[0x0E] ^= Rol32((KeyMirror[0x0A] + KeyMirror[0x06]), 0x07);
KeyMirror[0x02] ^= Rol32((KeyMirror[0x0E] + KeyMirror[0x0A]), 0x09);
KeyMirror[0x06] ^= Rol32((KeyMirror[0x02] + KeyMirror[0x0E]), 0x0D);
KeyMirror[0x0A] ^= Rol32((KeyMirror[0x06] + KeyMirror[0x02]), 0x12);
KeyMirror[0x03] ^= Rol32((KeyMirror[0x0F] + KeyMirror[0x0B]), 0x07);
KeyMirror[0x07] ^= Rol32((KeyMirror[0x03] + KeyMirror[0x0F]), 0x09);
KeyMirror[0x0B] ^= Rol32((KeyMirror[0x07] + KeyMirror[0x03]), 0x0D);
KeyMirror[0x0F] ^= Rol32((KeyMirror[0x0B] + KeyMirror[0x07]), 0x12);
KeyMirror[0x01] ^= Rol32((KeyMirror[0x00] + KeyMirror[0x03]), 0x07);
KeyMirror[0x02] ^= Rol32((KeyMirror[0x01] + KeyMirror[0x00]), 0x09);
KeyMirror[0x03] ^= Rol32((KeyMirror[0x02] + KeyMirror[0x01]), 0x0D);
KeyMirror[0x00] ^= Rol32((KeyMirror[0x03] + KeyMirror[0x02]), 0x12);
KeyMirror[0x06] ^= Rol32((KeyMirror[0x05] + KeyMirror[0x04]), 0x07);
KeyMirror[0x07] ^= Rol32((KeyMirror[0x06] + KeyMirror[0x05]), 0x09);
KeyMirror[0x04] ^= Rol32((KeyMirror[0x07] + KeyMirror[0x06]), 0x0D);
KeyMirror[0x05] ^= Rol32((KeyMirror[0x04] + KeyMirror[0x07]), 0x12);
KeyMirror[0x0B] ^= Rol32((KeyMirror[0x0A] + KeyMirror[0x09]), 0x07);
KeyMirror[0x08] ^= Rol32((KeyMirror[0x0B] + KeyMirror[0x0A]), 0x09);
KeyMirror[0x09] ^= Rol32((KeyMirror[0x08] + KeyMirror[0x0B]), 0x0D);
KeyMirror[0x0A] ^= Rol32((KeyMirror[0x09] + KeyMirror[0x08]), 0x12);
KeyMirror[0x0C] ^= Rol32((KeyMirror[0x0F] + KeyMirror[0x0E]), 0x07);
KeyMirror[0x0D] ^= Rol32((KeyMirror[0x0C] + KeyMirror[0x0F]), 0x09);
KeyMirror[0x0E] ^= Rol32((KeyMirror[0x0D] + KeyMirror[0x0C]), 0x0D);
KeyMirror[0x0F] ^= Rol32((KeyMirror[0x0E] + KeyMirror[0x0D]), 0x12);
}
// Set the number of remaining bytes
pbXorValue = (LPBYTE)XorValue;
BlockSize = (DWORD)CASCLIB_MIN(cbInBuffer, 0x40);
// Prepare the XOR constants
for(i = 0; i < 16; i++)
{
XorValue[i] = KeyMirror[i] + pState->Key[i];
}
// Decrypt the block
for(i = 0; i < BlockSize; i++)
{
pbOutBuffer[i] = pbInBuffer[i] ^ pbXorValue[i];
}
pState->Key[8] = pState->Key[8] + 1;
if(pState->Key[8] == 0)
pState->Key[9] = pState->Key[9] + 1;
// Adjust buffers
pbOutBuffer += BlockSize;
pbInBuffer += BlockSize;
cbInBuffer -= BlockSize;
}
return ERROR_SUCCESS;
}
static int Decrypt_Salsa20(LPBYTE pbOutBuffer, LPBYTE pbInBuffer, size_t cbInBuffer, LPBYTE pbKey, DWORD cbKeySize, LPBYTE pbVector)
{
CASC_SALSA20 SalsaState;
Initialize(&SalsaState, pbKey, cbKeySize, pbVector);
return Decrypt(&SalsaState, pbOutBuffer, pbInBuffer, cbInBuffer);
}
//-----------------------------------------------------------------------------
// Public functions
int CascDecrypt(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex)
{
ULONGLONG KeyName = 0;
LPBYTE pbBufferEnd = pbInBuffer + cbInBuffer;
LPBYTE pbKey;
DWORD KeyNameSize;
DWORD dwShift = 0;
DWORD IVSize;
BYTE Vector[0x08];
BYTE EncryptionType;
int nError;
// Verify and retrieve the key name size
if(pbInBuffer >= pbBufferEnd)
return ERROR_FILE_CORRUPT;
if(pbInBuffer[0] != 0 && pbInBuffer[0] != 8)
return ERROR_NOT_SUPPORTED;
KeyNameSize = *pbInBuffer++;
// Copy the key name
if((pbInBuffer + KeyNameSize) >= pbBufferEnd)
return ERROR_FILE_CORRUPT;
memcpy(&KeyName, pbInBuffer, KeyNameSize);
pbInBuffer += KeyNameSize;
// Verify and retrieve the Vector size
if(pbInBuffer >= pbBufferEnd)
return ERROR_FILE_CORRUPT;
if(pbInBuffer[0] != 4 && pbInBuffer[0] != 8)
return ERROR_NOT_SUPPORTED;
IVSize = *pbInBuffer++;
// Copy the initialization vector
if((pbInBuffer + IVSize) >= pbBufferEnd)
return ERROR_FILE_CORRUPT;
memset(Vector, 0, sizeof(Vector));
memcpy(Vector, pbInBuffer, IVSize);
pbInBuffer += IVSize;
// Verify and retrieve the encryption type
if(pbInBuffer >= pbBufferEnd)
return ERROR_FILE_CORRUPT;
if(pbInBuffer[0] != 'S' && pbInBuffer[0] != 'A')
return ERROR_NOT_SUPPORTED;
EncryptionType = *pbInBuffer++;
// Do we have enough space in the output buffer?
if((DWORD)(pbBufferEnd - pbInBuffer) > pcbOutBuffer[0])
return ERROR_INSUFFICIENT_BUFFER;
// Check if we know the key
pbKey = FindCascKey(KeyName);
if(pbKey == NULL)
return ERROR_UNKNOWN_FILE_KEY;
// Shuffle the Vector with the block index
// Note that there's no point to go beyond 32 bits, unless the file has
// more than 0xFFFFFFFF frames.
for(int i = 0; i < sizeof(dwFrameIndex); i++)
{
Vector[i] = Vector[i] ^ (BYTE)((dwFrameIndex >> dwShift) & 0xFF);
dwShift += 8;
}
// Perform the decryption-specific action
switch(EncryptionType)
{
case 'S': // Salsa20
nError = Decrypt_Salsa20(pbOutBuffer, pbInBuffer, (pbBufferEnd - pbInBuffer), pbKey, 0x10, Vector);
if(nError != ERROR_SUCCESS)
return nError;
// Supply the size of the output buffer
pcbOutBuffer[0] = (DWORD)(pbBufferEnd - pbInBuffer);
return ERROR_SUCCESS;
// case 'A':
// return ERROR_NOT_SUPPORTED;
}
assert(false);
return ERROR_NOT_SUPPORTED;
}
int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
{
// Check the buffer size
if((cbInBuffer - 1) > pcbOutBuffer[0])
return ERROR_INSUFFICIENT_BUFFER;
// Copy the data
memcpy(pbOutBuffer, pbInBuffer, cbInBuffer);
pcbOutBuffer[0] = cbInBuffer;
return ERROR_SUCCESS;
}

View File

@@ -11,14 +11,14 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
#include "CascMndxRoot.h"
#include "CascMndx.h"
#ifdef _DEBUG // The entire file is only valid for debug purposes
#ifdef _DEBUG // The entire feature is only valid for debug purposes
//-----------------------------------------------------------------------------
// Forward definitions
LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd);
//LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd);
//-----------------------------------------------------------------------------
// Sort compare functions
@@ -49,160 +49,6 @@ static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1,
return 0;
}
//-----------------------------------------------------------------------------
// Local functions
static char * StringFromMD5(LPBYTE md5, char * szBuffer)
{
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
}
static char * FormatFileName(const char * szFormat, TCascStorage * hs)
{
char * szFileName;
char * szSrc;
char * szTrg;
// Create copy of the file name
szFileName = szSrc = szTrg = NewStr(szFormat, 0);
if(szFileName != NULL)
{
// Format the file name
while(szSrc[0] != 0)
{
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++;
}
// 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 pbIndexKey,
int nDumpLevel)
{
PCASC_INDEX_ENTRY pIndexEntry;
TCascFile * hf;
QUERY_KEY QueryKey;
HANDLE hFile;
BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
char szBuffer[0x20];
QueryKey.pbData = pbIndexKey;
QueryKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(hs, &QueryKey);
if(pIndexEntry != NULL)
{
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E);
DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
// Mask the file offset
FileOffset &= 0x3FFFFFFF;
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
{
fprintf(fp, " NO INDEX ENTRY\n");
}
}
static void DumpEncodingEntry(
FILE * fp,
TCascStorage * hs,
PCASC_ENCODING_ENTRY pEncodingEntry,
int nDumpLevel)
{
LPBYTE pbIndexKey;
char szMd5[MD5_STRING_SIZE];
// If the encoding key exists
if(pEncodingEntry != NULL)
{
fprintf(fp, " Size %lx Key Count: %u\n",
ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE),
pEncodingEntry->KeyCount);
// Dump all index keys
pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++)
{
fprintf(fp, " %s\n", StringFromMD5(pbIndexKey, szMd5));
DumpIndexKey(fp, hs, pbIndexKey, nDumpLevel);
pbIndexKey += MD5_HASH_SIZE;
}
}
else
{
fprintf(fp, " NO ENCODING KEYS\n");
}
}
//-----------------------------------------------------------------------------
// Public functions
@@ -323,27 +169,92 @@ void CascDumpFileNames(const char * szFileName, void * pvMarFile)
Struct1C.FreeStruct40();
}
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo)
void CascDumpIndexEntry(
TCascStorage * /* hs */,
TDumpContext * dc,
PCASC_INDEX_ENTRY pIndexEntry,
int /* nDumpLevel */)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry;
FILE * fp;
char szMd5[MD5_STRING_SIZE];
// Create the dump file
fp = fopen(szFileName, "wt");
if(fp != NULL)
if(pIndexEntry != NULL)
{
fprintf(fp, "Indx Fl+Asset EncodingKey FileSize\n==== ======== ================================ ========\n");
for(DWORD i = 0; i < pMndxInfo->MndxEntriesValid; i++)
{
pRootEntry = pMndxInfo->ppValidEntries[i];
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E);
DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
fprintf(fp, "%04X %08X %s %08X\n", i,
pRootEntry->Flags,
StringFromMD5(pRootEntry->EncodingKey, szMd5),
pRootEntry->FileSize);
// Mask the file offset
FileOffset &= 0x3FFFFFFF;
dump_print(dc, " 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
// dump_print(dc, " FileSize: %X Rest: %s\n",
// ConvertBytesToInteger_4_LE(&HeaderArea[0x10]),
// StringFromBinary(&HeaderArea[0x14], 10, szBuffer));
// }
//}
}
else
{
dump_print(dc, " NO INDEX ENTRY\n");
}
}
void CascDumpEncodingEntry(
TCascStorage * hs,
TDumpContext * dc,
PCASC_ENCODING_ENTRY pEncodingEntry,
int nDumpLevel)
{
PCASC_INDEX_ENTRY pIndexEntry;
QUERY_KEY QueryKey;
LPBYTE pbIndexKey;
char szMd5[MD5_STRING_SIZE+1];
// If the encoding key exists
if(pEncodingEntry != NULL)
{
dump_print(dc, " Size %lx Key Count: %u\n",
ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE),
pEncodingEntry->KeyCount);
// Dump all index keys
pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++, pbIndexKey += MD5_HASH_SIZE)
{
// Dump the index key
dump_print(dc, " %s\n", StringFromMD5(pbIndexKey, szMd5));
// Dump the index entry as well
if(nDumpLevel >= DUMP_LEVEL_INDEX_ENTRIES)
{
QueryKey.pbData = pbIndexKey;
QueryKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(hs, &QueryKey);
CascDumpIndexEntry(hs, dc, pIndexEntry, nDumpLevel);
}
}
fclose(fp);
}
else
{
dump_print(dc, " NO ENCODING KEYS\n");
}
}
@@ -380,7 +291,7 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
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));
fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, (DWORD)ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey));
}
CASC_FREE(ppIndexEntries);
@@ -390,81 +301,6 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
}
}
void CascDumpRootFile(
TCascStorage * hs,
LPBYTE pbRootFile,
DWORD cbRootFile,
const char * szFormat,
const TCHAR * szListFile,
int nDumpLevel)
{
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;
// 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 listfile map
// DWORD dwTickCount = GetTickCount();
pListMap = ListFile_CreateMap(szListFile);
// dwTickCount = GetTickCount() - dwTickCount;
// Dump the root entries as-is
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
{
// Validate the root block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// Dump the locale block
fprintf(fp, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
"=========================================================\n",
BlockInfo.pLocaleBlockHdr->Flags,
BlockInfo.pLocaleBlockHdr->Locales,
BlockInfo.pLocaleBlockHdr->NumberOfFiles);
// 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;

View File

@@ -0,0 +1,981 @@
/*****************************************************************************/
/* CascFiles.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Various text file parsers */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascBuildCfg.cpp */
/* 30.10.15 1.00 Lad Renamed to CascFiles.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
typedef int (*PARSEINFOFILE)(TCascStorage * hs, void * pvListFile);
//-----------------------------------------------------------------------------
// Local structures
struct TBuildFileInfo
{
const TCHAR * szFileName;
CBLD_TYPE BuildFileType;
};
struct TGameIdString
{
const char * szGameInfo;
size_t cchGameInfo;
DWORD dwGameInfo;
};
static const TBuildFileInfo BuildTypes[] =
{
{_T(".build.info"), CascBuildInfo}, // Since HOTS build 30027, the game uses .build.info file for storage info
{_T(".build.db"), CascBuildDb}, // Older CASC storages
{NULL, CascBuildNone}
};
static const TCHAR * DataDirs[] =
{
_T("SC2Data"), // Starcraft II (Legacy of the Void) build 38749
_T("Data\\Casc"), // Overwatch
_T("Data"), // World of Warcraft, Diablo
_T("HeroesData"), // Heroes of the Storm
_T("BNTData"), // Heroes of the Storm, until build 30414
NULL,
};
static const TGameIdString GameIds[] =
{
{"Hero", 0x04, CASC_GAME_HOTS}, // Alpha build of Heroes of the Storm
{"WoW", 0x03, CASC_GAME_WOW6}, // Alpha build of World of Warcraft - Warlords of Draenor
{"Diablo3", 0x07, CASC_GAME_DIABLO3}, // Diablo III BETA 2.2.0
{"Prometheus", 0x0A, CASC_GAME_OVERWATCH}, // Overwatch BETA since build 24919
{"SC2", 0x03, CASC_GAME_STARCRAFT2}, // Starcraft II - Legacy of the Void
{NULL, 0, 0},
};
//-----------------------------------------------------------------------------
// Local functions
static bool inline IsValueSeparator(const char * szVarValue)
{
return ((0 <= szVarValue[0] && szVarValue[0] <= 0x20) || (szVarValue[0] == '|'));
}
static bool IsCharDigit(BYTE OneByte)
{
return ('0' <= OneByte && OneByte <= '9');
}
static DWORD GetLocaleMask(const char * szTag)
{
if(!strcmp(szTag, "enUS"))
return CASC_LOCALE_ENUS;
if(!strcmp(szTag, "koKR"))
return CASC_LOCALE_KOKR;
if(!strcmp(szTag, "frFR"))
return CASC_LOCALE_FRFR;
if(!strcmp(szTag, "deDE"))
return CASC_LOCALE_DEDE;
if(!strcmp(szTag, "zhCN"))
return CASC_LOCALE_ZHCN;
if(!strcmp(szTag, "esES"))
return CASC_LOCALE_ESES;
if(!strcmp(szTag, "zhTW"))
return CASC_LOCALE_ZHTW;
if(!strcmp(szTag, "enGB"))
return CASC_LOCALE_ENGB;
if(!strcmp(szTag, "enCN"))
return CASC_LOCALE_ENCN;
if(!strcmp(szTag, "enTW"))
return CASC_LOCALE_ENTW;
if(!strcmp(szTag, "esMX"))
return CASC_LOCALE_ESMX;
if(!strcmp(szTag, "ruRU"))
return CASC_LOCALE_RURU;
if(!strcmp(szTag, "ptBR"))
return CASC_LOCALE_PTBR;
if(!strcmp(szTag, "itIT"))
return CASC_LOCALE_ITIT;
if(!strcmp(szTag, "ptPT"))
return CASC_LOCALE_PTPT;
return 0;
}
static bool IsInfoVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName, const char * szVarType)
{
size_t nLength;
// Check the variable name
nLength = strlen(szVarName);
if((size_t)(szLineEnd - szLineBegin) > nLength)
{
// Check the variable name
if(!_strnicmp(szLineBegin, szVarName, nLength))
{
// Skip variable name and the exclamation mark
szLineBegin += nLength;
if(szLineBegin < szLineEnd && szLineBegin[0] == '!')
{
// Skip the exclamation mark
szLineBegin++;
// Check the variable type
nLength = strlen(szVarType);
if((size_t)(szLineEnd - szLineBegin) > nLength)
{
// Check the variable name
if(!_strnicmp(szLineBegin, szVarType, nLength))
{
// Skip variable type and the doublecolon
szLineBegin += nLength;
return (szLineBegin < szLineEnd && szLineBegin[0] == ':');
}
}
}
}
}
return false;
}
static const char * SkipInfoVariable(const char * szLineBegin, const char * szLineEnd)
{
while(szLineBegin < szLineEnd)
{
if(szLineBegin[0] == '|')
return szLineBegin + 1;
szLineBegin++;
}
return NULL;
}
static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
{
TCHAR * szIndexPath;
// Cpmbine the index path
szIndexPath = CombinePath(hs->szDataPath, szSubDir);
if(DirectoryExists(szIndexPath))
{
hs->szIndexPath = szIndexPath;
return hs->szIndexPath;
}
CASC_FREE(szIndexPath);
return NULL;
}
TCHAR * AppendBlobText(TCHAR * szBuffer, LPBYTE pbData, DWORD cbData, TCHAR chSeparator)
{
// Put the separator, if any
if(chSeparator != 0)
*szBuffer++ = chSeparator;
// Copy the blob data as text
for(DWORD i = 0; i < cbData; i++)
{
*szBuffer++ = IntToHexChar[pbData[0] >> 0x04];
*szBuffer++ = IntToHexChar[pbData[0] & 0x0F];
pbData++;
}
// Terminate the string
*szBuffer = 0;
// Return new buffer position
return szBuffer;
}
static const char * CheckLineVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName)
{
size_t nLineLength = (size_t)(szLineEnd - szLineBegin);
size_t nNameLength = strlen(szVarName);
// If the line longer than the variable name?
if(nLineLength > nNameLength)
{
if(!_strnicmp((const char *)szLineBegin, szVarName, nNameLength))
{
// Skip the variable name
szLineBegin += nNameLength;
// Skip the separator(s)
while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
szLineBegin++;
// Check if there is "="
if(szLineBegin >= szLineEnd || szLineBegin[0] != '=')
return NULL;
szLineBegin++;
// Skip the separator(s)
while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
szLineBegin++;
// Check if there is "="
if(szLineBegin >= szLineEnd)
return NULL;
// Return the begin of the variable
return szLineBegin;
}
}
return NULL;
}
static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const char * szLineEnd, bool bHexaValue)
{
const char * szLinePtr = szLineBegin;
// Sanity checks
assert(pVarBlob->pbData == NULL);
assert(pVarBlob->cbData == 0);
// Check length of the variable
while(szLinePtr < szLineEnd && szLinePtr[0] != '|')
szLinePtr++;
// Allocate space for the blob
if(bHexaValue)
{
// Initialize the blob
pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) / 2);
pVarBlob->cbData = (DWORD)((szLinePtr - szLineBegin) / 2);
return ConvertStringToBinary(szLineBegin, (size_t)(szLinePtr - szLineBegin), pVarBlob->pbData);
}
// Initialize the blob
pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1);
pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin);
// Check for success
if(pVarBlob->pbData == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Copy the string
memcpy(pVarBlob->pbData, szLineBegin, pVarBlob->cbData);
pVarBlob->pbData[pVarBlob->cbData] = 0;
return ERROR_SUCCESS;
}
static void AppendConfigFilePath(TCHAR * szFileName, PQUERY_KEY pFileKey)
{
size_t nLength = _tcslen(szFileName);
// If there is no slash, append if
if(nLength > 0 && szFileName[nLength - 1] != '\\' && szFileName[nLength - 1] != '/')
szFileName[nLength++] = _T('/');
// Get to the end of the file name
szFileName = szFileName + nLength;
// Append the "config" directory
_tcscpy(szFileName, _T("config"));
szFileName += 6;
// Append the first level directory
szFileName = AppendBlobText(szFileName, pFileKey->pbData, 1, _T('/'));
szFileName = AppendBlobText(szFileName, pFileKey->pbData + 1, 1, _T('/'));
szFileName = AppendBlobText(szFileName, pFileKey->pbData, pFileKey->cbData, _T('/'));
}
static DWORD GetBlobCount(const char * szLineBegin, const char * szLineEnd)
{
DWORD dwBlobCount = 0;
// Until we find an end of the line
while(szLineBegin < szLineEnd)
{
// Skip the blob
while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin) == false)
szLineBegin++;
// Increment the number of blobs
dwBlobCount++;
// Skip the separator
while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
szLineBegin++;
}
return dwBlobCount;
}
static int LoadBlobArray(
PQUERY_KEY pBlob,
const char * szLineBegin,
const char * szLineEnd,
DWORD dwMaxBlobs)
{
LPBYTE pbBufferEnd = pBlob->pbData + pBlob->cbData;
LPBYTE pbBuffer = pBlob->pbData;
int nError = ERROR_SUCCESS;
// Sanity check
assert(pBlob->pbData != NULL);
assert(pBlob->cbData != 0);
// Until we find an end of the line
while(szLineBegin < szLineEnd && dwMaxBlobs > 0)
{
const char * szBlobEnd = szLineBegin;
// Find the end of the text blob
while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd) == false)
szBlobEnd++;
// Verify the length of the found blob
if((szBlobEnd - szLineBegin) != MD5_STRING_SIZE)
return ERROR_BAD_FORMAT;
// Verify if there is enough space in the buffer
if((pbBufferEnd - pbBuffer) < MD5_HASH_SIZE)
return ERROR_NOT_ENOUGH_MEMORY;
// Perform the conversion
nError = ConvertStringToBinary(szLineBegin, MD5_STRING_SIZE, pbBuffer);
if(nError != ERROR_SUCCESS)
return nError;
// Move pointers
pbBuffer += MD5_HASH_SIZE;
dwMaxBlobs--;
// Skip the separator
while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd))
szBlobEnd++;
szLineBegin = szBlobEnd;
}
return nError;
}
static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd, DWORD dwBlobCount)
{
size_t nLength = (szLineEnd - szLineBegin);
// We expect each blob to have length of the encoding key and one space between
if(nLength > (dwBlobCount * MD5_STRING_SIZE) + ((dwBlobCount - 1) * sizeof(char)))
return ERROR_INVALID_PARAMETER;
// Allocate the blob buffer
pBlob->pbData = CASC_ALLOC(BYTE, dwBlobCount * MD5_HASH_SIZE);
if(pBlob->pbData == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Set the buffer size and load the blob array
pBlob->cbData = dwBlobCount * MD5_HASH_SIZE;
return LoadBlobArray(pBlob, szLineBegin, szLineEnd, dwBlobCount);
}
static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd)
{
return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, GetBlobCount(szLineBegin, szLineEnd));
}
static int LoadSingleBlob(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd)
{
return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, 1);
}
static int GetGameType(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd)
{
// Go through all games that we support
for(size_t i = 0; GameIds[i].szGameInfo != NULL; i++)
{
// Check the length of the variable
if((size_t)(szLineEnd - szVarBegin) == GameIds[i].cchGameInfo)
{
// Check the string
if(!_strnicmp(szVarBegin, GameIds[i].szGameInfo, GameIds[i].cchGameInfo))
{
hs->dwGameInfo = GameIds[i].dwGameInfo;
return ERROR_SUCCESS;
}
}
}
// Unknown/unsupported game
assert(false);
return ERROR_BAD_FORMAT;
}
// "B29049"
// "WOW-18125patch6.0.1"
// "30013_Win32_2_2_0_Ptr_ptr"
// "prometheus-0_8_0_0-24919"
static int GetBuildNumber(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd)
{
DWORD dwBuildNumber = 0;
// Skip all non-digit characters
while(szVarBegin < szLineEnd)
{
// There must be at least three digits (build 99 anyone?)
if(IsCharDigit(szVarBegin[0]) && IsCharDigit(szVarBegin[1]) && IsCharDigit(szVarBegin[2]))
{
// Convert the build number string to value
while(szVarBegin < szLineEnd && IsCharDigit(szVarBegin[0]))
dwBuildNumber = (dwBuildNumber * 10) + (*szVarBegin++ - '0');
break;
}
// Move to the next
szVarBegin++;
}
assert(dwBuildNumber != 0);
hs->dwBuildNumber = dwBuildNumber;
return (dwBuildNumber != 0) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
}
static int GetDefaultLocaleMask(TCascStorage * hs, PQUERY_KEY pTagsString)
{
char * szTagEnd = (char *)pTagsString->pbData + pTagsString->cbData;
char * szTagPtr = (char *)pTagsString->pbData;
char * szNext;
DWORD dwLocaleMask = 0;
while(szTagPtr < szTagEnd)
{
// Get the next part
szNext = strchr(szTagPtr, ' ');
if(szNext != NULL)
*szNext++ = 0;
// Check whether the current tag is a language identifier
dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr);
// Get the next part
if(szNext == NULL)
break;
// Skip spaces
while(szNext < szTagEnd && szNext[0] == ' ')
szNext++;
szTagPtr = szNext;
}
hs->dwDefaultLocale = dwLocaleMask;
return ERROR_SUCCESS;
}
static void * FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey)
{
TCHAR * szFileName;
void * pvListFile = NULL;
// Construct the local file name
szFileName = CascNewStr(hs->szDataPath, 8 + 3 + 3 + 32);
if(szFileName != NULL)
{
// Add the part where the config file path is
AppendConfigFilePath(szFileName, pFileKey);
// Load and verify the external listfile
pvListFile = ListFile_OpenExternal(szFileName);
if(pvListFile != NULL)
{
if(!ListFile_VerifyMD5(pvListFile, pFileKey->pbData))
{
ListFile_Free(pvListFile);
pvListFile = NULL;
}
}
// Free the file name
CASC_FREE(szFileName);
}
return pvListFile;
}
static int ParseFile_BuildInfo(TCascStorage * hs, void * pvListFile)
{
QUERY_KEY Active = {NULL, 0};
QUERY_KEY TagString = {NULL, 0};
QUERY_KEY CdnHost = {NULL, 0};
QUERY_KEY CdnPath = {NULL, 0};
char szOneLine1[0x200];
char szOneLine2[0x200];
size_t nLength1;
size_t nLength2;
int nError = ERROR_BAD_FORMAT;
// Extract the first line, cotaining the headers
nLength1 = ListFile_GetNextLine(pvListFile, szOneLine1, _maxchars(szOneLine1));
if(nLength1 == 0)
return ERROR_BAD_FORMAT;
// Now parse the second and the next lines. We are looking for line
// with "Active" set to 1
for(;;)
{
const char * szLinePtr1 = szOneLine1;
const char * szLineEnd1 = szOneLine1 + nLength1;
const char * szLinePtr2 = szOneLine2;
const char * szLineEnd2;
// Read the next line
nLength2 = ListFile_GetNextLine(pvListFile, szOneLine2, _maxchars(szOneLine2));
if(nLength2 == 0)
break;
szLineEnd2 = szLinePtr2 + nLength2;
// Parse all variables
while(szLinePtr1 < szLineEnd1)
{
// Check for variables we need
if(IsInfoVariable(szLinePtr1, szLineEnd1, "Active", "DEC"))
LoadInfoVariable(&Active, szLinePtr2, szLineEnd2, false);
if(IsInfoVariable(szLinePtr1, szLineEnd1, "Build Key", "HEX"))
LoadInfoVariable(&hs->CdnBuildKey, szLinePtr2, szLineEnd2, true);
if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Key", "HEX"))
LoadInfoVariable(&hs->CdnConfigKey, szLinePtr2, szLineEnd2, true);
if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Hosts", "STRING"))
LoadInfoVariable(&CdnHost, szLinePtr2, szLineEnd2, false);
if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Path", "STRING"))
LoadInfoVariable(&CdnPath, szLinePtr2, szLineEnd2, false);
if(IsInfoVariable(szLinePtr1, szLineEnd1, "Tags", "STRING"))
LoadInfoVariable(&TagString, szLinePtr2, szLineEnd2, false);
// Move both line pointers
szLinePtr1 = SkipInfoVariable(szLinePtr1, szLineEnd1);
if(szLinePtr1 == NULL)
break;
szLinePtr2 = SkipInfoVariable(szLinePtr2, szLineEnd2);
if(szLinePtr2 == NULL)
break;
}
// Stop parsing if found active config
if(Active.pbData != NULL && *Active.pbData == '1')
break;
// Free the blobs
FreeCascBlob(&Active);
FreeCascBlob(&hs->CdnBuildKey);
FreeCascBlob(&hs->CdnConfigKey);
FreeCascBlob(&CdnHost);
FreeCascBlob(&CdnPath);
FreeCascBlob(&TagString);
}
// All four must be present
if(hs->CdnBuildKey.pbData != NULL &&
hs->CdnConfigKey.pbData != NULL &&
CdnHost.pbData != NULL &&
CdnPath.pbData != NULL)
{
// Merge the CDN host and CDN path
hs->szUrlPath = CASC_ALLOC(TCHAR, CdnHost.cbData + CdnPath.cbData + 1);
if(hs->szUrlPath != NULL)
{
CopyString(hs->szUrlPath, (char *)CdnHost.pbData, CdnHost.cbData);
CopyString(hs->szUrlPath + CdnHost.cbData, (char *)CdnPath.pbData, CdnPath.cbData);
nError = ERROR_SUCCESS;
}
}
// If we found tags, we can extract language build from it
if(TagString.pbData != NULL)
GetDefaultLocaleMask(hs, &TagString);
FreeCascBlob(&CdnHost);
FreeCascBlob(&CdnPath);
FreeCascBlob(&TagString);
FreeCascBlob(&Active);
return nError;
}
static int ParseFile_BuildDb(TCascStorage * hs, void * pvListFile)
{
const char * szLinePtr;
const char * szLineEnd;
char szOneLine[0x200];
size_t nLength;
int nError;
// Load the single line from the text file
nLength = ListFile_GetNextLine(pvListFile, szOneLine, _maxchars(szOneLine));
if(nLength == 0)
return ERROR_BAD_FORMAT;
// Set the line range
szLinePtr = szOneLine;
szLineEnd = szOneLine + nLength;
// Extract the CDN build key
nError = LoadInfoVariable(&hs->CdnBuildKey, szLinePtr, szLineEnd, true);
if(nError == ERROR_SUCCESS)
{
// Skip the variable
szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
// Load the CDN config hash
nError = LoadInfoVariable(&hs->CdnConfigKey, szLinePtr, szLineEnd, true);
if(nError == ERROR_SUCCESS)
{
// Skip the variable
szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
// Skip the Locale/OS/code variable
szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
// Load the URL
hs->szUrlPath = CascNewStrFromAnsi(szLinePtr, szLineEnd);
if(hs->szUrlPath == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
// Verify all variables
if(hs->CdnBuildKey.pbData == NULL || hs->CdnConfigKey.pbData == NULL || hs->szUrlPath == NULL)
nError = ERROR_BAD_FORMAT;
return nError;
}
static int LoadCdnConfigFile(TCascStorage * hs, void * pvListFile)
{
const char * szLineBegin;
const char * szVarBegin;
const char * szLineEnd;
int nError = ERROR_SUCCESS;
// Keep parsing the listfile while there is something in there
for(;;)
{
// Get the next line
if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd))
break;
// Archive group
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archive-group");
if(szVarBegin != NULL)
{
nError = LoadSingleBlob(&hs->ArchivesGroup, szVarBegin, szLineEnd);
continue;
}
// Archives
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archives");
if(szVarBegin != NULL)
{
nError = LoadMultipleBlobs(&hs->ArchivesKey, szVarBegin, szLineEnd);
continue;
}
// Patch archive group
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archive-group");
if(szVarBegin != NULL)
{
LoadSingleBlob(&hs->PatchArchivesKey, szVarBegin, szLineEnd);
continue;
}
// Patch archives
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archives");
if(szVarBegin != NULL)
{
nError = LoadMultipleBlobs(&hs->PatchArchivesKey, szVarBegin, szLineEnd);
continue;
}
}
// Check if all required fields are present
if(hs->ArchivesKey.pbData == NULL || hs->ArchivesKey.cbData == 0)
return ERROR_BAD_FORMAT;
return nError;
}
static int LoadCdnBuildFile(TCascStorage * hs, void * pvListFile)
{
const char * szLineBegin;
const char * szVarBegin;
const char * szLineEnd = NULL;
int nError = ERROR_SUCCESS;
for(;;)
{
// Get the next line
if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd))
break;
// Game name
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-product");
if(szVarBegin != NULL)
{
GetGameType(hs, szVarBegin, szLineEnd);
continue;
}
// Game build number
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-name");
if(szVarBegin != NULL)
{
GetBuildNumber(hs, szVarBegin, szLineEnd);
continue;
}
// Root
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "root");
if(szVarBegin != NULL)
{
LoadSingleBlob(&hs->RootKey, szVarBegin, szLineEnd);
continue;
}
// Patch
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch");
if(szVarBegin != NULL)
{
LoadSingleBlob(&hs->PatchKey, szVarBegin, szLineEnd);
continue;
}
// Download
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "download");
if(szVarBegin != NULL)
{
LoadSingleBlob(&hs->DownloadKey, szVarBegin, szLineEnd);
continue;
}
// Install
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "install");
if(szVarBegin != NULL)
{
LoadSingleBlob(&hs->InstallKey, szVarBegin, szLineEnd);
continue;
}
// Encoding keys
szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "encoding");
if(szVarBegin != NULL)
{
nError = LoadMultipleBlobs(&hs->EncodingKey, szVarBegin, szLineEnd, 2);
continue;
}
}
// Check the encoding keys
if(hs->EncodingKey.pbData == NULL || hs->EncodingKey.cbData != MD5_HASH_SIZE * 2)
return ERROR_BAD_FORMAT;
return nError;
}
static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory)
{
TCHAR * szDataPath;
int nError = ERROR_FILE_NOT_FOUND;
// Try all known subdirectories
for(size_t i = 0; DataDirs[i] != NULL; i++)
{
// Create the eventual data path
szDataPath = CombinePath(szDirectory, DataDirs[i]);
if(szDataPath != NULL)
{
// Does that directory exist?
if(DirectoryExists(szDataPath))
{
hs->szDataPath = szDataPath;
return ERROR_SUCCESS;
}
// Free the data path
CASC_FREE(szDataPath);
}
}
return nError;
}
//-----------------------------------------------------------------------------
// Public functions
int LoadBuildInfo(TCascStorage * hs)
{
PARSEINFOFILE PfnParseProc = NULL;
void * pvListFile;
int nError = ERROR_SUCCESS;
switch(hs->BuildFileType)
{
case CascBuildInfo:
PfnParseProc = ParseFile_BuildInfo;
break;
case CascBuildDb:
PfnParseProc = ParseFile_BuildDb;
break;
default:
nError = ERROR_NOT_SUPPORTED;
break;
}
// Parse the appropriate build file
if(nError == ERROR_SUCCESS)
{
pvListFile = ListFile_OpenExternal(hs->szBuildFile);
if(pvListFile != NULL)
{
// Parse the info file
nError = PfnParseProc(hs, pvListFile);
ListFile_Free(pvListFile);
}
else
nError = ERROR_FILE_NOT_FOUND;
}
// If the .build.info OR .build.db file has been loaded,
// proceed with loading the CDN config file and CDN build file
if(nError == ERROR_SUCCESS)
{
// Load the configuration file
pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey);
if(pvListFile != NULL)
{
nError = LoadCdnConfigFile(hs, pvListFile);
ListFile_Free(pvListFile);
}
else
nError = ERROR_FILE_NOT_FOUND;
}
// Load the build file
if(nError == ERROR_SUCCESS)
{
pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey);
if(pvListFile != NULL)
{
nError = LoadCdnBuildFile(hs, pvListFile);
ListFile_Free(pvListFile);
}
else
nError = ERROR_FILE_NOT_FOUND;
}
// Fill the index directory
if(nError == ERROR_SUCCESS)
{
// First, check for more common "data" subdirectory
if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
return ERROR_SUCCESS;
// Second, try the "darch" subdirectory (older builds of HOTS - Alpha)
if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
return ERROR_SUCCESS;
nError = ERROR_FILE_NOT_FOUND;
}
return nError;
}
// Checks whether there is a ".agent.db". If yes, the function
// sets "szRootPath" and "szDataPath" in the storage structure
// and returns ERROR_SUCCESS
int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory)
{
TFileStream * pStream;
TCHAR * szBuildFile;
int nError = ERROR_FILE_NOT_FOUND;
// Try to find any of the root files used in the history
for(size_t i = 0; BuildTypes[i].szFileName != NULL; i++)
{
// Create the full name of the .agent.db file
szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName);
if(szBuildFile != NULL)
{
// Attempt to open the file
pStream = FileStream_OpenFile(szBuildFile, 0);
if(pStream != NULL)
{
// Free the stream
FileStream_Close(pStream);
// Check for the data directory
nError = CheckDataDirectory(hs, szDirectory);
if(nError == ERROR_SUCCESS)
{
hs->szBuildFile = szBuildFile;
hs->BuildFileType = BuildTypes[i].BuildFileType;
return ERROR_SUCCESS;
}
}
CASC_FREE(szBuildFile);
}
}
return nError;
}
// Parses single line from Overwatch.
// The line structure is: "#MD5|CHUNK_ID|FILENAME|INSTALLPATH"
// The line has all preceding spaces removed
int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, PQUERY_KEY PtrEncodingKey, char * szFileName, size_t nMaxChars)
{
size_t nLength;
int nError;
// Check the MD5 (aka encoding key)
if(szLinePtr[MD5_STRING_SIZE] != '|')
return ERROR_BAD_FORMAT;
// Convert the encoding key to binary
PtrEncodingKey->cbData = MD5_HASH_SIZE;
nError = ConvertStringToBinary(szLinePtr, MD5_STRING_SIZE, PtrEncodingKey->pbData);
if(nError != ERROR_SUCCESS)
return nError;
// Skip the MD5
szLinePtr += MD5_STRING_SIZE+1;
// Skip the chunk ID
szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
// Get the archived file name
szLineEnd = SkipInfoVariable(szLinePtr, szLineEnd);
nLength = (size_t)(szLineEnd - szLinePtr - 1);
// Get the file name
if(nLength > nMaxChars)
return ERROR_INSUFFICIENT_BUFFER;
memcpy(szFileName, szLinePtr, nLength);
szFileName[nLength] = 0;
return ERROR_SUCCESS;
}

View File

@@ -11,7 +11,6 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
#include "CascMndxRoot.h"
//-----------------------------------------------------------------------------
// Local functions
@@ -30,16 +29,22 @@ static void FreeSearchHandle(TCascSearch * pSearch)
// Close (dereference) the archive handle
if(pSearch->hs != NULL)
{
// Give root handler chance to free their stuff
RootHandler_EndSearch(pSearch->hs->pRootHandler, pSearch);
// Dereference the storage handle
CascCloseStorage((HANDLE)pSearch->hs);
pSearch->hs = NULL;
pSearch->hs = NULL;
}
// Free the file cache and frame array
if(pSearch->szMask != NULL)
CASC_FREE(pSearch->szMask);
if(pSearch->szListFile != NULL)
CASC_FREE(pSearch->szListFile);
if(pSearch->pStruct1C != NULL)
delete pSearch->pStruct1C;
// if(pSearch->pStruct1C != NULL)
// delete pSearch->pStruct1C;
if(pSearch->pCache != NULL)
ListFile_Free(pSearch->pCache);
@@ -47,58 +52,6 @@ static void FreeSearchHandle(TCascSearch * pSearch)
pSearch->szClassName = NULL;
CASC_FREE(pSearch);
}
/*
DWORD dwRootEntries = 0;
DWORD dwEncoEntries = 0;
DWORD dwIndexEntries = 0;
*/
static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry, PCASC_FIND_DATA pFindData, size_t nRootIndex)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
PCASC_INDEX_ENTRY pIndexEntry;
TCascStorage * hs = pSearch->hs;
QUERY_KEY QueryKey;
DWORD dwByteIndex = (DWORD)(nRootIndex / 0x08);
DWORD dwBitIndex = (DWORD)(nRootIndex & 0x07);
// First of all, check if that entry has been reported before
// If the bit is set, then the file has already been reported
// by a previous search iteration
if(pSearch->BitArray[dwByteIndex] & (1 << dwBitIndex))
return false;
pSearch->BitArray[dwByteIndex] |= (1 << dwBitIndex);
// Increment the number of root entries
// dwRootEntries++;
// Now try to find that encoding key in the array of encoding keys
QueryKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
QueryKey.cbData = MD5_HASH_SIZE;
pEncodingEntry = FindEncodingEntry(hs, &QueryKey, NULL);
if(pEncodingEntry == NULL)
return false;
// dwEncoEntries++;
// Now try to find the index entry. Note that we take the first key
QueryKey.pbData = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
QueryKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(hs, &QueryKey);
if(pIndexEntry == NULL)
return false;
// dwIndexEntries++;
// Fill the name hash and the MD5
memcpy(pFindData->EncodingKey, pRootEntry->EncodingKey, MD5_HASH_SIZE);
pFindData->FileNameHash = pRootEntry->FileNameHash;
pFindData->dwPackageIndex = 0;
pFindData->dwLocaleFlags = pRootEntry->Locales;
// Fill-in the file size
pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
return true;
}
static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szListFile, const char * szMask)
{
@@ -106,7 +59,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->RootTable.TableSize / 8) : 0);
cbToAllocate = sizeof(TCascSearch) + ((hs->pEncodingMap->TableSize + 7) / 8);
pSearch = (TCascSearch *)CASC_ALLOC(BYTE, cbToAllocate);
if(pSearch != NULL)
{
@@ -125,7 +78,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
// Save the other variables
if(szListFile != NULL)
{
pSearch->szListFile = NewStr(szListFile, 0);
pSearch->szListFile = CascNewStr(szListFile, 0);
if(pSearch->szListFile == NULL)
{
FreeSearchHandle(pSearch);
@@ -134,7 +87,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
}
// Allocate the search mask
pSearch->szMask = NewStr(szMask, 0);
pSearch->szMask = CascNewStr(szMask, 0);
if(pSearch->szMask == NULL)
{
FreeSearchHandle(pSearch);
@@ -145,64 +98,106 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
return pSearch;
}
static bool DoStorageSearch_ListFile(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
// Perform searching using root-specific provider.
// The provider may need the listfile
static bool DoStorageSearch_RootFile(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_ROOT_ENTRY pRootEntry;
TCascStorage * hs = pSearch->hs;
char szFileName2[MAX_PATH + 1];
DWORD TableIndex = 0;
PCASC_ENCODING_ENTRY pEncodingEntry;
PCASC_INDEX_ENTRY pIndexEntry;
QUERY_KEY EncodingKey;
QUERY_KEY IndexKey;
LPBYTE pbEncodingKey;
DWORD EncodingIndex = 0;
DWORD LocaleFlags = 0;
DWORD FileSize = CASC_INVALID_SIZE;
DWORD ByteIndex;
DWORD BitMask;
// Get next file from the listfile
while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
for(;;)
{
#ifdef _DEBUG
// if(!_stricmp(pSearch->szFileName, "Character\\BloodElf\\Female\\DeathKnightEyeGlow.blp"))
// DebugBreak();
#endif
// Attempt to find (the next) file from the root entry
pbEncodingKey = RootHandler_Search(pSearch->hs->pRootHandler, pSearch, &FileSize, &LocaleFlags);
if(pbEncodingKey == NULL)
return false;
// Normalize the file name found in the list file
NormalizeFileName_UpperBkSlash(szFileName2, pSearch->szFileName, MAX_PATH);
// Find the root entry
pRootEntry = FindRootEntry(hs, szFileName2, &TableIndex);
if(pRootEntry != NULL)
// Verify whether the encoding key exists in the encoding table
EncodingKey.pbData = pbEncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
pEncodingEntry = FindEncodingEntry(pSearch->hs, &EncodingKey, &EncodingIndex);
if(pEncodingEntry != NULL)
{
// Verify whether the file exists in the storage
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, TableIndex))
{
strcpy(pFindData->szFileName, pSearch->szFileName);
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
return true;
}
}
}
// Mark the item as already found
// Note: Duplicate items are allowed while we are searching using file names
// Do not exclude items from search if they were found before
ByteIndex = (DWORD)(EncodingIndex / 8);
BitMask = 1 << (EncodingIndex & 0x07);
pSearch->BitArray[ByteIndex] |= BitMask;
// Locate the index entry
IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry);
IndexKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(pSearch->hs, &IndexKey);
if(pIndexEntry == NULL)
continue;
// Listfile search ended
return false;
}
// If we retrieved the file size directly from the root provider, use it
// Otherwise, we need to retrieve it from the encoding entry
if(FileSize == CASC_INVALID_SIZE)
FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_ROOT_ENTRY pRootEntry;
TCascStorage * hs = pSearch->hs;
// Check if there is more files with the same name hash
while(pSearch->RootIndex < hs->RootTable.TableSize)
{
// Get the pointer to the root entry
pRootEntry = hs->RootTable.TablePtr + pSearch->RootIndex;
// Verify if that root entry exists in the CASC storage
// and was not found before
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex))
{
pFindData->szFileName[0] = 0;
pFindData->szPlainName = NULL;
// Fill-in the found file
strcpy(pFindData->szFileName, pSearch->szFileName);
memcpy(pFindData->EncodingKey, pEncodingEntry->EncodingKey, MD5_HASH_SIZE);
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
pFindData->dwLocaleFlags = LocaleFlags;
pFindData->dwFileSize = FileSize;
return true;
}
}
}
// Move to the next entry
pSearch->RootIndex++;
static bool DoStorageSearch_EncodingKey(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
PCASC_INDEX_ENTRY pIndexEntry;
TCascStorage * hs = pSearch->hs;
QUERY_KEY IndexKey;
DWORD ByteIndex;
DWORD BitMask;
// Check for encoding keys that haven't been found yet
while(pSearch->IndexLevel1 < hs->pEncodingMap->TableSize)
{
// Check if that entry has been reported before
ByteIndex = (DWORD)(pSearch->IndexLevel1 / 8);
BitMask = 1 << (pSearch->IndexLevel1 & 0x07);
if((pSearch->BitArray[ByteIndex] & BitMask) == 0)
{
// Locate the index entry
pEncodingEntry = (PCASC_ENCODING_ENTRY)hs->pEncodingMap->HashTable[pSearch->IndexLevel1];
if(pEncodingEntry != NULL)
{
IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry);
IndexKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(pSearch->hs, &IndexKey);
if(pIndexEntry != NULL)
{
// Fill-in the found file
memcpy(pFindData->EncodingKey, pEncodingEntry->EncodingKey, MD5_HASH_SIZE);
pFindData->szFileName[0] = 0;
pFindData->szPlainName = NULL;
pFindData->dwLocaleFlags = CASC_LOCALE_NONE;
pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
// Mark the entry as already-found
pSearch->BitArray[ByteIndex] |= BitMask;
return true;
}
}
}
// Go to the next encoding entry
pSearch->IndexLevel1++;
}
// Nameless search ended
@@ -211,10 +206,6 @@ static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindDat
static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
// Are we searching using the MNDX ?
if(pSearch->hs->pMndxInfo != NULL)
return DoStorageSearch_MNDX(pSearch, pFindData);
// State 0: No search done yet
if(pSearch->dwState == 0)
{
@@ -223,30 +214,25 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile);
// Move the search phase to the listfile searching
pSearch->RootIndex = 0;
pSearch->IndexLevel1 = 0;
pSearch->dwState++;
// If either file stream or listfile cache are invalid,
// move to the next phase
if(pSearch->pCache == NULL)
pSearch->dwState++;
}
// State 1: Searching the list file
if(pSearch->dwState == 1)
{
if(DoStorageSearch_ListFile(pSearch, pFindData))
if(DoStorageSearch_RootFile(pSearch, pFindData))
return true;
// Move to the nameless search state
assert(pSearch->RootIndex == 0);
pSearch->IndexLevel1 = 0;
pSearch->dwState++;
}
// State 2: Searching the remaining entries
if(pSearch->dwState == 2)
{
if(DoStorageSearch_Hash(pSearch, pFindData))
if(DoStorageSearch_EncodingKey(pSearch, pFindData))
return true;
// Move to the final search state
@@ -275,19 +261,12 @@ HANDLE WINAPI CascFindFirstFile(
if(szMask == NULL || pFindData == NULL)
nError = ERROR_INVALID_PARAMETER;
// Allocate the structure for archive search
// Init the search structure and search handle
if(nError == ERROR_SUCCESS)
{
// Clear the entire search structure
memset(pFindData, 0, sizeof(CASC_FIND_DATA));
// We must have listfile for non-MNDX storages
if(hs->pMndxInfo == NULL && szListFile == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// Allocate the search handle
pSearch = AllocateSearchHandle(hs, szListFile, szMask);
if(pSearch == NULL)

View File

@@ -0,0 +1,29 @@
;
; Export file for Windows
; Copyright (c) 2015 Ladislav Zezula
; ladik@zezula.net
;
LIBRARY CascLib.dll
EXPORTS
CascOpenStorage
CascGetStorageInfo
CascCloseStorage
CascOpenFileByIndexKey
CascOpenFileByEncodingKey
CascOpenFile
CascGetFileSize
CascSetFilePointer
CascReadFile
CascCloseFile
CascFindFirstFile
CascFindNextFile
CascFindClose
GetLastError=Kernel32.GetLastError
SetLastError=Kernel32.SetLastError

View File

@@ -39,7 +39,7 @@ extern "C" {
#define CASC_STOR_XXXXX 0x00000001 // Not used
// Values for CascOpenFile
#define CASC_FILE_XXXXX 0x00000001 // Not used
#define CASC_OPEN_BY_ENCODING_KEY 0x00000001 // The name is just the encoding key; skip ROOT file processing
// Flags for file stream
#define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file
@@ -103,7 +103,7 @@ extern "C" {
#ifndef MD5_HASH_SIZE
#define MD5_HASH_SIZE 0x10
#define MD5_STRING_SIZE 0x21
#define MD5_STRING_SIZE 0x20
#endif
#ifndef SHA1_DIGEST_SIZE
@@ -146,9 +146,7 @@ typedef struct _CASC_FIND_DATA
{
char szFileName[MAX_PATH]; // Full name of the found file
char * szPlainName; // Plain name of the found file
ULONGLONG FileNameHash; // File name hash
BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key
DWORD dwPackageIndex; // File package index (HOTS only)
DWORD dwLocaleFlags; // Locale flags (WoW only)
DWORD dwFileSize; // Size of the file
@@ -184,6 +182,16 @@ HANDLE WINAPI CascFindFirstFile(HANDLE hStorage, const char * szMask, PCASC_FIND
bool WINAPI CascFindNextFile(HANDLE hFind, PCASC_FIND_DATA pFindData);
bool WINAPI CascFindClose(HANDLE hFind);
//-----------------------------------------------------------------------------
// GetLastError/SetLastError support for non-Windows platform
#ifndef PLATFORM_WINDOWS
int GetLastError();
void SetLastError(int nError);
#endif // PLATFORM_WINDOWS
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -13,6 +13,9 @@
class TFileNameDatabase;
#define CASC_MAX_MAR_FILES 3 // Maximum of 3 MAR files are supported
#define CASC_MNDX_SIGNATURE 0x58444E4D // 'MNDX'
#define CASC_MAX_ENTRIES(type) (0xFFFFFFFF / sizeof(type))
#define CASC_SEARCH_INITIALIZING 0
@@ -353,13 +356,4 @@ inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex)
return ((Table.NameFragArray[ItemIndex].FragOffs & 0xFFFFFF00) == 0xFFFFFF00);
}
//-----------------------------------------------------------------------------
// CASC functions related to MNDX
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_ENTRY_MNDX * ppFoundInfo);
bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData);
void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo);
#endif // __CASC_MNDX_ROOT__

View File

@@ -11,10 +11,6 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
#include "CascMndxRoot.h"
//-----------------------------------------------------------------------------
// Local structures
//-----------------------------------------------------------------------------
// Local functions
@@ -31,79 +27,19 @@ PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey)
PCASC_INDEX_ENTRY pIndexEntry = NULL;
if(hs->pIndexEntryMap != NULL)
pIndexEntry = (PCASC_INDEX_ENTRY)Map_FindObject(hs->pIndexEntryMap, pIndexKey->pbData);
pIndexEntry = (PCASC_INDEX_ENTRY)Map_FindObject(hs->pIndexEntryMap, pIndexKey->pbData, NULL);
return pIndexEntry;
}
PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex)
PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, PDWORD PtrIndex)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
size_t StartEntry = 0;
size_t MidlEntry;
size_t EndEntry = hs->nEncodingEntries;
int nResult;
PCASC_ENCODING_ENTRY pEncodingEntry = NULL;
// Perform binary search
while(StartEntry < EndEntry)
{
// Calculate the middle of the interval
MidlEntry = StartEntry + ((EndEntry - StartEntry) / 2);
pEncodingEntry = hs->ppEncodingEntries[MidlEntry];
if(hs->pEncodingMap != NULL)
pEncodingEntry = (PCASC_ENCODING_ENTRY)Map_FindObject(hs->pEncodingMap, pEncodingKey->pbData, PtrIndex);
// Did we find it?
nResult = memcmp(pEncodingKey->pbData, pEncodingEntry->EncodingKey, MD5_HASH_SIZE);
if(nResult == 0)
{
if(PtrIndex != NULL)
PtrIndex[0] = MidlEntry;
return pEncodingEntry;
}
// Move the interval to the left or right
(nResult < 0) ? EndEntry = MidlEntry : StartEntry = MidlEntry + 1;
}
// Not found, sorry
return NULL;
}
// Also used in CascSearchFile
PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex)
{
PCASC_ROOT_ENTRY pRootEntry;
ULONGLONG FileNameHash;
DWORD TableIndex;
uint32_t dwHashHigh = 0;
uint32_t dwHashLow = 0;
// Calculate the HASH value of the normalized file name
hashlittle2(szFileName, strlen(szFileName), &dwHashHigh, &dwHashLow);
FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
// Get the first table index
TableIndex = (DWORD)(FileNameHash & (hs->RootTable.TableSize - 1));
assert(hs->RootTable.ItemCount < hs->RootTable.TableSize);
// Search the proper entry
for(;;)
{
// Does the has match?
pRootEntry = hs->RootTable.TablePtr + TableIndex;
if(pRootEntry->FileNameHash == FileNameHash)
{
if(PtrTableIndex != NULL)
PtrTableIndex[0] = TableIndex;
return pRootEntry;
}
// 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));
}
return pEncodingEntry;
}
static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexEntry)
@@ -187,7 +123,7 @@ static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DW
// 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;
IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry);
IndexKey.cbData = MD5_HASH_SIZE;
if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, ppCascFile))
{
@@ -259,13 +195,10 @@ bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey,
bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile)
{
PCASC_ROOT_ENTRY_MNDX pRootEntryMndx = NULL;
PCASC_ROOT_ENTRY pRootEntry;
PCASC_PACKAGE pPackage;
TCascStorage * hs;
QUERY_KEY EncodingKey;
char * szStrippedName;
char szFileName2[MAX_PATH+1];
LPBYTE pbEncodingKey;
BYTE KeyBuffer[MD5_HASH_SIZE];
int nError = ERROR_SUCCESS;
CASCLIB_UNUSED(dwLocale);
@@ -285,55 +218,37 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
return false;
}
// If the storage has a MNDX root directory, use it to search the entry
if(hs->pMndxInfo != NULL)
// If the user is opening the file via encoding key, skip the ROOT file processing
if((dwFlags & CASC_OPEN_BY_ENCODING_KEY) == 0)
{
// Convert the file name to lowercase + slashes
NormalizeFileName_LowerSlash(szFileName2, szFileName, MAX_PATH);
// Find the package number
pPackage = FindMndxPackage(hs, szFileName2);
if(pPackage != NULL)
// Let the root directory provider get us the encoding key
pbEncodingKey = RootHandler_GetKey(hs->pRootHandler, szFileName);
if(pbEncodingKey == NULL)
{
// Cut the package name off the full path
szStrippedName = szFileName2 + pPackage->nLength;
while(szStrippedName[0] == '/')
szStrippedName++;
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
}
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntryMndx);
if(nError == ERROR_SUCCESS)
{
// Prepare the encoding key
EncodingKey.pbData = pRootEntryMndx->EncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
}
}
else
{
nError = ERROR_FILE_NOT_FOUND;
}
// Setup the encoding key
EncodingKey.pbData = pbEncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
}
else
{
// Convert the file name to lowercase + slashes
NormalizeFileName_UpperBkSlash(szFileName2, szFileName, MAX_PATH);
// Check the length of the file name
if(strlen(szFileName) < MD5_STRING_SIZE)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Check the root directory for that hash
pRootEntry = FindRootEntry(hs, szFileName2, NULL);
if(pRootEntry != NULL)
{
// Prepare the root key
EncodingKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
EncodingKey.cbData = MD5_HASH_SIZE;
nError = ERROR_SUCCESS;
}
else
{
nError = ERROR_FILE_NOT_FOUND;
}
// Convert the file name to binary blob
EncodingKey.pbData = KeyBuffer;
EncodingKey.cbData = MD5_HASH_SIZE;
nError = ConvertStringToBinary(szFileName, MD5_STRING_SIZE, KeyBuffer);
}
// Use the root key to find the file in the encoding table entry
// Use the encoding key to find the file in the encoding table entry
if(nError == ERROR_SUCCESS)
{
if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, (TCascFile **)phFile))
@@ -344,10 +259,10 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
}
#ifdef CASCLIB_TEST
if(phFile[0] != NULL && pRootEntryMndx != NULL)
{
((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize;
}
// if(phFile[0] != NULL && pRootEntryMndx != NULL)
// {
// ((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize;
// }
#endif
if(nError != ERROR_SUCCESS)

View File

@@ -13,19 +13,12 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#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_INITIAL_ROOT_TABLE_SIZE 0x00100000
// Size of one segment in the ENCODING table
// The segment is filled by entries of type
#define CASC_ENCODING_SEGMENT_SIZE 0x1000
typedef struct _BLOCK_SIZE_AND_HASH
@@ -67,25 +60,10 @@ typedef struct _FILE_INDEX_HEADER_V2
} FILE_INDEX_HEADER_V2, *PFILE_INDEX_HEADER_V2;
typedef struct _FILE_ENCODING_HEADER
{
BYTE Magic[2]; // "EN"
BYTE field_2;
BYTE field_3;
BYTE field_4;
BYTE field_5[2];
BYTE field_7[2];
BYTE NumSegments[4]; // Number of entries (big endian)
BYTE field_D[4];
BYTE field_11;
BYTE SegmentsPos[4]; // Offset of encoding segments
} FILE_ENCODING_HEADER, *PFILE_ENCODING_HEADER;
typedef struct _FILE_ENCODING_SEGMENT
{
BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment
BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment
BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
} FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT;
@@ -143,28 +121,6 @@ static bool IsIndexFileName_V2(const TCHAR * szFileName)
_tcsicmp(szFileName + 0x0A, _T(".idx")) == 0);
}
static void QUERY_KEY_Free(PQUERY_KEY pBlob)
{
if(pBlob != NULL)
{
if(pBlob->pbData != NULL)
CASC_FREE(pBlob->pbData);
pBlob->pbData = NULL;
pBlob->cbData = 0;
}
}
static void QUERY_KEY_FreeArray(PQUERY_KEY pBlobArray)
{
// Free the buffer in the first blob
// (will also free all buffers in the array)
QUERY_KEY_Free(pBlobArray);
// Free the array itself
CASC_FREE(pBlobArray);
}
static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData)
{
PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData;
@@ -206,52 +162,99 @@ static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData)
return (HashHigh == pSizeAndHash->dwBlockHash);
}
LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
static bool CutLastPathPart(TCHAR * szWorkPath)
{
// Validate the file locale block
pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
if(pbFilePointer > pbFileEnd)
return NULL;
size_t nLength = _tcslen(szWorkPath);
// Validate the array of 32-bit integers
pBlockInfo->pInt32Array = (PDWORD)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pInt32Array + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
if(pbFilePointer > pbFileEnd)
return NULL;
for(nLength = _tcslen(szWorkPath); nLength > 0; nLength--)
{
if(szWorkPath[nLength] == '\\' || szWorkPath[nLength] == '/')
{
szWorkPath[nLength] = 0;
return true;
}
}
// Validate the array of root entries
pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
if(pbFilePointer > pbFileEnd)
return NULL;
return false;
}
// Return the position of the next block
return pbFilePointer;
static int InsertExtraFile(
TCascStorage * hs,
const char * szFileName,
PQUERY_KEY pQueryKey)
{
// If the given key is not encoding key (aka, it's an index key),
// we need to create a fake encoding entry
if(pQueryKey->cbData == MD5_HASH_SIZE * 2)
{
PCASC_ENCODING_ENTRY pNewEntry;
PCASC_INDEX_ENTRY pIndexEntry;
QUERY_KEY IndexKey;
// Find the entry in the index table in order to get the file size
IndexKey.pbData = pQueryKey->pbData + MD5_HASH_SIZE;
IndexKey.cbData = MD5_HASH_SIZE;
pIndexEntry = FindIndexEntry(hs, &IndexKey);
if(pIndexEntry == NULL)
return ERROR_FILE_NOT_FOUND;
// Create a fake entry in the encoding map
pNewEntry = (PCASC_ENCODING_ENTRY)Array_Insert(&hs->ExtraEntries, NULL, 1);
if(pNewEntry == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill the encoding entry
pNewEntry->KeyCount = 1;
pNewEntry->FileSizeBE[0] = pIndexEntry->FileSizeLE[3];
pNewEntry->FileSizeBE[1] = pIndexEntry->FileSizeLE[2];
pNewEntry->FileSizeBE[2] = pIndexEntry->FileSizeLE[1];
pNewEntry->FileSizeBE[3] = pIndexEntry->FileSizeLE[0];
memcpy(pNewEntry->EncodingKey, pQueryKey->pbData, MD5_HASH_SIZE);
memcpy(pNewEntry + 1, pQueryKey->pbData + MD5_HASH_SIZE, MD5_HASH_SIZE);
// Insert the entry to the map of encoding keys
Map_InsertObject(hs->pEncodingMap, pNewEntry, pNewEntry->EncodingKey);
}
// Now we need to insert the entry to the root handler in order
// to be able to translate file name to encoding key
return RootHandler_Insert(hs->pRootHandler, szFileName, pQueryKey->pbData);
}
static int InitializeCascDirectories(TCascStorage * hs, const TCHAR * szDataPath)
{
TCHAR * szLastPathPart;
TCHAR * szWorkPath;
int nError = ERROR_NOT_ENOUGH_MEMORY;
// Save the game data directory
hs->szDataPath = NewStr(szDataPath, 0);
// Save the root game directory
hs->szRootPath = NewStr(szDataPath, 0);
// Find the last part
szLastPathPart = hs->szRootPath;
for(size_t i = 0; hs->szRootPath[i] != 0; i++)
// Find the root directory of the storage. The root directory
// is the one where ".build.info" is.
szWorkPath = CascNewStr(szDataPath, 0);
if(szWorkPath != NULL)
{
if(hs->szRootPath[i] == '\\' || hs->szRootPath[i] == '/')
szLastPathPart = hs->szRootPath + i;
}
// Cut the last part
if(szLastPathPart != NULL)
szLastPathPart[0] = 0;
return (hs->szRootPath && hs->szDataPath) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
// Get the length and go up until we find the ".build.info" or ".build.db"
for(;;)
{
// Is this a game directory?
nError = CheckGameDirectory(hs, szWorkPath);
if(nError == ERROR_SUCCESS)
{
nError = ERROR_SUCCESS;
break;
}
// Cut one path part
if(!CutLastPathPart(szWorkPath))
{
nError = ERROR_FILE_NOT_FOUND;
break;
}
}
// Free the work path buffer
CASC_FREE(szWorkPath);
}
return nError;
}
static bool IndexDirectory_OnFileFound(
@@ -566,7 +569,7 @@ static int CreateArrayOfIndexEntries(TCascStorage * hs)
// 9e dc a7 8f e2 09 ad d8 b7 (encoding file)
// f3 5e bb fb d1 2b 3f ef 8b
// c8 69 9f 18 a2 5e df 7e 52
Map_InsertObject(pMap, pIndexEntry->IndexKey);
Map_InsertObject(pMap, pIndexEntry, pIndexEntry->IndexKey);
// Move to the next entry
pIndexEntry++;
@@ -581,28 +584,28 @@ static int CreateArrayOfIndexEntries(TCascStorage * hs)
return nError;
}
static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumberOfSegments)
static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumSegments)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
size_t nMaxEntries;
size_t nEntries = 0;
DWORD dwMaxEntries;
int nError = ERROR_SUCCESS;
// Sanity check
assert(hs->ppEncodingEntries == NULL);
assert(hs->pIndexEntryMap != NULL);
assert(hs->pEncodingMap == NULL);
// Calculate the largest eventual number of encodign entries
nMaxEntries = (dwNumberOfSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE);
// Calculate the largest eventual number of encoding entries
// Add space for extra entries
dwMaxEntries = (dwNumSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE);
// Allocate the array of pointers to encoding entries
hs->ppEncodingEntries = CASC_ALLOC(PCASC_ENCODING_ENTRY, nMaxEntries);
if(hs->ppEncodingEntries != NULL)
// Create the map of the encoding entries
hs->pEncodingMap = Map_Create(dwMaxEntries + CASC_EXTRA_FILES, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ENCODING_ENTRY, EncodingKey));
if(hs->pEncodingMap != NULL)
{
LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumberOfSegments);
LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments);
// Parse all segments
for(DWORD i = 0; i < dwNumberOfSegments; i++)
for(DWORD i = 0; i < dwNumSegments; i++)
{
LPBYTE pbEncodingEntry = pbStartOfSegment;
LPBYTE pbEndOfSegment = pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE - sizeof(CASC_ENCODING_ENTRY) - MD5_HASH_SIZE;
@@ -616,7 +619,7 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn
break;
// Insert the pointer the array
hs->ppEncodingEntries[nEntries++] = pEncodingEntry;
Map_InsertObject(hs->pEncodingMap, pEncodingEntry, pEncodingEntry->EncodingKey);
// Move to the next encoding entry
pbEncodingEntry += sizeof(CASC_ENCODING_ENTRY) + (pEncodingEntry->KeyCount * MD5_HASH_SIZE);
@@ -625,9 +628,6 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn
// Move to the next segment
pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE;
}
// Remember the total number of encoding entries
hs->nEncodingEntries = nEntries;
}
else
nError = ERROR_NOT_ENOUGH_MEMORY;
@@ -684,8 +684,16 @@ static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
if(dwBytesRead == sizeof(CASC_ENCODING_HEADER))
{
dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.NumSegments);
dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.SegmentsPos);
// Check the version and sizes
if(EncodingHeader.Version != 0x01 || EncodingHeader.ChecksumSizeA != MD5_HASH_SIZE || EncodingHeader.ChecksumSizeB != MD5_HASH_SIZE)
{
assert(false);
return NULL;
}
// Get the number of segments
dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.Entries_TableA);
dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.Size_StringTable1);
if(EncodingHeader.Magic[0] == 'E' && EncodingHeader.Magic[1] == 'N' && dwSegmentPos != 0 && dwNumSegments != 0)
nError = ERROR_SUCCESS;
}
@@ -721,36 +729,16 @@ static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
{
TCascFile * hf;
LPBYTE pbRootFile = NULL;
DWORD cbRootFile = 0;
DWORD dwBytesRead = 0;
BYTE StartOfFile[0x10];
int nError = ERROR_SUCCESS;
// Dummy read the first 16 bytes
CascReadFile(hFile, &StartOfFile, sizeof(StartOfFile), &dwBytesRead);
if(dwBytesRead != sizeof(StartOfFile))
// Retrieve the size of the ROOT file
cbRootFile = CascGetFileSize(hFile, NULL);
if(cbRootFile == 0)
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)
{
@@ -762,12 +750,9 @@ static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
// 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)))
// Read the entire file to memory
CascReadFile(hFile, pbRootFile, cbRootFile, &dwBytesRead);
if(dwBytesRead != cbRootFile)
nError = ERROR_FILE_CORRUPT;
}
@@ -780,17 +765,19 @@ static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
static int LoadEncodingFile(TCascStorage * hs)
{
PFILE_ENCODING_SEGMENT pEncodingSegment;
PCASC_ENCODING_ENTRY pEncodingEntry;
QUERY_KEY EncodingKey;
LPBYTE pbStartOfSegment;
LPBYTE pbEncodingFile = NULL;
HANDLE hFile = NULL;
DWORD cbEncodingFile = 0;
DWORD dwNumberOfSegments = 0;
DWORD dwNumSegments = 0;
DWORD dwSegmentsPos = 0;
int nError = ERROR_SUCCESS;
// Open the encoding file
if(!CascOpenFileByIndexKey((HANDLE)hs, &hs->EncodingEKey, 0, &hFile))
EncodingKey.pbData = hs->EncodingKey.pbData + MD5_HASH_SIZE;
EncodingKey.cbData = MD5_HASH_SIZE;
if(!CascOpenFileByIndexKey((HANDLE)hs, &EncodingKey, 0, &hFile))
nError = GetLastError();
// Load the entire ENCODING file to memory
@@ -808,20 +795,25 @@ static int LoadEncodingFile(TCascStorage * hs)
// Verify all encoding segments
if(nError == ERROR_SUCCESS)
{
// Save the encoding header
hs->pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile;
PCASC_ENCODING_HEADER pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile;
// Convert size and offset
dwNumberOfSegments = ConvertBytesToInteger_4(hs->pEncodingHeader->NumSegments);
dwSegmentsPos = ConvertBytesToInteger_4(hs->pEncodingHeader->SegmentsPos);
dwNumSegments = ConvertBytesToInteger_4(pEncodingHeader->Entries_TableA);
dwSegmentsPos = ConvertBytesToInteger_4(pEncodingHeader->Size_StringTable1);
// Store the encoding file to the CASC storage
hs->EncodingFile.pbData = pbEncodingFile;
hs->EncodingFile.cbData = cbEncodingFile;
// Allocate the array of encoding segments
pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumberOfSegments);
pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments);
// Go through all encoding segments and verify them
for(DWORD i = 0; i < dwNumberOfSegments; i++)
for(DWORD i = 0; i < dwNumSegments; i++)
{
PCASC_ENCODING_ENTRY pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment;
// Check if there is enough space in the buffer
if((pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE) > (pbEncodingFile + cbEncodingFile))
{
@@ -837,8 +829,7 @@ static int LoadEncodingFile(TCascStorage * hs)
// break;
// }
// Check if the encoding key matches
pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment;
// Check if the encoding key matches with the expected first value
if(memcmp(pEncodingEntry->EncodingKey, pEncodingSegment->FirstEncodingKey, MD5_HASH_SIZE))
{
nError = ERROR_FILE_CORRUPT;
@@ -856,241 +847,12 @@ static int LoadEncodingFile(TCascStorage * hs)
if(nError == ERROR_SUCCESS)
{
pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
nError = CreateMapOfEncodingKeys(hs, pEncodingSegment, dwNumberOfSegments);
nError = CreateMapOfEncodingKeys(hs, pEncodingSegment, dwNumSegments);
}
return nError;
}
typedef struct _CHECK_ROOT_ENTRY_INPUT
{
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;
// Now parse the root file
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
{
// Validate the file locale block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// WoW.exe (build 19116): Entries with flag 0x100 set are skipped
if(BlockInfo.pLocaleBlockHdr->Flags & 0x100)
continue;
// 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++)
{
// (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];
// Insert the root table item to the hash table
CascRootTable_InsertTableEntry(&hs->RootTable, &NewRootEntry);
NewRootEntry.SumValue++;
}
}
return 1;
}
// 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;
@@ -1100,49 +862,70 @@ static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask)
int nError = ERROR_SUCCESS;
// Sanity checks
assert(hs->RootTable.TablePtr == NULL);
assert(hs->RootTable.ItemCount == 0);
assert(hs->ppEncodingEntries != NULL);
assert(hs->pEncodingMap != NULL);
assert(hs->pRootHandler == NULL);
// Locale: The default parameter is 0 - in that case,
// we assign the default locale, loaded from the .build.info file
if(dwLocaleMask == 0)
dwLocaleMask = hs->dwDefaultLocale;
// 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+)
// Note: The "root" key file's MD5 hash is equal to its name
// in the configuration
// Load the entire ROOT file to memory
if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile))
nError = GetLastError();
// Load the entire ROOT file to memory
// Load the entire file to memory
if(nError == ERROR_SUCCESS)
{
// 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 encoding file
CascCloseFile(hFile);
}
// Check if the file is a MNDX file
if(nError == ERROR_SUCCESS)
// Check if the version of the ROOT file
if(nError == ERROR_SUCCESS && pbRootFile != NULL)
{
FileSignature = (PDWORD)pbRootFile;
if(FileSignature[0] == CASC_MNDX_SIGNATURE)
switch(FileSignature[0])
{
nError = LoadMndxRootFile(hs, pbRootFile, cbRootFile);
}
else
{
// WOW6: 00415000
nError = LoadWowRootFile(hs, pbRootFile, cbRootFile, dwLocaleMask);
case CASC_MNDX_ROOT_SIGNATURE:
nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile);
break;
case CASC_DIABLO3_ROOT_SIGNATURE:
nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile);
break;
case CASC_OVERWATCH_ROOT_SIGNATURE:
nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile);
break;
default:
nError = RootHandler_CreateWoW6(hs, pbRootFile, cbRootFile, dwLocaleMask);
break;
}
}
// Insert entry for the
if(nError == ERROR_SUCCESS)
{
InsertExtraFile(hs, "ENCODING", &hs->EncodingKey);
InsertExtraFile(hs, "ROOT", &hs->RootKey);
InsertExtraFile(hs, "DOWNLOAD", &hs->DownloadKey);
InsertExtraFile(hs, "INSTALL", &hs->InstallKey);
}
#ifdef _DEBUG
if(nError == ERROR_SUCCESS)
{
//RootFile_Dump(hs,
// pbRootFile,
// cbRootFile,
// _T("\\casc_root_%build%.txt"),
// _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"),
// DUMP_LEVEL_INDEX_ENTRIES);
}
#endif
// Free the root file
CASC_FREE(pbRootFile);
return nError;
@@ -1154,19 +937,19 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
if(hs != NULL)
{
// Free the MNDX info
if(hs->pPackages != NULL)
CASC_FREE(hs->pPackages);
if(hs->pMndxInfo != NULL)
FreeMndxInfo(hs->pMndxInfo);
// Free the root handler
if(hs->pRootHandler != NULL)
RootHandler_Close(hs->pRootHandler);
hs->pRootHandler = NULL;
// Free the extra encoding entries
Array_Free(&hs->ExtraEntries);
// Free the pointers to file entries
if(hs->RootTable.TablePtr != NULL)
CASC_FREE(hs->RootTable.TablePtr);
if(hs->ppEncodingEntries != NULL)
CASC_FREE(hs->ppEncodingEntries);
if(hs->pEncodingHeader != NULL)
CASC_FREE(hs->pEncodingHeader);
if(hs->pEncodingMap != NULL)
Map_Free(hs->pEncodingMap);
if(hs->EncodingFile.pbData != NULL)
CASC_FREE(hs->EncodingFile.pbData);
if(hs->pIndexEntryMap != NULL)
Map_Free(hs->pIndexEntryMap);
@@ -1195,25 +978,24 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
CASC_FREE(hs->szRootPath);
if(hs->szDataPath != NULL)
CASC_FREE(hs->szDataPath);
if(hs->szBuildFile != NULL)
CASC_FREE(hs->szBuildFile);
if(hs->szIndexPath != NULL)
CASC_FREE(hs->szIndexPath);
if(hs->szUrlPath != NULL)
CASC_FREE(hs->szUrlPath);
// Fre the blob arrays
QUERY_KEY_FreeArray(hs->pArchiveArray);
QUERY_KEY_FreeArray(hs->pPatchArchiveArray);
QUERY_KEY_FreeArray(hs->pEncodingKeys);
// Free the blobs
QUERY_KEY_Free(&hs->CdnConfigKey);
QUERY_KEY_Free(&hs->CdnBuildKey);
QUERY_KEY_Free(&hs->ArchiveGroup);
QUERY_KEY_Free(&hs->PatchArchiveGroup);
QUERY_KEY_Free(&hs->RootKey);
QUERY_KEY_Free(&hs->PatchKey);
QUERY_KEY_Free(&hs->DownloadKey);
QUERY_KEY_Free(&hs->InstallKey);
FreeCascBlob(&hs->CdnConfigKey);
FreeCascBlob(&hs->CdnBuildKey);
FreeCascBlob(&hs->ArchivesGroup);
FreeCascBlob(&hs->ArchivesKey);
FreeCascBlob(&hs->PatchArchivesKey);
FreeCascBlob(&hs->RootKey);
FreeCascBlob(&hs->PatchKey);
FreeCascBlob(&hs->DownloadKey);
FreeCascBlob(&hs->InstallKey);
FreeCascBlob(&hs->EncodingKey);
// Free the storage structure
hs->szClassName = NULL;
@@ -1266,6 +1048,13 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE
nError = LoadEncodingFile(hs);
}
// Initialize the dynamic array for extra files
// Reserve space for 0x20 encoding entries
if(nError == ERROR_SUCCESS)
{
nError = Array_Create(&hs->ExtraEntries, CASC_ENCODING_ENTRY_1, CASC_EXTRA_FILES);
}
// Load the index files
if(nError == ERROR_SUCCESS)
{
@@ -1309,8 +1098,7 @@ bool WINAPI CascGetStorageInfo(
break;
case CascStorageFeatures:
if(hs->pMndxInfo != NULL)
dwInfoValue |= CASC_FEATURE_LISTFILE;
dwInfoValue |= (hs->pRootHandler->dwRootFlags & ROOT_FLAG_HAS_NAMES) ? CASC_FEATURE_LISTFILE : 0;
break;
case CascStorageGameInfo:
@@ -1342,8 +1130,6 @@ bool WINAPI CascGetStorageInfo(
return true;
}
bool WINAPI CascCloseStorage(HANDLE hStorage)
{
TCascStorage * hs;

View File

@@ -176,6 +176,7 @@
#define _tcsrchr strrchr
#define _tcsstr strstr
#define _tcsspn strspn
#define _tcsncmp strncmp
#define _tprintf printf
#define _stprintf sprintf
#define _tremove remove

View File

@@ -98,9 +98,10 @@ static int LoadFileFrames(TCascFile * hf)
else
nError = GetLastError();
// Note: Do not take the FileSize from the sum of frames.
// This value is invalid when loading the ENCODING file.
// hf->FileSize = FileSize;
// Note: on ENCODING file, this value is almost always bigger
// then the real size of ENCODING. We handle this problem
// by calculating size of the ENCODIG file from its header.
hf->FileSize = FileSize;
#ifdef CASCLIB_TEST
hf->FileSize_FrameSum = FileSize;
@@ -264,6 +265,85 @@ static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer)
return NULL;
}
static int ProcessFileFrame(
LPBYTE pbOutBuffer,
DWORD cbOutBuffer,
LPBYTE pbInBuffer,
DWORD cbInBuffer,
DWORD dwFrameIndex)
{
LPBYTE pbTempBuffer;
LPBYTE pbWorkBuffer;
DWORD cbTempBuffer = CASCLIB_MAX(cbInBuffer, cbOutBuffer);
DWORD cbWorkBuffer = cbOutBuffer + 1;
DWORD dwStepCount = 0;
bool bWorkComplete = false;
int nError = ERROR_SUCCESS;
// Allocate the temporary buffer that will serve as output
pbWorkBuffer = pbTempBuffer = CASC_ALLOC(BYTE, cbTempBuffer);
if(pbWorkBuffer == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Perform the loop
for(;;)
{
// Set the output buffer.
// Even operations: extract to temporary buffer
// Odd operations: extract to output buffer
pbWorkBuffer = (dwStepCount & 0x01) ? pbOutBuffer : pbTempBuffer;
cbWorkBuffer = (dwStepCount & 0x01) ? cbOutBuffer : cbTempBuffer;
// Perform the operation specific to the operation ID
switch(pbInBuffer[0])
{
case 'E': // Encrypted files
nError = CascDecrypt(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex);
bWorkComplete = (nError != ERROR_SUCCESS);
break;
case 'Z': // ZLIB compressed files
nError = CascDecompress(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1);
bWorkComplete = true;
break;
case 'N': // Normal stored files
nError = CascDirectCopy(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1);
bWorkComplete = true;
break;
case 'F': // Recursive frames - not supported
default: // Unrecognized - if we unpacked something, we consider it done
nError = ERROR_NOT_SUPPORTED;
bWorkComplete = true;
assert(false);
break;
}
// Are we done?
if(bWorkComplete)
break;
// Set the input buffer to the work buffer
pbInBuffer = pbWorkBuffer;
cbInBuffer = cbWorkBuffer;
dwStepCount++;
}
// If the data are currently in the temporary buffer,
// we need to copy them to output buffer
if(nError == ERROR_SUCCESS && pbWorkBuffer != pbOutBuffer)
{
if(cbWorkBuffer != cbOutBuffer)
nError = ERROR_INSUFFICIENT_BUFFER;
memcpy(pbOutBuffer, pbWorkBuffer, cbOutBuffer);
}
// Free the temporary buffer
CASC_FREE(pbTempBuffer);
return nError;
}
//-----------------------------------------------------------------------------
// Public functions
@@ -299,7 +379,7 @@ DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
}
// Make sure that the file header area is loaded
nError = EnsureHeaderAreaIsLoaded(hf);
nError = EnsureFrameHeadersLoaded(hf);
if(nError != ERROR_SUCCESS)
{
SetLastError(nError);
@@ -387,7 +467,6 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
DWORD dwFilePointer = 0;
DWORD dwEndPointer = 0;
DWORD dwFrameSize;
DWORD cbOutBuffer;
bool bReadResult;
int nError = ERROR_SUCCESS;
@@ -423,7 +502,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
{
// Get the frame
pFrame = FindFileFrame(hf, hf->FilePointer);
if(pFrame == NULL)
if(pFrame == NULL || pFrame->CompressedSize < 1)
nError = ERROR_FILE_CORRUPT;
}
@@ -439,7 +518,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
// Perform block read from each file frame
while(dwFilePointer < dwEndPointer)
{
LPBYTE pbRawData = NULL;
LPBYTE pbFrameData = NULL;
DWORD dwFrameStart = pFrame->FrameFileOffset;
DWORD dwFrameEnd = pFrame->FrameFileOffset + pFrame->FrameSize;
@@ -457,8 +536,8 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
}
// We also need to allocate buffer for the raw data
pbRawData = CASC_ALLOC(BYTE, pFrame->CompressedSize);
if(pbRawData == NULL)
pbFrameData = CASC_ALLOC(BYTE, pFrame->CompressedSize);
if(pbFrameData == NULL)
{
nError = ERROR_NOT_ENOUGH_MEMORY;
break;
@@ -466,7 +545,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
// Load the raw file data to memory
FileOffset = pFrame->FrameArchiveOffset;
bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbRawData, pFrame->CompressedSize);
bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbFrameData, pFrame->CompressedSize);
// Note: The raw file data size could be less than expected
// Happened in WoW build 19342 with the ROOT file. MD5 in the frame header
@@ -484,43 +563,34 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
// If the frame offset is before EOF and frame end is beyond EOF, correct it
if(FileOffset < StreamSize && dwFrameSize < pFrame->CompressedSize)
{
memset(pbRawData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize));
memset(pbFrameData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize));
bReadResult = true;
}
}
// If the read result failed, we cannot finish reading it
if(bReadResult == false)
if(bReadResult && VerifyDataBlockHash(pbFrameData, pFrame->CompressedSize, pFrame->md5))
{
CASC_FREE(pbRawData);
nError = GetLastError();
break;
// Convert the source frame to the file cache
nError = ProcessFileFrame(hf->pbFileCache,
pFrame->FrameSize,
pbFrameData,
pFrame->CompressedSize,
(DWORD)(pFrame - hf->pFrames));
if(nError == ERROR_SUCCESS)
{
// Set the start and end of the cache
hf->CacheStart = dwFrameStart;
hf->CacheEnd = dwFrameEnd;
}
}
// Verify the block MD5
if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
else
{
CASC_FREE(pbRawData);
nError = ERROR_FILE_CORRUPT;
break;
}
// Decompress the file frame
cbOutBuffer = pFrame->FrameSize;
nError = CascDecompress(hf->pbFileCache, &cbOutBuffer, pbRawData, pFrame->CompressedSize);
if(nError != ERROR_SUCCESS || cbOutBuffer != pFrame->FrameSize)
{
CASC_FREE(pbRawData);
nError = ERROR_FILE_CORRUPT;
break;
}
// Set the start and end of the cache
hf->CacheStart = dwFrameStart;
hf->CacheEnd = dwFrameEnd;
// Free the decompress buffer, if needed
CASC_FREE(pbRawData);
// Free the raw frame data
CASC_FREE(pbFrameData);
}
// Copy the decompressed data

File diff suppressed because it is too large Load Diff

View File

@@ -12,20 +12,20 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
#include "CascMndxRoot.h"
#include "CascMndx.h"
//-----------------------------------------------------------------------------
// Local defines
#define CASC_MAR_SIGNATURE 0x0052414d // 'MAR\0'
#define CASC_MAR_SIGNATURE 0x0052414d // 'MAR\0'
//-----------------------------------------------------------------------------
// Local structures
typedef struct _FILE_MNDX_HEADER
{
DWORD Signature; // 'MNDX'
DWORD HeaderVersion; // Must be <= 2
DWORD Signature; // 'MNDX'
DWORD HeaderVersion; // Must be <= 2
DWORD FormatVersion;
} FILE_MNDX_HEADER, *PFILE_MNDX_HEADER;
@@ -39,6 +39,57 @@ typedef struct _FILE_MAR_INFO
DWORD MarDataOffsetHi;
} FILE_MAR_INFO, *PFILE_MAR_INFO;
typedef struct _CASC_MNDX_INFO
{
BYTE RootFileName[MD5_HASH_SIZE]; // Name (aka MD5) of the root file
DWORD HeaderVersion; // Must be <= 2
DWORD FormatVersion;
DWORD field_1C;
DWORD field_20;
DWORD MarInfoOffset; // Offset of the first MAR entry info
DWORD MarInfoCount; // Number of the MAR info entries
DWORD MarInfoSize; // Size of the MAR info entry
DWORD MndxEntriesOffset;
DWORD MndxEntriesTotal; // Total number of MNDX root entries
DWORD MndxEntriesValid; // Number of valid MNDX root entries
DWORD MndxEntrySize; // Size of one MNDX root entry
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_ROOT_ENTRY_MNDX pMndxEntries;
// PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
bool bRootFileLoaded; // true if the root info file was properly loaded
} CASC_MNDX_INFO, *PCASC_MNDX_INFO;
typedef struct _CASC_MNDX_PACKAGE
{
char * szFileName; // Pointer to file name
size_t nLength; // Length of the file name
} CASC_MNDX_PACKAGE, *PCASC_MNDX_PACKAGE;
typedef struct _CASC_MNDX_PACKAGES
{
char * szNameBuffer; // Pointer to the buffer for file names
size_t NameEntries; // Number of name entries in Names
size_t NameBufferUsed; // Number of bytes used in the name buffer
size_t NameBufferMax; // Total size of the name buffer
CASC_MNDX_PACKAGE Packages[1]; // List of packages
} CASC_MNDX_PACKAGES, *PCASC_MNDX_PACKAGES;
// 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; // Uncompressed file size, in bytes
} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX;
//-----------------------------------------------------------------------------
// Testing functions prototypes
@@ -2734,14 +2785,14 @@ static void MAR_FILE_Destructor(PMAR_FILE pMarFile)
#define CASC_PACKAGES_INIT 0x10
#define CASC_PACKAGES_DELTA 0x10
static PCASC_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMax)
static PCASC_MNDX_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMax)
{
PCASC_PACKAGES pPackages;
PCASC_MNDX_PACKAGES pPackages;
size_t cbToAllocate;
// Allocate space
cbToAllocate = sizeof(CASC_PACKAGES) + (nNameEntries * sizeof(CASC_PACKAGE)) + nNameBufferMax;
pPackages = (PCASC_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
cbToAllocate = sizeof(CASC_MNDX_PACKAGES) + (nNameEntries * sizeof(CASC_MNDX_PACKAGE)) + nNameBufferMax;
pPackages = (PCASC_MNDX_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
if(pPackages != NULL)
{
// Fill the structure
@@ -2757,8 +2808,8 @@ static PCASC_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMa
return pPackages;
}
static PCASC_PACKAGES InsertToPackageList(
PCASC_PACKAGES pPackages,
static PCASC_MNDX_PACKAGES InsertToPackageList(
PCASC_MNDX_PACKAGES pPackages,
const char * szFileName,
size_t cchFileName,
size_t nPackageIndex)
@@ -2777,11 +2828,11 @@ static PCASC_PACKAGES InsertToPackageList(
// If any of the two variables overflowed, we need to reallocate the name list
if(nNewNameEntries > pPackages->NameEntries || nNewNameBufferMax > pPackages->NameBufferMax)
{
PCASC_PACKAGES pOldPackages = pPackages;
PCASC_MNDX_PACKAGES pOldPackages = pPackages;
// Allocate new name list
cbToAllocate = sizeof(CASC_PACKAGES) + (nNewNameEntries * sizeof(CASC_PACKAGE)) + nNewNameBufferMax;
pPackages = (PCASC_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
cbToAllocate = sizeof(CASC_MNDX_PACKAGES) + (nNewNameEntries * sizeof(CASC_MNDX_PACKAGE)) + nNewNameBufferMax;
pPackages = (PCASC_MNDX_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
if(pPackages == NULL)
return NULL;
@@ -2822,17 +2873,17 @@ static PCASC_PACKAGES InsertToPackageList(
return pPackages;
}
static int LoadPackageNames(TCascStorage * hs)
static int LoadPackageNames(PCASC_MNDX_INFO pMndxInfo, PCASC_MNDX_PACKAGES * ppPackages)
{
TMndxFindResult Struct1C;
PCASC_PACKAGES pPackages = NULL;
PCASC_MNDX_PACKAGES pPackages = NULL;
PMAR_FILE pMarFile;
// Sanity checks
assert(hs->pMndxInfo != NULL);
assert(pMndxInfo != NULL);
// Prepare the file name search in the top level directory
pMarFile = hs->pMndxInfo->pMarFile1;
pMarFile = pMndxInfo->pMarFile1;
Struct1C.SetSearchPath("", 0);
// Allocate initial name list structure
@@ -2856,21 +2907,34 @@ static int LoadPackageNames(TCascStorage * hs)
return ERROR_NOT_ENOUGH_MEMORY;
}
// Set the name list to the CASC storage structure
hs->pPackages = pPackages;
// Give the packages to the caller
if(ppPackages != NULL)
ppPackages[0] = pPackages;
return ERROR_SUCCESS;
}
PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
//-----------------------------------------------------------------------------
// Implementation of root file functions
struct TRootHandler_MNDX : public TRootHandler
{
PCASC_PACKAGE pMatching = NULL;
PCASC_PACKAGE pPackage;
CASC_MNDX_INFO MndxInfo;
PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
PCASC_ROOT_ENTRY_MNDX pMndxEntries;
PCASC_MNDX_PACKAGES pPackages; // Linear list of present packages
};
PCASC_MNDX_PACKAGE FindMndxPackage(TRootHandler_MNDX * pRootHandler, const char * szFileName)
{
PCASC_MNDX_PACKAGE pMatching = NULL;
PCASC_MNDX_PACKAGE pPackage;
size_t nMaxLength = 0;
size_t nLength = strlen(szFileName);
// Packages must be loaded
assert(hs->pPackages != NULL);
pPackage = hs->pPackages->Packages;
assert(pRootHandler->pPackages != NULL);
pPackage = pRootHandler->pPackages->Packages;
//FILE * fp = fopen("E:\\packages.txt", "wt");
//for(size_t i = 0; i < hs->pPackages->NameEntries; i++, pPackage++)
@@ -2881,7 +2945,7 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
//fclose(fp);
// Find the longest matching name
for(size_t i = 0; i < hs->pPackages->NameEntries; i++, pPackage++)
for(size_t i = 0; i < pRootHandler->pPackages->NameEntries; i++, pPackage++)
{
if(pPackage->szFileName != NULL && pPackage->nLength < nLength && pPackage->nLength > nMaxLength)
{
@@ -2898,11 +2962,49 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
return pMatching;
}
static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndxFindResult * pStruct1C)
int SearchMndxInfo(TRootHandler_MNDX * pRootHandler, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry;
PCASC_MNDX_INFO pMndxInfo = &pRootHandler->MndxInfo;
TMndxFindResult Struct1C;
// Search the database for the file name
if(pMndxInfo->bRootFileLoaded)
{
Struct1C.SetSearchPath(szFileName, strlen(szFileName));
// Search the file name in the second MAR info (the one with stripped package names)
if(MAR_FILE_SearchFile(pMndxInfo->pMarFile2, &Struct1C) != ERROR_SUCCESS)
return ERROR_FILE_NOT_FOUND;
// The found MNDX index must fall into range of valid MNDX entries
if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid)
{
// HOTS: E945F4
pRootEntry = pRootHandler->ppValidEntries[Struct1C.FileNameIndex];
while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage)
{
// The highest bit serves as a terminator if set
if(pRootEntry->Flags & 0x80000000)
return ERROR_FILE_NOT_FOUND;
pRootEntry++;
}
// Give the root entry pointer to the caller
if(ppRootEntry != NULL)
ppRootEntry[0] = pRootEntry;
return ERROR_SUCCESS;
}
}
return ERROR_FILE_NOT_FOUND;
}
static LPBYTE FillFindData(TRootHandler_MNDX * pRootHandler, TCascSearch * pSearch, TMndxFindResult * pStruct1C, PDWORD PtrFileSize)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL;
TCascStorage * hs = pSearch->hs;
PCASC_PACKAGE pPackage;
PCASC_MNDX_PACKAGE pPackage;
char * szStrippedPtr;
char szStrippedName[MAX_PATH+1];
int nError;
@@ -2911,40 +3013,134 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx
assert(pStruct1C->cchFoundPath < MAX_PATH);
// 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;
memcpy(pSearch->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath);
pSearch->szFileName[pStruct1C->cchFoundPath] = 0;
// Fill the file size
pPackage = FindMndxPackage(hs, pFindData->szFileName);
if(pPackage != NULL)
pPackage = FindMndxPackage(pRootHandler, pSearch->szFileName);
if(pPackage == NULL)
return NULL;
// Cut the package name off the full path
szStrippedPtr = pSearch->szFileName + pPackage->nLength;
while(szStrippedPtr[0] == '/')
szStrippedPtr++;
// We need to convert the stripped name to lowercase, replacing backslashes with slashes
NormalizeFileName_LowerSlash(szStrippedName, szStrippedPtr, MAX_PATH);
// Search the package
nError = SearchMndxInfo(pRootHandler, szStrippedName, (DWORD)(pPackage - pRootHandler->pPackages->Packages), &pRootEntry);
if(nError != ERROR_SUCCESS)
return NULL;
// Give the file size
if(PtrFileSize != NULL)
PtrFileSize[0] = pRootEntry->FileSize;
return pRootEntry->EncodingKey;
}
static int MndxHandler_Insert(TRootHandler_MNDX *, const char *, LPBYTE)
{
return ERROR_NOT_SUPPORTED;
}
static LPBYTE MndxHandler_Search(TRootHandler_MNDX * pRootHandler, TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD /* PtrLocaleFlags */)
{
TMndxFindResult * pStruct1C = NULL;
PCASC_MNDX_INFO pMndxInfo = &pRootHandler->MndxInfo;
PMAR_FILE pMarFile = pMndxInfo->pMarFile3;
bool bFindResult = false;
// If the first time, allocate the structure for the search result
if(pSearch->pRootContext == NULL)
{
// Cut the package name off the full path
szStrippedPtr = pFindData->szFileName + pPackage->nLength;
while(szStrippedPtr[0] == '/')
szStrippedPtr++;
// Create the new search structure
pStruct1C = new TMndxFindResult;
if(pStruct1C == NULL)
return NULL;
// We need to convert the stripped name to lowercase, replacing backslashes with slashes
NormalizeFileName_LowerSlash(szStrippedName, szStrippedPtr, MAX_PATH);
// Search the package
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntry);
if(nError == ERROR_SUCCESS)
{
pFindData->dwFileSize = pRootEntry->FileSize;
}
// Setup the search mask
pStruct1C->SetSearchPath("", 0);
pSearch->pRootContext = pStruct1C;
}
return true;
// Make shortcut for the search structure
assert(pSearch->pRootContext != NULL);
pStruct1C = (TMndxFindResult *)pSearch->pRootContext;
// Search the next file name (our code)
pMarFile->pDatabasePtr->sub_1956CE0(pStruct1C, &bFindResult);
if(bFindResult == false)
return NULL;
// Give the file size and encoding key
return FillFindData(pRootHandler, pSearch, pStruct1C, PtrFileSize);
}
static void MndxHandler_EndSearch(TRootHandler_MNDX * /* pRootHandler */, TCascSearch * pSearch)
{
if(pSearch != NULL)
delete (TMndxFindResult *)pSearch->pRootContext;
pSearch->pRootContext = NULL;
}
static LPBYTE MndxHandler_GetKey(TRootHandler_MNDX * pRootHandler, const char * szFileName)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL;
PCASC_MNDX_PACKAGE pPackage;
char * szStrippedName;
char szNormName[MAX_PATH+1];
int nError;
// Convert the file name to lowercase + slashes
NormalizeFileName_LowerSlash(szNormName, szFileName, MAX_PATH);
// Find the package number
pPackage = FindMndxPackage(pRootHandler, szNormName);
if(pPackage == NULL)
return NULL;
// Cut the package name off the full path
szStrippedName = szNormName + pPackage->nLength;
while(szStrippedName[0] == '/')
szStrippedName++;
// Find the root entry
nError = SearchMndxInfo(pRootHandler, szStrippedName, (DWORD)(pPackage - pRootHandler->pPackages->Packages), &pRootEntry);
if(nError != ERROR_SUCCESS || pRootEntry == NULL)
return NULL;
// Return the encoding key
return pRootEntry->EncodingKey;
}
static void MndxHandler_Close(TRootHandler_MNDX * pRootHandler)
{
if(pRootHandler->MndxInfo.pMarFile1 != NULL)
MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile1);
if(pRootHandler->MndxInfo.pMarFile2 != NULL)
MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile2);
if(pRootHandler->MndxInfo.pMarFile3 != NULL)
MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile3);
if(pRootHandler->ppValidEntries != NULL)
CASC_FREE(pRootHandler->ppValidEntries);
if(pRootHandler->pMndxEntries != NULL)
CASC_FREE(pRootHandler->pMndxEntries);
if(pRootHandler->pPackages != NULL)
CASC_FREE(pRootHandler->pPackages);
CASC_FREE(pRootHandler);
}
//-----------------------------------------------------------------------------
// Public functions - MNDX info
int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
PFILE_MNDX_HEADER pMndxHeader = (PFILE_MNDX_HEADER)pbRootFile;
PCASC_MNDX_INFO pMndxInfo;
TRootHandler_MNDX * pRootHandler;
FILE_MAR_INFO MarInfo;
PMAR_FILE pMarFile;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
@@ -2957,13 +3153,24 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(pMndxHeader->Signature != CASC_MNDX_SIGNATURE || pMndxHeader->FormatVersion > 2 || pMndxHeader->FormatVersion < 1)
return ERROR_BAD_FORMAT;
// Allocate space for the CASC_MNDX_INFO structure
pMndxInfo = CASC_ALLOC(CASC_MNDX_INFO, 1);
if(pMndxInfo == NULL)
// Allocate the structure for the MNDX root file
hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_MNDX, 1);
if(pRootHandler == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill-in the handler functions
memset(pRootHandler, 0, sizeof(TRootHandler_MNDX));
pRootHandler->Insert = (ROOT_INSERT)MndxHandler_Insert;
pRootHandler->Search = (ROOT_SEARCH)MndxHandler_Search;
pRootHandler->EndSearch = (ROOT_ENDSEARCH)MndxHandler_EndSearch;
pRootHandler->GetKey = (ROOT_GETKEY)MndxHandler_GetKey;
pRootHandler->Close = (ROOT_CLOSE) MndxHandler_Close;
pMndxInfo = &pRootHandler->MndxInfo;
// Fill-in the flags
pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES;
// Copy the header into the MNDX info
memset(pMndxInfo, 0, sizeof(CASC_MNDX_INFO));
pMndxInfo->HeaderVersion = pMndxHeader->HeaderVersion;
pMndxInfo->FormatVersion = pMndxHeader->FormatVersion;
dwFilePointer += sizeof(FILE_MNDX_HEADER);
@@ -3047,10 +3254,10 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(nError == ERROR_SUCCESS && FileNameCount == pMndxInfo->MndxEntriesValid)
{
cbToAllocate = pMndxInfo->MndxEntriesTotal * pMndxInfo->MndxEntrySize;
pMndxInfo->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate);
if(pMndxInfo->pMndxEntries != NULL)
pRootHandler->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate);
if(pRootHandler->pMndxEntries != NULL)
{
if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pMndxInfo->pMndxEntries, cbToAllocate))
if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pRootHandler->pMndxEntries, cbToAllocate))
nError = ERROR_FILE_CORRUPT;
}
else
@@ -3064,15 +3271,15 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(nError == ERROR_SUCCESS)
{
assert(pMndxInfo->MndxEntriesValid <= pMndxInfo->MndxEntriesTotal);
pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1);
if(pMndxInfo->ppValidEntries != NULL)
pRootHandler->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1);
if(pRootHandler->ppValidEntries != NULL)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry = pMndxInfo->pMndxEntries;
PCASC_ROOT_ENTRY_MNDX pRootEntry = pRootHandler->pMndxEntries;
DWORD ValidEntryCount = 1; // edx
DWORD nIndex1 = 0;
// The first entry is always valid
pMndxInfo->ppValidEntries[nIndex1++] = pMndxInfo->pMndxEntries;
pRootHandler->ppValidEntries[nIndex1++] = pRootHandler->pMndxEntries;
// Put the remaining entries
for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pRootEntry++)
@@ -3082,7 +3289,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(pRootEntry->Flags & 0x80000000)
{
pMndxInfo->ppValidEntries[nIndex1++] = pRootEntry + 1;
pRootHandler->ppValidEntries[nIndex1++] = pRootEntry + 1;
ValidEntryCount++;
}
}
@@ -3090,131 +3297,28 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
// Verify the final number of valid entries
if((ValidEntryCount - 1) != pMndxInfo->MndxEntriesValid)
nError = ERROR_BAD_FORMAT;
// Mark the MNDX info as fully loaded
pMndxInfo->bRootFileLoaded = true;
}
else
nError = ERROR_NOT_ENOUGH_MEMORY;
}
// Save the MNDX info to the archive storage
// Load the MNDX packages
if(nError == ERROR_SUCCESS)
{
// Store the MNDX database into the archive
hs->pMndxInfo = pMndxInfo;
pMndxInfo = NULL;
nError = LoadPackageNames(pMndxInfo, &pRootHandler->pPackages);
pMndxInfo->bRootFileLoaded = (nError == ERROR_SUCCESS);
}
#if defined(_DEBUG) && defined(_X86_) && defined(CASCLIB_TEST)
// CascDumpNameFragTable("E:\\casc-name-fragment-table.txt", hs->pMndxInfo->pMarFile1);
// CascDumpFileNames("E:\\casc-listfile.txt", hs->pMndxInfo->pMarFile1);
TestMndxRootFile(hs->pMndxInfo);
// CascDumpNameFragTable("E:\\casc-name-fragment-table.txt", pMndxInfo->pMarFile1);
// CascDumpFileNames("E:\\casc-listfile.txt", pMndxInfo->pMarFile1);
// TestMndxRootFile(pRootHandler);
#endif
// Load the top level entries
nError = LoadPackageNames(hs);
}
// If anything failed, free the memory remaining allocated
if(nError != ERROR_SUCCESS)
{
if(pMndxInfo != NULL)
FreeMndxInfo(pMndxInfo);
pMndxInfo = NULL;
}
// Return the result
return nError;
}
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry;
TMndxFindResult Struct1C;
// Search the database for the file name
if(pMndxInfo->bRootFileLoaded)
{
Struct1C.SetSearchPath(szFileName, strlen(szFileName));
// Search the file name in the second MAR info (the one with stripped package names)
if(MAR_FILE_SearchFile(pMndxInfo->pMarFile2, &Struct1C) != ERROR_SUCCESS)
return ERROR_FILE_NOT_FOUND;
// The found MNDX index must fall into range of valid MNDX entries
if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid)
{
// HOTS: E945F4
pRootEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex];
while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage)
{
// The highest bit serves as a terminator if set
if(pRootEntry->Flags & 0x80000000)
return ERROR_FILE_NOT_FOUND;
pRootEntry++;
}
// Give the root entry pointer to the caller
if(ppRootEntry != NULL)
ppRootEntry[0] = pRootEntry;
return ERROR_SUCCESS;
}
}
return ERROR_FILE_NOT_FOUND;
}
bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
TMndxFindResult * pStruct1C = NULL;
PCASC_MNDX_INFO pMndxInfo = pSearch->hs->pMndxInfo;
PMAR_FILE pMarFile = pMndxInfo->pMarFile3;
bool bFindResult = false;
// Sanity checks
assert(pMndxInfo != NULL);
// If the first time, allocate the structure for the search result
if(pSearch->pStruct1C == NULL)
{
// Create the new search structure
pSearch->pStruct1C = pStruct1C = new TMndxFindResult;
if(pSearch->pStruct1C == NULL)
return false;
// Setup the search mask
pStruct1C->SetSearchPath("", 0);
}
// Make shortcut for the search structure
assert(pSearch->pStruct1C != NULL);
pStruct1C = (TMndxFindResult *)pSearch->pStruct1C;
// Search the next file name (our code)
pMarFile->pDatabasePtr->sub_1956CE0(pStruct1C, &bFindResult);
if(bFindResult)
return FillFindData(pSearch, pFindData, pStruct1C);
return false;
}
void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo)
{
if(pMndxInfo != NULL)
{
if(pMndxInfo->pMarFile1 != NULL)
MAR_FILE_Destructor(pMndxInfo->pMarFile1);
if(pMndxInfo->pMarFile2 != NULL)
MAR_FILE_Destructor(pMndxInfo->pMarFile2);
if(pMndxInfo->pMarFile3 != NULL)
MAR_FILE_Destructor(pMndxInfo->pMarFile3);
if(pMndxInfo->ppValidEntries != NULL)
CASC_FREE(pMndxInfo->ppValidEntries);
if(pMndxInfo->pMndxEntries != NULL)
CASC_FREE(pMndxInfo->pMndxEntries);
CASC_FREE(pMndxInfo);
}
}
//----------------------------------------------------------------------------
// Unit tests

View File

@@ -6,9 +6,9 @@ ASSUME FS: NOTHING
TMndxFindResult struc ; (sizeof=0x1C) ; XREF: GAME_OBJECT_03F7E848::sub_E94500_r
szSearchMask dd ? ; XREF: TMndxFindResult__SetPath+2D_w
; TMndxFindResult__Constructor+4_w ...
; TMndxFindResult__Constructor+4_w
cchSearchMask dd ? ; XREF: TMndxFindResult__SetPath:loc_1956E9A_w
; TMndxFindResult__Constructor+6_w ...
; TMndxFindResult__Constructor+6_w
field_8 dd ? ; XREF: TMndxFindResult__Constructor+9_w
szFoundPath dd ? ; XREF: TMndxFindResult__Constructor+C_w
; TFileNameDatabase__FindFileInDatabase+55_w
@@ -17,7 +17,7 @@ cchFoundPath dd ? ; XREF: TMndxFindResult__Constructor+F_w
FileNameIndex dd ? ; XREF: TMndxFindResult__Constructor+12_w
; TFileNameDatabase__FindFileInDatabase+6B_w
pStruct40 dd ? ; XREF: MAR_FILE__FindFileInDatabase+19_r
; TMndxFindResult__SetPath:loc_1956E8C_r ...
; TMndxFindResult__SetPath:loc_1956E8C_r
TMndxFindResult ends
; ---------------------------------------------------------------------------
@@ -25,9 +25,9 @@ TMndxFindResult ends
TRIPLET struc ; (sizeof=0xC)
BitIndex dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+39_r
NextKey dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+8C_r
; TSparseArray__GetItemValue:loc_1959B8F_r ...
; TSparseArray__GetItemValue:loc_1959B8F_r
Distance dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+3E_r
; TSparseArray__GetItemValue:loc_1959BBE_r ...
; TSparseArray__GetItemValue:loc_1959BBE_r
TRIPLET ends
; ---------------------------------------------------------------------------
@@ -44,33 +44,33 @@ NAME_ENTRY ends
TStruct14 struc ; (sizeof=0x14) ; XREF: TFileNameDatabase::sub_1959460r
HashValue dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+44r
; TGenericArray__InsertItem_STRUCT14+46w ...
; TGenericArray__InsertItem_STRUCT14+46w
field_4 dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+48r
; TGenericArray__InsertItem_STRUCT14+4Bw ...
; TGenericArray__InsertItem_STRUCT14+4Bw
field_8 dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+4Er
; TGenericArray__InsertItem_STRUCT14+51w ...
; TGenericArray__InsertItem_STRUCT14+51w
field_C dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+54r
; TGenericArray__InsertItem_STRUCT14+57w ...
; TGenericArray__InsertItem_STRUCT14+57w
field_10 dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+5Ar
; TGenericArray__InsertItem_STRUCT14+5Dw ...
; TGenericArray__InsertItem_STRUCT14+5Dw
TStruct14 ends
; ---------------------------------------------------------------------------
TGenericArray struc ; (sizeof=0x15) ; XREF: TGenericArray::LoadDwordsArrayWithCopyr
; TFileNameDatabase::LoadBytesr ...
; TFileNameDatabase::LoadBytesr
DataBuffer dd ? ; XREF: TMndxFindResult__CreateStruct40+24w
; TMndxFindResult__CreateStruct40+35w ...
; TMndxFindResult__CreateStruct40+35w
field_4 dd ? ; XREF: TMndxFindResult__CreateStruct40+26w
; TMndxFindResult__CreateStruct40+38w ...
; TMndxFindResult__CreateStruct40+38w
ItemArray dd ? ; XREF: TMndxFindResult__CreateStruct40+29w
; TMndxFindResult__CreateStruct40+3Bw ...
; TMndxFindResult__CreateStruct40+3Bw
ItemCount dd ? ; XREF: TMndxFindResult__CreateStruct40+2Cw
; TMndxFindResult__CreateStruct40+3Ew ...
; TMndxFindResult__CreateStruct40+3Ew
MaxItemCount dd ? ; XREF: TFileNameDatabasePtr__CreateDatabase+27o
; TMndxFindResult__CreateStruct40+2Fw ...
; TMndxFindResult__CreateStruct40+2Fw
bIsValidArray db ? ; XREF: LoadAndVerifyIndexFileHeader+31w
; TMndxFindResult__CreateStruct40+32w ...
; TMndxFindResult__CreateStruct40+32w
TGenericArray ends
; ---------------------------------------------------------------------------
@@ -81,7 +81,7 @@ DataBuffer dd ? ; XREF: TArchiveDatabase__Destructor+64
; TArchiveDatabase__Constructor+1A3w
field_4 dd ? ; XREF: TArchiveDatabase__Constructor+1A9w
ItemArray dd ? ; XREF: sub_1957350+31r
; sub_19573D0+1Fr ...
; sub_19573D0+1Fr
ItemCount dd ? ; XREF: TArchiveDatabase__Constructor+1B5w
MaxItemCount dd ? ; XREF: TArchiveDatabase__Constructor+1BBw
bIsValidArray db ? ; XREF: TArchiveDatabase__Constructor+1C1w
@@ -89,9 +89,9 @@ bIsValidArray db ? ; XREF: TArchiveDatabase__Constructor+1C
db ? ; undefined
db ? ; undefined
BitsPerEntry dd ? ; XREF: sub_1957350+1Fr
; sub_19573D0+6r ...
; sub_19573D0+6r
EntryBitMask dd ? ; XREF: sub_1957350:loc_19573B1r
; sub_19573D0:loc_1957419r ...
; sub_19573D0:loc_1957419r
TotalEntries dd ? ; XREF: TGenericArrayEx__LoadFromStream:loc_195861Bw
; TArchiveDatabase__Constructor+1D3w
TBitEntryArray ends
@@ -100,50 +100,50 @@ TBitEntryArray ends
TStruct40 struc ; (sizeof=0x40)
array_00 TGenericArray <> ; XREF: TMndxFindResult__CreateStruct40+24w
; TMndxFindResult__CreateStruct40+26w ...
; TMndxFindResult__CreateStruct40+26w
db ? ; undefined
db ? ; undefined
db ? ; undefined
array_18 TGenericArray <> ; XREF: TMndxFindResult__CreateStruct40+35w
; TMndxFindResult__CreateStruct40+38w ...
; TMndxFindResult__CreateStruct40+38w
db ? ; undefined
db ? ; undefined
db ? ; undefined
HashValue dd ? ; XREF: TMndxFindResult__CreateStruct40+47w
; TFileNameDatabase__CheckNextPathFragment+1Ar ...
; TFileNameDatabase__CheckNextPathFragment+1Ar
CharIndex dd ? ; XREF: TMndxFindResult__CreateStruct40+4Aw
; TFileNameDatabase__CheckNextPathFragment+11r ...
; TFileNameDatabase__CheckNextPathFragment+11r
ItemCount dd ? ; XREF: TMndxFindResult__CreateStruct40+4Dw
; TStruct40__InitSearchBuffers+6Bw ...
; TStruct40__InitSearchBuffers+6Bw
SearchPhase dd ? ; XREF: TMndxFindResult__CreateStruct40+50w
; TFileNameDatabase__FindFileInDatabase+17w ...
; TFileNameDatabase__FindFileInDatabase+17w
TStruct40 ends
; ---------------------------------------------------------------------------
TSparseArray struc ; (sizeof=0x65) ; XREF: TSparseArray::LoadFromStream_Exchange_r
; TNameIndexStruct_r ...
; TNameIndexStruct_r
ItemIsPresent TGenericArray <> ; XREF: LoadAndVerifyIndexFileHeader+31_w
; TFileNameDatabase__Destructor+4C_r ...
; TFileNameDatabase__Destructor+4C_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
TotalItemCount dd ? ; XREF: TSparseArray__ExchangeWith+4D_r
; TSparseArray__ExchangeWith+56_w ...
; TSparseArray__ExchangeWith+56_w
ValidItemCount dd ? ; XREF: TFileNameDatabasePtr__GetFileNameCount:loc_1956D32_r
; TSparseArray__ExchangeWith+59_r ...
; TSparseArray__ExchangeWith+59_r
ArrayTriplets_20 TGenericArray <> ; XREF: TFileNameDatabase__Destructor+40_r
; TFileNameDatabase__Destructor+94_r ...
; TFileNameDatabase__Destructor+94_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
ArrayDwords_38 TGenericArray <> ; XREF: TFileNameDatabasePtr__CreateDatabase+27_o
; TFileNameDatabase__Destructor+34_r ...
; TFileNameDatabase__Destructor+34_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
ArrayDwords_50 TGenericArray <> ; XREF: TFileNameDatabase__Destructor+28_r
; TFileNameDatabase__Destructor+7C_r ...
; TFileNameDatabase__Destructor+7C_r
TSparseArray ends
; ---------------------------------------------------------------------------
@@ -151,12 +151,12 @@ TSparseArray ends
TNameIndexStruct struc ; (sizeof=0x7D) ; XREF: TNameIndexStruct::LoadFromStream_Exchange_r
; TFileNameDatabaser
NameFragments TGenericArray <> ; XREF: TFileNameDatabase__Destructor+58_r
; TFileNameDatabase__LoadFromStream+82_r ...
; TFileNameDatabase__LoadFromStream+82_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
Struct68 TSparseArray <> ; XREF: LoadAndVerifyIndexFileHeader+31_w
; TFileNameDatabase__Destructor+28_r ...
; TFileNameDatabase__Destructor+28_r
TNameIndexStruct ends
; ---------------------------------------------------------------------------
@@ -164,11 +164,11 @@ TNameIndexStruct ends
TByteStream struc ; (sizeof=0x18) ; XREF: TFileNameDatabasePtr::CreateDatabase_r
; TFileNameDatabase_r
pbData dd ? ; XREF: TByteStream__Constructor+4_w
; TByteStream__IsMarDataValid+2_r ...
; TByteStream__IsMarDataValid+2_r
pvMappedFile dd ? ; XREF: TByteStream__Constructor+6_w
; TByteStream__Destructor+3_r
cbData dd ? ; XREF: TByteStream__Constructor+9_w
; TByteStream__GetBytes:loc_1959A05_r ...
; TByteStream__GetBytes:loc_1959A05_r
field_C dd ? ; XREF: TByteStream__Constructor+C_w
hFile dd ? ; XREF: TByteStream__Constructor+F_w
; TByteStream__Destructor:loc_19599D2_r
@@ -183,11 +183,11 @@ TStruct10 struc ; (sizeof=0x10) ; XREF: TStruct10::sub_1957800_r
field_0 dd ? ; XREF: sub_19572E0+24_w
; TFileNameDatabase__Constructor+21A_w
field_4 dd ? ; XREF: sub_1956FD0+28_w
; sub_1956FD0:loc_1957001_w ...
; sub_1956FD0:loc_1957001_w
field_8 dd ? ; XREF: sub_19572E0+4A_w
; sub_19572E0+5B_w ...
; sub_19572E0+5B_w
field_C dd ? ; XREF: sub_1957050:loc_1957074_w
; sub_1957050:loc_1957081_w ...
; sub_1957050:loc_1957081_w
TStruct10 ends
; ---------------------------------------------------------------------------
@@ -195,52 +195,52 @@ TStruct10 ends
TFileNameDatabasePtr struc ; (sizeof=0x4) ; XREF: TFileNameDatabase_r
; MAR_FILE_r
pDatabase dd ? ; XREF: MAR_FILE__Constructor+1D_w
; MAR_FILE__CreateDatabase+22_w ...
; MAR_FILE__CreateDatabase+22_w
TFileNameDatabasePtr ends
; ---------------------------------------------------------------------------
TFileNameDatabase struc ; (sizeof=0x240) ; XREF: TFileNameDatabase::LoadFromStream_Exchange_r
Struct68_00 TSparseArray <> ; XREF: TFileNameDatabasePtr__CreateDatabase+27_o
; TFileNameDatabase__Destructor+D9_r ...
; TFileNameDatabase__Destructor+D9_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
FileNameIndexes TSparseArray <> ; XREF: TFileNameDatabasePtr__GetFileNameCount:loc_1956D32_r
; TFileNameDatabase__Destructor+AC_r ...
; TFileNameDatabase__Destructor+AC_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
Struct68_D0 TSparseArray <> ; XREF: TFileNameDatabase__Destructor+7C_r
; TFileNameDatabase__Destructor+88_r ...
; TFileNameDatabase__Destructor+88_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
FrgmDist_LoBits TGenericArray <> ; XREF: TFileNameDatabase__Destructor+70_r
; TFileNameDatabase__CheckNextPathFragment:loc_1957AC9_r ...
; TFileNameDatabase__CheckNextPathFragment:loc_1957AC9_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
FrgmDist_HiBits TBitEntryArray <> ; XREF: TFileNameDatabase__Destructor+64_r
; TFileNameDatabase__CheckNextPathFragment+119_r ...
; TFileNameDatabase__CheckNextPathFragment+119_r
IndexStruct_174 TNameIndexStruct <> ; XREF: TFileNameDatabase__Destructor+28_r
; TFileNameDatabase__Destructor+34_r ...
; TFileNameDatabase__Destructor+34_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
NextDB TFileNameDatabasePtr <> ; XREF: TFileNameDatabase__Destructor+1D_o
; TFileNameDatabase__CheckNextPathFragment+52_r ...
; TFileNameDatabase__CheckNextPathFragment+52_r
NameFragTable TGenericArray <> ; XREF: TFileNameDatabase__Destructor+E_r
; TFileNameDatabase__CheckNextPathFragment+2F_r ...
; TFileNameDatabase__CheckNextPathFragment+2F_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
NameFragIndexMask dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+26_r
; sub_1957B80:loc_1957B95_r ...
; sub_1957B80:loc_1957B95_r
field_214 dd ? ; XREF: TFileNameDatabase__Constructor+20E_w
; TFileNameDatabase__LoadFromStream+110_w
Struct10 TStruct10 <> ; XREF: TFileNameDatabase__Constructor+21A_w
; TFileNameDatabase__Constructor+224_w ...
; TFileNameDatabase__Constructor+224_w
MarStream TByteStream <> ; XREF: TFileNameDatabase__Destructor+3_o
; TFileNameDatabase__Constructor+214_o
TFileNameDatabase ends
@@ -249,11 +249,11 @@ TFileNameDatabase ends
MAR_FILE struc ; (sizeof=0xC)
pDatabasePtr TFileNameDatabasePtr <> ; XREF: MAR_FILE__Constructor+1D_w
; MAR_FILE__CreateDatabase+22_w ...
; MAR_FILE__CreateDatabase+22_w
pbMarFileData dd ? ; XREF: MAR_FILE__Constructor+1A_w
; MAR_FILE__CreateDatabase+1B_r ...
; MAR_FILE__CreateDatabase+1B_r
cbMarFileData dd ? ; XREF: MAR_FILE__Constructor+12_w
; MAR_FILE__CreateDatabase+18_r ...
; MAR_FILE__CreateDatabase+18_r
MAR_FILE ends
.DATA
@@ -368,7 +368,7 @@ operator_delete ENDP
TSparseArray__GetItemValue proc near ; CODE XREF: sub_1957350+1A_p
; TFileNameDatabase__CheckNextPathFragment+103_p ...
; TFileNameDatabase__CheckNextPathFragment+103_p
arg_0 = dword ptr 8
@@ -451,7 +451,7 @@ loc_1959BDE: ; CODE XREF: TSparseArray__GetItemValue+
add esi, eax
loc_1959BE0: ; CODE XREF: TSparseArray__GetItemValue+26_j
; TSparseArray__GetItemValue+45_j ...
; TSparseArray__GetItemValue+45_j
mov ebx, edi ; jumptable 01959B88 default case
shr ebx, 5
test bl, 1
@@ -525,7 +525,7 @@ TSparseArray__GetItemValue endp
TArchiveDatabase__sub_1959CB0 proc near
; CODE XREF: TFileNameDatabase__CheckNextPathFragment+A1_p
; sub_1958B00+E8_p ...
; sub_1958B00+E8_p
pThis = dword ptr -4
dwKey = dword ptr 8
@@ -614,7 +614,7 @@ loc_1959D55: ; CODE XREF: TFileNameDatabase__FindNext
mov ecx, [ebp+pThis]
loc_1959D5F: ; CODE XREF: TFileNameDatabase__FindNextMatch+62_j
; TFileNameDatabase__FindNextMatch+7C_j ...
; TFileNameDatabase__FindNextMatch+7C_j
mov ecx, [ecx+28h]
lea esi, [eax+eax*2]
mov edi, [ecx+esi*4] ; EDI = Struct68_00.ArrayTriplets_20.ItemArray[eax]
@@ -709,7 +709,7 @@ loc_1959E49: ; CODE XREF: TFileNameDatabase__FindNext
lea edx, [edx+esi-1C0h]
loc_1959E53: ; CODE XREF: TFileNameDatabase__FindNextMatch+FE_j
; TFileNameDatabase__FindNextMatch+10B_j ...
; TFileNameDatabase__FindNextMatch+10B_j
mov eax, [ebp+pThis]
mov ebx, [eax+8]
mov ecx, [ebx+edi*4]
@@ -809,7 +809,7 @@ TArchiveDatabase__sub_1959CB0 endp
; Attributes: bp-based frame
TNameIndexStruct__CheckNameFragment proc near ; CODE XREF: TFileNameDatabase__CheckNextPathFragment+6B_p
; TFileNameDatabase__CheckNextPathFragment+191_p ...
; TFileNameDatabase__CheckNextPathFragment+191_p
pThis = dword ptr -4
pUnknownStruct1C= dword ptr 8
@@ -845,7 +845,7 @@ loc_195A1A1: ; CODE XREF: TNameIndexStruct__CheckName
jb short loc_195A1A1
loc_195A1BD: ; CODE XREF: TNameIndexStruct__CheckNameFragment+2C_j
; TNameIndexStruct__CheckNameFragment+5Ej ...
; TNameIndexStruct__CheckNameFragment+5Ej
pop edi
pop esi
xor al, al
@@ -907,7 +907,7 @@ TNameIndexStruct__CheckNameFragment endp
; Attributes: bp-based frame
sub_1957350 proc near ; CODE XREF: sub_1957B80+D8p
; sub_1957B80:loc_1957C73p ...
; sub_1957B80:loc_1957C73p
arg_0 = dword ptr 8
@@ -971,7 +971,7 @@ sub_1957350 endp
; Attributes: bp-based frame
sub_1959F50 proc near ; CODE XREF: sub_1957B80+14C_p
; sub_1958980+62_p ...
; sub_1958980+62_p
var_4 = dword ptr -4
arg_0 = dword ptr 8
@@ -1046,7 +1046,7 @@ loc_1959FCA: ; CODE XREF: sub_1959F50+76_j
mov ecx, [ebp+var_4]
loc_1959FD4: ; CODE XREF: sub_1959F50+51_j
; sub_1959F50+5B_j ...
; sub_1959F50+5B_j
mov edi, [ecx+28h]
lea esi, [eax+eax*2]
sub edx, [edi+esi*4]
@@ -1124,7 +1124,7 @@ loc_195A064: ; CODE XREF: sub_1959F50+CD_j
sub edx, esi
loc_195A066: ; CODE XREF: sub_1959F50+B5_j
; sub_1959F50+BC_j ...
; sub_1959F50+BC_j
mov ebx, [ecx+8]
mov esi, [ebx+edi*4]
mov eax, esi
@@ -1223,7 +1223,7 @@ sub_1959F50 endp
; Attributes: bp-based frame
sub_1957B80 proc near ; CODE XREF: TFileNameDatabase__CheckNextPathFragment+5E_p
; TFileNameDatabase__CheckNextPathFragment+180_p ...
; TFileNameDatabase__CheckNextPathFragment+180_p
pStruct40 = dword ptr -4
arg_0 = dword ptr 8
@@ -1300,7 +1300,7 @@ loc_1957C05: ; CODE XREF: sub_1957B80+6C_j
jb loc_1957B95
loc_1957C25: ; CODE XREF: sub_1957B80+67_j
; sub_1957B80+7C_j ...
; sub_1957B80+7C_j
pop edi
pop esi
xor al, al
@@ -1765,7 +1765,7 @@ TFileNameDatabase__FindFileInDatabase endp
; Attributes: bp-based frame
TGenericArray__SetMaxItems_BYTE proc near ; CODE XREF: sub_19582E0+2Cp
; TStruct40__InitSearchBuffers+2Cp ...
; TStruct40__InitSearchBuffers+2Cp
ByteCount = dword ptr 8
@@ -1820,7 +1820,7 @@ TGenericArray__SetMaxItems_BYTE endp
TGenericArray__SetMaxItems_STRUCT14 proc near
; CODE XREF: TGenericArray__InsertItem_STRUCT14+2Cp
; TGenericArray__sub_19583A0+2Dp ...
; TGenericArray__sub_19583A0+2Dp
var_4 = dword ptr -4
ItemCount = dword ptr 8
@@ -2086,7 +2086,7 @@ TGenericArray__InsertItem_STRUCT14 endp
; Attributes: bp-based frame
CopyNameFragment proc near ; CODE XREF: sub_1958980+DAp
; sub_1958D70+6Dp ...
; sub_1958D70+6Dp
pThis = dword ptr -4
pStruct1C = dword ptr 8
@@ -2284,7 +2284,7 @@ CopyNameFragment endp
; Attributes: bp-based frame
sub_1958D70 proc near ; CODE XREF: sub_1958980+C9p
; sub_1958D70+59p ...
; sub_1958D70+59p
var_C = dword ptr -0Ch
var_8 = dword ptr -8
@@ -2582,7 +2582,7 @@ sub_1958D70 endp
; Attributes: bp-based frame
TFileNameDatabase__sub_1959CB0 proc near ; CODE XREF: TFileNameDatabase__CheckNextPathFragment+A1p
; TFileNameDatabase__sub_1958B00+E8p ...
; TFileNameDatabase__sub_1958B00+E8p
pThis = dword ptr -4
dwKey = dword ptr 8
@@ -2671,7 +2671,7 @@ loc_1959D55: ; CODE XREF: TFileNameDatabase__sub_1959
mov ecx, [ebp+pThis]
loc_1959D5F: ; CODE XREF: TFileNameDatabase__sub_1959CB0+62j
; TFileNameDatabase__sub_1959CB0+7Cj ...
; TFileNameDatabase__sub_1959CB0+7Cj
mov ecx, [ecx+TFileNameDatabase.Struct68_00.ArrayTriplets_20.ItemArray]
lea esi, [eax+eax*2]
mov edi, [ecx+esi*4] ; EDI = Struct68_00.ArrayTriplets_20.ItemArray[eax]
@@ -2766,7 +2766,7 @@ loc_1959E49: ; CODE XREF: TFileNameDatabase__sub_1959
lea edx, [edx+esi-1C0h]
loc_1959E53: ; CODE XREF: TFileNameDatabase__sub_1959CB0+FEj
; TFileNameDatabase__sub_1959CB0+10Bj ...
; TFileNameDatabase__sub_1959CB0+10Bj
mov eax, [ebp+pThis]
mov ebx, [eax+TFileNameDatabase.Struct68_00.ItemIsPresent.ItemArray]
mov ecx, [ebx+edi*4]
@@ -2866,7 +2866,7 @@ TFileNameDatabase__sub_1959CB0 endp
; Attributes: bp-based frame
TNameIndexStruct__CheckAndCopyNameFragment proc near ; CODE XREF: TArchiveDatabase__sub_1958B00+74p
; TArchiveDatabase__sub_1958B00+1E0p ...
; TArchiveDatabase__sub_1958B00+1E0p
var_C = dword ptr -0Ch
pThis = dword ptr -8
@@ -3206,7 +3206,7 @@ TNameIndexStruct__CheckAndCopyNameFragment endp
; Attributes: bp-based frame
sub_1959010 proc near ; CODE XREF: TArchiveDatabase__sub_1958B00+67p
; TArchiveDatabase__sub_1958B00+1CFp ...
; TArchiveDatabase__sub_1958B00+1CFp
pFragmentInfo = dword ptr -0Ch
var_8 = dword ptr -8
@@ -3262,7 +3262,7 @@ loc_195907F: ; CODE XREF: sub_1959010+5Ej
jnz loc_195912E
loc_1959087: ; CODE XREF: sub_1959010+90j
; sub_1959010+1F3j ...
; sub_1959010+1F3j
pop edi
pop esi
xor al, al
@@ -3962,14 +3962,14 @@ loc_19594B0: ; CODE XREF: TFileNameDatabase__sub_1959
; ---------------------------------------------------------------------------
loc_1959522: ; CODE XREF: TFileNameDatabase__sub_1959460+26Bj
; TFileNameDatabase__sub_1959460+2D9j ...
; TFileNameDatabase__sub_1959460+2D9j
mov ebx, [ebp+pThis]
jmp short loc_1959530
; ---------------------------------------------------------------------------
align 10h
loc_1959530: ; CODE XREF: TFileNameDatabase__sub_1959460+23j
; TFileNameDatabase__sub_1959460+97j ...
; TFileNameDatabase__sub_1959460+97j
mov edx, [esi+TStruct40.ItemCount]
cmp edx, [esi+TStruct40.array_18.ItemCount]
jnz loc_19595BD

View File

@@ -0,0 +1,199 @@
/*****************************************************************************/
/* CascRootFile_Ovr.cpp Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Support for loading Overwatch ROOT file */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 28.10.15 1.00 Lad The first version of CascRootFile_Ovr.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Structure definitions for Overwatch root file
typedef struct _CASC_FILE_ENTRY
{
ENCODING_KEY EncodingKey; // Encoding key
ULONGLONG FileNameHash; // File name hash
DWORD dwFileName; // Offset of the file name in the name cache
} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY;
struct TRootHandler_Ovr : public TRootHandler
{
// Linear global list of file entries
DYNAMIC_ARRAY FileTable;
// Linear global list of names
DYNAMIC_ARRAY FileNames;
// Global map of FileName -> FileEntry
PCASC_MAP pRootMap;
};
//-----------------------------------------------------------------------------
// Local functions
static int InsertFileEntry(
TRootHandler_Ovr * pRootHandler,
const char * szFileName,
LPBYTE pbEncodingKey)
{
PCASC_FILE_ENTRY pFileEntry;
size_t nLength = strlen(szFileName);
// Attempt to insert the file name to the global buffer
szFileName = (char *)Array_Insert(&pRootHandler->FileNames, szFileName, nLength + 1);
if(szFileName == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Attempt to insert the entry to the array of file entries
pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
if(pFileEntry == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill the file entry
pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey;
pFileEntry->FileNameHash = CalcFileNameHash(szFileName);
pFileEntry->dwFileName = Array_IndexOf(&pRootHandler->FileNames, szFileName);
// Insert the file entry to the map
assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL);
Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
return ERROR_SUCCESS;
}
//-----------------------------------------------------------------------------
// Implementation of Overwatch root file
static int OvrHandler_Insert(
TRootHandler_Ovr * pRootHandler,
const char * szFileName,
LPBYTE pbEncodingKey)
{
return InsertFileEntry(pRootHandler, szFileName, pbEncodingKey);
}
static LPBYTE OvrHandler_Search(TRootHandler_Ovr * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */)
{
PCASC_FILE_ENTRY pFileEntry;
// Are we still inside the root directory range?
while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount)
{
// Retrieve the file item
pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1);
strcpy(pSearch->szFileName, (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName));
// Prepare the pointer to the next search
pSearch->IndexLevel1++;
return pFileEntry->EncodingKey.Value;
}
// No more entries
return NULL;
}
static void OvrHandler_EndSearch(TRootHandler_Ovr * /* pRootHandler */, TCascSearch * /* pSearch */)
{
// Do nothing
}
static LPBYTE OvrHandler_GetKey(TRootHandler_Ovr * pRootHandler, const char * szFileName)
{
ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
return (LPBYTE)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL);
}
static void OvrHandler_Close(TRootHandler_Ovr * pRootHandler)
{
if(pRootHandler != NULL)
{
// Free the file map
if(pRootHandler->pRootMap)
Map_Free(pRootHandler->pRootMap);
pRootHandler->pRootMap = NULL;
// Free the array of the file names and file items
Array_Free(&pRootHandler->FileTable);
Array_Free(&pRootHandler->FileNames);
// Free the root file itself
CASC_FREE(pRootHandler);
}
}
//-----------------------------------------------------------------------------
// Public functions
int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TRootHandler_Ovr * pRootHandler;
ENCODING_KEY KeyBuffer;
QUERY_KEY EncodingKey = {KeyBuffer.Value, MD5_HASH_SIZE};
void * pTextFile;
size_t nLength;
char szOneLine[0x200];
char szFileName[MAX_PATH+1];
DWORD dwFileCountMax = hs->pEncodingMap->TableSize;
int nError = ERROR_SUCCESS;
// Allocate the root handler object
hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Ovr, 1);
if(pRootHandler == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill-in the handler functions
memset(pRootHandler, 0, sizeof(TRootHandler_Ovr));
pRootHandler->Insert = (ROOT_INSERT)OvrHandler_Insert;
pRootHandler->Search = (ROOT_SEARCH)OvrHandler_Search;
pRootHandler->EndSearch = (ROOT_ENDSEARCH)OvrHandler_EndSearch;
pRootHandler->GetKey = (ROOT_GETKEY)OvrHandler_GetKey;
pRootHandler->Close = (ROOT_CLOSE)OvrHandler_Close;
// Fill-in the flags
pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES;
// Allocate the linear array of file entries
nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, 0x10000);
if(nError != ERROR_SUCCESS)
return nError;
// Allocate the buffer for the file names
nError = Array_Create(&pRootHandler->FileNames, char, 0x10000);
if(nError != ERROR_SUCCESS)
return nError;
// Create map of ROOT_ENTRY -> FileEntry
pRootHandler->pRootMap = Map_Create(dwFileCountMax, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash));
if(pRootHandler->pRootMap == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Parse the ROOT file
pTextFile = ListFile_FromBuffer(pbRootFile, cbRootFile);
if(pTextFile != NULL)
{
// Skip the first line, containing "#MD5|CHUNK_ID|FILENAME|INSTALLPATH"
ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine));
// Parse the next lines
while((nLength = ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine))) > 0)
{
// Parse the line
nError = ParseRootFileLine(szOneLine, szOneLine + nLength, &EncodingKey, szFileName, _maxchars(szFileName));
if(nError == ERROR_SUCCESS)
{
InsertFileEntry(pRootHandler, szFileName, KeyBuffer.Value);
}
}
ListFile_Free(pTextFile);
}
// Succeeded
return nError;
}

View File

@@ -0,0 +1,531 @@
/*****************************************************************************/
/* 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 */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascOpenStorage.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local structures
#define ROOT_SEARCH_PHASE_INITIALIZING 0
#define ROOT_SEARCH_PHASE_LISTFILE 1
#define ROOT_SEARCH_PHASE_NAMELESS 2
#define ROOT_SEARCH_PHASE_FINISHED 2
// On-disk version of locale block
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;
// On-disk version of root entry
typedef struct _FILE_ROOT_ENTRY
{
ENCODING_KEY EncodingKey; // MD5 of the file
ULONGLONG FileNameHash; // Jenkins hash of the file name
} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
typedef struct _CASC_ROOT_BLOCK
{
PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
PDWORD FileDataIds; // Pointer to the array of File Data IDs
PFILE_ROOT_ENTRY pRootEntries;
} CASC_ROOT_BLOCK, *PCASC_ROOT_BLOCK;
// 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_FILE_ENTRY
{
ENCODING_KEY EncodingKey; // File encoding key (MD5)
ULONGLONG FileNameHash; // Jenkins hash of the file name
DWORD FileDataId; // File Data Index
DWORD Locales; // Locale flags of the file
} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY;
struct TRootHandler_WoW6 : public TRootHandler
{
// Linear global list of file entries
DYNAMIC_ARRAY FileTable;
// Global map of FileName -> FileEntry
PCASC_MAP pRootMap;
// For counting files
DWORD dwTotalFileCount;
DWORD FileDataId;
};
// Prototype for root file parsing routine
typedef int (*PARSE_ROOT)(TRootHandler_WoW6 * pRootHandler, PCASC_ROOT_BLOCK pBlockInfo);
//-----------------------------------------------------------------------------
// Local functions
static bool IsFileDataIdName(const char * szFileName)
{
BYTE BinaryValue[4];
// File name must begin with "File", case insensitive
if(AsciiToUpperTable_BkSlash[szFileName[0]] == 'F' &&
AsciiToUpperTable_BkSlash[szFileName[1]] == 'I' &&
AsciiToUpperTable_BkSlash[szFileName[2]] == 'L' &&
AsciiToUpperTable_BkSlash[szFileName[3]] == 'E')
{
// Then, 8 hexadecimal digits must follow
if(ConvertStringToBinary(szFileName + 4, 8, BinaryValue) == ERROR_SUCCESS)
{
// Must be followed by an extension or end-of-string
return (szFileName[0x0C] == 0 || szFileName[0x0C] == '.');
}
}
return false;
}
// Search by FileDataId
PCASC_FILE_ENTRY FindRootEntry(DYNAMIC_ARRAY & FileTable, DWORD FileDataId)
{
PCASC_FILE_ENTRY pStartEntry = (PCASC_FILE_ENTRY)FileTable.ItemArray;
PCASC_FILE_ENTRY pMidlEntry;
PCASC_FILE_ENTRY pEndEntry = pStartEntry + FileTable.ItemCount - 1;
int nResult;
// Perform binary search on the table
while(pStartEntry < pEndEntry)
{
// Calculate the middle of the interval
pMidlEntry = pStartEntry + ((pEndEntry - pStartEntry) / 2);
// Did we find it?
nResult = (int)FileDataId - (int)pMidlEntry->FileDataId;
if(nResult == 0)
return pMidlEntry;
// Move the interval to the left or right
(nResult < 0) ? pEndEntry = pMidlEntry : pStartEntry = pMidlEntry + 1;
}
return NULL;
}
// Search by file name hash
// Also used in CascSearchFile
PCASC_FILE_ENTRY FindRootEntry(PCASC_MAP pRootMap, const char * szFileName, DWORD * PtrTableIndex)
{
// Calculate the HASH value of the normalized file name
ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
// Perform the hash search
return (PCASC_FILE_ENTRY)Map_FindObject(pRootMap, &FileNameHash, PtrTableIndex);
}
LPBYTE VerifyLocaleBlock(PCASC_ROOT_BLOCK pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
{
// Validate the file locale block
pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
if(pbFilePointer > pbFileEnd)
return NULL;
// Validate the array of 32-bit integers
pBlockInfo->FileDataIds = (PDWORD)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->FileDataIds + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
if(pbFilePointer > pbFileEnd)
return NULL;
// Validate the array of root entries
pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer;
pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
if(pbFilePointer > pbFileEnd)
return NULL;
// Return the position of the next block
return pbFilePointer;
}
static int ParseRoot_CountFiles(
TRootHandler_WoW6 * pRootHandler,
PCASC_ROOT_BLOCK pRootBlock)
{
// Add the file count to the total file count
pRootHandler->dwTotalFileCount += pRootBlock->pLocaleBlockHdr->NumberOfFiles;
return ERROR_SUCCESS;
}
static int ParseRoot_AddRootEntries(
TRootHandler_WoW6 * pRootHandler,
PCASC_ROOT_BLOCK pRootBlock)
{
PCASC_FILE_ENTRY pFileEntry;
// Sanity checks
assert(pRootHandler->FileTable.ItemArray != NULL);
assert(pRootHandler->FileTable.ItemCountMax != 0);
// WoW.exe (build 19116): Blocks with zero files are skipped
for(DWORD i = 0; i < pRootBlock->pLocaleBlockHdr->NumberOfFiles; i++)
{
// Create new entry, with overflow check
if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
return ERROR_INSUFFICIENT_BUFFER;
pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
// (004147A3) Prepare the CASC_FILE_ENTRY structure
pFileEntry->FileNameHash = pRootBlock->pRootEntries[i].FileNameHash;
pFileEntry->FileDataId = pRootHandler->FileDataId + pRootBlock->FileDataIds[i];
pFileEntry->Locales = pRootBlock->pLocaleBlockHdr->Locales;
pFileEntry->EncodingKey = pRootBlock->pRootEntries[i].EncodingKey;
// Also, insert the entry to the map
Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
// Update the local File Data Id
assert((pFileEntry->FileDataId + 1) > pFileEntry->FileDataId);
pRootHandler->FileDataId = pFileEntry->FileDataId + 1;
// Move to the next root entry
pFileEntry++;
}
return ERROR_SUCCESS;
}
static int ParseWowRootFileInternal(
TRootHandler_WoW6 * pRootHandler,
PARSE_ROOT pfnParseRoot,
LPBYTE pbRootFile,
LPBYTE pbRootFileEnd,
DWORD dwLocaleMask,
bool bLoadBlocksWithFlags80,
BYTE HighestBitValue)
{
CASC_ROOT_BLOCK RootBlock;
// Now parse the root file
while(pbRootFile < pbRootFileEnd)
{
// Validate the file locale block
pbRootFile = VerifyLocaleBlock(&RootBlock, pbRootFile, pbRootFileEnd);
if(pbRootFile == NULL)
break;
// WoW.exe (build 19116): Entries with flag 0x100 set are skipped
if(RootBlock.pLocaleBlockHdr->Flags & 0x100)
continue;
// WoW.exe (build 19116): Entries with flag 0x80 set are skipped if arg_4 is set to FALSE (which is by default)
if((RootBlock.pLocaleBlockHdr->Flags & 0x80) && bLoadBlocksWithFlags80 == 0)
continue;
// WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to arg_8 are skipped
if((RootBlock.pLocaleBlockHdr->Flags >> 0x1F) != HighestBitValue)
continue;
// WoW.exe (build 19116): Locales other than defined mask are skipped too
if((RootBlock.pLocaleBlockHdr->Locales & dwLocaleMask) == 0)
continue;
// Now call the custom function
pfnParseRoot(pRootHandler, &RootBlock);
}
return ERROR_SUCCESS;
}
/*
// Code from WoW.exe
if(dwLocaleMask == 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 ParseWowRootFile2(
TRootHandler_WoW6 * pRootHandler,
PARSE_ROOT pfnParseRoot,
LPBYTE pbRootFile,
LPBYTE pbRootFileEnd,
DWORD dwLocaleMask,
BYTE HighestBitValue)
{
// Load the locale as-is
ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, false, HighestBitValue);
// If we wanted enGB, we also load enUS for the missing files
if(dwLocaleMask == CASC_LOCALE_ENGB)
ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_ENUS, false, HighestBitValue);
if(dwLocaleMask == CASC_LOCALE_PTPT)
ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_PTBR, false, HighestBitValue);
return ERROR_SUCCESS;
}
// WoW.exe: 004146C7 (BuildManifest::Load)
static int ParseWowRootFile(
TRootHandler_WoW6 * pRootHandler,
PARSE_ROOT pfnParseRoot,
LPBYTE pbRootFile,
LPBYTE pbRootFileEnd,
DWORD dwLocaleMask)
{
ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 0);
ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 1);
return ERROR_SUCCESS;
}
//-----------------------------------------------------------------------------
// Implementation of WoW6 root file
static int WowHandler_Insert(
TRootHandler_WoW6 * pRootHandler,
const char * szFileName,
LPBYTE pbEncodingKey)
{
PCASC_FILE_ENTRY pFileEntry;
DWORD FileDataId = 0;
// Don't let the number of items to overflow
if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
return ERROR_NOT_ENOUGH_MEMORY;
// Insert the item to the linear file list
pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
if(pFileEntry != NULL)
{
// Get the file data ID of the previous item (0 if this is the first one)
if(pRootHandler->FileTable.ItemCount > 1)
FileDataId = pFileEntry[-1].FileDataId;
// Fill-in the new entry
pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey;
pFileEntry->FileNameHash = CalcFileNameHash(szFileName);
pFileEntry->FileDataId = FileDataId + 1;
pFileEntry->Locales = CASC_LOCALE_ALL;
// Verify collisions (debug version only)
assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL);
// Insert the entry to the map
Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
}
return ERROR_SUCCESS;
}
static LPBYTE WowHandler_Search(TRootHandler_WoW6 * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD PtrLocaleFlags)
{
PCASC_FILE_ENTRY pFileEntry;
// Only if we have a listfile
if(pSearch->pCache != NULL)
{
// Keep going through the listfile
while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
{
// Find the root entry
pFileEntry = FindRootEntry(pRootHandler->pRootMap, pSearch->szFileName, NULL);
if(pFileEntry != NULL)
{
// Give the caller the locale mask
if(PtrLocaleFlags != NULL)
PtrLocaleFlags[0] = pFileEntry->Locales;
return pFileEntry->EncodingKey.Value;
}
}
}
// No more files
return NULL;
}
static LPBYTE WowHandler_GetKey(TRootHandler_WoW6 * pRootHandler, const char * szFileName)
{
PCASC_FILE_ENTRY pFileEntry;
DWORD FileDataId;
BYTE FileDataIdLE[4];
// Open by FileDataId. The file name must be as following:
// File########.xxx, where '#' are hexa-decimal numbers (case insensitive).
// Extension is ignored in that case
if(IsFileDataIdName(szFileName))
{
ConvertStringToBinary(szFileName + 4, 8, FileDataIdLE);
FileDataId = ConvertBytesToInteger_4(FileDataIdLE);
pFileEntry = FindRootEntry(pRootHandler->FileTable, FileDataId);
}
else
{
// Find by the file name hash
pFileEntry = FindRootEntry(pRootHandler->pRootMap, szFileName, NULL);
}
return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL;
}
static void WowHandler_EndSearch(TRootHandler_WoW6 * /* pRootHandler */, TCascSearch * pSearch)
{
if(pSearch->pRootContext != NULL)
CASC_FREE(pSearch->pRootContext);
pSearch->pRootContext = NULL;
}
static void WowHandler_Close(TRootHandler_WoW6 * pRootHandler)
{
if(pRootHandler != NULL)
{
Array_Free(&pRootHandler->FileTable);
Map_Free(pRootHandler->pRootMap);
CASC_FREE(pRootHandler);
}
}
#ifdef _DEBUG
static void TRootHandlerWoW6_Dump(
TCascStorage * hs,
TDumpContext * dc, // Pointer to an opened file
LPBYTE pbRootFile,
DWORD cbRootFile,
const TCHAR * szListFile,
int nDumpLevel)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
CASC_ROOT_BLOCK BlockInfo;
PLISTFILE_MAP pListMap;
QUERY_KEY EncodingKey;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
LPBYTE pbFilePointer;
char szOneLine[0x100];
DWORD i;
// Create the listfile map
pListMap = ListFile_CreateMap(szListFile);
// Dump the root entries
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
{
// Validate the root block
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
if(pbFilePointer == NULL)
break;
// Dump the locale block
dump_print(dc, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
"=========================================================\n",
BlockInfo.pLocaleBlockHdr->Flags,
BlockInfo.pLocaleBlockHdr->Locales,
BlockInfo.pLocaleBlockHdr->NumberOfFiles);
// Dump the hashes and encoding keys
for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
{
// Dump the entry
dump_print(dc, "%08X %08X-%08X %s %s\n",
(DWORD)(BlockInfo.FileDataIds[i]),
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20),
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash),
StringFromMD5(BlockInfo.pRootEntries[i].EncodingKey.Value, szOneLine),
ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash));
// Find the encoding entry in the encoding table
if(nDumpLevel >= DUMP_LEVEL_ENCODING_FILE)
{
EncodingKey.pbData = BlockInfo.pRootEntries[i].EncodingKey.Value;
EncodingKey.cbData = MD5_HASH_SIZE;
pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL);
CascDumpEncodingEntry(hs, dc, pEncodingEntry, nDumpLevel);
}
}
// Put extra newline
dump_print(dc, "\n");
}
ListFile_FreeMap(pListMap);
}
#endif
//-----------------------------------------------------------------------------
// Public functions
int RootHandler_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask)
{
TRootHandler_WoW6 * pRootHandler;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
int nError;
// Verify the size
if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK))
nError = ERROR_FILE_CORRUPT;
// Allocate the root handler object
hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_WoW6, 1);
if(pRootHandler == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill-in the handler functions
memset(pRootHandler, 0, sizeof(TRootHandler_WoW6));
pRootHandler->Insert = (ROOT_INSERT)WowHandler_Insert;
pRootHandler->Search = (ROOT_SEARCH)WowHandler_Search;
pRootHandler->EndSearch = (ROOT_ENDSEARCH)WowHandler_EndSearch;
pRootHandler->GetKey = (ROOT_GETKEY)WowHandler_GetKey;
pRootHandler->Close = (ROOT_CLOSE)WowHandler_Close;
#ifdef _DEBUG
pRootHandler->Dump = TRootHandlerWoW6_Dump; // Support for ROOT file dump
#endif // _DEBUG
// Count the files that are going to be loaded
ParseWowRootFile(pRootHandler, ParseRoot_CountFiles, pbRootFile, pbRootFileEnd, dwLocaleMask);
pRootHandler->dwTotalFileCount += CASC_EXTRA_FILES;
// Create linear table that will contain all root items
nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, pRootHandler->dwTotalFileCount);
if(nError != ERROR_SUCCESS)
return nError;
// Create the map of FileHash ->FileEntry
pRootHandler->pRootMap = Map_Create(pRootHandler->dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash));
if(pRootHandler->pRootMap == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Parse the root file again and insert all files to the map
ParseWowRootFile(pRootHandler, ParseRoot_AddRootEntries, pbRootFile, pbRootFileEnd, dwLocaleMask);
return ERROR_SUCCESS;
}

View File

@@ -99,7 +99,7 @@ void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength)
szTarget[cchLength] = 0;
}
char * NewStr(const char * szString, size_t nCharsToReserve)
char * CascNewStr(const char * szString, size_t nCharsToReserve)
{
char * szNewString = NULL;
size_t nLength;
@@ -118,7 +118,7 @@ char * NewStr(const char * szString, size_t nCharsToReserve)
return szNewString;
}
wchar_t * NewStr(const wchar_t * szString, size_t nCharsToReserve)
wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve)
{
wchar_t * szNewString = NULL;
size_t nLength;
@@ -137,22 +137,20 @@ wchar_t * NewStr(const wchar_t * szString, size_t nCharsToReserve)
return szNewString;
}
TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd)
TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd)
{
TCHAR * szNewString = NULL;
TCHAR * szStringPtr = NULL;
size_t nLength = (size_t)(pbStringEnd - pbStringBegin);
if(pbStringEnd > pbStringBegin)
// Only if the entry is valid
if(szBegin != NULL && szEnd > szBegin)
{
szNewString = szStringPtr = CASC_ALLOC(TCHAR, nLength + 1);
// Allocate and copy the string
szNewString = CASC_ALLOC(TCHAR, (szEnd - szBegin + 1));
if(szNewString != NULL)
{
CopyString(szStringPtr, (const char *)pbStringBegin, nLength);
szStringPtr[nLength] = 0;
}
CopyString(szNewString, szBegin, (szEnd - szBegin));
}
// Return the string
return szNewString;
}
@@ -218,30 +216,60 @@ TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir)
return szFullPath;
}
void NormalizeFileName_UpperBkSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars)
TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength)
{
char * szTrgFileEnd = szTrgFileName + cchMaxChars;
size_t i;
TCHAR * szFullPath = NULL;
TCHAR * szSubDir;
// Normalize the file name: ToLower + BackSlashToSlash
for(i = 0; szSrcFileName[i] != 0 && szTrgFileName < szTrgFileEnd; i++)
szTrgFileName[i] = AsciiToUpperTable_BkSlash[szSrcFileName[i]];
// Create the subdir string
szSubDir = CASC_ALLOC(TCHAR, nLength + 1);
if(szSubDir != NULL)
{
CopyString(szSubDir, szString, nLength);
szFullPath = CombinePath(szPath, szSubDir);
CASC_FREE(szSubDir);
}
assert(szSrcFileName[i] == 0);
szTrgFileName[i] = 0;
return szFullPath;
}
void NormalizeFileName_LowerSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars)
size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars)
{
char * szTrgFileEnd = szTrgFileName + cchMaxChars;
char * szNormNameEnd = szNormName + cchMaxChars;
size_t i;
// Normalize the file name: ToLower + BackSlashToSlash
for(i = 0; szSrcFileName[i] != 0 && szTrgFileName < szTrgFileEnd; i++)
szTrgFileName[i] = AsciiToLowerTable_Slash[szSrcFileName[i]];
for(i = 0; szFileName[0] != 0 && szNormName < szNormNameEnd; i++)
*szNormName++ = NormTable[*szFileName++];
assert(szSrcFileName[i] == 0);
szTrgFileName[i] = 0;
// Terminate the string
szNormName[0] = 0;
return i;
}
size_t NormalizeFileName_UpperBkSlash(char * szNormName, const char * szFileName, size_t cchMaxChars)
{
return NormalizeFileName(AsciiToUpperTable_BkSlash, szNormName, szFileName, cchMaxChars);
}
size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, size_t cchMaxChars)
{
return NormalizeFileName(AsciiToLowerTable_Slash, szNormName, szFileName, cchMaxChars);
}
ULONGLONG CalcFileNameHash(const char * szFileName)
{
char szNormName[MAX_PATH+1];
uint32_t dwHashHigh = 0;
uint32_t dwHashLow = 0;
size_t nLength;
// Normalize the file name - convert to uppercase, slashes to backslashes
nLength = NormalizeFileName_UpperBkSlash(szNormName, szFileName, MAX_PATH);
// Calculate the HASH value of the normalized file name
hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow);
return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
}
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue)
@@ -256,6 +284,22 @@ int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue)
return (Digit > 0x0F) ? ERROR_BAD_FORMAT : ERROR_SUCCESS;
}
int ConvertStringToInt08(const char * szString, PDWORD PtrValue)
{
BYTE DigitOne = AsciiToUpperTable_BkSlash[szString[0]] - '0';
BYTE DigitTwo = AsciiToUpperTable_BkSlash[szString[1]] - '0';
// Fix the digits
if(DigitOne > 9)
DigitOne -= 'A' - '9' - 1;
if(DigitTwo > 9)
DigitTwo -= 'A' - '9' - 1;
// Combine them into a value
PtrValue[0] = (DigitOne << 0x04) | DigitTwo;
return (DigitOne <= 0x0F && DigitTwo <= 0x0F) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
}
int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrValue)
{
// The number of digits must be even
@@ -290,6 +334,41 @@ int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrVa
return ERROR_SUCCESS;
}
// Converts string blob to binary blob.
int ConvertStringToBinary(
const char * szString,
size_t nMaxDigits,
LPBYTE pbBinary)
{
const char * szStringEnd = szString + nMaxDigits;
DWORD dwCounter = 0;
BYTE DigitValue;
BYTE ByteValue = 0;
// Convert the string
while(szString < szStringEnd)
{
// Retrieve the digit converted to hexa
DigitValue = (BYTE)(AsciiToUpperTable_BkSlash[szString[0]] - '0');
if(DigitValue > 9)
DigitValue -= 'A' - '9' - 1;
if(DigitValue > 0x0F)
return ERROR_BAD_FORMAT;
// Insert the digit to the binary buffer
ByteValue = (ByteValue << 0x04) | DigitValue;
dwCounter++;
// If we reached the second digit, it means that we need
// to flush the byte value and move on
if((dwCounter & 0x01) == 0)
*pbBinary++ = ByteValue;
szString++;
}
return ERROR_SUCCESS;
}
char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer)
{
char * szSaveBuffer = szBuffer;
@@ -308,6 +387,11 @@ char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer)
return szSaveBuffer;
}
char * StringFromMD5(LPBYTE md5, char * szBuffer)
{
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
}
//-----------------------------------------------------------------------------
// File name utilities
@@ -341,87 +425,55 @@ const char * GetPlainFileName(const char * szFileName)
bool CheckWildCard(const char * szString, const char * szWildCard)
{
const char * szSubString;
int nSubStringLength;
int nMatchCount = 0;
const char * szWildCardPtr;
// When the mask is empty, it never matches
if(szWildCard == NULL || *szWildCard == 0)
return false;
// If the wildcard contains just "*", then it always matches
if(szWildCard[0] == '*' && szWildCard[1] == 0)
return true;
// Do normal test
for(;;)
{
// If there is '?' in the wildcard, we skip one char
while(*szWildCard == '?')
while(szWildCard[0] == '?')
{
if(szString[0] == 0)
return false;
szWildCard++;
szString++;
}
// If there is '*', means zero or more chars. We have to
// find the sequence after '*'
if(*szWildCard == '*')
// Handle '*'
szWildCardPtr = szWildCard;
if(szWildCardPtr[0] != 0)
{
// More stars is equal to one star
while(*szWildCard == '*' || *szWildCard == '?')
szWildCard++;
// If we found end of the wildcard, it's a match
if(*szWildCard == 0)
return true;
// Determine the length of the substring in szWildCard
szSubString = szWildCard;
while(*szSubString != 0 && *szSubString != '?' && *szSubString != '*')
szSubString++;
nSubStringLength = (int)(szSubString - szWildCard);
nMatchCount = 0;
// Now we have to find a substring in szString,
// that matches the substring in szWildCard
while(*szString != 0)
if(szWildCardPtr[0] == '*')
{
// Calculate match count
while(nMatchCount < nSubStringLength)
{
if(AsciiToUpperTable_BkSlash[(BYTE)szString[nMatchCount]] != AsciiToUpperTable_BkSlash[(BYTE)szWildCard[nMatchCount]])
break;
if(szString[nMatchCount] == 0)
break;
nMatchCount++;
}
szWildCardPtr++;
// If the match count has reached substring length, we found a match
if(nMatchCount == nSubStringLength)
{
szWildCard += nMatchCount;
szString += nMatchCount;
break;
}
if(szWildCardPtr[0] == '*')
continue;
// No match, move to the next char in szString
nMatchCount = 0;
szString++;
if(szWildCardPtr[0] == 0)
return true;
if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] == AsciiToUpperTable_BkSlash[szString[0]])
{
if(CheckWildCard(szString, szWildCardPtr))
return true;
}
}
else
{
if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] != AsciiToUpperTable_BkSlash[szString[0]])
return false;
szWildCard = szWildCardPtr + 1;
}
if(szString[0] == 0)
return false;
szString++;
}
else
{
// If we came to the end of the string, compare it to the wildcard
if(AsciiToUpperTable_BkSlash[(BYTE)*szString] != AsciiToUpperTable_BkSlash[(BYTE)*szWildCard])
return false;
// If we arrived to the end of the string, it's a match
if(*szString == 0)
return true;
// Otherwise, continue in comparing
szWildCard++;
szString++;
return (szString[0] == 0) ? true : false;
}
}
}

View File

@@ -28,14 +28,6 @@ extern unsigned char AsciiToLowerTable_Slash[256];
extern unsigned char AsciiToUpperTable_BkSlash[256];
extern unsigned char IntToHexChar[];
//-----------------------------------------------------------------------------
// GetLastError/SetLastError support for non-Windows platform
#ifndef PLATFORM_WINDOWS
int GetLastError();
void SetLastError(int nError);
#endif // PLATFORM_WINDOWS
//-----------------------------------------------------------------------------
// String manipulation
@@ -43,19 +35,25 @@ void CopyString(char * szTarget, const char * szSource, size_t cchLength);
void CopyString(wchar_t * szTarget, const char * szSource, size_t cchLength);
void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength);
char * NewStr(const char * szString, size_t nCharsToReserve);
wchar_t * NewStr(const wchar_t * szString, size_t nCharsToReserve);
char * CascNewStr(const char * szString, size_t nCharsToReserve);
wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve);
TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd);
TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd);
TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir);
TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength);
void NormalizeFileName_UpperBkSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars);
void NormalizeFileName_LowerSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars);
size_t NormalizeFileName_UpperBkSlash(char * szNormName, const char * szFileName, size_t cchMaxChars);
size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, size_t cchMaxChars);
ULONGLONG CalcFileNameHash(const char * szFileName);
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue);
int ConvertStringToInt08(const char * szString, PDWORD PtrValue);
int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrValue);
int ConvertStringToBinary(const char * szString, size_t nMaxDigits, LPBYTE pbBinary);
char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer);
char * StringFromMD5(LPBYTE md5, char * szBuffer);
//-----------------------------------------------------------------------------
// File name utilities

View File

@@ -0,0 +1,29 @@
/*****************************************************************************/
/* Directory.h Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Directory functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 30.10.15 1.00 Lad The first version of Directory.h */
/*****************************************************************************/
#ifndef __DIRECTORY_H__
#define __DIRECTORY_H__
//-----------------------------------------------------------------------------
// Scanning a directory
typedef bool (*INDEX_FILE_FOUND)(const TCHAR * szFileName, PDWORD IndexArray, PDWORD OldIndexArray, void * pvContext);
bool DirectoryExists(const TCHAR * szDirectory);
int ScanIndexDirectory(
const TCHAR * szIndexPath,
INDEX_FILE_FOUND pfnOnFileFound,
PDWORD IndexArray,
PDWORD OldIndexArray,
void * pvContext
);
#endif // __DIRECTORY_H__

View File

@@ -0,0 +1,153 @@
/*****************************************************************************/
/* DumpContext.cpp Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Implementation of dump context */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 16.03.15 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
static TCHAR * FormatFileName(TCascStorage * hs, const TCHAR * szNameFormat)
{
TCHAR * szFileName;
TCHAR * szSrc;
TCHAR * szTrg;
// Create copy of the file name
szFileName = szSrc = szTrg = CascNewStr(szNameFormat, 0);
if(szFileName != NULL)
{
// Format the file name
while(szSrc[0] != 0)
{
if(szSrc[0] == _T('%'))
{
// Replace "%build%" with a build number
if(!_tcsncmp(szSrc, _T("%build%"), 7))
{
szTrg += _stprintf(szTrg, _T("%u"), hs->dwBuildNumber);
szSrc += 7;
continue;
}
}
// Just copy the character
*szTrg++ = *szSrc++;
}
// Terminate the target file name
szTrg[0] = 0;
}
return szFileName;
}
//-----------------------------------------------------------------------------
// Public functions
TDumpContext * CreateDumpContext(TCascStorage * hs, const TCHAR * szNameFormat)
{
TDumpContext * dc;
TCHAR * szFileName;
// Validate the storage handle
if(hs != NULL)
{
// Calculate the number of bytes needed for dump context
dc = CASC_ALLOC(TDumpContext, 1);
if(dc != NULL)
{
// Initialize the dump context
memset(dc, 0, sizeof(TDumpContext));
// Format the real file name
szFileName = FormatFileName(hs, szNameFormat);
if(szFileName != NULL)
{
// Create the file
dc->pStream = FileStream_CreateFile(szFileName, 0);
if(dc->pStream != NULL)
{
// Initialize buffers
dc->pbBufferBegin =
dc->pbBufferPtr = dc->DumpBuffer;
dc->pbBufferEnd = dc->DumpBuffer + CASC_DUMP_BUFFER_SIZE;
// Success
CASC_FREE(szFileName);
return dc;
}
// File create failed --> delete the file name
CASC_FREE(szFileName);
}
// Free the dump context
CASC_FREE(dc);
}
}
return NULL;
}
int dump_print(TDumpContext * dc, const char * szFormat, ...)
{
va_list argList;
char szBuffer[0x200];
int nLength = 0;
// Only if the dump context is valid
if(dc != NULL)
{
// Print the buffer using sprintf
va_start(argList, szFormat);
nLength = vsprintf(szBuffer, szFormat, argList);
assert(nLength < 0x200);
va_end(argList);
// Copy the string. Replace "\n" with "\n\r"
for(int i = 0; i < nLength; i++)
{
// Flush the buffer, if needed
if((dc->pbBufferPtr + 2) >= dc->pbBufferEnd)
{
FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin));
dc->pbBufferPtr = dc->pbBufferBegin;
}
// Copy the char
if(szBuffer[i] == 0x0A)
*dc->pbBufferPtr++ = 0x0D;
*dc->pbBufferPtr++ = szBuffer[i];
}
}
return nLength;
}
int dump_close(TDumpContext * dc)
{
// Only if the dump context is valid
if(dc != NULL)
{
// Flush the dump context if there are some data
if(dc->pbBufferPtr > dc->pbBufferBegin)
FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin));
dc->pbBufferPtr = dc->pbBufferBegin;
// Free the file stream and the entire context
if(dc->pStream != NULL)
FileStream_Close(dc->pStream);
CASC_FREE(dc);
}
return 0;
}

View File

@@ -0,0 +1,38 @@
/*****************************************************************************/
/* DumpContext.h Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Interface for TDumpContext */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 16.03.15 1.00 Lad Created */
/*****************************************************************************/
#ifndef __DUMP_CONTEXT_H__
#define __DUMP_CONTEXT_H__
//-----------------------------------------------------------------------------
// Defines
// Size of the buffer for the dump context
#define CASC_DUMP_BUFFER_SIZE 0x10000
// Structure for dump context
struct TDumpContext
{
TFileStream * pStream; // Pointer to the open stream
LPBYTE pbBufferBegin; // Begin of the dump buffer
LPBYTE pbBufferPtr; // Current dump buffer pointer
LPBYTE pbBufferEnd; // End of the dump buffer
BYTE DumpBuffer[CASC_DUMP_BUFFER_SIZE]; // Dump buffer
};
//-----------------------------------------------------------------------------
// Dump context functions
TDumpContext * CreateDumpContext(struct _TCascStorage * hs, const TCHAR * szNameFormat);
int dump_print(TDumpContext * dc, const char * szFormat, ...);
int dump_close(TDumpContext * dc);
#endif // __DUMP_CONTEXT_H__

View File

@@ -0,0 +1,101 @@
/*****************************************************************************/
/* DynamicArray.cpp Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Description: */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 30.10.15 1.00 Lad The first version of DynamicArray.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
static bool EnlargeArray(PDYNAMIC_ARRAY pArray, size_t NewItemCount)
{
char * NewItemArray;
size_t ItemCountMax;
// We expect the array to be already allocated
assert(pArray->ItemArray != NULL);
assert(pArray->ItemCountMax != 0);
// Shall we enlarge the table?
if(NewItemCount > pArray->ItemCountMax)
{
// Calculate new table size
ItemCountMax = pArray->ItemCountMax;
while(ItemCountMax < NewItemCount)
ItemCountMax = ItemCountMax << 1;
// Allocate new table
NewItemArray = CASC_REALLOC(char, pArray->ItemArray, pArray->ItemSize * ItemCountMax);
if(NewItemArray == NULL)
return false;
// Set the new table size
pArray->ItemCountMax = ItemCountMax;
pArray->ItemArray = NewItemArray;
}
return true;
}
//-----------------------------------------------------------------------------
// Public functions
int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax)
{
pArray->ItemArray = CASC_ALLOC(char, (ItemSize * ItemCountMax));
if(pArray->ItemArray == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
pArray->ItemCountMax = ItemCountMax;
pArray->ItemCount = 0;
pArray->ItemSize = ItemSize;
return ERROR_SUCCESS;
}
void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount)
{
char * NewItemPtr;
// Try to enlarge the buffer, if needed
if(!EnlargeArray(pArray, pArray->ItemCount + NewItemCount))
return NULL;
NewItemPtr = pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize);
// Copy the old item(s), if any
if(NewItems != NULL)
memcpy(NewItemPtr, NewItems, (NewItemCount * pArray->ItemSize));
// Increment the size of the array
pArray->ItemCount += NewItemCount;
return NewItemPtr;
}
void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex)
{
assert(ItemIndex < pArray->ItemCount);
return pArray->ItemArray + (ItemIndex * pArray->ItemSize);
}
size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr)
{
char * ArrayItem = (char *)ArrayPtr;
assert(pArray->ItemArray <= ArrayItem && ArrayItem <= pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize));
return ((ArrayItem - pArray->ItemArray) / pArray->ItemSize);
}
void Array_Free(PDYNAMIC_ARRAY pArray)
{
if(pArray != NULL && pArray->ItemArray != NULL)
{
CASC_FREE(pArray->ItemArray);
}
}

View File

@@ -0,0 +1,37 @@
/*****************************************************************************/
/* DynamicArray.h Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Common array implementation */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 30.10.15 1.00 Lad The first version of DynamicArray.h */
/*****************************************************************************/
#ifndef __DYNAMIC_ARRAY_H__
#define __DYNAMIC_ARRAY_H__
//-----------------------------------------------------------------------------
// Structures
typedef struct _DYNAMIC_ARRAY
{
char * ItemArray; // Pointer to items
size_t ItemCountMax; // Current number of items
size_t ItemCount; // Total size of the buffer
size_t ItemSize; // Size of the single item
} DYNAMIC_ARRAY, *PDYNAMIC_ARRAY;
//-----------------------------------------------------------------------------
// Functions for managing the array
int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax);
void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount);
void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex);
size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr);
void Array_Free(PDYNAMIC_ARRAY pArray);
#define Array_Create(pArray, type, ItemCountMax) Array_Create_(pArray, sizeof(type), ItemCountMax)
#endif // __DYNAMIC_ARRAY__

View File

@@ -16,7 +16,6 @@
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
#include "FileStream.h"
#ifdef _MSC_VER
#pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream

View File

@@ -13,176 +13,33 @@
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Listfile entry structure
// Listfile cache structure
#define CACHE_BUFFER_SIZE 0x1000 // Size of the cache buffer
typedef bool (*RELOAD_CACHE)(void * pvCacheContext, LPBYTE pbBuffer, DWORD dwBytesToRead);
typedef void (*CLOSE_STREAM)(void * pvCacheContext);
struct TListFileCache
typedef struct _LISTFILE_CACHE
{
RELOAD_CACHE pfnReloadCache; // Function for reloading the cache
CLOSE_STREAM pfnCloseStream; // Function for closing the stream
void * pvCacheContext; // Reload context passed to reload function
char * szMask; // Self-relative pointer to file mask
DWORD dwFileSize; // Total size of the cached file
DWORD dwFilePos; // Position of the cache in the file
BYTE * pBegin; // The begin of the listfile cache
BYTE * pPos;
BYTE * pEnd; // The last character in the file cache
char * pBegin; // The begin of the listfile cache
char * pPos; // Current position in the cache
char * pEnd; // The last character in the file cache
BYTE Buffer[CACHE_BUFFER_SIZE];
// char MaskBuff[1] // Followed by the name mask (if any)
};
// Followed by the cache (variable length)
} LISTFILE_CACHE, *PLISTFILE_CACHE;
//-----------------------------------------------------------------------------
// Local functions
// Creating the listfile cache for the given amount of data
static bool ReloadCache_ExternalFile(void * pvCacheContext, LPBYTE pbBuffer, DWORD dwBytesToRead)
static PLISTFILE_CACHE CreateListFileCache(DWORD dwFileSize)
{
TFileStream * pStream = (TFileStream *)pvCacheContext;
return FileStream_Read(pStream, NULL, pbBuffer, dwBytesToRead);
}
static void CloseStream_ExternalFile(void * pvCacheContext)
{
TFileStream * pStream = (TFileStream *)pvCacheContext;
return FileStream_Close(pStream);
}
// Reloads the cache. Returns number of characters
// that has been loaded into the cache.
static DWORD ReloadListFileCache(TListFileCache * pCache)
{
DWORD dwBytesToRead = 0;
// Only do something if the cache is empty
if(pCache->pPos >= pCache->pEnd)
{
// Move the file position forward
pCache->dwFilePos += CACHE_BUFFER_SIZE;
if(pCache->dwFilePos >= pCache->dwFileSize)
return 0;
// Get the number of bytes remaining
dwBytesToRead = pCache->dwFileSize - pCache->dwFilePos;
if(dwBytesToRead > CACHE_BUFFER_SIZE)
dwBytesToRead = CACHE_BUFFER_SIZE;
// Load the next data chunk to the cache
// If we didn't read anything, it might mean that the block
// of the file is not available
// We stop reading the file at this point, because the rest
// of the listfile is unreliable
if(!pCache->pfnReloadCache(pCache->pvCacheContext, pCache->Buffer, dwBytesToRead))
return 0;
// Set the buffer pointers
pCache->pBegin =
pCache->pPos = &pCache->Buffer[0];
pCache->pEnd = pCache->pBegin + dwBytesToRead;
}
return dwBytesToRead;
}
static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, size_t nMaxChars)
{
char * szLineBegin = szLine;
char * szLineEnd = szLine + nMaxChars - 1;
char * szExtraString = NULL;
// Skip newlines, spaces, tabs and another non-printable stuff
for(;;)
{
// If we need to reload the cache, do it
if(pCache->pPos == pCache->pEnd)
{
if(ReloadListFileCache(pCache) == 0)
break;
}
// If we found a non-whitespace character, stop
if(pCache->pPos[0] > 0x20)
break;
// Skip the character
pCache->pPos++;
}
// Copy the remaining characters
while(szLine < szLineEnd)
{
// If we need to reload the cache, do it now and resume copying
if(pCache->pPos == pCache->pEnd)
{
if(ReloadListFileCache(pCache) == 0)
break;
}
// If we have found a newline, stop loading
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[0] == '~')
szExtraString = szLine;
// Copy the character
*szLine++ = *pCache->pPos++;
}
// Terminate line with zero
*szLine = 0;
// If there was extra string after the file name, clear it
if(szExtraString != NULL)
{
if(szExtraString[0] == '~' && szExtraString[1] == 'P')
{
szLine = szExtraString;
*szExtraString = 0;
}
}
// Return the length of the line
return (szLine - szLineBegin);
}
static TListFileCache * CreateListFileCache(RELOAD_CACHE pfnReloadCache, CLOSE_STREAM pfnCloseStream, void * pvCacheContext, DWORD dwFileSize)
{
TListFileCache * pCache = NULL;
DWORD dwBytesToRead;
PLISTFILE_CACHE pCache;
// Allocate cache for one file block
pCache = (TListFileCache *)CASC_ALLOC(BYTE, sizeof(TListFileCache));
pCache = (PLISTFILE_CACHE)CASC_ALLOC(BYTE, sizeof(LISTFILE_CACHE) + dwFileSize);
if(pCache != NULL)
{
// Clear the entire structure
memset(pCache, 0, sizeof(TListFileCache));
pCache->pfnReloadCache = pfnReloadCache;
pCache->pfnCloseStream = pfnCloseStream;
pCache->pvCacheContext = pvCacheContext;
pCache->dwFileSize = dwFileSize;
// Load the file cache from the file
dwBytesToRead = CASCLIB_MIN(CACHE_BUFFER_SIZE, dwFileSize);
if(pfnReloadCache(pvCacheContext, pCache->Buffer, dwBytesToRead))
{
// Allocate pointers
pCache->pBegin = pCache->pPos = &pCache->Buffer[0];
pCache->pEnd = pCache->pBegin + dwBytesToRead;
}
else
{
ListFile_Free(pCache);
pCache = NULL;
}
// Set the initial pointers
pCache->pBegin =
pCache->pPos = (char *)(pCache + 1);
pCache->pEnd = pCache->pBegin + dwFileSize;
}
// Return the cache
@@ -194,7 +51,7 @@ static TListFileCache * CreateListFileCache(RELOAD_CACHE pfnReloadCache, CLOSE_S
void * ListFile_OpenExternal(const TCHAR * szListFile)
{
TListFileCache * pCache;
PLISTFILE_CACHE pCache = NULL;
TFileStream * pStream;
ULONGLONG FileSize = 0;
@@ -204,46 +61,135 @@ void * ListFile_OpenExternal(const TCHAR * szListFile)
{
// Retrieve the size of the external listfile
FileStream_GetSize(pStream, &FileSize);
if(0 < FileSize && FileSize <= 0xFFFFFFFF)
{
// Create the cache for the listfile
pCache = CreateListFileCache(ReloadCache_ExternalFile, CloseStream_ExternalFile, pStream, (DWORD)FileSize);
if(0 < FileSize && FileSize <= 0x30000000)
{
// Create the in-memory cache for the entire listfile
// The listfile does not have any data loaded yet
pCache = CreateListFileCache((DWORD)FileSize);
if(pCache != NULL)
return pCache;
{
if(!FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize))
{
ListFile_Free(pCache);
pCache = NULL;
}
}
}
// Close the file stream
FileStream_Close(pStream);
}
return NULL;
return pCache;
}
void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer)
{
PLISTFILE_CACHE pCache = NULL;
// Create the in-memory cache for the entire listfile
// The listfile does not have any data loaded yet
pCache = CreateListFileCache(cbBuffer);
if(pCache != NULL)
memcpy(pCache->pBegin, pbBuffer, cbBuffer);
return pCache;
}
// Performs the MD5-based check on the listfile
bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5)
{
PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile;
// Must be at the beginning
assert(pCache->pPos == pCache->pBegin);
// Verify the MD5 hash for the entire block
return VerifyDataBlockHash(pCache->pBegin, (DWORD)(pCache->pEnd - pCache->pBegin), pbHashMD5);
}
size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd)
{
PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile;
char * szExtraString = NULL;
char * szLineBegin;
char * szLineEnd;
// Skip newlines, spaces, tabs and another non-printable stuff
while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20)
pCache->pPos++;
// Remember the begin of the line
szLineBegin = pCache->pPos;
// Copy the remaining characters
while(pCache->pPos < pCache->pEnd)
{
// If we have found a newline, stop loading
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[0] == '~')
szExtraString = pCache->pPos;
// Move the position by one character forward
pCache->pPos++;
}
// Remember the end of the line
szLineEnd = (szExtraString != NULL && szExtraString[0] == '~' && szExtraString[1] == 'P') ? szExtraString : pCache->pPos;
// Give the caller the positions of the begin and end of the line
pszLineBegin[0] = szLineBegin;
pszLineEnd[0] = szLineEnd;
return (size_t)(szLineEnd - szLineBegin);
}
size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars)
{
const char * szLineBegin = NULL;
const char * szLineEnd = NULL;
size_t nLength;
// Retrieve the next line
nLength = ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd);
// Check the length
if(nLength > nMaxChars)
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return 0;
}
// Copy the line to the user buffer
memcpy(szBuffer, szLineBegin, nLength);
szBuffer[nLength] = 0;
return nLength;
}
size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars)
{
TListFileCache * pCache = (TListFileCache *)pvListFile;
size_t nLength = 0;
int nError = ERROR_INVALID_PARAMETER;
int nError = ERROR_SUCCESS;
// Check for parameters
if(pCache != NULL)
for(;;)
{
for(;;)
// Read the (next) line
nLength = ListFile_GetNextLine(pvListFile, szBuffer, nMaxChars);
if(nLength == 0)
{
// Read the (next) line
nLength = ReadListFileLine(pCache, szBuffer, nMaxChars);
if(nLength == 0)
{
nError = ERROR_NO_MORE_FILES;
break;
}
nError = ERROR_NO_MORE_FILES;
break;
}
// If some mask entered, check it
if(CheckWildCard(szBuffer, szMask))
{
nError = ERROR_SUCCESS;
break;
}
// If some mask entered, check it
if(CheckWildCard(szBuffer, szMask))
{
nError = ERROR_SUCCESS;
break;
}
}
@@ -254,14 +200,9 @@ size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer,
void ListFile_Free(void * pvListFile)
{
TListFileCache * pCache = (TListFileCache *)pvListFile;
// Valid parameter check
if(pCache != NULL)
if(pvListFile != NULL)
{
if(pCache->pfnCloseStream != NULL)
pCache->pfnCloseStream(pCache->pvCacheContext);
CASC_FREE(pCache);
CASC_FREE(pvListFile);
}
}
@@ -293,11 +234,8 @@ static PLISTFILE_MAP ListMap_Create()
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;
@@ -314,14 +252,10 @@ static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szF
// Get the pointer to the first entry
pListEntry = (PLISTFILE_ENTRY)((LPBYTE)(pListMap + 1) + pListMap->cbBuffer);
// Get the name hash
NormalizeFileName_UpperBkSlash(szFileName2, szFileName, MAX_PATH);
hashlittle2(szFileName2, nLength, &dwHashHigh, &dwHashLow);
// Calculate the HASH value of the normalized file name
pListEntry->FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
pListEntry->FileNameHash = CalcFileNameHash(szFileName);
pListEntry->cbEntrySize = (DWORD)cbEntrySize;
// Copy the file name to the entry
memcpy(pListEntry->szFileName, szFileName, nLength);
pListEntry->szFileName[nLength] = 0;
@@ -357,7 +291,7 @@ static PLISTFILE_MAP ListMap_Finish(PLISTFILE_MAP pListMap)
pbEntry += pListEntry->cbEntrySize;
// Insert the entry to the map
Map_InsertObject(pMap, pListEntry);
Map_InsertObject(pMap, pListEntry, &pListEntry->FileNameHash);
}
return pListMap;
@@ -408,7 +342,7 @@ const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash)
PLISTFILE_ENTRY pListEntry = NULL;
if(pListMap != NULL)
pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash);
pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash, NULL);
return (pListEntry != NULL) ? pListEntry->szFileName : "";
}

View File

@@ -37,6 +37,10 @@ typedef struct _LISTFILE_MAP
// Functions for parsing an external listfile
void * ListFile_OpenExternal(const TCHAR * szListFile);
void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer);
bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5);
size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd);
size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars);
size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars);
void ListFile_Free(void * pvListFile);

View File

@@ -15,51 +15,82 @@
//-----------------------------------------------------------------------------
// Local functions
static DWORD CalcHashIndex(PCASC_MAP pMap, void * pvIdentifier)
// Returns the extension, right after "."
static const char * String_GetExtension(const char * szString)
{
const char * szExtension = strrchr(szString, '.');
return (szExtension != NULL) ? szExtension + 1 : NULL;
}
static DWORD CalcHashIndex_Key(PCASC_MAP pMap, void * pvKey)
{
LPBYTE pbKey = (LPBYTE)pvKey;
DWORD dwHash = 0x7EEE7EEE;
// Is it a string table?
if(pMap->KeyLength == KEY_LENGTH_STRING)
{
char * szString = (char *)pvIdentifier;
// Construct the hash from the first 8 digits
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[0];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[1];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[2];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[3];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[4];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[5];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[6];
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[7];
for(size_t i = 0; szString[i] != 0; i++)
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ szString[i];
}
else
{
LPBYTE pbHash = (LPBYTE)pvIdentifier;
// Return the hash limited by the table size
return (dwHash % pMap->TableSize);
}
// Construct the hash from the first 4 digits
for(size_t i = 0; i < pMap->KeyLength; i++)
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbHash[i];
static DWORD CalcHashIndex_String(PCASC_MAP pMap, const char * szString, const char * szStringEnd)
{
LPBYTE pbKeyEnd = (LPBYTE)szStringEnd;
LPBYTE pbKey = (LPBYTE)szString;
DWORD dwHash = 0x7EEE7EEE;
// Hash the string itself
while(pbKey < pbKeyEnd)
{
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]];
pbKey++;
}
// Return the hash limited by the table size
return (dwHash % pMap->TableSize);
}
static bool CompareIdentifier(PCASC_MAP pMap, void * pvTableEntry, void * pvIdentifier)
static bool CompareObject_Key(PCASC_MAP pMap, void * pvObject, void * pvKey)
{
// Is it a string table?
if(pMap->KeyLength == KEY_LENGTH_STRING)
{
char * szTableEntry = (char *)pvTableEntry;
char * szIdentifier = (char *)pvIdentifier;
LPBYTE pbObjectKey = (LPBYTE)pvObject + pMap->KeyOffset;
return (strcmp(szTableEntry, szIdentifier) == 0);
}
else
return (memcmp(pbObjectKey, pvKey, pMap->KeyLength) == 0);
}
static bool CompareObject_String(
PCASC_MAP pMap,
const char * szExistingString,
const char * szString,
const char * szStringEnd)
{
// Keep compiler happy
CASCLIB_UNUSED(pMap);
// Compare the whole part, case insensitive
while(szString < szStringEnd)
{
return (memcmp(pvTableEntry, pvIdentifier, pMap->KeyLength) == 0);
if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString])
return false;
szExistingString++;
szString++;
}
return true;
}
//-----------------------------------------------------------------------------
// Public functions
PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwMemberOffset)
PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset)
{
PCASC_MAP pMap;
size_t cbToAllocate;
@@ -76,7 +107,7 @@ PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwMemberOffset)
memset(pMap, 0, cbToAllocate);
pMap->KeyLength = dwKeyLength;
pMap->TableSize = dwTableSize;
pMap->MemberOffset = dwMemberOffset;
pMap->KeyOffset = dwKeyOffset;
}
// Return the allocated map
@@ -104,24 +135,28 @@ size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray)
return pMap->ItemCount;
}
void * Map_FindObject(PCASC_MAP pMap, void * pvIdentifier)
void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex)
{
void * pvTableEntry;
void * pvObject;
DWORD dwHashIndex;
// Verify pointer to the map
if(pMap != NULL)
{
// Construct the main index
dwHashIndex = CalcHashIndex(pMap, pvIdentifier);
dwHashIndex = CalcHashIndex_Key(pMap, pvKey);
while(pMap->HashTable[dwHashIndex] != NULL)
{
// Get the pointer at that position
pvTableEntry = pMap->HashTable[dwHashIndex];
pvObject = pMap->HashTable[dwHashIndex];
// Compare the hash
if(CompareIdentifier(pMap, pvTableEntry, pvIdentifier))
return ((LPBYTE)pvTableEntry - pMap->MemberOffset);
if(CompareObject_Key(pMap, pvObject, pvKey))
{
if(PtrIndex != NULL)
PtrIndex[0] = dwHashIndex;
return pvObject;
}
// Move to the next entry
dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
@@ -132,9 +167,9 @@ void * Map_FindObject(PCASC_MAP pMap, void * pvIdentifier)
return NULL;
}
bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey)
{
void * pvTableEntry;
void * pvExistingObject;
DWORD dwHashIndex;
// Verify pointer to the map
@@ -145,14 +180,14 @@ bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
return false;
// Construct the hash index
dwHashIndex = CalcHashIndex(pMap, pvIdentifier);
dwHashIndex = CalcHashIndex_Key(pMap, pvKey);
while(pMap->HashTable[dwHashIndex] != NULL)
{
// Get the pointer at that position
pvTableEntry = pMap->HashTable[dwHashIndex];
pvExistingObject = pMap->HashTable[dwHashIndex];
// Check if hash being inserted conflicts with an existing hash
if(CompareIdentifier(pMap, pvTableEntry, pvIdentifier))
if(CompareObject_Key(pMap, pvExistingObject, pvKey))
return false;
// Move to the next entry
@@ -160,7 +195,7 @@ bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
}
// Insert at that position
pMap->HashTable[dwHashIndex] = pvIdentifier;
pMap->HashTable[dwHashIndex] = pvNewObject;
pMap->ItemCount++;
return true;
}
@@ -169,6 +204,78 @@ bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
return false;
}
bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension)
{
const char * szExistingString;
const char * szStringEnd = NULL;
DWORD dwHashIndex;
// Verify pointer to the map
if(pMap != NULL)
{
// Limit check
if((pMap->ItemCount + 1) >= pMap->TableSize)
return false;
// Retrieve the length of the string without extension
if(bCutExtension)
szStringEnd = String_GetExtension(szString);
if(szStringEnd == NULL)
szStringEnd = szString + strlen(szString);
// Construct the hash index
dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd);
while(pMap->HashTable[dwHashIndex] != NULL)
{
// Get the pointer at that position
szExistingString = (const char *)pMap->HashTable[dwHashIndex];
// Check if hash being inserted conflicts with an existing hash
if(CompareObject_String(pMap, szExistingString, szString, szStringEnd))
return false;
// Move to the next entry
dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
}
// Insert at that position
pMap->HashTable[dwHashIndex] = (void *)szString;
pMap->ItemCount++;
return true;
}
// Failed
return false;
}
const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd)
{
const char * szExistingString;
DWORD dwHashIndex;
// Verify pointer to the map
if(pMap != NULL)
{
// Construct the main index
dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd);
while(pMap->HashTable[dwHashIndex] != NULL)
{
// Get the pointer at that position
szExistingString = (const char *)pMap->HashTable[dwHashIndex];
// Compare the hash
if(CompareObject_String(pMap, szExistingString, szString, szStringEnd))
return szExistingString;
// Move to the next entry
dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
}
}
// Not found, sorry
return NULL;
}
void Map_Free(PCASC_MAP pMap)
{
if(pMap != NULL)

View File

@@ -20,20 +20,23 @@ typedef struct _CASC_MAP
{
size_t TableSize;
size_t ItemCount; // Number of items in the map
size_t MemberOffset; // How far is the hash from the begin of the structure (in bytes)
size_t KeyOffset; // How far is the hash from the begin of the structure (in bytes)
size_t KeyLength; // Length of the hash key
void * HashTable[1]; // Pointer table
} CASC_MAP, *PCASC_MAP;
typedef bool (*MAP_COMPARE)(PCASC_MAP pMap, void * pvObject, void * pvKey);
//-----------------------------------------------------------------------------
// Functions
PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwMemberOffset);
PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset);
size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray);
void * Map_FindObject(PCASC_MAP pMap, void * pvIdentifier);
bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier);
void Map_Sort(PCASC_MAP pMap);
void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex);
bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey);
const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd);
bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension);
void Map_Free(PCASC_MAP pMap);
#endif // __HASHTOPTR_H__

View File

@@ -0,0 +1,78 @@
/*****************************************************************************/
/* RootHandler.cpp Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Implementation of root handler */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 09.03.15 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Common support
int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey)
{
if(pRootHandler == NULL || pRootHandler->Insert == NULL)
return ERROR_NOT_SUPPORTED;
return pRootHandler->Insert(pRootHandler, szFileName, pbEncodingKey);
}
LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags)
{
// Check if the root structure is valid at all
if(pRootHandler == NULL)
return NULL;
return pRootHandler->Search(pRootHandler, pSearch, PtrFileSize, PtrLocaleFlags);
}
void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch)
{
// Check if the root structure is valid at all
if(pRootHandler != NULL)
{
pRootHandler->EndSearch(pRootHandler, pSearch);
}
}
LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName)
{
// Check if the root structure is valid at all
if(pRootHandler == NULL)
return NULL;
return pRootHandler->GetKey(pRootHandler, szFileName);
}
void RootHandler_Dump(TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel)
{
TDumpContext * dc;
// Only if the ROOT provider suports the dump option
if(hs->pRootHandler != NULL && hs->pRootHandler->Dump != NULL)
{
// Create the dump file
dc = CreateDumpContext(hs, szNameFormat);
if(dc != NULL)
{
// Dump the content and close the file
hs->pRootHandler->Dump(hs, dc, pbRootHandler, cbRootHandler, szListFile, nDumpLevel);
dump_close(dc);
}
}
}
void RootHandler_Close(TRootHandler * pRootHandler)
{
// Check if the root structure is allocated at all
if(pRootHandler != NULL)
{
pRootHandler->Close(pRootHandler);
}
}

View File

@@ -0,0 +1,88 @@
/*****************************************************************************/
/* RootHandler.h Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Interface for root handlers */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 09.03.15 1.00 Lad Created */
/*****************************************************************************/
#ifndef __ROOT_HANDLER_H__
#define __ROOT_HANDLER_H__
//-----------------------------------------------------------------------------
// Defines
#define CASC_MNDX_ROOT_SIGNATURE 0x58444E4D // 'MNDX'
#define CASC_DIABLO3_ROOT_SIGNATURE 0x8007D0C4
#define CASC_OVERWATCH_ROOT_SIGNATURE 0x35444D23 // '#MD5'
#define ROOT_FLAG_HAS_NAMES 0x00000001 // The root file contains file names
#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file
#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file
#define DUMP_LEVEL_INDEX_ENTRIES 3 // Dump root file + encoding file + index entries
//-----------------------------------------------------------------------------
// Root file function prototypes
typedef int (*ROOT_INSERT)(
struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
const char * szFileName, // Pointer to the file name
LPBYTE pbEncodingKey // Pointer to the encoding key of the file name
);
typedef LPBYTE (*ROOT_SEARCH)(
struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
struct _TCascSearch * pSearch, // Pointer to the initialized search structure
PDWORD PtrFileSize, // Pointer to receive file size (optional)
PDWORD PtrLocaleFlags // Pointer to receive locale flags (optional)
);
typedef void (*ROOT_ENDSEARCH)(
struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
struct _TCascSearch * pSearch // Pointer to the initialized search structure
);
typedef LPBYTE (*ROOT_GETKEY)(
struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
const char * szFileName // Pointer to the name of a file
);
typedef void (*ROOT_DUMP)(
struct _TCascStorage * hs, // Pointer to the open storage
TDumpContext * dc, // Opened dump context
LPBYTE pbRootHandler, // Pointer to the loaded ROOT file
DWORD cbRootHandler, // Length of the loaded ROOT file
const TCHAR * szListFile,
int nDumpLevel
);
typedef void (*ROOT_CLOSE)(
struct TRootHandler * pRootHandler // Pointer to an initialized root handler
);
struct TRootHandler
{
ROOT_INSERT Insert; // Inserts an existing file name
ROOT_SEARCH Search; // Performs the root file search
ROOT_ENDSEARCH EndSearch; // Performs cleanup after searching
ROOT_GETKEY GetKey; // Retrieves encoding key for a file name
ROOT_DUMP Dump;
ROOT_CLOSE Close; // Closing the root file
DWORD dwRootFlags; // Root flags - see the ROOT_FLAG_XXX
};
//-----------------------------------------------------------------------------
// Public functions
int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey);
LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags);
void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch);
LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName);
void RootHandler_Dump(struct _TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel);
void RootHandler_Close(TRootHandler * pRootHandler);
#endif // __ROOT_HANDLER_H__

View File

@@ -19,8 +19,8 @@
#if (ARGTYPE == 0)
void crypt_argchk(char *v, char *s, int d)
{
fprintf(stderr, "LTC_ARGCHK '%s' failure on line %d of file %s\n",
v, d, s);
fprintf(stderr, "LTC_ARGCHK '%s' failure on line %d of file %s\n",
v, d, s);
(void)raise(SIGABRT);
}
#endif