base: DestinyCore upstream @2d631f4e (fork SylvaniaCore ; sql/old retire)

This commit is contained in:
SylvaniaCore deploy
2026-06-17 09:00:37 +00:00
commit f8fcb8ad38
4834 changed files with 2810723 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
/*****************************************************************************/
/* CascCommon.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Common functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascCommon.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Functions
LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData)
{
LPBYTE pbFileData = NULL;
HANDLE hFile = NULL;
DWORD cbFileData = pcbFileData[0];
DWORD dwBytesRead = 0;
int nError = ERROR_SUCCESS;
// Open the file either by CKey or by EKey
if(OpenFileByCKeyEntry(hs, pCKeyEntry, CASC_STRICT_DATA_CHECK, &hFile))
{
// Retrieve the size of the file. Note that the caller might specify
// the real size of the file, in case the file size is not retrievable
// or if the size is wrong. Example: ENCODING file has size specified in BUILD
if(cbFileData == 0 || cbFileData == CASC_INVALID_SIZE)
{
cbFileData = CascGetFileSize(hFile, NULL);
if(cbFileData == 0 || cbFileData == CASC_INVALID_SIZE)
nError = ERROR_FILE_CORRUPT;
}
// Retrieve the size of the ENCODING file
if(nError == ERROR_SUCCESS)
{
// Allocate space for the ENCODING file
pbFileData = CASC_ALLOC(BYTE, cbFileData);
if(pbFileData != NULL)
{
// Read the entire file to memory
CascReadFile(hFile, pbFileData, cbFileData, &dwBytesRead);
if(dwBytesRead != cbFileData)
{
nError = ERROR_FILE_CORRUPT;
}
}
else
{
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
// Close the file
CascCloseFile(hFile);
}
else
{
nError = GetLastError();
}
// Handle errors
if(nError != ERROR_SUCCESS)
{
// Free the file data
CASC_FREE(pbFileData);
cbFileData = 0;
// Set the last error
SetLastError(nError);
}
// Give the loaded file length
if(pcbFileData != NULL)
*pcbFileData = cbFileData;
return pbFileData;
}
LPBYTE LoadFileToMemory(const TCHAR * szFileName, DWORD * pcbFileData)
{
TFileStream * pStream;
ULONGLONG FileSize = 0;
LPBYTE pbFileData = NULL;
DWORD cbFileData = 0;
// Open the stream for read-only access and read the file
// Note that this fails when the game is running (sharing violation).
pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
if(pStream != NULL)
{
// Retrieve the file size
FileStream_GetSize(pStream, &FileSize);
cbFileData = (DWORD)FileSize;
// Do not load zero files or too larget files
if(0 < FileSize && FileSize <= 0x2000000)
{
// Allocate file data buffer. Make it 1 byte longer
// so string functions can put '\0' there
pbFileData = CASC_ALLOC(BYTE, cbFileData + 1);
if(pbFileData != NULL)
{
if(!FileStream_Read(pStream, NULL, pbFileData, cbFileData))
{
CASC_FREE(pbFileData);
cbFileData = 0;
}
}
else
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
cbFileData = 0;
}
}
else
{
SetLastError(ERROR_BAD_FORMAT);
cbFileData = 0;
assert(false);
}
// Close the file stream
FileStream_Close(pStream);
}
// Give out values
if(pcbFileData != NULL)
pcbFileData[0] = cbFileData;
return pbFileData;
}
void FreeCascBlob(PQUERY_KEY pBlob)
{
if(pBlob != NULL)
{
CASC_FREE(pBlob->pbData);
pBlob->cbData = 0;
}
}
+464
View File
@@ -0,0 +1,464 @@
/*****************************************************************************/
/* CascCommon.h Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Common functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascCommon.h */
/*****************************************************************************/
#ifndef __CASCCOMMON_H__
#define __CASCCOMMON_H__
//-----------------------------------------------------------------------------
// Compression support
// Include functions from zlib
#ifndef __SYS_ZLIB
#include "zlib/zlib.h"
#else
#include <zlib.h>
#endif
#include "CascPort.h"
#include "common/Common.h"
#include "common/Array.h"
#include "common/Map.h"
#include "common/FileTree.h"
#include "common/FileStream.h"
#include "common/Directory.h"
#include "common/ListFile.h"
#include "common/Csv.h"
#include "common/RootHandler.h"
// Headers from Alexander Peslyak's MD5 implementation
#include "md5/md5.h"
// For HashStringJenkins
#include "jenkins/lookup.h"
// On-disk CASC structures
#include "CascStructs.h"
//-----------------------------------------------------------------------------
// CascLib private defines
#ifdef _DEBUG
#define BREAK_ON_XKEY3(CKey, v0, v1, v2) if(CKey[0] == v0 && CKey[1] == v1 && CKey[2] == v2) { __debugbreak(); }
#define BREAKIF(condition) if(condition) { __debugbreak(); }
#else
#define BREAK_ON_XKEY3(CKey, v0, v1, v2) { /* NOTHING */ }
#define BREAKIF(condition) { /* NOTHING */ }
#endif
//-----------------------------------------------------------------------------
// In-memory structures
typedef enum _CBLD_TYPE
{
CascBuildNone = 0, // No build type found
CascBuildInfo, // .build.info
CascBuildDb, // .build.db (older storages)
CascVersionsDb // versions (downloaded online)
} CBLD_TYPE, *PCBLD_TYPE;
// Tag file entry, loaded from the DOWNLOAD file
typedef struct _CASC_TAG_ENTRY
{
USHORT HashType; // Hash type
char TagName[1]; // Tag name. Variable length.
} CASC_TAG_ENTRY, *PCASC_TAG_ENTRY;
// Normalized header of the index files.
// Both version 1 and version 2 are converted to this structure
typedef struct _CASC_INDEX_HEADER
{
USHORT IndexVersion; // 5 for index v 1.0, 7 for index version 2.0
BYTE BucketIndex; // Should be the same as the first byte of the hex filename.
BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry
BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry
BYTE EKeyLength; // Length, in bytes, of the (trimmed) EKey in the EKey entry
BYTE FileOffsetBits; // Number of bits of the archive file offset in StorageOffset field. Rest is data segment index
BYTE Alignment;
ULONGLONG SegmentSize; // Size of one data segment (aka data.### file)
size_t HeaderLength; // Length of the on-disk header structure, in bytes
size_t HeaderPadding; // Length of padding after the header
size_t EntryLength; // Length of the on-disk EKey entry structure, in bytes
size_t EKeyCount; // Number of EKey entries. Only supplied for index files version 1.
} CASC_INDEX_HEADER, *PCASC_INDEX_HEADER;
// Normalized footer of the archive index files (md5.index)
typedef struct _CASC_ARCINDEX_FOOTER
{
BYTE TocHash[MD5_HASH_SIZE]; // Hash of the structure
BYTE FooterHash[MD5_HASH_SIZE]; // MD5 hash of the footer, possibly shortened
BYTE Version; // Version of the index footer
BYTE OffsetBytes; // Length, in bytes, of the file offset field
BYTE SizeBytes; // Length, in bytes, of the file size field
BYTE EKeyBytes; // Length, in bytes, of the EKey field
BYTE FooterHashBytes; // Length, in bytes, of the hash and checksum
BYTE Alignment[3];
size_t ElementCount; // total number of elements in the index file
size_t PageLength; // Length, in bytes, of the index page
size_t ItemLength; // Length, in bytes, of the single item
size_t FooterLength; // Length, in bytes, of the index footer structure
} CASC_ARCINDEX_FOOTER, *PCASC_ARCINDEX_FOOTER;
// Normalized structure for archive index entry
typedef struct _CASC_ARCINDEX_ENTRY
{
BYTE IndexHash[MD5_HASH_SIZE];
BYTE EKey[MD5_HASH_SIZE];
DWORD ArchiveOffset;
DWORD EncodedSize;
} CASC_ARCINDEX_ENTRY, *PCASC_ARCINDEX_ENTRY;
// Normalized header of the ENCODING file
typedef struct _CASC_ENCODING_HEADER
{
USHORT Magic; // FILE_MAGIC_ENCODING ('EN')
USHORT Version; // Expected to be 1 by CascLib
BYTE CKeyLength; // The content key length in ENCODING file. Usually 0x10
BYTE EKeyLength; // The encoded key length in ENCODING file. Usually 0x10
DWORD CKeyPageSize; // Size of the CKey page in bytes
DWORD EKeyPageSize; // Size of the CKey page in bytes
DWORD CKeyPageCount; // Number of CKey pages in the page table
DWORD EKeyPageCount; // Number of EKey pages in the page table
DWORD ESpecBlockSize; // Size of the ESpec string block, in bytes
} CASC_ENCODING_HEADER, *PCASC_ENCODING_HEADER;
typedef struct _CASC_DOWNLOAD_HEADER
{
USHORT Magic; // FILE_MAGIC_DOWNLOAD ('DL')
USHORT Version; // Version
USHORT EKeyLength; // Length of the EKey
USHORT EntryHasChecksum; // If nonzero, then the entry has checksum
DWORD EntryCount;
DWORD TagCount;
USHORT FlagByteSize;
USHORT BasePriority;
size_t HeaderLength; // Length of the on-disk header, in bytes
size_t EntryLength; // Length of the on-disk entry, in bytes
} CASC_DOWNLOAD_HEADER, *PCASC_DOWNLOAD_HEADER;
typedef struct _CASC_DOWNLOAD_ENTRY
{
BYTE EKey[MD5_HASH_SIZE];
ULONGLONG EncodedSize;
DWORD Checksum;
DWORD Flags;
BYTE Priority;
} CASC_DOWNLOAD_ENTRY, *PCASC_DOWNLOAD_ENTRY;
// Capturable tag structure for loading from DOWNLOAD manifest
typedef struct _CASC_TAG_ENTRY1
{
const char * szTagName; // Tag name
LPBYTE Bitmap; // Bitmap
size_t BitmapLength; // Length of the bitmap, in bytes
size_t NameLength; // Length of the tag name, in bytes, not including '\0'
size_t TagLength; // Length of the on-disk tag, in bytes
DWORD TagValue; // Tag value
} CASC_TAG_ENTRY1, *PCASC_TAG_ENTRY1;
// Tag structure for storing in arrays
typedef struct _CASC_TAG_ENTRY2
{
size_t NameLength; // Length of the on-disk tag, in bytes
DWORD TagValue; // Tag value
char szTagName[0x08]; // Tag string. This member can be longer than declared. Aligned to 8 bytes.
} CASC_TAG_ENTRY2, *PCASC_TAG_ENTRY2;
typedef struct _CASC_INSTALL_HEADER
{
USHORT Magic; // FILE_MAGIC_DOWNLOAD ('DL')
BYTE Version; // Version
BYTE EKeyLength; // Length of the EKey
DWORD EntryCount;
DWORD TagCount;
size_t HeaderLength; // Length of the on-disk header, in bytes
} CASC_INSTALL_HEADER, *PCASC_INSTALL_HEADER;
typedef struct _CASC_FILE_FRAME
{
CONTENT_KEY FrameHash; // MD5 hash of the file frame
DWORD DataFileOffset; // Offset in the data file (data.###)
DWORD FileOffset; // File offset of this frame
DWORD EncodedSize; // Encoded size of the frame
DWORD ContentSize; // Content size of the frame
} CASC_FILE_FRAME, *PCASC_FILE_FRAME;
//-----------------------------------------------------------------------------
// Structures for CASC storage and CASC file
struct TCascStorage
{
TCascStorage();
~TCascStorage();
TCascStorage * AddRef();
TCascStorage * Release();
static TCascStorage * IsValid(HANDLE hStorage);
PFNPROGRESSCALLBACK PfnCallback; // Callback to be called during CascOpen(Online)Storage
void * PtrCallbackParam;
const TCHAR * szIndexFormat; // Format of the index file name
const char * szClassName; // "TCascStorage"
const char * szProductName; // String representation of the product name
TCHAR * szRootPath; // Path where the build file is
TCHAR * szDataPath; // This is the directory where data files are
TCHAR * szIndexPath; // This is the directory where index files are
TCHAR * szBuildFile; // Build file name (.build.info or .build.db)
TCHAR * szCdnServers; // Multi-SZ list of CDN servers
TCHAR * szCdnPath; // Remote CDN sub path for the product
TCHAR * szCodeName; // Product code name. Only for online storages
char * szRegion; // Product region. Only when "versions" is used as storage root file
CASC_PRODUCT Product; // Product enum value (see CASC_PRODUCT)
DWORD dwBuildNumber; // Product build number
DWORD dwRefCount; // Number of references
DWORD dwDefaultLocale; // Default locale, read from ".build.info"
DWORD dwFeatures; // List of CASC features. See CASC_FEATURE_XXX
bool bAllowOrphans; // If TRUE, then orphaned items are allowed
CBLD_TYPE BuildFileType; // Type of the build file
QUERY_KEY CdnConfigKey; // Currently selected CDN config file. Points to "config\%02X\%02X\%s
QUERY_KEY CdnBuildKey; // Currently selected CDN build file. Points to "config\%02X\%02X\%s
QUERY_KEY ArchiveGroup; // 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 PatchArchivesGroup; // Key array of the "patch-archive-group"
QUERY_KEY BuildFiles; // List of supported build files
TFileStream * DataFiles[CASC_MAX_DATA_FILES]; // Array of open data files
CASC_CKEY_ENTRY EncodingCKey; // Information about ENCODING file
CASC_CKEY_ENTRY DownloadCKey; // Information about DOWNLOAD file
CASC_CKEY_ENTRY InstallCKey; // Information about INSTALL file
CASC_CKEY_ENTRY PatchFile; // Information about PATCH file
CASC_CKEY_ENTRY RootFile; // Information about ROOT file
CASC_CKEY_ENTRY SizeFile; // Information about SIZE file
CASC_CKEY_ENTRY VfsRoot; // The main VFS root file
CASC_ARRAY VfsRootList; // List of CASC_EKEY_ENTRY for each TVFS sub-root
TRootHandler * pRootHandler; // Common handler for various ROOT file formats
CASC_ARRAY ArcIndexArray; // Array of CASC_ARCINDEX_ENTRY, loaded from archive indexes
CASC_ARRAY IndexArray; // Array of CASC_CKEY_ENTRY loaded from local index files
CASC_ARRAY CKeyArray; // Array of CASC_CKEY_ENTRY, one entry for each physical file
CASC_ARRAY TagsArray; // Array of CASC_DOWNLOAD_TAG2
CASC_MAP ArcIndexMap; // Map of EKey -> CASC_ARCINDEX_ENTRY
CASC_MAP CKeyMap; // Map of CKey -> CASC_CKEY_ENTRY
CASC_MAP EKeyMap; // Map of EKey -> CASC_EKEY_ENTRY
size_t LocalFiles; // Number of files that are present locally
size_t TotalFiles; // Total number of files in the storage, some may not be present locally
size_t EKeyEntries; // Number of CKeyEntry-ies loaded from text build file
size_t OrphanItems; // Number of EKey entries in indexes that do not have their counterpart in ENCODING
size_t SkippedItems; // Number of EKey entries in indexes that were ignored due to insufficient capacity of CKeyArray
size_t EKeyLength; // EKey length from the index files
DWORD FileOffsetBits; // Nimber of bits in the storage offset which mean data segent offset
CASC_ARRAY ExtraKeysList; // List additional encryption keys
CASC_MAP EncryptionKeys; // Map of encryption keys
};
struct TCascFile
{
TCascFile(TCascStorage * ahs, PCASC_CKEY_ENTRY apCKeyEntry)
{
ULONGLONG StorageOffset;
ULONGLONG FileOffsMask;
// Reference the storage handle
hs = ahs->AddRef();
szClassName = "TCascFile";
// Init class variables
pCKeyEntry = apCKeyEntry;
pFrames = NULL;
pbFileCache = NULL;
cbFileCache = 0;
// Supply the sizes
StorageOffset = pCKeyEntry->StorageOffset;
FileOffsMask = ((ULONGLONG)1 << hs->FileOffsetBits) - 1;
ArchiveIndex = (DWORD)(StorageOffset >> hs->FileOffsetBits);
ArchiveOffset = (DWORD)(StorageOffset & FileOffsMask);
EncodedSize = pCKeyEntry->EncodedSize;
ContentSize = pCKeyEntry->ContentSize;
FilePointer = 0;
FrameCount = 0;
bVerifyIntegrity = false;
bDownloadFileIf = false;
bLocalFileStream = false;
}
~TCascFile()
{
// Close (dereference) the archive handle
hs = hs->Release();
szClassName = NULL;
// Free the file cache and frame array
CASC_FREE(pbFileCache);
CASC_FREE(pFrames);
// If we are supposed to close the file stream, do it
if (pStream && bLocalFileStream)
FileStream_Close(pStream);
}
static TCascFile * IsValid(HANDLE hFile)
{
TCascFile * hf = (TCascFile *)hFile;
return (hf != INVALID_HANDLE_VALUE &&
hf != NULL &&
hf->hs != NULL &&
hf->szClassName != NULL &&
hf->pCKeyEntry != NULL) ? hf : NULL;
}
TCascStorage * hs; // Pointer to storage structure
TFileStream * pStream; // An open data stream
const char * szClassName; // "TCascFile"
PCASC_CKEY_ENTRY pCKeyEntry;
PCASC_FILE_FRAME pFrames; // Array of file frames
DWORD ArchiveIndex; // Index of the archive (data.###)
DWORD ArchiveOffset; // Offset in the archive (data.###)
DWORD FilePointer; // Current file pointer
DWORD EncodedSize; // Encoded size. This is the size of encoded header, all file frame headers and all file frames
DWORD ContentSize; // Content size. This is the size of the file content, aka the file size
DWORD FrameCount; // Number of the file frames
DWORD bVerifyIntegrity:1; // If true, then the data are validated more strictly when read
DWORD bDownloadFileIf:1; // If true, then the data will be downloaded from the online storage if missing
DWORD bLocalFileStream:1; // If true, then the file stream is a local file
DWORD bOvercomeEncrypted:1; // If true, then CascReadFile will fill the part that is encrypted (and key was not found) with zeros
LPBYTE pbFileCache; // Pointer to file cache
DWORD cbFileCache; // Size of the file cache
};
struct TCascSearch
{
TCascSearch(TCascStorage * ahs, LPCTSTR aszListFile, const char * aszMask)
{
// Init the class
szClassName = "TCascSearch";
hs = ahs->AddRef();
// Init provider-specific data
pCache = NULL;
nFileIndex = 0;
nSearchState = 0;
bListFileUsed = false;
// Allocate mask
szListFile = CascNewStr(aszListFile);
szMask = CascNewStr((aszMask != NULL) ? aszMask : "*");
}
~TCascSearch()
{
// Dereference the CASC storage
hs = hs->Release();
szClassName = NULL;
// Free the rest of the members
CASC_FREE(szMask);
CASC_FREE(szListFile);
CASC_FREE(pCache);
}
static TCascSearch * IsValid(HANDLE hFind)
{
TCascSearch * pSearch = (TCascSearch *)hFind;
return (hFind != INVALID_HANDLE_VALUE &&
hFind != NULL &&
pSearch->szClassName != NULL &&
!strcmp(pSearch->szClassName, "TCascSearch") &&
pSearch->szMask != NULL) ? pSearch : NULL;
}
TCascStorage * hs; // Pointer to the storage handle
const char * szClassName; // Contains "TCascSearch"
TCHAR * szListFile; // Name of the listfile
void * pCache; // Listfile cache
char * szMask; // Search mask
// Provider-specific data
size_t nFileIndex; // Root-specific search context
DWORD nSearchState:8; // The current search state (0 = listfile, 1 = nameless, 2 = done)
DWORD bListFileUsed:1; // TRUE: The listfile has already been loaded
};
//-----------------------------------------------------------------------------
// Common functions (CascCommon.cpp)
bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD dwOpenFlags, HANDLE * PtrFileHandle);
LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData);
LPBYTE LoadFileToMemory(const TCHAR * szFileName, DWORD * pcbFileData);
void FreeCascBlob(PQUERY_KEY pQueryKey);
//-----------------------------------------------------------------------------
// Text file parsing (CascFiles.cpp)
int DownloadFileFromCDN(TCascStorage * hs, const TCHAR * szSubDir, LPBYTE pbEKey, const TCHAR * szExtension, TCHAR * szOutLocalPath, size_t cchOutLocalPath);
int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory);
int LoadCdnsInfo(TCascStorage * hs);
int LoadBuildInfo(TCascStorage * hs);
int LoadCdnConfigFile(TCascStorage * hs);
int LoadCdnBuildFile(TCascStorage * hs);
//-----------------------------------------------------------------------------
// Internal file functions
void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, size_t * pcbLengthNeeded);
PCASC_CKEY_ENTRY FindCKeyEntry_CKey(TCascStorage * hs, LPBYTE pbCKey, PDWORD PtrIndex = NULL);
PCASC_CKEY_ENTRY FindCKeyEntry_EKey(TCascStorage * hs, LPBYTE pbEKey, PDWORD PtrIndex = NULL);
size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount);
int CascDecompress(LPBYTE pvOutBuffer, PDWORD pcbOutBuffer, LPBYTE pvInBuffer, DWORD cbInBuffer);
int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer);
int CascLoadEncryptionKeys(TCascStorage * hs);
int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex);
//-----------------------------------------------------------------------------
// Support for index files
int LoadIndexFiles(TCascStorage * hs);
//-----------------------------------------------------------------------------
// Support for ROOT file
int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask);
int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
int RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
//-----------------------------------------------------------------------------
// Dumpers (CascDumpData.cpp)
#ifdef _DEBUG
void CascDumpFile(HANDLE hFile, const char * szDumpFile = NULL);
void CascDumpStorage(HANDLE hStorage, const char * szDumpFile = NULL);
#endif
#endif // __CASCCOMMON_H__
+56
View File
@@ -0,0 +1,56 @@
/*****************************************************************************/
/* CascDecompress.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Decompression functions */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 02.05.14 1.00 Lad The first version of CascDecompress.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Public functions
int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
{
z_stream z; // Stream information for zlib
uInt cbOutBuffer = *pcbOutBuffer;
int nResult;
int nError = ERROR_FILE_CORRUPT;
// Fill the stream structure for zlib
z.next_in = pbInBuffer;
z.avail_in = cbInBuffer;
z.total_in = cbInBuffer;
z.next_out = pbOutBuffer;
z.avail_out = cbOutBuffer;
z.total_out = 0;
z.zalloc = NULL;
z.zfree = NULL;
// Reset the total number of output bytes
cbOutBuffer = 0;
// Initialize the decompression structure
if((nResult = inflateInit(&z)) == Z_OK)
{
// Call zlib to decompress the data
nResult = inflate(&z, Z_NO_FLUSH);
if (nResult == Z_OK || nResult == Z_STREAM_END)
{
// Give the size of the uncompressed data
cbOutBuffer = z.total_out;
nError = ERROR_SUCCESS;
}
inflateEnd(&z);
}
// Give the caller the number of bytes needed
pcbOutBuffer[0] = cbOutBuffer;
return nError;
}
+520
View File
@@ -0,0 +1,520 @@
/*****************************************************************************/
/* 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
#define CASC_EXTRA_KEYS 0x80
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;
//-----------------------------------------------------------------------------
// Known encryption keys. See https://wowdev.wiki/CASC for updates
static const char * szKeyConstant16 = "expand 16-byte k";
static const char * szKeyConstant32 = "expand 32-byte k";
static CASC_ENCRYPTION_KEY CascKeys[] =
{
// Key Name Encryption key Seen in
// ---------------------- ------------------------------------------------------------------------------------------------ -----------
// Battle.net app
{ 0x2C547F26A2613E01ULL, { 0x37, 0xC5, 0x0C, 0x10, 0x2D, 0x4C, 0x9E, 0x3A, 0x5A, 0xC0, 0x69, 0xF0, 0x72, 0xB1, 0x41, 0x7D } }, // Battle.net App Alpha 1.5.0
// Starcraft
// { 0xD0CAE11366CEEA83ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? }}, // 1.12.3.2609 (build 45364)
// Overwatch
{ 0xFB680CB6A8BF81F3ULL, { 0x62, 0xD9, 0x0E, 0xFA, 0x7F, 0x36, 0xD7, 0x1C, 0x39, 0x8A, 0xE2, 0xF1, 0xFE, 0x37, 0xBD, 0xB9 } }, // 0.8.0.24919_retailx64 (hardcoded)
{ 0x402CD9D8D6BFED98ULL, { 0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41 } }, // 0.8.0.24919_retailx64 (hardcoded)
{ 0xDBD3371554F60306ULL, { 0x34, 0xE3, 0x97, 0xAC, 0xE6, 0xDD, 0x30, 0xEE, 0xFD, 0xC9, 0x8A, 0x2A, 0xB0, 0x93, 0xCD, 0x3C } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0x11A9203C9881710AULL, { 0x2E, 0x2C, 0xB8, 0xC3, 0x97, 0xC2, 0xF2, 0x4E, 0xD0, 0xB5, 0xE4, 0x52, 0xF1, 0x8D, 0xC2, 0x67 } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0xA19C4F859F6EFA54ULL, { 0x01, 0x96, 0xCB, 0x6F, 0x5E, 0xCB, 0xAD, 0x7C, 0xB5, 0x28, 0x38, 0x91, 0xB9, 0x71, 0x2B, 0x4B } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0x87AEBBC9C4E6B601ULL, { 0x68, 0x5E, 0x86, 0xC6, 0x06, 0x3D, 0xFD, 0xA6, 0xC9, 0xE8, 0x52, 0x98, 0x07, 0x6B, 0x3D, 0x42 } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0xDEE3A0521EFF6F03ULL, { 0xAD, 0x74, 0x0C, 0xE3, 0xFF, 0xFF, 0x92, 0x31, 0x46, 0x81, 0x26, 0x98, 0x57, 0x08, 0xE1, 0xB9 } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0x8C9106108AA84F07ULL, { 0x53, 0xD8, 0x59, 0xDD, 0xA2, 0x63, 0x5A, 0x38, 0xDC, 0x32, 0xE7, 0x2B, 0x11, 0xB3, 0x2F, 0x29 } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0x49166D358A34D815ULL, { 0x66, 0x78, 0x68, 0xCD, 0x94, 0xEA, 0x01, 0x35, 0xB9, 0xB1, 0x6C, 0x93, 0xB1, 0x12, 0x4A, 0xBA } }, // 0.8.0.24919_retailx64 (streamed from server)
{ 0x1463A87356778D14ULL, { 0x69, 0xBD, 0x2A, 0x78, 0xD0, 0x5C, 0x50, 0x3E, 0x93, 0x99, 0x49, 0x59, 0xB3, 0x0E, 0x5A, 0xEC } }, // ? 1.0.3.0 (streamed from server)
{ 0x5E152DE44DFBEE01ULL, { 0xE4, 0x5A, 0x17, 0x93, 0xB3, 0x7E, 0xE3, 0x1A, 0x8E, 0xB8, 0x5C, 0xEE, 0x0E, 0xEE, 0x1B, 0x68 } }, // ? 1.0.3.0 (streamed from server)
{ 0x9B1F39EE592CA415ULL, { 0x54, 0xA9, 0x9F, 0x08, 0x1C, 0xAD, 0x0D, 0x08, 0xF7, 0xE3, 0x36, 0xF4, 0x36, 0x8E, 0x89, 0x4C } }, // ? 1.0.3.0 (streamed from server)
{ 0x24C8B75890AD5917ULL, { 0x31, 0x10, 0x0C, 0x00, 0xFD, 0xE0, 0xCE, 0x18, 0xBB, 0xB3, 0x3F, 0x3A, 0xC1, 0x5B, 0x30, 0x9F } }, // ? 1.0.3.0 (included in game)
{ 0xEA658B75FDD4890FULL, { 0xDE, 0xC7, 0xA4, 0xE7, 0x21, 0xF4, 0x25, 0xD1, 0x33, 0x03, 0x98, 0x95, 0xC3, 0x60, 0x36, 0xF8 } }, // ? 1.0.3.0 (included in game)
{ 0x026FDCDF8C5C7105ULL, { 0x8F, 0x41, 0x80, 0x9D, 0xA5, 0x53, 0x66, 0xAD, 0x41, 0x6D, 0x3C, 0x33, 0x74, 0x59, 0xEE, 0xE3 } }, // (included in game)
{ 0xCAE3FAC925F20402ULL, { 0x98, 0xB7, 0x8E, 0x87, 0x74, 0xBF, 0x27, 0x50, 0x93, 0xCB, 0x1B, 0x5F, 0xC7, 0x14, 0x51, 0x1B } }, // (included in game)
{ 0x061581CA8496C80CULL, { 0xDA, 0x2E, 0xF5, 0x05, 0x2D, 0xB9, 0x17, 0x38, 0x0B, 0x8A, 0xA6, 0xEF, 0x7A, 0x5F, 0x8E, 0x6A } }, //
{ 0xBE2CB0FAD3698123ULL, { 0x90, 0x2A, 0x12, 0x85, 0x83, 0x6C, 0xE6, 0xDA, 0x58, 0x95, 0x02, 0x0D, 0xD6, 0x03, 0xB0, 0x65 } }, //
{ 0x57A5A33B226B8E0AULL, { 0xFD, 0xFC, 0x35, 0xC9, 0x9B, 0x9D, 0xB1, 0x1A, 0x32, 0x62, 0x60, 0xCA, 0x24, 0x6A, 0xCB, 0x41 } }, // 1.1.0.0.30200 Ana
{ 0x42B9AB1AF5015920ULL, { 0xC6, 0x87, 0x78, 0x82, 0x3C, 0x96, 0x4C, 0x6F, 0x24, 0x7A, 0xCC, 0x0F, 0x4A, 0x25, 0x84, 0xF8 } }, // 1.2.0.1.30684 Summer Games
{ 0x4F0FE18E9FA1AC1AULL, { 0x89, 0x38, 0x1C, 0x74, 0x8F, 0x65, 0x31, 0xBB, 0xFC, 0xD9, 0x77, 0x53, 0xD0, 0x6C, 0xC3, 0xCD } }, // 1.2.0.1.30684
{ 0x7758B2CF1E4E3E1BULL, { 0x3D, 0xE6, 0x0D, 0x37, 0xC6, 0x64, 0x72, 0x35, 0x95, 0xF2, 0x7C, 0x5C, 0xDB, 0xF0, 0x8B, 0xFA } }, // 1.2.0.1.30684
{ 0xE5317801B3561125ULL, { 0x7D, 0xD0, 0x51, 0x19, 0x9F, 0x84, 0x01, 0xF9, 0x5E, 0x4C, 0x03, 0xC8, 0x84, 0xDC, 0xEA, 0x33 } }, // 1.4.0.2.32143 Halloween Terror
{ 0x16B866D7BA3A8036ULL, { 0x13, 0x95, 0xE8, 0x82, 0xBF, 0x25, 0xB4, 0x81, 0xF6, 0x1A, 0x4D, 0x62, 0x11, 0x41, 0xDA, 0x6E } }, // 1.4.1.0.31804 Bastion Blizzcon 2016 skin
{ 0x11131FFDA0D18D30ULL, { 0xC3, 0x2A, 0xD1, 0xB8, 0x25, 0x28, 0xE0, 0xA4, 0x56, 0x89, 0x7B, 0x3C, 0xE1, 0xC2, 0xD2, 0x7E } }, // 1.5.0.1.32795 Sombra
{ 0xCAC6B95B2724144AULL, { 0x73, 0xE4, 0xBE, 0xA1, 0x45, 0xDF, 0x2B, 0x89, 0xB6, 0x5A, 0xEF, 0x02, 0xF8, 0x3F, 0xA2, 0x60 } }, // 1.5.0.1.32795 Ecopoint: Antarctica
{ 0xB7DBC693758A5C36ULL, { 0xBC, 0x3A, 0x92, 0xBF, 0xE3, 0x02, 0x51, 0x8D, 0x91, 0xCC, 0x30, 0x79, 0x06, 0x71, 0xBF, 0x10 } }, // 1.5.0.1.32795 Genji Oni skin
{ 0x90CA73B2CDE3164BULL, { 0x5C, 0xBF, 0xF1, 0x1F, 0x22, 0x72, 0x0B, 0xAC, 0xC2, 0xAE, 0x6A, 0xAD, 0x8F, 0xE5, 0x33, 0x17 } }, // 1.6.1.0.33236 Oasis map
{ 0x6DD3212FB942714AULL, { 0xE0, 0x2C, 0x16, 0x43, 0x60, 0x2E, 0xC1, 0x6C, 0x3A, 0xE2, 0xA4, 0xD2, 0x54, 0xA0, 0x8F, 0xD9 } }, // 1.6.1.0.33236
{ 0x11DDB470ABCBA130ULL, { 0x66, 0x19, 0x87, 0x66, 0xB1, 0xC4, 0xAF, 0x75, 0x89, 0xEF, 0xD1, 0x3A, 0xD4, 0xDD, 0x66, 0x7A } }, // 1.6.1.0.33236 Winter Wonderland
{ 0x5BEF27EEE95E0B4BULL, { 0x36, 0xBC, 0xD2, 0xB5, 0x51, 0xFF, 0x1C, 0x84, 0xAA, 0x3A, 0x39, 0x94, 0xCC, 0xEB, 0x03, 0x3E } }, //
{ 0x9359B46E49D2DA42ULL, { 0x17, 0x3D, 0x65, 0xE7, 0xFC, 0xAE, 0x29, 0x8A, 0x93, 0x63, 0xBD, 0x6A, 0xA1, 0x89, 0xF2, 0x00 } }, // Diablo's 20th anniversary
{ 0x1A46302EF8896F34ULL, { 0x80, 0x29, 0xAD, 0x54, 0x51, 0xD4, 0xBC, 0x18, 0xE9, 0xD0, 0xF5, 0xAC, 0x44, 0x9D, 0xC0, 0x55 } }, // 1.7.0.2.34156 Year of the Rooster
{ 0x693529F7D40A064CULL, { 0xCE, 0x54, 0x87, 0x3C, 0x62, 0xDA, 0xA4, 0x8E, 0xFF, 0x27, 0xFC, 0xC0, 0x32, 0xBD, 0x07, 0xE3 } }, // 1.8.0.0.34470 CTF Maps
{ 0x388B85AEEDCB685DULL, { 0xD9, 0x26, 0xE6, 0x59, 0xD0, 0x4A, 0x09, 0x6B, 0x24, 0xC1, 0x91, 0x51, 0x07, 0x6D, 0x37, 0x9A } }, // 1.8.0.0.34470 Numbani Update (Doomfist teaser)
{ 0xE218F69AAC6C104DULL, { 0xF4, 0x3D, 0x12, 0xC9, 0x4A, 0x9A, 0x52, 0x84, 0x97, 0x97, 0x1F, 0x1C, 0xBE, 0x41, 0xAD, 0x4D } }, // 1.9.0.0.34986 Orisa
{ 0xF432F0425363F250ULL, { 0xBA, 0x69, 0xF2, 0xB3, 0x3C, 0x27, 0x68, 0xF5, 0xF2, 0x9B, 0xFE, 0x78, 0xA5, 0xA1, 0xFA, 0xD5 } }, // 1.10.0.0.35455 Uprising
{ 0x061D52F86830B35DULL, { 0xD7, 0x79, 0xF9, 0xC6, 0xCC, 0x9A, 0x4B, 0xE1, 0x03, 0xA4, 0xE9, 0x0A, 0x73, 0x38, 0xF7, 0x93 } }, // 1.10.0.0.35455 D.Va Officer Skin (HotS Nexus Challenge 2)
{ 0x1275C84CF113EF65ULL, { 0xCF, 0x58, 0xB6, 0x93, 0x3E, 0xAF, 0x98, 0xAF, 0x53, 0xE7, 0x6F, 0x84, 0x26, 0xCC, 0x7E, 0x6C } }, //
{ 0xD9C7C7AC0F14C868ULL, { 0x3A, 0xFD, 0xF6, 0x8E, 0x3A, 0x5D, 0x63, 0xBA, 0xBA, 0x1E, 0x68, 0x21, 0x88, 0x3F, 0x06, 0x7D } }, //
{ 0xBD4E42661A432951ULL, { 0x6D, 0xE8, 0xE2, 0x8C, 0x85, 0x11, 0x64, 0x4D, 0x55, 0x95, 0xFC, 0x45, 0xE5, 0x35, 0x14, 0x72 } }, // 1.11.0.0.36376 Anniversary event
{ 0xC43CB14355249451ULL, { 0x0E, 0xA2, 0xB4, 0x4F, 0x96, 0xA2, 0x69, 0xA3, 0x86, 0x85, 0x6D, 0x04, 0x9A, 0x3D, 0xEC, 0x86 } }, // 1.12.0.0.37104 Horizon Lunar Colony
{ 0xE6D914F8E4744953ULL, { 0xC8, 0x47, 0x7C, 0x28, 0x9D, 0xCE, 0x66, 0xD9, 0x13, 0x65, 0x07, 0xA3, 0x3A, 0xA3, 0x33, 0x01 } }, // 1.13.0.0.37646 Doomfist
{ 0x5694C503F8C80178ULL, { 0x7F, 0x4C, 0xF1, 0xC1, 0xFB, 0xBA, 0xD9, 0x2B, 0x18, 0x43, 0x36, 0xD6, 0x77, 0xEB, 0xF9, 0x37 } }, // 1.13.0.0.37646 Doomfist
{ 0x21DBFD65F3E54269ULL, { 0xAB, 0x58, 0x0C, 0x38, 0x37, 0xCA, 0xF8, 0xA4, 0x61, 0xF2, 0x43, 0xA5, 0x66, 0xB2, 0xAE, 0x4D } }, // 1.13.0.0.37646 Summer Games 2017
// { 0x27ABA5F88DD8D078ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 1.13.0.0.37646
{ 0x21E1F90E71D33C71ULL, { 0x32, 0x87, 0x42, 0x33, 0x91, 0x62, 0xB3, 0x26, 0x76, 0xC8, 0x03, 0xC2, 0x25, 0x59, 0x31, 0xA6 } }, // 1.14.1.0.39083 Deathmatch
{ 0xD9CB055BCDD40B6EULL, { 0x49, 0xFB, 0x44, 0x77, 0xA4, 0xA0, 0x82, 0x53, 0x27, 0xE9, 0xA7, 0x36, 0x82, 0xBE, 0xCD, 0x0C } }, // 1.15.0.0.????? Junkertown
{ 0x8175CE3C694C6659ULL, { 0xE3, 0xF3, 0xFA, 0x77, 0x26, 0xC7, 0x0D, 0x26, 0xAE, 0x13, 0x0D, 0x96, 0x9D, 0xDD, 0xF3, 0x99 } }, // 1.16.0.0.40011 Halloween 2017
{ 0xB8DE51690075435AULL, { 0xC0, 0x7E, 0x92, 0x60, 0xBB, 0x71, 0x12, 0x17, 0xE7, 0xDE, 0x6F, 0xED, 0x91, 0x1F, 0x42, 0x96 } }, // 1.16.0.0.????? Winston Blizzcon 2017 skin
{ 0xF6CF23955B5D437DULL, { 0xAE, 0xBA, 0x22, 0x73, 0x28, 0xA5, 0xB0, 0xAA, 0x9F, 0x51, 0xDA, 0xE3, 0xF6, 0xA7, 0xDF, 0xE4 } }, // 1.17.0.2.41350 Moira
{ 0x0E4D9426F2891F5CULL, { 0x9F, 0xF0, 0x64, 0xC3, 0x8B, 0xE5, 0x2C, 0xCD, 0xF7, 0x37, 0x48, 0x18, 0x0F, 0x62, 0x82, 0x05 } }, // 1.18.1.2.42076 Winter Wonderland 2017
{ 0x9240BA6A2A0CF684ULL, { 0xDF, 0x2E, 0x37, 0xD7, 0x8B, 0x43, 0x10, 0x8F, 0xA6, 0x24, 0x20, 0x68, 0xB7, 0x0D, 0x1F, 0x65 } }, // 1.19.1.3.42563 Overwatch League
{ 0x82297FBAB7F5EB80ULL, { 0xB5, 0x34, 0xC2, 0x09, 0x65, 0x85, 0x2F, 0xB1, 0x5A, 0xEC, 0xAC, 0x17, 0xE3, 0x81, 0xB4, 0x17 } }, // 1.19.1.3.42563 Jan 2017 Lootbox Update
{ 0x9ADF00AA1A174A69ULL, { 0x9A, 0x4A, 0xC8, 0x99, 0x26, 0x1A, 0x2F, 0x1C, 0x69, 0x69, 0xF3, 0x93, 0x97, 0xC3, 0x58, 0xE7 } }, // 1.19.1.3.42563 Blizzard World
{ 0xCFA05AA76B49F881ULL, { 0x52, 0x6D, 0xDD, 0xEF, 0x19, 0xBF, 0x37, 0x3C, 0x25, 0xB6, 0x29, 0xA3, 0x34, 0xCD, 0x72, 0x37 } }, // 1.19.1.3.42563 WoW's Battle For Azeroth Preorder
{ 0x493455579DA0B18EULL, { 0xC0, 0xBA, 0xBF, 0x72, 0xAD, 0x2C, 0x05, 0xDF, 0xC1, 0x40, 0x17, 0xD1, 0xAD, 0xBF, 0x59, 0x77 } }, // 1.19.3.1.43036 Inaugural Season Spray/Icon ??
{ 0x6362C5AD65DAE686ULL, { 0x62, 0xF6, 0x03, 0xD5, 0x39, 0x0F, 0x76, 0x3E, 0xD5, 0x17, 0x73, 0xF0, 0x16, 0x4F, 0xED, 0xB5 } }, // 1.19.3.1.43036 White/Gray OWL Skins ??
{ 0x8162E5313A9C135DULL, { 0xF4, 0x07, 0x83, 0x4D, 0x95, 0x21, 0x58, 0x7C, 0x50, 0x12, 0xB0, 0xA5, 0x9D, 0x7E, 0x06, 0x4B } }, // 1.20.0.2.43435 Lunar New Year 2018 (Dog) / Ayutthaya / Comp CTF
// { 0x68EAE8FDC008C381ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 1.20.0.2.43435
{ 0xF412C6327C4BF091ULL, { 0x6F, 0xAF, 0xC6, 0x48, 0xCB, 0xF1, 0xC2, 0x11, 0x5B, 0x76, 0x95, 0x93, 0xC1, 0x70, 0xE7, 0x32 } }, // 1.20.0.2.43435 SC2 20th Anniversary (Kerrigan Skin)
{ 0x3B3ED0874091B174ULL, { 0x5D, 0x09, 0xC2, 0x68, 0x8B, 0x1D, 0x9F, 0x1A, 0x4D, 0xB6, 0x46, 0x02, 0xC1, 0x66, 0x1D, 0x24 } }, // 1.21.?.?.????? Brigitte
{ 0x37FD04E05D2A6292ULL, { 0xF0, 0x64, 0x55, 0xE5, 0x6C, 0xD1, 0x44, 0x91, 0x42, 0x95, 0xF2, 0xEF, 0x15, 0x3D, 0x23, 0xBA } }, // 1.21.?.?.????? Brigitte Cosmetics
{ 0xC0DDC77552BE5794ULL, { 0xF2, 0xBB, 0x7F, 0x35, 0x99, 0x0E, 0x29, 0x00, 0xCB, 0xD8, 0x77, 0xB4, 0xD3, 0xA7, 0x13, 0x9C } }, // 1.22.?.?.????? OWL away skins
{ 0x68D8EB839DC15D75ULL, { 0x29, 0xAF, 0xFE, 0xCA, 0x52, 0x99, 0xC4, 0x14, 0x0A, 0x12, 0xA6, 0x6F, 0x95, 0x4E, 0xF1, 0xE3 } }, // 1.22.?.?.????? Archives 2018 (Retribution)
{ 0x209F33BBAC9D1295ULL, { 0xBD, 0x53, 0x54, 0x38, 0xD0, 0xCD, 0xEE, 0x0E, 0x95, 0x67, 0xE0, 0xEF, 0x67, 0x1C, 0x80, 0x9F } }, // 1.23.?.?.????? Rialto
{ 0xA55F8C6F20454D94ULL, { 0x42, 0xD7, 0x62, 0x85, 0x41, 0x24, 0x61, 0xB0, 0xC7, 0x5A, 0xB7, 0x5F, 0xB5, 0x2F, 0x59, 0x6E } }, // 1.23.?.?.????? Mercy BCRF Items
{ 0x3EEEDB8E7C29A09BULL, { 0x83, 0x7A, 0xC7, 0x93, 0x05, 0xE4, 0xBF, 0xBA, 0x3B, 0x22, 0x26, 0xF1, 0xB9, 0x82, 0x00, 0xBF } }, // 1.24.?.?.????? Anniversary 2018 Map/Items
{ 0x22C1AF6758F8449EULL, { 0x28, 0xAA, 0x1B, 0xD2, 0xB9, 0xA1, 0xE3, 0x63, 0x39, 0x89, 0xB1, 0xBB, 0xF6, 0x4A, 0xEA, 0xC4 } }, // 1.26.?.?.????? Emily / Comp Season 11 / Comp 3v3 Items
{ 0x11B2E01B9331799EULL, { 0x9A, 0x1C, 0x99, 0x30, 0x3D, 0x6A, 0x58, 0x97, 0x8E, 0x29, 0xC9, 0x88, 0x73, 0x32, 0x7A, 0x6A } }, // 1.26.?.?.????? Wrecking Ball
{ 0x8498337C740329B3ULL, { 0xEC, 0x73, 0xD6, 0x63, 0xE3, 0xFC, 0x72, 0x41, 0x6B, 0x3E, 0x46, 0x79, 0x15, 0x70, 0x8B, 0x4F } }, // 1.26.?.?.????? Wrecking Ball Cosmetics
{ 0xE6E8BCABE3CC96C1ULL, { 0x57, 0x72, 0x7E, 0x52, 0x60, 0x06, 0x65, 0xEA, 0xC0, 0x2B, 0xD8, 0x7F, 0x06, 0x92, 0x89, 0x8F } }, // 1.26.?.?.????? OWL Grand Finals Unlocks / Lucio Emote
{ 0x5D4AC0DC6F3113BAULL, { 0xD1, 0xC9, 0xF1, 0xFF, 0x69, 0x58, 0x5D, 0x13, 0x98, 0xEB, 0xA0, 0x46, 0x34, 0x81, 0xF5, 0xCF } }, // 1.27.?.?.????? Summer Games 2018
{ 0x27F9D85973DCD5AFULL, { 0xC5, 0xFE, 0x10, 0x15, 0xBC, 0xE0, 0xB7, 0x84, 0x8F, 0x02, 0x28, 0x68, 0xAF, 0xF6, 0x54, 0xD1 } }, // 1.27.?.?.????? Summer Games 2018
{ 0x67AAD845CC0F03BDULL, { 0x6C, 0xD8, 0xAD, 0x3F, 0x37, 0xF5, 0x4A, 0xBE, 0xC7, 0x63, 0x02, 0x94, 0xD4, 0x90, 0x41, 0xBF } }, // 1.27.?.?.????? D.Va Nano Cola Challenge
{ 0xCA13F0C79042A1A0ULL, { 0xFC, 0xD8, 0x9C, 0xE8, 0x81, 0x2E, 0x63, 0x46, 0x07, 0x6F, 0xC8, 0x2D, 0xD7, 0xA9, 0x24, 0x87 } }, // 1.28.?.?.????? Busan
{ 0xC4D84093A32684BDULL, { 0x38, 0xE1, 0x82, 0x42, 0x3E, 0xED, 0xD8, 0xE3, 0xF5, 0x7A, 0xC1, 0xD4, 0x07, 0xB4, 0x70, 0xD0 } }, // 1.28.?.?.????? Blizzcon 2018 Sombra Demon Hunter Skin
{ 0x0BFE5A2B3C606BA1ULL, { 0xD6, 0x41, 0x9B, 0x8E, 0x42, 0x82, 0x0B, 0x0B, 0x24, 0xE0, 0x8D, 0xA4, 0x44, 0xA0, 0x68, 0x22 } }, // 1.29.?.?.????? Halloween Terror 2018
{ 0x402CD9D8D6BFED98ULL, { 0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41 } }, // 1.29.?.?.?????
{ 0xF1CBDF48147D26C6ULL, { 0x4B, 0x69, 0x44, 0x69, 0x51, 0x57, 0xD4, 0x3D, 0x33, 0xE4, 0x0B, 0x56, 0x92, 0x44, 0x5A, 0xDB } }, // 1.30.?.?.????? 1.30 + World Cup Viewer
{ 0x01A48CFAE25F85CDULL, { 0x60, 0xD7, 0x7A, 0x58, 0x06, 0x2D, 0x03, 0xDC, 0x69, 0x3C, 0x01, 0x95, 0x4D, 0xD1, 0x80, 0x21 } }, // 1.30.?.?.????? 1.30 + World Cup Viewer
{ 0xFEBBF66DAEF6C9BEULL, { 0x4A, 0x22, 0x0A, 0xE3, 0xA6, 0x80, 0x8E, 0xD2, 0x69, 0x74, 0x10, 0xC9, 0x4C, 0x1C, 0xE9, 0x70 } }, // 1.30.0.1.????? Ashe
{ 0x2AD4834DAB3986ABULL, { 0xEA, 0x69, 0x71, 0xF7, 0x8A, 0xBE, 0xBD, 0x2A, 0xF8, 0x83, 0xD4, 0x6A, 0x54, 0x86, 0xF3, 0x9F } }, // 1.31.?.?.????? Winter 2018
{ 0xD89B24D62F00A04EULL, { 0xC3, 0xA4, 0xD0, 0x10, 0xAA, 0xA7, 0x28, 0x7A, 0x3E, 0xAC, 0xCD, 0x45, 0xED, 0x47, 0x13, 0x45 } }, // 1.31.?.?.????? Bastet Challenge
{ 0x32DDC40236DAEA7BULL, { 0x2D, 0xBD, 0xE4, 0xFB, 0x9F, 0xDA, 0x77, 0x6A, 0xA2, 0x94, 0x87, 0x08, 0x54, 0xFE, 0x9B, 0x02 } }, // 1.31.?.?.????? Lunar New Year 2019 (Pig)
{ 0xF481EFC2302EE415ULL, { 0x37, 0xA7, 0xF2, 0xB8, 0x7D, 0x0B, 0x8B, 0x70, 0x04, 0x56, 0xC6, 0xA9, 0x2C, 0x71, 0xD6, 0xDD } }, // 1.33.?.?.????? Paris Map
{ 0xD1AC8C1903524D9AULL, { 0xD7, 0x81, 0xD0, 0xAA, 0x35, 0xE5, 0xC1, 0x06, 0xBC, 0xA7, 0xCF, 0x01, 0xDE, 0xBD, 0x14, 0x94 } }, // 1.34.0.1.55918 Baptiste
{ 0x71EEBE93590AA903ULL, { 0x0C, 0xCD, 0x10, 0xD4, 0x55, 0x3E, 0xEC, 0x7E, 0x97, 0xFD, 0x36, 0xA9, 0xE8, 0xAD, 0xF0, 0xFF } }, // 1.34.0.1.55918 Baptiste Cosmetics
// Streamed WoW keys
{ 0xFA505078126ACB3EULL, { 0xBD, 0xC5, 0x18, 0x62, 0xAB, 0xED, 0x79, 0xB2, 0xDE, 0x48, 0xC8, 0xE7, 0xE6, 0x6C, 0x62, 0x00 } }, // 15 WOW-20740patch7.0.1_Beta <not used between 7.0 and 7.3>
{ 0xFF813F7D062AC0BCULL, { 0xAA, 0x0B, 0x5C, 0x77, 0xF0, 0x88, 0xCC, 0xC2, 0xD3, 0x90, 0x49, 0xBD, 0x26, 0x7F, 0x06, 0x6D } }, // 25 WOW-20740patch7.0.1_Beta <not used between 7.0 and 7.3>
{ 0xD1E9B5EDF9283668ULL, { 0x8E, 0x4A, 0x25, 0x79, 0x89, 0x4E, 0x38, 0xB4, 0xAB, 0x90, 0x58, 0xBA, 0x5C, 0x73, 0x28, 0xEE } }, // 39 WOW-20740patch7.0.1_Beta Enchanted Torch pet
{ 0xB76729641141CB34ULL, { 0x98, 0x49, 0xD1, 0xAA, 0x7B, 0x1F, 0xD0, 0x98, 0x19, 0xC5, 0xC6, 0x62, 0x83, 0xA3, 0x26, 0xEC } }, // 40 WOW-20740patch7.0.1_Beta Enchanted Pen pet
{ 0xFFB9469FF16E6BF8ULL, { 0xD5, 0x14, 0xBD, 0x19, 0x09, 0xA9, 0xE5, 0xDC, 0x87, 0x03, 0xF4, 0xB8, 0xBB, 0x1D, 0xFD, 0x9A } }, // 41 WOW-20740patch7.0.1_Beta <not used between 7.0 and 7.3>
{ 0x23C5B5DF837A226CULL, { 0x14, 0x06, 0xE2, 0xD8, 0x73, 0xB6, 0xFC, 0x99, 0x21, 0x7A, 0x18, 0x08, 0x81, 0xDA, 0x8D, 0x62 } }, // 42 WOW-20740patch7.0.1_Beta Enchanted Cauldron pet
// { 0x3AE403EF40AC3037ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 51 WOW-21249patch7.0.3_Beta <not used between 7.0 and 7.3>
{ 0xE2854509C471C554ULL, { 0x43, 0x32, 0x65, 0xF0, 0xCD, 0xEB, 0x2F, 0x4E, 0x65, 0xC0, 0xEE, 0x70, 0x08, 0x71, 0x4D, 0x9E } }, // 52 WOW-21249patch7.0.3_Beta Warcraft movie items
{ 0x8EE2CB82178C995AULL, { 0xDA, 0x6A, 0xFC, 0x98, 0x9E, 0xD6, 0xCA, 0xD2, 0x79, 0x88, 0x59, 0x92, 0xC0, 0x37, 0xA8, 0xEE } }, // 55 WOW-21531patch7.0.3_Beta BlizzCon 2016 Murlocs
{ 0x5813810F4EC9B005ULL, { 0x01, 0xBE, 0x8B, 0x43, 0x14, 0x2D, 0xD9, 0x9A, 0x9E, 0x69, 0x0F, 0xAD, 0x28, 0x8B, 0x60, 0x82 } }, // 56 WOW-21531patch7.0.3_Beta Fel Kitten
{ 0x7F9E217166ED43EAULL, { 0x05, 0xFC, 0x92, 0x7B, 0x9F, 0x4F, 0x5B, 0x05, 0x56, 0x81, 0x42, 0x91, 0x2A, 0x05, 0x2B, 0x0F } }, // 57 WOW-21531patch7.0.3_Beta Legion music <not used in 7.3>
{ 0xC4A8D364D23793F7ULL, { 0xD1, 0xAC, 0x20, 0xFD, 0x14, 0x95, 0x7F, 0xAB, 0xC2, 0x71, 0x96, 0xE9, 0xF6, 0xE7, 0x02, 0x4A } }, // 58 WOW-21691patch7.0.3_Beta Demon Hunter #1 cinematic (legion_dh1)
{ 0x40A234AEBCF2C6E5ULL, { 0xC6, 0xC5, 0xF6, 0xC7, 0xF7, 0x35, 0xD7, 0xD9, 0x4C, 0x87, 0x26, 0x7F, 0xA4, 0x99, 0x4D, 0x45 } }, // 59 WOW-21691patch7.0.3_Beta Demon Hunter #2 cinematic (legion_dh2)
{ 0x9CF7DFCFCBCE4AE5ULL, { 0x72, 0xA9, 0x7A, 0x24, 0xA9, 0x98, 0xE3, 0xA5, 0x50, 0x0F, 0x38, 0x71, 0xF3, 0x76, 0x28, 0xC0 } }, // 60 WOW-21691patch7.0.3_Beta Val'sharah #1 cinematic (legion_val_yd)
{ 0x4E4BDECAB8485B4FULL, { 0x38, 0x32, 0xD7, 0xC4, 0x2A, 0xAC, 0x92, 0x68, 0xF0, 0x0B, 0xE7, 0xB6, 0xB4, 0x8E, 0xC9, 0xAF } }, // 61 WOW-21691patch7.0.3_Beta Val'sharah #2 cinematic (legion_val_yx)
{ 0x94A50AC54EFF70E4ULL, { 0xC2, 0x50, 0x1A, 0x72, 0x65, 0x4B, 0x96, 0xF8, 0x63, 0x50, 0xC5, 0xA9, 0x27, 0x96, 0x2F, 0x7A } }, // 62 WOW-21691patch7.0.3_Beta Sylvanas warchief cinematic (legion_org_vs)
{ 0xBA973B0E01DE1C2CULL, { 0xD8, 0x3B, 0xBC, 0xB4, 0x6C, 0xC4, 0x38, 0xB1, 0x7A, 0x48, 0xE7, 0x6C, 0x4F, 0x56, 0x54, 0xA3 } }, // 63 WOW-21691patch7.0.3_Beta Stormheim Sylvanas vs Greymane cinematic (legion_sth)
{ 0x494A6F8E8E108BEFULL, { 0xF0, 0xFD, 0xE1, 0xD2, 0x9B, 0x27, 0x4F, 0x6E, 0x7D, 0xBD, 0xB7, 0xFF, 0x81, 0x5F, 0xE9, 0x10 } }, // 64 WOW-21691patch7.0.3_Beta Harbingers Gul'dan video (legion_hrb_g)
{ 0x918D6DD0C3849002ULL, { 0x85, 0x70, 0x90, 0xD9, 0x26, 0xBB, 0x28, 0xAE, 0xDA, 0x4B, 0xF0, 0x28, 0xCA, 0xCC, 0x4B, 0xA3 } }, // 65 WOW-21691patch7.0.3_Beta Harbingers Khadgar video (legion_hrb_k)
{ 0x0B5F6957915ADDCAULL, { 0x4D, 0xD0, 0xDC, 0x82, 0xB1, 0x01, 0xC8, 0x0A, 0xBA, 0xC0, 0xA4, 0xD5, 0x7E, 0x67, 0xF8, 0x59 } }, // 66 WOW-21691patch7.0.3_Beta Harbingers Illidan video (legion_hrb_i)
{ 0x794F25C6CD8AB62BULL, { 0x76, 0x58, 0x3B, 0xDA, 0xCD, 0x52, 0x57, 0xA3, 0xF7, 0x3D, 0x15, 0x98, 0xA2, 0xCA, 0x2D, 0x99 } }, // 67 WOW-21846patch7.0.3_Beta Suramar cinematic (legion_su_i)
{ 0xA9633A54C1673D21ULL, { 0x1F, 0x8D, 0x46, 0x7F, 0x5D, 0x6D, 0x41, 0x1F, 0x8A, 0x54, 0x8B, 0x63, 0x29, 0xA5, 0x08, 0x7E } }, // 68 WOW-21846patch7.0.3_Beta legion_su_r cinematic
{ 0x5E5D896B3E163DEAULL, { 0x8A, 0xCE, 0x8D, 0xB1, 0x69, 0xE2, 0xF9, 0x8A, 0xC3, 0x6A, 0xD5, 0x2C, 0x08, 0x8E, 0x77, 0xC1 } }, // 69 WOW-21846patch7.0.3_Beta Broken Shore intro cinematic (legion_bs_i)
{ 0x0EBE36B5010DFD7FULL, { 0x9A, 0x89, 0xCC, 0x7E, 0x3A, 0xCB, 0x29, 0xCF, 0x14, 0xC6, 0x0B, 0xC1, 0x3B, 0x1E, 0x46, 0x16 } }, // 70 WOW-21846patch7.0.3_Beta Alliance Broken Shore cinematic (legion_bs_a)
{ 0x01E828CFFA450C0FULL, { 0x97, 0x2B, 0x6E, 0x74, 0x42, 0x0E, 0xC5, 0x19, 0xE6, 0xF9, 0xD9, 0x7D, 0x59, 0x4A, 0xA3, 0x7C } }, // 71 WOW-21846patch7.0.3_Beta Horde Broken Shore cinematic (legion_bs_h)
{ 0x4A7BD170FE18E6AEULL, { 0xAB, 0x55, 0xAE, 0x1B, 0xF0, 0xC7, 0xC5, 0x19, 0xAF, 0xF0, 0x28, 0xC1, 0x56, 0x10, 0xA4, 0x5B } }, // 72 WOW-21846patch7.0.3_Beta Khadgar & Light's Heart cinematic (legion_iq_lv)
{ 0x69549CB975E87C4FULL, { 0x7B, 0x6F, 0xA3, 0x82, 0xE1, 0xFA, 0xD1, 0x46, 0x5C, 0x85, 0x1E, 0x3F, 0x47, 0x34, 0xA1, 0xB3 } }, // 73 WOW-21846patch7.0.3_Beta legion_iq_id cinematic
{ 0x460C92C372B2A166ULL, { 0x94, 0x6D, 0x56, 0x59, 0xF2, 0xFA, 0xF3, 0x27, 0xC0, 0xB7, 0xEC, 0x82, 0x8B, 0x74, 0x8A, 0xDB } }, // 74 WOW-21952patch7.0.3_Beta Stormheim Alliance cinematic (legion_g_a_sth)
{ 0x8165D801CCA11962ULL, { 0xCD, 0x0C, 0x0F, 0xFA, 0xAD, 0x93, 0x63, 0xEC, 0x14, 0xDD, 0x25, 0xEC, 0xDD, 0x2A, 0x5B, 0x62 } }, // 75 WOW-21952patch7.0.3_Beta Stormheim Horde cinematic (legion_g_h_sth)
{ 0xA3F1C999090ADAC9ULL, { 0xB7, 0x2F, 0xEF, 0x4A, 0x01, 0x48, 0x8A, 0x88, 0xFF, 0x02, 0x28, 0x0A, 0xA0, 0x7A, 0x92, 0xBB } }, // 81 WOW-22578patch7.1.0_PTR Firecat Mount
// { 0x18AFDF5191923610ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 82 WOW-22578patch7.1.0_PTR <not used between 7.1 and 7.3>
// { 0x3C258426058FBD93ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 91 WOW-23436patch7.2.0_PTR <not used between 7.2 and 7.3>
{ 0x094E9A0474876B98ULL, { 0xE5, 0x33, 0xBB, 0x6D, 0x65, 0x72, 0x7A, 0x58, 0x32, 0x68, 0x0D, 0x62, 0x0B, 0x0B, 0xC1, 0x0B } }, // 92 WOW-23910patch7.2.5_PTR shadowstalkerpanthermount, shadowstalkerpantherpet
{ 0x3DB25CB86A40335EULL, { 0x02, 0x99, 0x0B, 0x12, 0x26, 0x0C, 0x1E, 0x9F, 0xDD, 0x73, 0xFE, 0x47, 0xCB, 0xAB, 0x70, 0x24 } }, // 93 WOW-23789patch7.2.0_PTR legion_72_ots
{ 0x0DCD81945F4B4686ULL, { 0x1B, 0x78, 0x9B, 0x87, 0xFB, 0x3C, 0x92, 0x38, 0xD5, 0x28, 0x99, 0x7B, 0xFA, 0xB4, 0x41, 0x86 } }, // 94 WOW-23789patch7.2.0_PTR legion_72_tst
{ 0x486A2A3A2803BE89ULL, { 0x32, 0x67, 0x9E, 0xA7, 0xB0, 0xF9, 0x9E, 0xBF, 0x4F, 0xA1, 0x70, 0xE8, 0x47, 0xEA, 0x43, 0x9A } }, // 95 WOW-23789patch7.2.0_PTR legion_72_ars
{ 0x71F69446AD848E06ULL, { 0xE7, 0x9A, 0xEB, 0x88, 0xB1, 0x50, 0x9F, 0x62, 0x8F, 0x38, 0x20, 0x82, 0x01, 0x74, 0x1C, 0x30 } }, // 97 WOW-24473patch7.3.0_PTR BlizzCon 2017 Mounts (AllianceShipMount and HordeZeppelinMount)
{ 0x211FCD1265A928E9ULL, { 0xA7, 0x36, 0xFB, 0xF5, 0x8D, 0x58, 0x7B, 0x39, 0x72, 0xCE, 0x15, 0x4A, 0x86, 0xAE, 0x45, 0x40 } }, // 98 WOW-24473patch7.3.0_PTR "Shadow" fox pet (store)
{ 0x0ADC9E327E42E98CULL, { 0x01, 0x7B, 0x34, 0x72, 0xC1, 0xDE, 0xE3, 0x04, 0xFA, 0x0B, 0x2F, 0xF8, 0xE5, 0x3F, 0xF7, 0xD6 } }, // 99 WOW-23910patch7.2.5_PTR legion_72_tsf
{ 0xBAE9F621B60174F1ULL, { 0x38, 0xC3, 0xFB, 0x39, 0xB4, 0x97, 0x17, 0x60, 0xB4, 0xB9, 0x82, 0xFE, 0x9F, 0x09, 0x50, 0x14 } }, // 100 WOW-24727patch7.3.0_PTR Rejection of the Gift cinematic (legion_73_agi)
{ 0x34DE1EEADC97115EULL, { 0x2E, 0x3A, 0x53, 0xD5, 0x9A, 0x49, 0x1E, 0x5C, 0xD1, 0x73, 0xF3, 0x37, 0xF7, 0xCD, 0x8C, 0x61 } }, // 101 WOW-24727patch7.3.0_PTR Resurrection of Alleria Windrunner cinematic (legion_73_avt)
{ 0xE07E107F1390A3DFULL, { 0x29, 0x0D, 0x27, 0xB0, 0xE8, 0x71, 0xF8, 0xC5, 0xB1, 0x4A, 0x14, 0xE5, 0x14, 0xD0, 0xF0, 0xD9 } }, // 102 WOW-25079patch7.3.2_PTR Tottle battle pet, Raptor mount, Horse mount (104 files)
{ 0x32690BF74DE12530ULL, { 0xA2, 0x55, 0x62, 0x10, 0xAE, 0x54, 0x22, 0xE6, 0xD6, 0x1E, 0xDA, 0xAF, 0x12, 0x2C, 0xB6, 0x37 } }, // 103 WOW-24781patch7.3.0_PTR legion_73_pan
{ 0xBF3734B1DCB04696ULL, { 0x48, 0x94, 0x61, 0x23, 0x05, 0x0B, 0x00, 0xA7, 0xEF, 0xB1, 0xC0, 0x29, 0xEE, 0x6C, 0xC4, 0x38 } }, // 104 WOW-25079patch7.3.2_PTR legion_73_afn
{ 0x74F4F78002A5A1BEULL, { 0xC1, 0x4E, 0xEC, 0x8D, 0x5A, 0xEE, 0xF9, 0x3F, 0xA8, 0x11, 0xD4, 0x50, 0xB4, 0xE4, 0x6E, 0x91 } }, // 105 WOW-25079patch7.3.2_PTR SilithusPhase01 map
// { 0x423F07656CA27D23ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 107 WOW-25600patch7.3.5_PTR bltestmap
// { 0x0691678F83E8A75DULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 108 WOW-25600patch7.3.5_PTR filedataid 1782602-1782603
// { 0x324498590F550556ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 109 WOW-25600patch7.3.5_PTR filedataid 1782615-1782619
// { 0xC02C78F40BEF5998ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 110 WOW-25600patch7.3.5_PTR test/testtexture.blp (fdid 1782613)
// { 0x47011412CCAAB541ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 111 WOW-25600patch7.3.5_PTR unused in 25600
// { 0x23B6F5764CE2DDD6ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 112 WOW-25600patch7.3.5_PTR unused in 25600
// { 0x8E00C6F405873583ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 113 WOW-25600patch7.3.5_PTR filedataid 1783470-1783472
{ 0x78482170E4CFD4A6ULL, { 0x76, 0x85, 0x40, 0xC2, 0x0A, 0x5B, 0x15, 0x35, 0x83, 0xAD, 0x7F, 0x53, 0x13, 0x0C, 0x58, 0xFE } }, // 114 WOW-25600patch7.3.5_PTR Magni Bronzebeard VO
{ 0xB1EB52A64BFAF7BFULL, { 0x45, 0x81, 0x33, 0xAA, 0x43, 0x94, 0x9A, 0x14, 0x16, 0x32, 0xC4, 0xF8, 0x59, 0x6D, 0xE2, 0xB0 } }, // 115 WOW-25600patch7.3.5_PTR dogmount, 50 files
{ 0xFC6F20EE98D208F6ULL, { 0x57, 0x79, 0x0E, 0x48, 0xD3, 0x55, 0x00, 0xE7, 0x0D, 0xF8, 0x12, 0x59, 0x4F, 0x50, 0x7B, 0xE7 } }, // 117 WOW-25632patch7.3.5_PTR shop stuff
{ 0x402CFABF2020D9B7ULL, { 0x67, 0x19, 0x7B, 0xCD, 0x9D, 0x0E, 0xF0, 0xC4, 0x08, 0x53, 0x78, 0xFA, 0xA6, 0x9A, 0x32, 0x64 } }, // 118 WOW-25678patch7.3.5_PTR filedataid 1854762
{ 0x6FA0420E902B4FBEULL, { 0x27, 0xB7, 0x50, 0x18, 0x4E, 0x53, 0x29, 0xC4, 0xE4, 0x45, 0x5C, 0xBD, 0x3E, 0x1F, 0xD5, 0xAB } }, // 119 WOW-25744patch7.3.5_PTR legion_735_epa / legion_735_eph
{ 0x1076074F2B350A2DULL, { 0x88, 0xBF, 0x0C, 0xD0, 0xD5, 0xBA, 0x15, 0x9A, 0xE7, 0xCB, 0x91, 0x6A, 0xFB, 0xE1, 0x38, 0x65 } }, // 121 WOW-26287patch8.0.1_Beta skiff
{ 0x816F00C1322CDF52ULL, { 0x6F, 0x83, 0x22, 0x99, 0xA7, 0x57, 0x89, 0x57, 0xEE, 0x86, 0xB7, 0xF9, 0xF1, 0x5B, 0x01, 0x88 } }, // 122 WOW-26287patch8.0.1_Beta snowkid
{ 0xDDD295C82E60DB3CULL, { 0x34, 0x29, 0xCC, 0x59, 0x27, 0xD1, 0x62, 0x97, 0x65, 0x97, 0x4F, 0xD9, 0xAF, 0xAB, 0x75, 0x80 } }, // 123 WOW-26287patch8.0.1_Beta redbird
{ 0x83E96F07F259F799ULL, { 0x91, 0xF7, 0xD0, 0xE7, 0xA0, 0x2C, 0xDE, 0x0D, 0xE0, 0xBD, 0x36, 0x7F, 0xAB, 0xCB, 0x8A, 0x6E } }, // 124 WOW-26522patch8.0.1_Beta BlizzCon 2018 (Alliance and Horde banners and cloaks)
{ 0x49FBFE8A717F03D5ULL, { 0xC7, 0x43, 0x77, 0x70, 0xCF, 0x15, 0x3A, 0x31, 0x35, 0xFA, 0x6D, 0xC5, 0xE4, 0xC8, 0x5E, 0x65 } }, // 225 WOW-27826patch8.1.0_PTR Meatwagon mount (Warcraft 3: Reforged)
{ 0xC1E5D7408A7D4484ULL, { 0xA7, 0xD8, 0x8E, 0x52, 0x74, 0x9F, 0xA5, 0x45, 0x9D, 0x64, 0x45, 0x23, 0xF8, 0x35, 0x96, 0x51 } }, // 226 WOW-26871patch8.0.1_Beta Sylvanas Warbringer cinematic
{ 0xE46276EB9E1A9854ULL, { 0xCC, 0xCA, 0x36, 0xE3, 0x02, 0xF9, 0x45, 0x9B, 0x1D, 0x60, 0x52, 0x6A, 0x31, 0xBE, 0x77, 0xC8 } }, // 227 WOW-26871patch8.0.1_Beta ltc_a, ltc_h and ltt cinematics
{ 0xD245B671DD78648CULL, { 0x19, 0xDC, 0xB4, 0xD4, 0x5A, 0x65, 0x8B, 0x54, 0x35, 0x1D, 0xB7, 0xDD, 0xC8, 0x1D, 0xE7, 0x9E } }, // 228 WOW-26871patch8.0.1_Beta stz, zia, kta, jnm & ja cinematics
{ 0x4C596E12D36DDFC3ULL, { 0xB8, 0x73, 0x19, 0x26, 0x38, 0x94, 0x99, 0xCB, 0xD4, 0xAD, 0xBF, 0x50, 0x06, 0xCA, 0x03, 0x91 } }, // 229 WOW-26871patch8.0.1_Beta bar cinematic
{ 0x0C9ABD5081C06411ULL, { 0x25, 0xA7, 0x7C, 0xD8, 0x00, 0x19, 0x7E, 0xE6, 0xA3, 0x2D, 0xD6, 0x3F, 0x04, 0xE1, 0x15, 0xFA } }, // 230 WOW-26871patch8.0.1_Beta zcf cinematic
{ 0x3C6243057F3D9B24ULL, { 0x58, 0xAE, 0x3E, 0x06, 0x42, 0x10, 0xE3, 0xED, 0xF9, 0xC1, 0x25, 0x9C, 0xDE, 0x91, 0x4C, 0x5D } }, // 231 WOW-26871patch8.0.1_Beta ktf cinematic
{ 0x7827FBE24427E27DULL, { 0x34, 0xA4, 0x32, 0x04, 0x20, 0x73, 0xCD, 0x0B, 0x51, 0x62, 0x70, 0x68, 0xD2, 0xE0, 0xBD, 0x3E } }, // 232 WOW-26871patch8.0.1_Beta rot cinematic
{ 0xFAF9237E1186CF66ULL, { 0xAE, 0x78, 0x78, 0x40, 0x04, 0x1E, 0x9B, 0x41, 0x98, 0xF4, 0x79, 0x71, 0x4D, 0xAD, 0x56, 0x2C } }, // 233 WOW-28048patch8.1.0_PTR encrypted db2 sections (battle pet?)
// { 0x5DD92EE32BBF9ABDULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 234 WOW-27004patch8.0.1_Subm filedataid 2238294
{ 0x0B68A7AF5F85F7EEULL, { 0x27, 0xAA, 0x01, 0x10, 0x82, 0xF5, 0xE8, 0xBB, 0xBD, 0x71, 0xD1, 0xBA, 0x04, 0xF6, 0xAB, 0xA4 } }, // 236 WOW-28151patch8.1.0_PTR encrypted06 Flying pig mount
// { 0x01531713C83FCC39ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 237 WOW-28151patch8.1.0_PTR fdid 2460009, 2460732
// { 0x76E4F6739A35E8D7ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 238 WOW-28294patch8.1.0_PTR starts at fdid 2492654, total of 12 fdids
{ 0x66033F28DC01923CULL, { 0x9F, 0x95, 0x19, 0x86, 0x14, 0x90, 0xC5, 0xA9, 0xFF, 0xD4, 0xD8, 0x2A, 0x6D, 0x00, 0x67, 0xDB } }, // 239 WOW-28294patch8.1.0_PTR vulpine familiar mount
{ 0xFCF34A9B05AE7E6AULL, { 0xE7, 0xC2, 0xC8, 0xF7, 0x7E, 0x30, 0xAC, 0x24, 0x0F, 0x39, 0xEC, 0x23, 0x97, 0x12, 0x96, 0xE5 } }, // 240 WOW-28151patch8.1.0_PTR fdid 2468985, 2471011, 2471012, 2471014, 2471016, 2471018, 2530045
{ 0xE2F6BD41298A2AB9ULL, { 0xC5, 0xDC, 0x1B, 0xB4, 0x3B, 0x8C, 0xF3, 0xF0, 0x85, 0xD6, 0x98, 0x68, 0x26, 0xB9, 0x28, 0xEC } }, // 241 WOW-28151patch8.1.0_PTR fdid 2468988, 2471019, 2471020, 2471021, 2471022, 2471023
{ 0x14C4257E557B49A1ULL, { 0x06, 0x4A, 0x97, 0x09, 0xF4, 0x2D, 0x50, 0xCB, 0x5F, 0x8B, 0x94, 0xBC, 0x1A, 0xCF, 0xDD, 0x5D } }, // 242 WOW-28440patch8.1.0_PTR dor cinematic
{ 0x1254E65319C6EEFFULL, { 0x79, 0xD2, 0xB3, 0xD1, 0xCC, 0xB0, 0x15, 0x47, 0x4E, 0x71, 0x58, 0x81, 0x38, 0x64, 0xB8, 0xE6 } }, // 243 WOW-28440patch8.1.0_PTR akt cinematic
// { 0xC8753773ADF1174CULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 244 WOW-28938patch8.1.5_PTR starts at fdid 2615771, total of 14 fdids
// { 0x2170BCAA9FA96E22ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 245 WOW-28938patch8.1.5_PTR alpaca mount
// { 0x75485627AA225F4DULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 246 WOW-28938patch8.1.5_PTR fdid 2741546, 2741548, 2741549
{ 0x08717B15BF3C7955ULL, { 0x4B, 0x06, 0xBF, 0x9D, 0x17, 0x66, 0x3C, 0xEB, 0x33, 0x12, 0xEA, 0x3C, 0x69, 0xFB, 0xC5, 0xDD } }, // 248 WOW-29220patch8.1.5_PTR fdid 2823166
{ 0x9FD609902B4B2E07ULL, { 0xAB, 0xE0, 0xC5, 0xF9, 0xC1, 0x23, 0xE6, 0xE2, 0x4E, 0x7B, 0xEA, 0x43, 0xC2, 0xBF, 0x00, 0xAC } }, // 250 WOW-29418patch8.1.5_PTR dpr cinematic
};
//-----------------------------------------------------------------------------
// Local functions
static DWORD Rol32(DWORD dwValue, DWORD dwRolCount)
{
return (dwValue << dwRolCount) | (dwValue >> (32 - dwRolCount));
}
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 CascLoadEncryptionKeys(TCascStorage * hs)
{
size_t nKeyCount = (sizeof(CascKeys) / sizeof(CASC_ENCRYPTION_KEY));
size_t nMaxItems = nKeyCount + CASC_EXTRA_KEYS;
int nError;
// Create fast map of KeyName -> Key
nError = hs->EncryptionKeys.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_ENCRYPTION_KEY, KeyName));
if(nError != ERROR_SUCCESS)
return nError;
// Insert all static keys
for (size_t i = 0; i < nKeyCount; i++)
hs->EncryptionKeys.InsertObject(&CascKeys[i], &CascKeys[i].KeyName);
// Create array for extra keys
nError = hs->ExtraKeysList.Create<CASC_ENCRYPTION_KEY>(CASC_EXTRA_KEYS);
return nError;
}
LPBYTE CascFindKey(TCascStorage * hs, ULONGLONG KeyName)
{
PCASC_ENCRYPTION_KEY pKey;
pKey = (PCASC_ENCRYPTION_KEY)hs->EncryptionKeys.FindObject(&KeyName);
return (pKey != NULL) ? pKey->Key : NULL;
}
int CascDecrypt(TCascStorage * hs, 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 = CascFindKey(hs, KeyName);
if(pbKey == NULL)
return ERROR_FILE_ENCRYPTED;
// 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(size_t 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;
}
//-----------------------------------------------------------------------------
// Public functions
bool WINAPI CascAddEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPBYTE Key)
{
PCASC_ENCRYPTION_KEY pEncKey;
TCascStorage * hs;
// Validate the storage handle
hs = TCascStorage::IsValid(hStorage);
if (hs == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// Don't allow more than CASC_EXTRA_KEYS keys
if (hs->ExtraKeysList.ItemCount() >= CASC_EXTRA_KEYS)
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return false;
}
// Insert the key to the array
pEncKey = (PCASC_ENCRYPTION_KEY)hs->ExtraKeysList.Insert(1);
if (pEncKey == NULL)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return false;
}
// Fill the key
memcpy(pEncKey->Key, Key, sizeof(pEncKey->Key));
pEncKey->KeyName = KeyName;
// Also insert the key to the map
return hs->EncryptionKeys.InsertObject(pEncKey, &pEncKey->KeyName);
}
+533
View File
@@ -0,0 +1,533 @@
/*****************************************************************************/
/* CascDumpData.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* System-dependent directory functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 07.05.14 1.00 Lad The first version of CascDumpData.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
#ifdef _DEBUG // The entire feature is only valid for debug purposes
//-----------------------------------------------------------------------------
// Forward definitions
int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, size_t cbFileData);
int CaptureDownloadHeader(CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, size_t cbFileData);
int CaptureDownloadEntry(CASC_DOWNLOAD_HEADER & DlHeader, CASC_DOWNLOAD_ENTRY & DlEntry, LPBYTE pbFilePtr, LPBYTE pbFileEnd);
int CaptureDownloadTag(CASC_DOWNLOAD_HEADER & DlHeader, CASC_TAG_ENTRY1 & DlTag, LPBYTE pbFilePtr, LPBYTE pbFileEnd);
//-----------------------------------------------------------------------------
// Local functions
static char * StringFromLPTSTR(const TCHAR * szString, char * szBuffer, size_t cchBuffer)
{
char * szSaveBuffer = szBuffer;
char * szBufferEnd = szBuffer + cchBuffer - 1;
while (szBuffer < szBufferEnd && szString[0] != 0)
*szBuffer++ = (char)*szString++;
szBuffer[0] = 0;
return szSaveBuffer;
}
// Either opens a dump file or returns "stdout"
static FILE * OpenDumpFile(const char * szDumpFile)
{
if(szDumpFile != NULL)
{
return fopen(szDumpFile, "wt");
}
else
{
return stdout;
}
}
// Closes the dump file if it was open by OpenDumpFile
static void CloseDumpFile(const char * szDumpFile, FILE * fp)
{
if(szDumpFile != NULL && fp != NULL)
fclose(fp);
}
static void DumpKey(FILE * fp, const char * szInFormat, LPBYTE pbData, size_t cbData)
{
const char * szFormatSpec;
LPBYTE pbDataEnd = pbData + cbData;
char szFormat[0x100];
char szKey[MD5_STRING_SIZE + 1];
// First line: Copy the line with the complete format
CascStrCopy(szFormat, _countof(szFormat), szInFormat);
szFormatSpec = strstr(szFormat, "%s");
// Dump all keys, every one on a new line
while(pbData < pbDataEnd)
{
// Fump the line
StringFromBinary(pbData, MD5_HASH_SIZE, szKey);
fprintf(fp, szFormat, szKey);
// If there will be more lines, then we clear the entire part until "%s"
if(szFormatSpec != NULL)
memset(szFormat, ' ', (szFormatSpec - szFormat));
// Move pointers
pbData += MD5_HASH_SIZE;
}
}
/*
void CascDumpSparseArray(const char * szFileName, void * pvSparseArray)
{
TSparseArray * pSparseArray = (TSparseArray *)pvSparseArray;
FILE * fp;
// Create the dump file
fp = fopen(szFileName, "wt");
if(fp != NULL)
{
// Write header
fprintf(fp, "## Value\n-- -----\n");
// Write the values
for(DWORD i = 0; i < pSparseArray->TotalItemCount; i++)
{
DWORD Value = 0;
if(pSparseArray->IsItemPresent(i))
{
Value = pSparseArray->GetItemValue(i);
fprintf(fp, "%02X %02X\n", i, Value);
}
else
{
fprintf(fp, "%02X --\n", i);
}
}
fclose(fp);
}
}
void CascDumpNameFragTable(const char * szFileName, void * pMarFile)
{
TFileNameDatabase * pDB = ((PMAR_FILE)pMarFile)->pDatabasePtr->pDB;
FILE * fp;
// Create the dump file
fp = fopen(szFileName, "wt");
if(fp != NULL)
{
PNAME_FRAG pNameTable = pDB->NameFragTable.ItemArray;
const char * szNames = pDB->IndexStruct_174.ItemArray;
const char * szLastEntry;
char szMatchType[0x100];
// Dump the table header
fprintf(fp, "Indx ThisHash NextHash FragOffs\n");
fprintf(fp, "---- -------- -------- --------\n");
// Dump all name entries
for(DWORD i = 0; i < pDB->NameFragTable.ItemCount; i++)
{
// Reset both match types
szMatchType[0] = 0;
szLastEntry = "";
// Only if the table entry is not empty
if(pNameTable->ItemIndex != 0xFFFFFFFF)
{
// Prepare the entry
if(IS_SINGLE_CHAR_MATCH(pDB->NameFragTable, i))
CascStrPrintf(szMatchType, _countof(szMatchType), "SINGLE_CHAR (\'%c\')", (pNameTable->FragOffs & 0xFF));
else
CascStrPrintf(szMatchType, _countof(szMatchType), "NAME_FRAGMT (\"%s\")", szNames + pNameTable->FragOffs);
}
// Dump the entry
fprintf(fp, "0x%02X %08x %08x %08x %s%s\n", i, pNameTable->ItemIndex,
pNameTable->NextIndex,
pNameTable->FragOffs,
szMatchType,
szLastEntry);
pNameTable++;
}
fclose(fp);
}
}
void CascDumpFileNames(const char * szFileName, void * pvMarFile)
{
TMndxFindResult Struct1C;
PMAR_FILE pMarFile = (PMAR_FILE)pvMarFile;
FILE * fp;
char szNameBuff[0x200];
bool bFindResult;
// Create the dump file
fp = fopen(szFileName, "wt");
if(fp != NULL)
{
// Set an empty path as search mask (?)
Struct1C.SetSearchPath("", 0);
// Keep searching
for(;;)
{
// Search the next file name
pMarFile->pDatabasePtr->sub_1956CE0(&Struct1C, &bFindResult);
// Stop the search in case of failure
if(!bFindResult)
break;
// Printf the found file name
memcpy(szNameBuff, Struct1C.szFoundPath, Struct1C.cchFoundPath);
szNameBuff[Struct1C.cchFoundPath] = 0;
fprintf(fp, "%s\n", szNameBuff);
}
fclose(fp);
}
// Free the search structures
Struct1C.FreeStruct40();
}
void DumpEKeyEntries(TCascStorage * hs, FILE * fp)
{
// Dump header
fprintf(fp, "=== Ekey Entries ============================================================\n");
// Dump index entries from all index files
for(int file = 0; file < CASC_INDEX_COUNT; file++)
{
PCASC_EKEY_ENTRY pEKeyEntry = hs->IndexFile[file].pEKeyEntries;
DWORD nEKeyEntries = hs->IndexFile[file].nEKeyEntries;
// Only if they are present
if(pEKeyEntry && nEKeyEntries)
{
// fprintf(fp, "--- Index: %03u --------------------------------------------------------------\n", file);
for(DWORD entry = 0; entry < nEKeyEntries; entry++, pEKeyEntry++)
DumpEKeyEntry(fp, pEKeyEntry->EKey, CASC_EKEY_SIZE, pEKeyEntry->StorageOffset, ConvertBytesToInteger_4_LE(pEKeyEntry->EncodedSize));
}
}
// Dump tail
fprintf(fp, "=============================================================================\n\n");
}
*/
//-----------------------------------------------------------------------------
// Dumping the ENCODING manifest
/*
static void DumpESpecEntries(FILE * fp, LPBYTE pbDataPtr, LPBYTE pbDataEnd)
{
fprintf(fp, "--- ESpec Entries -----------------------------------------------------------\n");
while(pbDataPtr < pbDataEnd)
{
const char * szESpecEntry = (const char *)pbDataPtr;
// Find the end of the entry
while((pbDataPtr < pbDataEnd) && (pbDataPtr[0] != 0))
{
pbDataPtr++;
}
// Dump the entry
if(pbDataPtr[0] == 0)
{
fprintf(fp, "%s\n", szESpecEntry);
pbDataPtr++;
}
}
}
static void DumpCKeyEntry(PCASC_CKEY_ENTRY pCKeyEntry, FILE * fp, DWORD nIndex)
{
LPBYTE pbEKey;
char szStringBuff[MD5_STRING_SIZE + 1];
// Print the CKey and number of EKeys
fprintf(fp, "[0x%02X] %s (EKeys: %02u): ", nIndex, StringFromBinary(pCKeyEntry->CKey, MD5_HASH_SIZE, szStringBuff), pCKeyEntry->EKeyCount);
pbEKey = pCKeyEntry->EKey;
// Print the EKeys
for(USHORT i = 0; i < pCKeyEntry->EKeyCount; i++, pbEKey += MD5_HASH_SIZE)
fprintf(fp, " %s ", StringFromBinary(pbEKey, MD5_HASH_SIZE, szStringBuff));
fprintf(fp, "\n");
}
static void DumpCKeyPages(FILE * fp, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd)
{
// Get the CKey page header and the page itself
PFILE_CKEY_PAGE pPageHeader = (PFILE_CKEY_PAGE)pbDataPtr;
LPBYTE pbPageEntry = (LPBYTE)(pPageHeader + EnHeader.CKeyPageCount);
fprintf(fp, "--- CKey Pages --------------------------------------------------------------\n");
for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++, pPageHeader++)
{
LPBYTE pbCKeyEntry = pbPageEntry;
LPBYTE pbEndOfPage = pbPageEntry + EnHeader.CKeyPageSize;
DWORD nCKeyIndex = 0;
// Check if there is enough space in the buffer
if((pbPageEntry + EnHeader.CKeyPageSize) > pbDataEnd)
break;
DumpKey(fp, "First CKey: %s\n", pPageHeader->FirstKey, EnHeader.CKeyLength);
DumpKey(fp, "Page hash: %s\n", pPageHeader->SegmentHash, MD5_HASH_SIZE);
fprintf(fp, "\n");
// Parse all CKey entries
while(pbCKeyEntry <= pbEndOfPage)
{
PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)pbCKeyEntry;
// Get pointer to the encoding entry
if(pCKeyEntry->EKeyCount == 0)
break;
// Dump this encoding entry
DumpCKeyEntry(pCKeyEntry, fp, nCKeyIndex++);
// Move to the next encoding entry
pbCKeyEntry += sizeof(FILE_CKEY_ENTRY) + ((pCKeyEntry->EKeyCount - 1) * EnHeader.CKeyLength);
}
// Move to the next segment
pbPageEntry += EnHeader.CKeyPageSize;
fprintf(fp, "\n");
}
}
void DumpEncodingManifest(TCascStorage * hs, LPBYTE pbData, ULONG cbData, FILE * fp)
{
// Dump header
fprintf(fp, "=== ENCODING Manifest =======================================================\n");
// Dump the encoding file
if(hs->EncodingManifest.pbData && hs->EncodingManifest.cbData)
{
CASC_ENCODING_HEADER EnHeader;
LPBYTE pbFilePtr = hs->EncodingManifest.pbData;
DWORD cbDataSize;
// Capture the header of the ENCODING file
if(CaptureEncodingHeader(EnHeader, hs->EncodingManifest.pbData, hs->EncodingManifest.cbData) == ERROR_SUCCESS)
{
// Dump the ENCODING file info
fprintf(fp, "Magic: %u\n", EnHeader.Magic);
fprintf(fp, "Version: %u\n", EnHeader.Version);
fprintf(fp, "CKey Length: %02X\n", EnHeader.CKeyLength);
fprintf(fp, "EKey Length: %02X\n", EnHeader.EKeyLength);
fprintf(fp, "CKey page size: %08X\n", EnHeader.CKeyPageSize);
fprintf(fp, "CKey page count: %08X\n", EnHeader.CKeyPageCount);
fprintf(fp, "EKey page size: %08X\n", EnHeader.EKeyPageSize);
fprintf(fp, "EKey page count: %08X\n", EnHeader.EKeyPageCount);
fprintf(fp, "ESpec block size: %08X\n", EnHeader.ESpecBlockSize);
pbFilePtr += sizeof(FILE_ENCODING_HEADER);
// Dump all ESpec entries
DumpESpecEntries(fp, pbFilePtr, pbFilePtr + EnHeader.ESpecBlockSize);
pbFilePtr += EnHeader.ESpecBlockSize;
// Parse all CKey pages and dump them
cbDataSize = EnHeader.CKeyPageCount * (sizeof(FILE_CKEY_PAGE) + EnHeader.CKeyPageSize);
DumpCKeyPages(fp, EnHeader, pbFilePtr, pbFilePtr + cbDataSize);
pbFilePtr += cbDataSize;
}
}
else
{
fprintf(fp, "(empty)\n");
}
// Dump tail
fprintf(fp, "=============================================================================\n\n");
}
*/
//-----------------------------------------------------------------------------
// Dumping the DOWNLOAD manifest
/*
static void DumpDownloadEntries(FILE * fp, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbDownloadPtr, LPBYTE pbDownloadEnd)
{
fprintf(fp, "--- DOWNLOAD Entries --------------------------------------------------------\n");
fprintf(fp, "Index EKey FileSize Checksum Flags Prio\n");
fprintf(fp, "-------- -------------------------------- ---------- -------- -------- ----\n");
for(DWORD i = 0; i < DlHeader.EntryCount; i++)
{
CASC_DOWNLOAD_ENTRY DlEntry;
char szEKey[MD5_STRING_SIZE+1];
char szChksum[0x40];
char szFlags[0x40];
if(CaptureDownloadEntry(DlHeader, DlEntry, pbDownloadPtr, pbDownloadEnd) != ERROR_SUCCESS)
break;
// Dump the entry
StringFromBinary(DlEntry.EKey, DlHeader.EKeyLength, szEKey);
CascStrPrintf(szChksum, _countof(szChksum), (DlHeader.EntryHasChecksum ? "%08X" : "<none> "), DlEntry.Checksum);
CascStrPrintf(szFlags, _countof(szFlags), (DlHeader.FlagByteSize ? "%08X" : "<none> "), DlEntry.Flags);
fprintf(fp, "[%06X] %-16s %010I64X %s %s %04X\n", i, szEKey, DlEntry.FileSize, szChksum, szFlags, DlEntry.Priority);
// Move to the next entry
pbDownloadPtr += DlHeader.EntryLength;
}
fprintf(fp, "\n");
}
static void DumpDownloadTags(FILE * fp, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFilePtr, LPBYTE pbFileEnd)
{
CASC_TAG_ENTRY1 DlTag;
fprintf(fp, "--- DOWNLOAD Tags ---------------------------------------------------------\n");
for(DWORD i = 0; i < DlHeader.TagCount; i++)
{
// Capture the download tag
if(CaptureDownloadTag(DlHeader, DlTag, pbFilePtr, pbFileEnd) != ERROR_SUCCESS)
break;
// Dump the tag
fprintf(fp, "[%02X]: %08X = %s\n", i, DlTag.TagValue, DlTag.szTagName);
pbFilePtr += DlTag.TagLength;
}
}
void DumpDownloadManifest(TCascStorage * hs, FILE * fp)
{
// Dump header
fprintf(fp, "=== DOWNLOAD Manifest =======================================================\n");
// Dump the encoding file
if(hs->DownloadManifest.pbData && hs->DownloadManifest.cbData)
{
CASC_DOWNLOAD_HEADER DlHeader;
LPBYTE pbFileEnd = hs->DownloadManifest.pbData + hs->DownloadManifest.cbData;
LPBYTE pbFilePtr = hs->DownloadManifest.pbData;
// Capture the header of the ENCODING file
if(CaptureDownloadHeader(DlHeader, hs->DownloadManifest.pbData, hs->DownloadManifest.cbData) == ERROR_SUCCESS)
{
// Dump the DOWNLOAD header
fprintf(fp, "Magic: %u\n", DlHeader.Magic);
fprintf(fp, "Version: %u\n", DlHeader.Version);
fprintf(fp, "EKey Length: %02X\n", DlHeader.EKeyLength);
fprintf(fp, "Checksum present: %s\n", DlHeader.EntryHasChecksum ? "Yes" : "No");
fprintf(fp, "Entry Count: %08X\n", DlHeader.EntryCount);
fprintf(fp, "Tag Count: %08X\n", DlHeader.TagCount);
fprintf(fp, "Flag byte size: %08X\n", DlHeader.FlagByteSize);
fprintf(fp, "Base priority: %08X\n", DlHeader.BasePriority);
fprintf(fp, "Header length: %08X\n", (DWORD)DlHeader.HeaderLength);
fprintf(fp, "Entry length: %08X\n", (DWORD)DlHeader.EntryLength);
fprintf(fp, "\n");
pbFilePtr += DlHeader.HeaderLength;
// Dump all download entries
DumpDownloadEntries(fp, DlHeader, pbFilePtr, pbFileEnd);
pbFilePtr += DlHeader.EntryLength * DlHeader.EntryCount;
// Dump all tags
DumpDownloadTags(fp, DlHeader, pbFilePtr, pbFileEnd);
}
}
else
{
fprintf(fp, "(empty)\n");
}
// Dump tail
fprintf(fp, "=============================================================================\n\n");
}
*/
//-----------------------------------------------------------------------------
// Public dumping functions
void CascDumpFile(const char * szDumpFile, HANDLE hFile)
{
FILE * fp;
DWORD dwBytesRead = 1;
DWORD dwFilePos = CascSetFilePointer(hFile, 0, NULL, FILE_BEGIN);
BYTE Buffer[0x1000];
// Create/open the dump file
fp = OpenDumpFile(szDumpFile);
if(fp != NULL)
{
// Read data as long as we can, write as long as we can
while(dwBytesRead != 0)
{
// Read from the source file
if(!CascReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead))
break;
// Write to the destination file
if(fwrite(Buffer, 1, dwBytesRead, fp) != dwBytesRead)
break;
}
// Restore the file pointer
CascSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN);
// Close the dump file
CloseDumpFile(szDumpFile, fp);
}
}
void CascDumpStorage(HANDLE hStorage, const char * szDumpFile)
{
TCascStorage * hs;
FILE * fp = stdout;
char szStringBuff[0x800];
// Verify the storage handle
hs = TCascStorage::IsValid(hStorage);
if(hs == NULL)
return;
// Create/open the dump file
fp = OpenDumpFile(szDumpFile);
if(fp != NULL)
{
// Dump the basic storage info
fprintf(fp, "=== Basic Storage Info ======================================================\n");
fprintf(fp, "DataPath: %s\n", StringFromLPTSTR(hs->szDataPath, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "IndexPath: %s\n", StringFromLPTSTR(hs->szIndexPath, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "BuildFile: %s\n", StringFromLPTSTR(hs->szBuildFile, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "CDN Server: %s\n", StringFromLPTSTR(hs->szCdnServers, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "CDN Path: %s\n", StringFromLPTSTR(hs->szCdnPath, szStringBuff, sizeof(szStringBuff)));
DumpKey(fp, "CDN Config Key: %s\n", hs->CdnConfigKey.pbData, hs->CdnConfigKey.cbData);
DumpKey(fp, "CDN Build Key: %s\n", hs->CdnBuildKey.pbData, hs->CdnBuildKey.cbData);
DumpKey(fp, "Archives Key: %s\n", hs->ArchivesKey.pbData, hs->ArchivesKey.cbData);
DumpKey(fp, "ROOT file: %s\n", hs->RootFile.CKey, CASC_CKEY_SIZE);
DumpKey(fp, "PATCH file: %s\n", hs->PatchFile.CKey, CASC_CKEY_SIZE);
DumpKey(fp, "ENCODING file: %s\n", hs->EncodingCKey.CKey, CASC_CKEY_SIZE);
DumpKey(fp, "DOWNLOAD file: %s\n", hs->DownloadCKey.CKey, CASC_CKEY_SIZE);
DumpKey(fp, "INSTALL file: %s\n", hs->InstallCKey.CKey, CASC_CKEY_SIZE);
fprintf(fp, "\n");
// Dump the complete ENCODING manifest
// DumpEncodingManifest(hs, fp);
// Dump the complete ENCODING manifest
// DumpDownloadManifest(hs, fp);
// Close the dump file
CloseDumpFile(szDumpFile, fp);
}
}
#endif // _DEBUG
File diff suppressed because it is too large Load Diff
+255
View File
@@ -0,0 +1,255 @@
/*****************************************************************************/
/* CascFindFile.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* System-dependent directory functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 10.05.14 1.00 Lad The first version of CascFindFile.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
// Reset the search structure. Called before each search
static void ResetFindData(PCASC_FIND_DATA pFindData)
{
// Reset the variables
ZeroMemory16(pFindData->CKey);
ZeroMemory16(pFindData->EKey);
pFindData->szFileName[0] = 0;
pFindData->szPlainName = pFindData->szFileName;
pFindData->TagBitMask = 0;
pFindData->dwFileDataId = CASC_INVALID_ID;
pFindData->dwFileSize = CASC_INVALID_SIZE;
pFindData->dwLocaleFlags = CASC_INVALID_ID;
pFindData->dwContentFlags = CASC_INVALID_ID;
pFindData->NameType = CascNameFull;
pFindData->bFileAvailable = false;
pFindData->bCanOpenByName = false;
pFindData->bCanOpenByDataId = false;
pFindData->bCanOpenByCKey = false;
pFindData->bCanOpenByEKey = false;
}
static void SupplyFakeFileName(PCASC_FIND_DATA pFindData)
{
// If the file can be open by file data id, create fake file name
if(pFindData->bCanOpenByDataId)
{
CascStrPrintf(pFindData->szFileName, _countof(pFindData->szFileName), "FILE%08X.dat", pFindData->dwFileDataId);
pFindData->NameType = CascNameDataId;
return;
}
// If the file can be open by CKey, convert the CKey to file name
if(pFindData->bCanOpenByCKey)
{
StringFromBinary(pFindData->CKey, MD5_HASH_SIZE, pFindData->szFileName);
pFindData->NameType = CascNameCKey;
return;
}
// CKey should be always present
StringFromBinary(pFindData->EKey, MD5_HASH_SIZE, pFindData->szFileName);
pFindData->NameType = CascNameEKey;
assert(pFindData->bCanOpenByEKey != false);
}
static bool CopyCKeyEntryToFindData(PCASC_FIND_DATA pFindData, PCASC_CKEY_ENTRY pCKeyEntry)
{
// Supply both keys
CopyMemory16(pFindData->CKey, pCKeyEntry->CKey);
CopyMemory16(pFindData->EKey, pCKeyEntry->EKey);
pFindData->bCanOpenByCKey = (pCKeyEntry->Flags & CASC_CE_HAS_CKEY) ? true : false;
pFindData->bCanOpenByEKey = (pCKeyEntry->Flags & CASC_CE_HAS_EKEY) ? true : false;
// Supply the tag mask
pFindData->TagBitMask = pCKeyEntry->TagBitMask;
// Supply the plain name. Only do that if the found name is not a CKey/EKey
if(pFindData->szFileName[0] != 0)
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
// If we retrieved the file size directly from the root provider, use it
// Otherwise, supply EncodedSize or ContentSize, whichever is available (but ContentSize > EncodedSize)
if(pFindData->dwFileSize == CASC_INVALID_SIZE)
pFindData->dwFileSize = pCKeyEntry->ContentSize;
// Set flag indicating that the file is locally available
pFindData->bFileAvailable = (pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL);
// Supply a fake file name, if there is none supplied by the root handler
if(pFindData->szFileName[0] == 0)
SupplyFakeFileName(pFindData);
return true;
}
// Perform searching using root-specific provider.
// The provider may need the listfile
static bool DoStorageSearch_RootFile(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_CKEY_ENTRY pCKeyEntry;
TCascStorage * hs = pSearch->hs;
// Reset the search structure
ResetFindData(pFindData);
// Enumerate over all files
for(;;)
{
// Attempt to find (the next) file from the root handler
pCKeyEntry = hs->pRootHandler->Search(pSearch, pFindData);
if(pCKeyEntry == NULL)
return false;
// The entry is expected to be referenced by the root directory
assert(pCKeyEntry->RefCount != 0);
// Copy the CKey entry to the find data and return it
return CopyCKeyEntryToFindData(pFindData, pCKeyEntry);
}
}
static bool DoStorageSearch_CKey(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_CKEY_ENTRY pCKeyEntry;
TCascStorage * hs = pSearch->hs;
size_t nTotalItems = hs->CKeyArray.ItemCount();
// Reset the find data structure
ResetFindData(pFindData);
// Check for CKeys that haven't been found yet
while(pSearch->nFileIndex < nTotalItems)
{
// Locate the n-th CKey entry. If this entry is not referenced by the root handler, we include it in the search result
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(pSearch->nFileIndex++);
if((pCKeyEntry->Flags & CASC_CE_FOLDER_ENTRY) == 0 && pCKeyEntry->RefCount == 0)
{
return CopyCKeyEntryToFindData(pFindData, pCKeyEntry);
}
}
// Nameless search ended
return false;
}
static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
// State 0: No search done yet
if(pSearch->nSearchState == 0)
{
// Does the search specify listfile?
if(pSearch->szListFile != NULL)
pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile);
// Move the search phase to the listfile searching
pSearch->nSearchState = 1;
pSearch->nFileIndex = 0;
}
// State 1: Searching the list file
if(pSearch->nSearchState == 1)
{
if(DoStorageSearch_RootFile(pSearch, pFindData))
return true;
// Move to the nameless search state
pSearch->nSearchState = 2;
pSearch->nFileIndex = 0;
}
// State 2: Searching the remaining entries by CKey
if(pSearch->nSearchState == 2 && (pSearch->szMask == NULL || !strcmp(pSearch->szMask, "*")))
{
if(DoStorageSearch_CKey(pSearch, pFindData))
return true;
// Move to the final search state
pSearch->nSearchState++;
pSearch->nFileIndex = 0;
}
return false;
}
//-----------------------------------------------------------------------------
// Public functions
HANDLE WINAPI CascFindFirstFile(
HANDLE hStorage,
LPCSTR szMask,
PCASC_FIND_DATA pFindData,
LPCTSTR szListFile)
{
TCascStorage * hs;
TCascSearch * pSearch = NULL;
int nError = ERROR_SUCCESS;
// Check parameters
if((hs = TCascStorage::IsValid(hStorage)) == NULL)
nError = ERROR_INVALID_HANDLE;
if(szMask == NULL || pFindData == NULL)
nError = ERROR_INVALID_PARAMETER;
// Init the search structure and search handle
if(nError == ERROR_SUCCESS)
{
// Allocate the search handle
pSearch = new TCascSearch(hs, szListFile, szMask);
if(pSearch == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
// Perform search
if(nError == ERROR_SUCCESS)
{
if(!DoStorageSearch(pSearch, pFindData))
nError = ERROR_NO_MORE_FILES;
}
if(nError != ERROR_SUCCESS)
{
delete pSearch;
pSearch = (TCascSearch *)INVALID_HANDLE_VALUE;
}
return (HANDLE)pSearch;
}
bool WINAPI CascFindNextFile(
HANDLE hFind,
PCASC_FIND_DATA pFindData)
{
TCascSearch * pSearch;
pSearch = TCascSearch::IsValid(hFind);
if(pSearch == NULL || pFindData == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Perform search
return DoStorageSearch(pSearch, pFindData);
}
bool WINAPI CascFindClose(HANDLE hFind)
{
TCascSearch * pSearch;
pSearch = TCascSearch::IsValid(hFind);
if(pSearch == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
delete pSearch;
return true;
}
+774
View File
@@ -0,0 +1,774 @@
/*****************************************************************************/
/* CascIndexFiles.cpp Copyright (c) Ladislav Zezula 2019 */
/*---------------------------------------------------------------------------*/
/* Index file support */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 23.05.19 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local variables
static const TCHAR * szAllowedHexChars = _T("0123456789aAbBcCdDeEfF");
static const TCHAR * szIndexFormat_V1 = _T("data.i%x%x");
static const TCHAR * szIndexFormat_V2 = _T("%02x%08x.idx");
// Limit for "orphaned" items - those that are in index files, but are not in ENCODING manifest
#define CASC_MAX_ORPHANED_ITEMS 0x100
//-----------------------------------------------------------------------------
// Local functions
// "data.iXY"
static bool IsIndexFileName_V1(const TCHAR * szFileName)
{
// Check if the name looks like a valid index file
return (_tcslen(szFileName) == 8 &&
_tcsnicmp(szFileName, _T("data.i"), 6) == 0 &&
_tcsspn(szFileName + 6, szAllowedHexChars) == 2);
}
static bool IsIndexFileName_V2(const TCHAR * szFileName)
{
// Check if the name looks like a valid index file
return (_tcslen(szFileName) == 14 &&
_tcsspn(szFileName, _T("0123456789aAbBcCdDeEfF")) == 0x0A &&
_tcsicmp(szFileName + 0x0A, _T(".idx")) == 0);
}
static bool IndexDirectory_OnFileFound(
const TCHAR * szFileName,
PDWORD IndexArray,
PDWORD OldIndexArray,
void * pvContext)
{
TCascStorage * hs = (TCascStorage *)pvContext;
DWORD IndexValue = 0;
DWORD IndexVersion = 0;
// Auto-detect the format of the index file name
if(hs->szIndexFormat == NULL)
{
if(IsIndexFileName_V2(szFileName))
hs->szIndexFormat = szIndexFormat_V2;
else if(IsIndexFileName_V1(szFileName))
hs->szIndexFormat = szIndexFormat_V1;
else
return false;
}
if(hs->szIndexFormat == szIndexFormat_V2)
{
// Check the index file name format
if(!IsIndexFileName_V2(szFileName))
return false;
// Get the main index from the first two digits
if(ConvertStringToInt32(szFileName, 2, &IndexValue) != ERROR_SUCCESS)
return false;
if(ConvertStringToInt32(szFileName + 2, 8, &IndexVersion) != ERROR_SUCCESS)
return false;
}
else if(hs->szIndexFormat == szIndexFormat_V1)
{
// Check the index file name format
if(!IsIndexFileName_V1(szFileName))
return false;
// Get the main index from the first two digits
if(ConvertDigitToInt32(szFileName + 6, &IndexValue) != ERROR_SUCCESS)
return false;
if(ConvertDigitToInt32(szFileName + 7, &IndexVersion) != ERROR_SUCCESS)
return false;
}
else
{
// Should never happen
assert(false);
return false;
}
// The index value must not be greater than 0x0F
if(IndexValue >= CASC_INDEX_COUNT)
return false;
// If the new subindex is greater than the previous one,
// use this one instead
if(IndexVersion > IndexArray[IndexValue])
{
OldIndexArray[IndexValue] = IndexArray[IndexValue];
IndexArray[IndexValue] = IndexVersion;
}
else if(IndexVersion > OldIndexArray[IndexValue])
{
OldIndexArray[IndexValue] = IndexVersion;
}
// Note: WoW6 only keeps last two index files
// Any additional index files are deleted at this point
return true;
}
static TCHAR * CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion)
{
TCHAR szPlainName[0x40];
// Sanity checks
assert(hs->szIndexFormat != NULL);
assert(hs->szIndexPath != NULL);
assert(IndexValue <= 0x0F);
// Create the full path
CascStrPrintf(szPlainName, _countof(szPlainName), hs->szIndexFormat, IndexValue, IndexVersion);
return CombinePath(hs->szIndexPath, szPlainName);
}
// Verifies a guarded block - data availability and checksum match
static LPBYTE CaptureGuardedBlock1(LPBYTE pbFileData, LPBYTE pbFileEnd)
{
PFILE_INDEX_GUARDED_BLOCK pBlock = (PFILE_INDEX_GUARDED_BLOCK)pbFileData;
// Check the guarded block
if((pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK)) >= pbFileEnd)
return NULL;
if((pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK) + pBlock->BlockSize) > pbFileEnd)
return NULL;
// Verify the hash
if(hashlittle(pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK), pBlock->BlockSize, 0) != pBlock->BlockHash)
return NULL;
// Give the output
return (LPBYTE)(pBlock + 1);
}
// Second method of checking a guarded block; hash is calculated entry-by-entry.
// Unfortunately, due to implementation in hashlittle2(), we cannot calculate the hash of a continuous block
static LPBYTE CaptureGuardedBlock2(LPBYTE pbFileData, LPBYTE pbFileEnd, size_t EntryLength, PDWORD PtrBlockSize = NULL)
{
PFILE_INDEX_GUARDED_BLOCK pBlock = (PFILE_INDEX_GUARDED_BLOCK)pbFileData;
LPBYTE pbEntryPtr;
size_t EntryCount;
unsigned int HashHigh = 0;
unsigned int HashLow = 0;
// Check the guarded block. There must be enough bytes to contain FILE_INDEX_GUARDED_BLOCK
// and also the block length must not be NULL
if ((pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK)) >= pbFileEnd)
return NULL;
if (pBlock->BlockSize == 0 || (pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK) + pBlock->BlockSize) > pbFileEnd)
return NULL;
// Compute the hash entry-by-entry
pbEntryPtr = pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK);
EntryCount = pBlock->BlockSize / EntryLength;
for (size_t i = 0; i < EntryCount; i++)
{
hashlittle2(pbEntryPtr, EntryLength, &HashHigh, &HashLow);
pbEntryPtr += EntryLength;
}
// Verify hash
if (HashHigh != pBlock->BlockHash)
return NULL;
// Give the output
if (PtrBlockSize != NULL)
PtrBlockSize[0] = pBlock->BlockSize;
return (LPBYTE)(pBlock + 1);
}
// Third method of checking a guarded block; There is 32-bit hash, followed by EKey entry
// The hash covers the EKey entry plus one byte
static LPBYTE CaptureGuardedBlock3(LPBYTE pbFileData, LPBYTE pbFileEnd, size_t EntryLength)
{
PDWORD PtrEntryHash = (PDWORD)pbFileData;
DWORD EntryHash;
// Check the guarded block. There must be enough bytes to contain single entry and the hash
// Also, the hash must be present
if ((pbFileData + sizeof(DWORD) + EntryLength) >= pbFileEnd)
return NULL;
if (PtrEntryHash[0] == 0)
return NULL;
EntryHash = hashlittle(pbFileData + sizeof(DWORD), EntryLength+1, 0) | 0x80000000;
if(EntryHash != PtrEntryHash[0])
return NULL;
// Give the output
return (LPBYTE)(PtrEntryHash + 1);
}
static bool CaptureIndexEntry(CASC_INDEX_HEADER & InHeader, PCASC_CKEY_ENTRY pCKeyEntry, LPBYTE pbEKeyEntry)
{
// Zero both CKey and EKey
ZeroMemory16(pCKeyEntry->CKey);
ZeroMemory16(pCKeyEntry->EKey);
// Copy the EKey. We assume 9 bytes
pCKeyEntry->EKey[0x00] = pbEKeyEntry[0];
pCKeyEntry->EKey[0x01] = pbEKeyEntry[1];
pCKeyEntry->EKey[0x02] = pbEKeyEntry[2];
pCKeyEntry->EKey[0x03] = pbEKeyEntry[3];
pCKeyEntry->EKey[0x04] = pbEKeyEntry[4];
pCKeyEntry->EKey[0x05] = pbEKeyEntry[5];
pCKeyEntry->EKey[0x06] = pbEKeyEntry[6];
pCKeyEntry->EKey[0x07] = pbEKeyEntry[7];
pCKeyEntry->EKey[0x08] = pbEKeyEntry[8];
pCKeyEntry->EKey[0x09] = pbEKeyEntry[9];
pbEKeyEntry += InHeader.EKeyLength;
// Copy the storage offset
pCKeyEntry->StorageOffset = ConvertBytesToInteger_5(pbEKeyEntry);
pbEKeyEntry += InHeader.StorageOffsetLength;
// Clear the tag bit mask
pCKeyEntry->TagBitMask = 0;
// Copy the encoded length
pCKeyEntry->EncodedSize = ConvertBytesToInteger_4_LE(pbEKeyEntry);
pCKeyEntry->ContentSize = CASC_INVALID_SIZE;
pCKeyEntry->RefCount = 0;
pCKeyEntry->Priority = 0;
pCKeyEntry->Flags = CASC_CE_FILE_IS_LOCAL | CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL;
// We ignore items that have EncodedSize of 0x1E
return (pCKeyEntry->EncodedSize > FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature));
}
static void CheckForEncodingManifestCKey(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry)
{
// If the encoding file was not found yet
if(hs->EncodingCKey.StorageOffset == CASC_INVALID_OFFS64)
{
if(!memcmp(pCKeyEntry->EKey, hs->EncodingCKey.EKey, hs->EKeyLength))
{
hs->EncodingCKey.StorageOffset = pCKeyEntry->StorageOffset;
hs->EncodingCKey.EncodedSize = pCKeyEntry->EncodedSize;
hs->EncodingCKey.Flags |= CASC_CE_FILE_IS_LOCAL;
}
}
}
static int LoadIndexItems(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbEKeyEntry, LPBYTE pbEKeyEnd)
{
size_t EntryLength = InHeader.EntryLength;
while((pbEKeyEntry + EntryLength) <= pbEKeyEnd)
{
CASC_CKEY_ENTRY CKeyEntry;
// Capture the index entry and verify it.
if(CaptureIndexEntry(InHeader, &CKeyEntry, pbEKeyEntry))
{
// Insert new entry to the array of CKey entries
if(hs->IndexArray.Insert(&CKeyEntry, 1) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Verify whether the key is not a CKEy entry for ENCODING file
CheckForEncodingManifestCKey(hs, &CKeyEntry);
}
pbEKeyEntry += EntryLength;
}
return ERROR_SUCCESS;
}
static int CaptureIndexHeader_V1(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex)
{
PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData;
LPBYTE pbKeyEntries;
LPBYTE pbFileEnd = pbFileData + cbFileData;
size_t cbKeyEntries;
DWORD HeaderHash;
// Check the available size. Note that the index file can be just a header.
if((pbFileData + sizeof(FILE_INDEX_HEADER_V1)) > pbFileEnd)
return ERROR_BAD_FORMAT;
if(pIndexHeader->IndexVersion != 0x05 || pIndexHeader->BucketIndex != (BYTE)BucketIndex || pIndexHeader->field_8 == 0)
return ERROR_BAD_FORMAT;
if(pIndexHeader->EncodedSizeLength != 0x04 || pIndexHeader->StorageOffsetLength != 0x05 || pIndexHeader->EKeyLength != 0x09)
return ERROR_NOT_SUPPORTED;
// Verify the header hash
HeaderHash = pIndexHeader->HeaderHash;
pIndexHeader->HeaderHash = 0;
if(hashlittle(pbFileData, sizeof(FILE_INDEX_HEADER_V1), 0) != HeaderHash)
return ERROR_BAD_FORMAT;
// Return the header hash back
pIndexHeader->HeaderHash = HeaderHash;
// Copy the fields
InHeader.IndexVersion = pIndexHeader->IndexVersion;
InHeader.BucketIndex = pIndexHeader->BucketIndex;
InHeader.StorageOffsetLength = pIndexHeader->StorageOffsetLength;
InHeader.EncodedSizeLength = pIndexHeader->EncodedSizeLength;
InHeader.EKeyLength = pIndexHeader->EKeyLength;
InHeader.FileOffsetBits = pIndexHeader->FileOffsetBits;
InHeader.Alignment = 0;
InHeader.SegmentSize = pIndexHeader->SegmentSize;
// Determine the size of the header
InHeader.HeaderLength = sizeof(FILE_INDEX_HEADER_V1);
InHeader.HeaderPadding = 0;
InHeader.EntryLength = pIndexHeader->EKeyLength + pIndexHeader->StorageOffsetLength + pIndexHeader->EncodedSizeLength;
InHeader.EKeyCount = pIndexHeader->EKeyCount1 + pIndexHeader->EKeyCount2;
// Verify the entries hash - 1st block
pbKeyEntries = pbFileData + InHeader.HeaderLength;
cbKeyEntries = pIndexHeader->EKeyCount1 * InHeader.EntryLength;
if((pbKeyEntries + cbKeyEntries) > pbFileEnd)
return ERROR_FILE_CORRUPT;
if(hashlittle(pbKeyEntries, cbKeyEntries, 0) != pIndexHeader->KeysHash1)
return ERROR_FILE_CORRUPT;
// Verify the entries hash - 2nd block
pbKeyEntries = pbKeyEntries + cbKeyEntries;
cbKeyEntries = pIndexHeader->EKeyCount2 * InHeader.EntryLength;
if((pbKeyEntries + cbKeyEntries) > pbFileEnd)
return ERROR_FILE_CORRUPT;
if(hashlittle(pbKeyEntries, cbKeyEntries, 0) != pIndexHeader->KeysHash2)
return ERROR_FILE_CORRUPT;
return ERROR_SUCCESS;
}
static int CaptureIndexHeader_V2(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex)
{
PFILE_INDEX_HEADER_V2 pIndexHeader;
LPBYTE pbFileEnd = pbFileData + cbFileData;
// Check for guarded block
if((pbFileData = CaptureGuardedBlock1(pbFileData, pbFileEnd)) == NULL)
return ERROR_FILE_CORRUPT;
pIndexHeader = (PFILE_INDEX_HEADER_V2)pbFileData;
// Verify the content of the index header
if(pIndexHeader->IndexVersion != 0x07 || pIndexHeader->BucketIndex != (BYTE)BucketIndex || pIndexHeader->ExtraBytes != 0x00)
return ERROR_BAD_FORMAT;
if(pIndexHeader->EncodedSizeLength != 0x04 || pIndexHeader->StorageOffsetLength != 0x05 || pIndexHeader->EKeyLength != 0x09)
return ERROR_BAD_FORMAT;
// Capture the values from the index header
InHeader.IndexVersion = pIndexHeader->IndexVersion;
InHeader.BucketIndex = pIndexHeader->BucketIndex;
InHeader.StorageOffsetLength = pIndexHeader->StorageOffsetLength;
InHeader.EncodedSizeLength = pIndexHeader->EncodedSizeLength;
InHeader.EKeyLength = pIndexHeader->EKeyLength;
InHeader.FileOffsetBits = pIndexHeader->FileOffsetBits;
InHeader.Alignment = 0;
InHeader.SegmentSize = pIndexHeader->SegmentSize;
// Supply the lengths
InHeader.HeaderLength = sizeof(FILE_INDEX_GUARDED_BLOCK) + sizeof(FILE_INDEX_HEADER_V2);
InHeader.HeaderPadding = 8;
InHeader.EntryLength = pIndexHeader->EKeyLength + pIndexHeader->StorageOffsetLength + pIndexHeader->EncodedSizeLength;
InHeader.EKeyCount = 0;
return ERROR_SUCCESS;
}
static int LoadIndexFile_V1(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData)
{
LPBYTE pbEKeyEntries = pbFileData + InHeader.HeaderLength + InHeader.HeaderPadding;
// Remember the values from the index header
hs->FileOffsetBits = InHeader.FileOffsetBits;
hs->EKeyLength = InHeader.EKeyLength;
// Load the entries from a continuous array
return LoadIndexItems(hs, InHeader, pbEKeyEntries, pbFileData + cbFileData);
}
static int LoadIndexFile_V2(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData)
{
LPBYTE pbEKeyEntry;
LPBYTE pbFileEnd = pbFileData + cbFileData;
LPBYTE pbFilePtr = pbFileData + InHeader.HeaderLength + InHeader.HeaderPadding;
size_t EKeyEntriesLength;
DWORD BlockSize = 0;
int nError = ERROR_NOT_SUPPORTED;
// Remember the values from the index header
hs->FileOffsetBits = InHeader.FileOffsetBits;
hs->EKeyLength = InHeader.EKeyLength;
// Get the pointer to the first block of EKey entries
if((pbEKeyEntry = CaptureGuardedBlock2(pbFilePtr, pbFileEnd, InHeader.EntryLength, &BlockSize)) != NULL)
{
// Supply the number of EKey entries
InHeader.HeaderPadding += sizeof(FILE_INDEX_GUARDED_BLOCK);
// Load the continuous array of EKeys
return LoadIndexItems(hs, InHeader, pbEKeyEntry, pbEKeyEntry + BlockSize);
}
// Get the pointer to the second block of EKey entries.
// They are alway at the position aligned to 4096
EKeyEntriesLength = pbFileEnd - pbFilePtr;
if(EKeyEntriesLength >= 0x7800)
{
LPBYTE pbStartPage = pbFileData + 0x1000;
LPBYTE pbEndPage = pbStartPage + FILE_INDEX_PAGE_SIZE;
size_t AlignedLength = ALIGN_TO_SIZE(InHeader.EntryLength, 4);
// Parse the chunks with the EKey entries
while(pbStartPage < pbFileEnd)
{
pbEKeyEntry = pbStartPage;
while(pbEKeyEntry < pbEndPage)
{
CASC_CKEY_ENTRY CKeyEntry;
// Check the EKey entry protected by 32-bit hash
if((pbEKeyEntry = CaptureGuardedBlock3(pbEKeyEntry, pbEndPage, InHeader.EntryLength)) == NULL)
break;
// CASC\\0001: Encoding
//BREAK_ON_XKEY3(pbEKeyEntry, 0xbc, 0xe8, 0x23);
// Capture the index entry and verify it.
if(CaptureIndexEntry(InHeader, &CKeyEntry, pbEKeyEntry))
{
// Insert the EKey entry to the array
if(hs->IndexArray.Insert(&CKeyEntry, 1) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Check whether the CKey entry is an encoding entry
CheckForEncodingManifestCKey(hs, &CKeyEntry);
}
// Move to the next entry
pbEKeyEntry += AlignedLength;
}
// Move to the next chunk
pbStartPage += FILE_INDEX_PAGE_SIZE;
}
nError = ERROR_SUCCESS;
}
return nError;
}
static int LoadIndexFile(TCascStorage * hs, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex)
{
CASC_INDEX_HEADER InHeader;
// Check for CASC version 2
if(CaptureIndexHeader_V2(InHeader, pbFileData, cbFileData, BucketIndex) == ERROR_SUCCESS)
return LoadIndexFile_V2(hs, InHeader, pbFileData, cbFileData);
// Check for CASC index version 1
if(CaptureIndexHeader_V1(InHeader, pbFileData, cbFileData, BucketIndex) == ERROR_SUCCESS)
return LoadIndexFile_V1(hs, InHeader, pbFileData, cbFileData);
// Should never happen
assert(false);
return ERROR_BAD_FORMAT;
}
static int LoadIndexFile(TCascStorage * hs, const TCHAR * szFileName, DWORD BucketIndex)
{
LPBYTE pbFileData;
DWORD cbFileData;
int nError = ERROR_SUCCESS;
// WoW6 actually reads THE ENTIRE file to memory. Verified on Mac build (x64).
pbFileData = LoadFileToMemory(szFileName, &cbFileData);
if(pbFileData && cbFileData)
{
// Parse and load the index file
nError = LoadIndexFile(hs, pbFileData, cbFileData, BucketIndex);
CASC_FREE(pbFileData);
}
else
{
nError = GetLastError();
}
return nError;
}
static int LoadLocalIndexFiles(TCascStorage * hs)
{
TCHAR * szFileName;
DWORD OldIndexArray[CASC_INDEX_COUNT];
DWORD IndexArray[CASC_INDEX_COUNT];
int nError;
// Scan all index files and load contained EKEY entries
memset(OldIndexArray, 0, sizeof(OldIndexArray));
memset(IndexArray, 0, sizeof(IndexArray));
nError = ScanIndexDirectory(hs->szIndexPath, IndexDirectory_OnFileFound, IndexArray, OldIndexArray, hs);
if(nError == ERROR_SUCCESS)
{
// Initialize the array of index files
if((nError = hs->IndexArray.Create(sizeof(CASC_CKEY_ENTRY), 0x200000)) == ERROR_SUCCESS)
{
// Load each index file
for(DWORD i = 0; i < CASC_INDEX_COUNT; i++)
{
// Create the name of the index file
if((szFileName = CreateIndexFileName(hs, i, IndexArray[i])) != NULL)
{
// Inform the user about what we are doing
if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading index files", NULL, i, CASC_INDEX_COUNT))
{
nError = ERROR_CANCELLED;
break;
}
// Load the index file
if((nError = LoadIndexFile(hs, szFileName, i)) != ERROR_SUCCESS)
break;
CASC_FREE(szFileName);
}
}
// Remember the number of files that are present locally
hs->LocalFiles = hs->IndexArray.ItemCount();
}
}
return nError;
}
//-----------------------------------------------------------------------------
// Online index files
static int CaptureArcIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, DWORD cbIndexFile)
{
FILE_INDEX_FOOTER<0x08> * pFooter08;
BYTE checksum_data[0x40] = { 0 };
BYTE md5_hash[MD5_HASH_SIZE];
DWORD checksum_data_length;
// Clear the entire structure
memset(&InFooter, 0, sizeof(CASC_ARCINDEX_FOOTER));
// Check the variant for checksum == 0x08
pFooter08 = (FILE_INDEX_FOOTER<0x08> *)(pbIndexFile + cbIndexFile - sizeof(FILE_INDEX_FOOTER<0x08>));
if (pFooter08->Version == 1 && pFooter08->Reserved[0] == 0 && pFooter08->Reserved[1] == 0 && pFooter08->FooterHashBytes == 8)
{
// Copy the entire structure
memcpy(InFooter.TocHash, pFooter08->TocHash, MD5_HASH_SIZE);
memcpy(InFooter.FooterHash, pFooter08->FooterHash, pFooter08->FooterHashBytes);
InFooter.Version = pFooter08->Version;
InFooter.OffsetBytes = pFooter08->OffsetBytes;
InFooter.SizeBytes = pFooter08->SizeBytes;
InFooter.EKeyBytes = pFooter08->EKeySizeBytes;
InFooter.FooterHashBytes = pFooter08->FooterHashBytes;
InFooter.PageLength = pFooter08->PageSizeKB << 10;
InFooter.ItemLength = pFooter08->EKeySizeBytes + pFooter08->OffsetBytes + pFooter08->SizeBytes;
InFooter.FooterLength = sizeof(FILE_INDEX_FOOTER<0x08>);
InFooter.ElementCount = ConvertBytesToInteger_4_LE(pFooter08->ElementCount);
// Verify the hash. FooterHash needs to be cleared in order to calculate footer hash properly
checksum_data_length = FIELD_OFFSET(FILE_INDEX_FOOTER<0x08>, FooterHash) - FIELD_OFFSET(FILE_INDEX_FOOTER<0x08>, Version);
memcpy(checksum_data, &pFooter08->Version, checksum_data_length);
CascCalculateDataBlockHash(checksum_data, sizeof(FILE_INDEX_FOOTER<0x08>) - MD5_HASH_SIZE, md5_hash);
if(!memcmp(md5_hash, InFooter.FooterHash, InFooter.FooterHashBytes))
return ERROR_SUCCESS;
}
assert(false);
return ERROR_BAD_FORMAT;
}
static int CaptureArcIndexEntry(CASC_ARCINDEX_FOOTER & InFooter, CASC_ARCINDEX_ENTRY & InEntry, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd)
{
// If there enough bytes for one entry/
if ((pbIndexPage + InFooter.ItemLength) > pbIndexPageEnd)
return ERROR_BAD_FORMAT;
// Copy the item
memcpy(InEntry.EKey, pbIndexPage, InFooter.EKeyBytes);
pbIndexPage += InFooter.EKeyBytes;
// Copy the archive offset
InEntry.EncodedSize = ConvertBytesToInteger_X(pbIndexPage, InFooter.OffsetBytes);
pbIndexPage += InFooter.OffsetBytes;
// Copy thefile encoded size
InEntry.ArchiveOffset = ConvertBytesToInteger_X(pbIndexPage, InFooter.SizeBytes);
if (InEntry.ArchiveOffset >= 0x10000000)
return ERROR_BAD_FORMAT;
// Is there a valid hash?
return CascIsValidMD5(InEntry.EKey) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
}
static int VerifyIndexSize(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, size_t cbIndexFile, LPBYTE * PtrIndexEnd)
{
size_t nPageCount;
// Set the new length (without the footer)
cbIndexFile = cbIndexFile - InFooter.FooterLength;
nPageCount = cbIndexFile / (InFooter.PageLength + MD5_HASH_SIZE);
// There must be equal or more pages
if(((InFooter.PageLength + MD5_HASH_SIZE) * nPageCount) > cbIndexFile)
return ERROR_BAD_FORMAT;
// Return the end-of-index
nPageCount = cbIndexFile / (InFooter.PageLength + MD5_HASH_SIZE);
PtrIndexEnd[0] = pbIndexFile + (nPageCount * InFooter.PageLength);
return ERROR_SUCCESS;
}
static int LoadArchiveIndexPage(TCascStorage * hs, CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexHash, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd)
{
CASC_ARCINDEX_ENTRY InEntry;
int nError;
while (pbIndexPage < pbIndexPageEnd)
{
// Capture the index entry
nError = CaptureArcIndexEntry(InFooter, InEntry, pbIndexPage, pbIndexPageEnd);
if (nError != ERROR_SUCCESS)
break;
// Insert the index entry to the array
memcpy(InEntry.IndexHash, pbIndexHash, MD5_HASH_SIZE);
if (hs->ArcIndexArray.Insert(&InEntry, 1) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Move to the next entry
pbIndexPage += InFooter.ItemLength;
}
return ERROR_SUCCESS;
}
static int LoadArchiveIndexFile(TCascStorage * hs, LPBYTE pbIndexHash, LPBYTE pbIndexFile, DWORD cbIndexFile)
{
CASC_ARCINDEX_FOOTER InFooter;
LPBYTE pbIndexEnd = NULL;
int nError;
// Validate and capture the footer
nError = CaptureArcIndexFooter(InFooter, pbIndexFile, cbIndexFile);
if (nError != ERROR_SUCCESS)
return nError;
// Verify the size of the index file
nError = VerifyIndexSize(InFooter, pbIndexFile, cbIndexFile, &pbIndexEnd);
if (nError != ERROR_SUCCESS)
return nError;
// Parse all pages
while (pbIndexFile < pbIndexEnd)
{
// Load the entire page
nError = LoadArchiveIndexPage(hs, InFooter, pbIndexHash, pbIndexFile, pbIndexFile + InFooter.PageLength);
if (nError != ERROR_SUCCESS)
break;
// Move to the next page
pbIndexFile += InFooter.PageLength;
}
return ERROR_SUCCESS;
}
static int BuildMapOfArcIndices(TCascStorage * hs)
{
PCASC_ARCINDEX_ENTRY pEntry;
size_t nItemCount = hs->ArcIndexArray.ItemCount();
int nError;
// Create the map
nError = hs->ArcIndexMap.Create(nItemCount, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ARCINDEX_ENTRY, EKey));
if (nError != ERROR_SUCCESS)
return nError;
// Insert all items
for(size_t i = 0; i < nItemCount; i++)
{
pEntry = (PCASC_ARCINDEX_ENTRY)hs->ArcIndexArray.ItemAt(i);
if (pEntry != NULL)
{
if (!hs->ArcIndexMap.InsertObject(pEntry, pEntry->EKey))
{
return ERROR_NOT_ENOUGH_MEMORY;
}
}
}
return nError;
}
static int LoadArchiveIndexFiles(TCascStorage * hs)
{
LPBYTE pbFileData;
TCHAR szLocalPath[MAX_PATH];
DWORD cbFileData = 0;
size_t nArchiveCount = (hs->ArchivesKey.cbData / MD5_HASH_SIZE);
int nError = ERROR_SUCCESS;
// Create the array object for the indices
nError = hs->ArcIndexArray.Create(sizeof(CASC_ARCINDEX_ENTRY), 10000);
if (nError != ERROR_SUCCESS)
return nError;
// Load all the indices
for (size_t i = 0; i < nArchiveCount; i++)
{
LPBYTE pbIndexHash = hs->ArchivesKey.pbData + (i * MD5_HASH_SIZE);
// Inform the user about what we are doing
if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Downloading archive indexes", NULL, (DWORD)(i), (DWORD)(nArchiveCount)))
{
nError = ERROR_CANCELLED;
break;
}
// Make sure that we have local copy of the file
nError = DownloadFileFromCDN(hs, _T("data"), pbIndexHash, _T(".index"), szLocalPath, _countof(szLocalPath));
if (nError == ERROR_SUCCESS)
{
// Load the index file to memory
pbFileData = LoadFileToMemory(szLocalPath, &cbFileData);
if (pbFileData && cbFileData)
{
nError = LoadArchiveIndexFile(hs, pbIndexHash, pbFileData, cbFileData);
CASC_FREE(pbFileData);
}
}
// Break if an error
if (nError != ERROR_SUCCESS)
break;
}
// Build map of EKey -> CASC_ARCINDEX_ENTRY
if (nError == ERROR_SUCCESS)
nError = BuildMapOfArcIndices(hs);
return nError;
}
//-----------------------------------------------------------------------------
// Public functions
int LoadIndexFiles(TCascStorage * hs)
{
if (hs->dwFeatures & CASC_FEATURE_ONLINE)
{
return LoadArchiveIndexFiles(hs);
}
else
{
return LoadLocalIndexFiles(hs);
}
}
+30
View File
@@ -0,0 +1,30 @@
;
; Export file for Windows
; Copyright (c) 2015 Ladislav Zezula
; ladik@zezula.net
;
LIBRARY CascLib.dll
EXPORTS
CascOpenStorage
CascOpenOnlineStorage
CascGetStorageInfo
CascAddEncryptionKey
CascCloseStorage
CascOpenFile
CascGetFileInfo
CascGetFileSize
CascSetFilePointer
CascReadFile
CascCloseFile
CascFindFirstFile
CascFindNextFile
CascFindClose
GetLastError=Kernel32.GetLastError
SetLastError=Kernel32.SetLastError
+342
View File
@@ -0,0 +1,342 @@
/*****************************************************************************/
/* CascLib.h Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* CascLib library v 1.00 */
/* */
/* Author : Ladislav Zezula */
/* E-mail : ladik@zezula.net */
/* WWW : http://www.zezula.net */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad Created */
/*****************************************************************************/
#ifndef __CASCLIB_H__
#define __CASCLIB_H__
#ifdef _MSC_VER
#pragma warning(disable:4668) // 'XXX' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
#pragma warning(disable:4820) // 'XXX' : '2' bytes padding added after data member 'XXX::yyy'
#endif
#include "CascPort.h"
#ifdef __cplusplus
extern "C" {
#endif
//-----------------------------------------------------------------------------
// Use the apropriate library
//
// The library type is encoded in the library name as the following
// CascLibXYZ.lib
//
// X - D for Debug version, R for Release version
// Y - A for ANSI version, U for Unicode version
// Z - S for static-linked CRT library, D for multithreaded DLL CRT library
//
#if defined(_MSC_VER) && !defined(__CASCLIB_SELF__) && !defined(CASCLIB_NO_AUTO_LINK_LIBRARY)
#ifdef _DEBUG // DEBUG VERSIONS
#ifndef _UNICODE
#ifdef _DLL
#pragma comment(lib, "CascLibDAD.lib") // Debug Ansi CRT-DLL version
#else
#pragma comment(lib, "CascLibDAS.lib") // Debug Ansi CRT-LIB version
#endif
#else
#ifdef _DLL
#pragma comment(lib, "CascLibDUD.lib") // Debug Unicode CRT-DLL version
#else
#pragma comment(lib, "CascLibDUS.lib") // Debug Unicode CRT-LIB version
#endif
#endif
#else // RELEASE VERSIONS
#ifndef _UNICODE
#ifdef _DLL
#pragma comment(lib, "CascLibRAD.lib") // Release Ansi CRT-DLL version
#else
#pragma comment(lib, "CascLibRAS.lib") // Release Ansi CRT-LIB version
#endif
#else
#ifdef _DLL
#pragma comment(lib, "CascLibRUD.lib") // Release Unicode CRT-DLL version
#else
#pragma comment(lib, "CascLibRUS.lib") // Release Unicode CRT-LIB version
#endif
#endif
#endif
#endif
//-----------------------------------------------------------------------------
// Defines
#define CASCLIB_VERSION 0x0132 // Current version of CascLib (1.50)
#define CASCLIB_VERSION_STRING "1.50" // String version of CascLib version
// Values for CascOpenFile
#define CASC_OPEN_BY_NAME 0x00000000 // Open the file by name. This is the default value
#define CASC_OPEN_BY_CKEY 0x00000001 // The name is just the content key; skip ROOT file processing
#define CASC_OPEN_BY_EKEY 0x00000002 // The name is just the encoded key; skip ROOT file processing
#define CASC_OPEN_BY_FILEID 0x00000003 // The name is CASC_IDTONAME(FileDataId)
#define CASC_OPEN_TYPE_MASK 0x0000000F // The mask which gets open type from the dwFlags
#define CASC_OPEN_FLAGS_MASK 0xFFFFFFF0 // The mask which gets open type from the dwFlags
#define CASC_STRICT_DATA_CHECK 0x00000010 // Verify all data read from a file
#define CASC_OVERCOME_ENCRYPTED 0x00000020 // When CascReadFile encounters a block encrypted with a key that is missing, the block is filled with zeros and returned as success
#define CASC_LOCALE_ALL 0xFFFFFFFF
#define CASC_LOCALE_NONE 0x00000000
#define CASC_LOCALE_UNKNOWN1 0x00000001
#define CASC_LOCALE_ENUS 0x00000002
#define CASC_LOCALE_KOKR 0x00000004
#define CASC_LOCALE_RESERVED 0x00000008
#define CASC_LOCALE_FRFR 0x00000010
#define CASC_LOCALE_DEDE 0x00000020
#define CASC_LOCALE_ZHCN 0x00000040
#define CASC_LOCALE_ESES 0x00000080
#define CASC_LOCALE_ZHTW 0x00000100
#define CASC_LOCALE_ENGB 0x00000200
#define CASC_LOCALE_ENCN 0x00000400
#define CASC_LOCALE_ENTW 0x00000800
#define CASC_LOCALE_ESMX 0x00001000
#define CASC_LOCALE_RURU 0x00002000
#define CASC_LOCALE_PTBR 0x00004000
#define CASC_LOCALE_ITIT 0x00008000
#define CASC_LOCALE_PTPT 0x00010000
// Content flags on WoW
#define CASC_CFLAG_LOAD_ON_WINDOWS 0x08
#define CASC_CFLAG_LOAD_ON_MAC 0x10
#define CASC_CFLAG_LOW_VIOLENCE 0x80
#define CASC_CFLAG_DONT_LOAD 0x100
#define CASC_CFLAG_NO_NAME_HASH 0x10000000
#define CASC_CFLAG_BUNDLE 0x40000000
#define CASC_CFLAG_NO_COMPRESSION 0x80000000
#ifndef MD5_HASH_SIZE
#define MD5_HASH_SIZE 0x10
#define MD5_STRING_SIZE 0x20
#endif
#ifndef SHA1_DIGEST_SIZE
#define SHA1_DIGEST_SIZE 0x14 // 160 bits
#endif
// Return value for CascGetFileSize and CascSetFilePointer
#define CASC_INVALID_INDEX 0xFFFFFFFF
#define CASC_INVALID_SIZE 0xFFFFFFFF
#define CASC_INVALID_POS 0xFFFFFFFF
#define CASC_INVALID_ID 0xFFFFFFFF
#define CASC_INVALID_OFFS64 0xFFFFFFFFFFFFFFFF
// Flags for CASC_STORAGE_FEATURES::dwFeatures
#define CASC_FEATURE_FILE_NAMES 0x00000001 // File names are supported by the storage
#define CASC_FEATURE_ROOT_CKEY 0x00000002 // Present if the storage's ROOT returns CKey
#define CASC_FEATURE_TAGS 0x00000002 // Tags are supported by the storage
#define CASC_FEATURE_FNAME_HASHES 0x00000004 // The storage contains file name hashes on ALL files
#define CASC_FEATURE_FNAME_HASHES_OPTIONAL 0x00000008 // The storage contains file name hashes for SOME files
#define CASC_FEATURE_FILE_DATA_IDS 0x00000010 // The storage indexes files by FileDataId
#define CASC_FEATURE_LOCALE_FLAGS 0x00000020 // Locale flags are supported
#define CASC_FEATURE_CONTENT_FLAGS 0x00000040 // Content flags are supported
#define CASC_FEATURE_ONLINE 0x00000080 // The storage is an online storage
// Macro to convert FileDataId to the argument of CascOpenFile
#define CASC_FILE_DATA_ID(FileDataId) ((LPCSTR)(size_t)FileDataId)
#define CASC_FILE_DATA_ID_FROM_STRING(szFileName) ((DWORD)(size_t)szFileName)
//-----------------------------------------------------------------------------
// Structures
typedef enum _CASC_STORAGE_INFO_CLASS
{
// Returns the number of local files in the storage. Note that files
// can exist under different names, so the total number of files in the archive
// can be higher than the value returned by this info class
CascStorageLocalFileCount,
// Returns the total file count, including the offline files
CascStorageTotalFileCount,
// Returns the CASC_STORAGE_FEATURES structure.
CascStorageFeatures,
CascStorageInstalledLocales,
CascStorageProduct, // Gives CASC_STORAGE_PRODUCT
CascStorageTags, // Gives CASC_STORAGE_TAGS structure
CascStorageInfoClassMax
} CASC_STORAGE_INFO_CLASS, *PCASC_STORAGE_INFO_CLASS;
typedef enum _CASC_FILE_INFO_CLASS
{
CascFileContentKey,
CascFileEncodedKey,
CascFileFullInfo, // Gives CASC_FILE_FULL_INFO structure
CascFileInfoClassMax
} CASC_FILE_INFO_CLASS, *PCASC_FILE_INFO_CLASS;
// See https://wowdev.wiki/TACT#Products
typedef enum _CASC_PRODUCT
{
UnknownProduct,
HeroesOfTheStorm,
Diablo3,
Overwatch,
StarCraft1,
StarCraft2,
WorldOfWarcraft,
WarCraft3,
Destiny2,
CallOfDutyBlackOps4,
Odin,
MaxProductValue
} CASC_PRODUCT, *PCASC_PRODUCT;
// CascLib may provide a fake name, constructed from file data id, CKey or EKey.
// This enum helps to see what name was actually returned
// Note that any of these names can be passed to CascOpenFile with no extra flags
typedef enum _CASC_NAME_TYPE
{
CascNameFull, // Fully qualified file name
CascNameDataId, // Name created from file data id (FILE%08X.dat)
CascNameCKey, // Name created as string representation of CKey
CascNameEKey // Name created as string representation of EKey
} CASC_NAME_TYPE, *PCASC_NAME_TYPE;
// Structure for SFileFindFirstFile and SFileFindNextFile
typedef struct _CASC_FIND_DATA
{
// Full name of the found file. In case when this is CKey/EKey,
// this will be just string representation of the key stored in 'FileKey'
char szFileName[MAX_PATH];
// Content key. This is present if the CASC_FEATURE_ROOT_CKEY is present
BYTE CKey[MD5_HASH_SIZE];
// Encoded key. This is always present.
BYTE EKey[MD5_HASH_SIZE];
// Tag mask. Only valid if the storage supports tags, otherwise 0
ULONGLONG TagBitMask;
// Plain name of the found file. Pointing inside the 'szFileName' array
char * szPlainName;
// File data ID. Only valid if the storage supports file data IDs, otherwise CASC_INVALID_ID
DWORD dwFileDataId;
// Size of the file, as retrieved from CKey entry or EKey entry
DWORD dwFileSize;
// Locale flags. Only valid if the storage supports locale flags, otherwise CASC_INVALID_ID
DWORD dwLocaleFlags;
// Content flags. Only valid if the storage supports content flags, otherwise CASC_INVALID_ID
DWORD dwContentFlags;
// Hints as for which open method is suitable
DWORD bFileAvailable:1; // If true the file is available locally
DWORD bCanOpenByName:1;
DWORD bCanOpenByDataId:1;
DWORD bCanOpenByCKey:1;
DWORD bCanOpenByEKey:1;
CASC_NAME_TYPE NameType;
} CASC_FIND_DATA, *PCASC_FIND_DATA;
typedef struct _CASC_STORAGE_TAG
{
LPCSTR szTagName; // Tag name (zero terminated, ANSI)
DWORD TagNameLength; // Length of the tag name
DWORD TagValue; // Tag value
} CASC_STORAGE_TAG, *PCASC_STORAGE_TAG;
typedef struct _CASC_STORAGE_TAGS
{
size_t TagCount; // Number of items in the Tags array
size_t Reserved; // Reserved for future use
CASC_STORAGE_TAG Tags[1]; // Array of CASC tags
} CASC_STORAGE_TAGS, *PCASC_STORAGE_TAGS;
typedef struct _CASC_STORAGE_PRODUCT
{
LPCSTR szProductName;
DWORD dwBuildNumber;
CASC_PRODUCT Product;
} CASC_STORAGE_PRODUCT, *PCASC_STORAGE_PRODUCT;
typedef struct _CASC_FILE_FULL_INFO
{
BYTE CKey[MD5_HASH_SIZE]; // CKey
BYTE EKey[MD5_HASH_SIZE]; // EKey
char DataFileName[0x10]; // Plain name of the data file where the file is stored
ULONGLONG StorageOffset; // Offset of the file over the entire storage
ULONGLONG SegmentOffset; // Offset of the file in the segment file ("data.###")
ULONGLONG TagBitMask; // Bitmask of tags. Zero if not supported
ULONGLONG FileNameHash; // Hash of the file name. Zero if not supported
DWORD SegmentIndex; // Index of the segment file (aka 0 = "data.000")
DWORD FileDataId; // File data ID. CASC_INVALID_ID if not supported.
DWORD ContentSize; // Content size of the file
DWORD EncodedSize; // Encoded size of the file
DWORD LocaleFlags; // Locale flags. CASC_INVALID_ID if not supported.
DWORD ContentFlags; // Locale flags. CASC_INVALID_ID if not supported
} CASC_FILE_FULL_INFO, *PCASC_FILE_FULL_INFO;
//-----------------------------------------------------------------------------
// Some operations (e.g. opening an online storage) may take long time.
// This callback allows an application to be notified about loading progress.
// This callback only works for a single CascOpen(Online)Storage call.
typedef bool (WINAPI * PFNPROGRESSCALLBACK)( // Return 'true' to cancel the loading process
void * PtrUserParam, // User-specific parameter passed to the callback
LPCSTR szWork, // Text for the current activity (example: "Loading "ENCODING" file")
LPCSTR szObject, // (optional) name of the object tied to the activity (example: index file name)
DWORD nCurrent, // (optional) current object being processed
DWORD nTotal // (optional) If non-zero, this is the total number of objects to process
);
void WINAPI CascSetProgressCallback(
PFNPROGRESSCALLBACK PtrUserCallback, // Pointer to the callback function that will be called during opening the storage
void * PtrUserParam // Arbitrary user parameter that will be passed to the callback
);
//-----------------------------------------------------------------------------
// Functions for storage manipulation
bool WINAPI CascOpenStorage(LPCTSTR szDataPath, DWORD dwLocaleMask, HANDLE * phStorage);
bool WINAPI CascOpenOnlineStorage(LPCTSTR szLocalCache, LPCSTR szCodeName, LPCSTR szRegion, DWORD dwLocaleMask, HANDLE * phStorage);
bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded);
bool WINAPI CascAddEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPBYTE Key);
bool WINAPI CascCloseStorage(HANDLE hStorage);
bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * phFile);
bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded);
DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh);
DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod);
bool WINAPI CascReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, PDWORD pdwRead);
bool WINAPI CascCloseFile(HANDLE hFile);
HANDLE WINAPI CascFindFirstFile(HANDLE hStorage, LPCSTR szMask, PCASC_FIND_DATA pFindData, LPCTSTR szListFile);
bool WINAPI CascFindNextFile(HANDLE hFind, PCASC_FIND_DATA pFindData);
bool WINAPI CascFindClose(HANDLE hFind);
//-----------------------------------------------------------------------------
// GetLastError/SetLastError support for non-Windows platform
#ifndef PLATFORM_WINDOWS
DWORD GetLastError();
void SetLastError(DWORD dwErrCode);
#endif // PLATFORM_WINDOWS
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __CASCLIB_H__
+187
View File
@@ -0,0 +1,187 @@
/*****************************************************************************/
/* CascOpenFile.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* System-dependent directory functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 01.05.14 1.00 Lad The first version of CascOpenFile.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
PCASC_CKEY_ENTRY FindCKeyEntry_CKey(TCascStorage * hs, LPBYTE pbCKey, PDWORD PtrIndex)
{
return (PCASC_CKEY_ENTRY)hs->CKeyMap.FindObject(pbCKey, PtrIndex);
}
PCASC_CKEY_ENTRY FindCKeyEntry_EKey(TCascStorage * hs, LPBYTE pbEKey, PDWORD PtrIndex)
{
return (PCASC_CKEY_ENTRY)hs->EKeyMap.FindObject(pbEKey, PtrIndex);
}
bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD dwOpenFlags, HANDLE * PtrFileHandle)
{
TCascFile * hf = NULL;
int nError = ERROR_FILE_NOT_FOUND;
// If the CKey entry is NULL, we consider the file non-existant
if(pCKeyEntry != NULL)
{
// Create the file handle structure
if((hf = new TCascFile(hs, pCKeyEntry)) != NULL)
{
hf->bVerifyIntegrity = (dwOpenFlags & CASC_STRICT_DATA_CHECK) ? true : false;
hf->bDownloadFileIf = (hs->dwFeatures & CASC_FEATURE_ONLINE) ? true : false;
hf->bOvercomeEncrypted = (dwOpenFlags & CASC_OVERCOME_ENCRYPTED) ? true : false;
nError = ERROR_SUCCESS;
}
else
{
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
// Give the output parameter, no matter what
PtrFileHandle[0] = (HANDLE)hf;
// Handle last error
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return (nError == ERROR_SUCCESS);
}
//-----------------------------------------------------------------------------
// Public functions
bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * PtrFileHandle)
{
PCASC_CKEY_ENTRY pCKeyEntry = NULL;
TCascStorage * hs;
const char * szFileName;
DWORD FileDataId = CASC_INVALID_ID;
BYTE CKeyEKeyBuffer[MD5_HASH_SIZE];
int nError = ERROR_SUCCESS;
// This parameter is not used
CASCLIB_UNUSED(dwLocaleFlags);
// Validate the storage handle
hs = TCascStorage::IsValid(hStorage);
if(hs == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// Validate the other parameters
if(PtrFileHandle == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Retrieve the CKey/EKey from the file name in different modes
switch(dwOpenFlags & CASC_OPEN_TYPE_MASK)
{
case CASC_OPEN_BY_NAME:
// The 'pvFileName' must be zero terminated ANSI file name
szFileName = (const char *)pvFileName;
if(szFileName == NULL || szFileName[0] == 0)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// The first chance: Try to find the file by name (using the root handler)
pCKeyEntry = hs->pRootHandler->GetFile(hs, szFileName);
if(pCKeyEntry != NULL)
break;
// Second chance: If the file name is actually a file data id, we convert it to file data ID
if(IsFileDataIdName(szFileName, FileDataId))
{
pCKeyEntry = hs->pRootHandler->GetFile(hs, FileDataId);
if(pCKeyEntry != NULL)
break;
}
// Third chance: If the file name is a string representation of CKey/EKey, we try to query for CKey
if(IsFileCKeyEKeyName(szFileName, CKeyEKeyBuffer))
{
pCKeyEntry = FindCKeyEntry_CKey(hs, CKeyEKeyBuffer);
if(pCKeyEntry != NULL)
break;
pCKeyEntry = FindCKeyEntry_EKey(hs, CKeyEKeyBuffer);
if(pCKeyEntry != NULL)
break;
}
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
case CASC_OPEN_BY_CKEY:
// The 'pvFileName' must be a pointer to 16-byte CKey or EKey
if(pvFileName == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Search the CKey map in order to find the CKey entry
pCKeyEntry = FindCKeyEntry_CKey(hs, (LPBYTE)pvFileName);
break;
case CASC_OPEN_BY_EKEY:
// The 'pvFileName' must be a pointer to 16-byte CKey or EKey
if(pvFileName == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Search the CKey map in order to find the CKey entry
pCKeyEntry = FindCKeyEntry_EKey(hs, (LPBYTE)pvFileName);
break;
case CASC_OPEN_BY_FILEID:
// Retrieve the file CKey/EKey
pCKeyEntry = hs->pRootHandler->GetFile(hs, CASC_FILE_DATA_ID_FROM_STRING(pvFileName));
break;
default:
// Unknown open mode
nError = ERROR_INVALID_PARAMETER;
break;
}
// Perform the open operation
return OpenFileByCKeyEntry(hs, pCKeyEntry, dwOpenFlags, PtrFileHandle);
}
bool WINAPI CascCloseFile(HANDLE hFile)
{
TCascFile * hf;
hf = TCascFile::IsValid(hFile);
if (hf != NULL)
{
delete hf;
return true;
}
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
File diff suppressed because it is too large Load Diff
+312
View File
@@ -0,0 +1,312 @@
/*****************************************************************************/
/* CascPort.h Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Portability module for the CascLib library. Contains a wrapper symbols */
/* to make the compilation under Linux work */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad Created */
/*****************************************************************************/
#ifndef __CASCPORT_H__
#define __CASCPORT_H__
#ifndef __cplusplus
#define bool char
#define true 1
#define false 0
#endif
//-----------------------------------------------------------------------------
// Defines for Windows
#if !defined(PLATFORM_DEFINED) && (defined(_WIN32) || defined(_WIN64))
// In MSVC 8.0, there are some functions declared as deprecated.
#if _MSC_VER >= 1400
#ifndef _CRT_SECURE_NO_DEPRECATE
#define _CRT_SECURE_NO_DEPRECATE
#endif
#ifndef _CRT_NON_CONFORMING_SWPRINTFS
#define _CRT_NON_CONFORMING_SWPRINTFS
#endif
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
//#pragma warning(disable:4995) // warning C4995: 'sprintf': name was marked as #pragma deprecated
#include <tchar.h>
#include <assert.h>
#include <intrin.h> // Support for intrinsic functions
#include <ctype.h>
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <direct.h>
#include <malloc.h>
#include <windows.h>
#include <wininet.h>
#include <strsafe.h>
#include <sys/types.h>
#define PLATFORM_LITTLE_ENDIAN
#ifdef _WIN64
#define PLATFORM_64BIT
#else
#define PLATFORM_32BIT
#endif
#define PATH_SEP_CHAR '\\'
#define PATH_SEP_STRING "\\"
#pragma intrinsic(memcmp, memcpy)
#define PLATFORM_WINDOWS
#define PLATFORM_DEFINED // The platform is known now
#endif
//-----------------------------------------------------------------------------
// Defines for Mac
#if !defined(PLATFORM_DEFINED) && defined(__APPLE__) // Mac BSD API
// Macintosh
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <cassert>
// Support for PowerPC on Max OS X
#if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1)
#include <stdint.h>
#include <CoreFoundation/CFByteOrder.h>
#endif
#define PKEXPORT
#define __SYS_ZLIB
#define __SYS_BZLIB
#ifndef __BIG_ENDIAN__
#define PLATFORM_LITTLE_ENDIAN
#endif
#define PATH_SEP_CHAR '/'
#define PATH_SEP_STRING "/"
#define PLATFORM_MAC
#define PLATFORM_DEFINED // The platform is known now
#define FIELD_OFFSET(t,f) offsetof(t,f)
#endif
//-----------------------------------------------------------------------------
// Assumption: we are not on Windows nor Macintosh, so this must be linux *grin*
#if !defined(PLATFORM_DEFINED)
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <wchar.h>
#include <assert.h>
#include <errno.h>
#define PATH_SEP_CHAR '/'
#define PATH_SEP_STRING "/"
#define PLATFORM_LITTLE_ENDIAN
#define PLATFORM_LINUX
#define PLATFORM_DEFINED
#define FIELD_OFFSET(t,f) offsetof(t,f)
#endif
//-----------------------------------------------------------------------------
// Definition of Windows-specific types for non-Windows platforms
#ifndef PLATFORM_WINDOWS
#if __LP64__
#define PLATFORM_64BIT
#else
#define PLATFORM_32BIT
#endif
// Typedefs for ANSI C
typedef unsigned char BYTE;
typedef unsigned short USHORT;
typedef int LONG;
typedef unsigned int DWORD;
typedef long long LONGLONG;
typedef unsigned long long ULONGLONG;
typedef unsigned long long *PULONGLONG;
typedef void * HANDLE;
typedef char TCHAR;
typedef unsigned int LCID;
typedef LONG * PLONG;
typedef DWORD * PDWORD;
typedef BYTE * LPBYTE;
typedef char * LPSTR;
typedef const char * LPCSTR;
typedef TCHAR * LPTSTR;
typedef const TCHAR * LPCTSTR;
#ifdef PLATFORM_32BIT
#define _LZMA_UINT32_IS_ULONG
#endif
// Some Windows-specific defines
#ifndef MAX_PATH
#define MAX_PATH 1024
#endif
#define WINAPI
#define FILE_BEGIN SEEK_SET
#define FILE_CURRENT SEEK_CUR
#define FILE_END SEEK_END
#define INVALID_HANDLE_VALUE ((HANDLE)-1)
#define _T(x) x
#define _tcslen strlen
#define _tcscat strcat
#define _tcschr strchr
#define _tcsrchr strrchr
#define _tcsstr strstr
#define _tcsspn strspn
#define _tcsncmp strncmp
#define _tprintf printf
#define _tremove remove
#define _taccess access
#define _access access
#define _stricmp strcasecmp
#define _strnicmp strncasecmp
#define _tcsicmp strcasecmp
#define _tcsnicmp strncasecmp
#endif // !PLATFORM_WINDOWS
// 64-bit calls are supplied by "normal" calls on Mac
#if defined(PLATFORM_MAC)
#define stat64 stat
#define fstat64 fstat
#define lseek64 lseek
#define ftruncate64 ftruncate
#define off64_t off_t
#define O_LARGEFILE 0
#endif
// Platform-specific error codes for UNIX-based platforms
#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
#define ERROR_SUCCESS 0
#define ERROR_FILE_NOT_FOUND ENOENT
#define ERROR_PATH_NOT_FOUND ENOENT
#define ERROR_ACCESS_DENIED EPERM
#define ERROR_INVALID_HANDLE EBADF
#define ERROR_NOT_ENOUGH_MEMORY ENOMEM
#define ERROR_NOT_SUPPORTED ENOTSUP
#define ERROR_INVALID_PARAMETER EINVAL
#define ERROR_DISK_FULL ENOSPC
#define ERROR_ALREADY_EXISTS EEXIST
#define ERROR_INSUFFICIENT_BUFFER ENOBUFS
#define ERROR_BAD_FORMAT 1000 // No such error code under Linux
#define ERROR_NO_MORE_FILES 1001 // No such error code under Linux
#define ERROR_HANDLE_EOF 1002 // No such error code under Linux
#define ERROR_CAN_NOT_COMPLETE 1003 // No such error code under Linux
#define ERROR_FILE_CORRUPT 1004 // No such error code under Linux
#define ERROR_FILE_ENCRYPTED 1005 // Returned by encrypted stream when can't find file key
#endif
#ifndef ERROR_FILE_INCOMPLETE
#define ERROR_FILE_INCOMPLETE 1006 // The required file part is missing
#endif
#ifndef ERROR_FILE_OFFLINE
#define ERROR_FILE_OFFLINE 1007 // The file is not available in the local storage
#endif
#ifndef ERROR_BUFFER_OVERFLOW
#define ERROR_BUFFER_OVERFLOW 1008
#endif
#ifndef ERROR_CANCELLED
#define ERROR_CANCELLED 1009
#endif
#ifndef _countof
#define _countof(x) (sizeof(x) / sizeof(x[0]))
#endif
//-----------------------------------------------------------------------------
// Swapping functions
#ifdef PLATFORM_LITTLE_ENDIAN
#define BSWAP_INT16_UNSIGNED(a) (a)
#define BSWAP_INT16_SIGNED(a) (a)
#define BSWAP_INT32_UNSIGNED(a) (a)
#define BSWAP_INT32_SIGNED(a) (a)
#define BSWAP_INT64_SIGNED(a) (a)
#define BSWAP_INT64_UNSIGNED(a) (a)
#define BSWAP_ARRAY16_UNSIGNED(a,b) {}
#define BSWAP_ARRAY32_UNSIGNED(a,b) {}
#define BSWAP_ARRAY64_UNSIGNED(a,b) {}
#else
#ifdef __cplusplus
extern "C" {
#endif
int16_t SwapInt16(uint16_t);
uint16_t SwapUInt16(uint16_t);
int32_t SwapInt32(uint32_t);
uint32_t SwapUInt32(uint32_t);
int64_t SwapInt64(uint64_t);
uint64_t SwapUInt64(uint64_t);
void ConvertUInt16Buffer(void * ptr, size_t length);
void ConvertUInt32Buffer(void * ptr, size_t length);
void ConvertUInt64Buffer(void * ptr, size_t length);
#ifdef __cplusplus
}
#endif
#define BSWAP_INT16_SIGNED(a) SwapInt16((a))
#define BSWAP_INT16_UNSIGNED(a) SwapUInt16((a))
#define BSWAP_INT32_SIGNED(a) SwapInt32((a))
#define BSWAP_INT32_UNSIGNED(a) SwapUInt32((a))
#define BSWAP_INT64_SIGNED(a) SwapInt64((a))
#define BSWAP_INT64_UNSIGNED(a) SwapUInt64((a))
#define BSWAP_ARRAY16_UNSIGNED(a,b) ConvertUInt16Buffer((a),(b))
#define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b))
#define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b))
#endif
//-----------------------------------------------------------------------------
// Forbidden functions, do not use
#ifdef __CASCLIB_SELF__
#define strcpy UNSAFE_DO_NOT_USE_STRCPY
#define strcat UNSAFE_DO_NOT_USE_STRCAT
#define sprintf UNSAFE_DO_NOT_USE_SPRINTF
#endif
#endif // __CASCPORT_H__
+845
View File
@@ -0,0 +1,845 @@
/****************************************************************************/
/* CascOpenFile.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* System-dependent directory functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 01.05.14 1.00 Lad The first version of CascOpenFile.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local functions
static int EnsureDataStreamIsOpen(TCascFile * hf)
{
TCascStorage * hs = hf->hs;
TFileStream * pStream = NULL;
ULONGLONG EncodedSize = 0;
TCHAR * szDataFile;
TCHAR szCachePath[MAX_PATH];
TCHAR szPlainName[0x80];
int nError;
// If the file is available locally, we rely on data files.
// If not, we download the file and open the stream
if(hf->pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL)
{
// If the file is not open yet, do it
if(hs->DataFiles[hf->ArchiveIndex] == NULL)
{
// Prepare the name of the data file
CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), hf->ArchiveIndex);
szDataFile = CombinePath(hs->szIndexPath, szPlainName);
// Open the data file
if(szDataFile != NULL)
{
// Open the data stream with read+write sharing to prevent Battle.net agent
// detecting a corruption and redownloading the entire package
pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_FLAG_WRITE_SHARE | STREAM_PROVIDER_FLAT | STREAM_FLAG_FILL_MISSING | BASE_PROVIDER_FILE);
hs->DataFiles[hf->ArchiveIndex] = pStream;
CASC_FREE(szDataFile);
}
}
// Return error or success
hf->pStream = hs->DataFiles[hf->ArchiveIndex];
return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
}
else
{
if(hf->bDownloadFileIf)
{
// Create the local folder path and download the file from CDN
nError = DownloadFileFromCDN(hf->hs, _T("data"), hf->pCKeyEntry->EKey, NULL, szCachePath, _countof(szCachePath));
if(nError == ERROR_SUCCESS)
{
hf->pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
if(hf->pStream != NULL)
{
// Supply the file size, if unknown yet
if(hf->EncodedSize == CASC_INVALID_SIZE)
{
FileStream_GetSize(hf->pStream, &EncodedSize);
hf->pCKeyEntry->EncodedSize = (DWORD)EncodedSize;
hf->EncodedSize = (DWORD)EncodedSize;
}
hf->bLocalFileStream = true;
return ERROR_SUCCESS;
}
}
}
return ERROR_FILE_OFFLINE;
}
}
#ifdef _DEBUG
static unsigned int table_16C57A8[0x10] =
{
0x049396B8, 0x72A82A9B, 0xEE626CCA, 0x9917754F,
0x15DE40B1, 0xF5A8A9B6, 0x421EAC7E, 0xA9D55C9A,
0x317FD40C, 0x04FAF80D, 0x3D6BE971, 0x52933CFD,
0x27F64B7D, 0xC6F5C11B, 0xD5757E3A, 0x6C388745
};
// Obtained from Agent.exe v 2.15.0.6296 (d14ec9d9a1b396a42964b05f40ea55f37eae5478d550c07ebb6cb09e50968d62)
// Note the "Checksum" value probably won't match with older game versions.
static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderOffset)
{
LPBYTE pbBlteHeader = (LPBYTE)pBlteHeader;
DWORD dwInt32;
BYTE EncodedOffset[4] = { 0 };
BYTE HashedHeader[4] = { 0 };
BYTE JenkinsHash[4];
BYTE Checksum[4];
size_t i, j;
// Seems to be hardcoded to zero
assert(pBlteHeader->field_15 == 0);
// Calculate the Jenkins hash and write it to the header
dwInt32 = hashlittle(pbBlteHeader, FIELD_OFFSET(BLTE_ENCODED_HEADER, JenkinsHash), 0x3D6BE971);
ConvertIntegerToBytes_4_LE(dwInt32, JenkinsHash);
// assert(memcmp(pBlteHeader->JenkinsHash, JenkinsHash, sizeof(JenkinsHash)) == 0);
// Encode the lower 32-bits of the offset
dwInt32 = (DWORD)(HeaderOffset + FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature));
dwInt32 = table_16C57A8[dwInt32 & 0x0F] ^ dwInt32;
ConvertIntegerToBytes_4_LE(dwInt32, EncodedOffset);
// Calculate checksum of the so-far filled structure
for (i = 0; i < FIELD_OFFSET(BLTE_ENCODED_HEADER, Checksum); i++)
HashedHeader[i & 3] ^= pbBlteHeader[i];
// XOR the two values together to get the final checksum.
for (j = 0; j < 4; j++, i++)
Checksum[j] = HashedHeader[i & 3] ^ EncodedOffset[i & 3];
// assert(memcmp(pBlteHeader->Checksum, Checksum, sizeof(Checksum)) == 0);
}
#endif
static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize)
{
PBLTE_ENCODED_HEADER pEncodedHeader = (PBLTE_ENCODED_HEADER)pbEncodedBuffer;
PBLTE_HEADER pBlteHeader = (PBLTE_HEADER)pbEncodedBuffer;
DWORD ExpectedHeaderSize;
DWORD ExHeaderSize = 0;
DWORD HeaderSize;
DWORD FrameCount = 0;
CASCLIB_UNUSED(HeaderOffset);
// On files within storage segments ("data.###"), there is BLTE_ENCODED_HEADER
// On local files, there is just PBLTE_HEADER
if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE)
{
// There must be at least some bytes
if (cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F))
return ERROR_BAD_FORMAT;
if (pEncodedHeader->EncodedSize != hf->EncodedSize)
return ERROR_BAD_FORMAT;
#ifdef _DEBUG
// Not really needed, it's here just for explanation of what the values mean
//assert(memcmp(hf->pCKeyEntry->EKey, pEncodedHeader->EKey.Value, MD5_HASH_SIZE) == 0);
VerifyHeaderSpan(pEncodedHeader, HeaderOffset);
#endif
// Capture the EKey
ExHeaderSize = FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature);
pBlteHeader = (PBLTE_HEADER)(pbEncodedBuffer + ExHeaderSize);
}
// Verify the signature
if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE)
return ERROR_BAD_FORMAT;
// Capture the header size. If this is non-zero, then array
// of chunk headers follow. Otherwise, the file is just one chunk
HeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSize);
if (HeaderSize != 0)
{
if (pBlteHeader->MustBe0F != 0x0F)
return ERROR_BAD_FORMAT;
// Verify the header size
FrameCount = ConvertBytesToInteger_3(pBlteHeader->FrameCount);
ExpectedHeaderSize = 0x0C + FrameCount * sizeof(BLTE_FRAME);
if (ExpectedHeaderSize != HeaderSize)
return ERROR_BAD_FORMAT;
// Give the values
pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F) + sizeof(DWORD);
}
else
{
pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F);
}
// Give the frame count
hf->FrameCount = FrameCount;
return ERROR_SUCCESS;
}
static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize)
{
LPBYTE pbNewBuffer;
// Reallocate the buffer
pbNewBuffer = CASC_REALLOC(BYTE, pbEncodedBuffer, cbTotalHeaderSize);
if (pbNewBuffer != NULL)
{
// Load the missing data
DataFileOffset += cbEncodedBuffer;
if (FileStream_Read(hf->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
{
return pbNewBuffer;
}
}
// If anything failed, we free the original buffer and return NULL;
CASC_FREE(pbEncodedBuffer);
return NULL;
}
static int LoadFileFrames(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize)
{
PBLTE_FRAME pFileFrame;
DWORD ContentSize = 0;
DWORD FileOffset = 0;
int nError = ERROR_SUCCESS;
assert(hf != NULL);
assert(hf->pStream != NULL);
assert(hf->pFrames == NULL);
if (hf->FrameCount != 0)
{
// Move the raw archive offset
DataFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME));
// Allocate array of file frames
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount);
if (hf->pFrames != NULL)
{
// Copy the frames to the file structure
for (DWORD i = 0; i < hf->FrameCount; i++, pbFramePtr += sizeof(BLTE_FRAME))
{
// Capture the file frame
if ((pbFramePtr + sizeof(BLTE_FRAME)) > pbFrameEnd)
return ERROR_BAD_FORMAT;
pFileFrame = (PBLTE_FRAME)pbFramePtr;
// Convert the file frame to the native format
hf->pFrames[i].DataFileOffset = (DWORD)DataFileOffset;
hf->pFrames[i].FileOffset = CASC_INVALID_POS;
hf->pFrames[i].EncodedSize = ConvertBytesToInteger_4(pFileFrame->EncodedSize);
hf->pFrames[i].ContentSize = ConvertBytesToInteger_4(pFileFrame->ContentSize);
hf->pFrames[i].FrameHash = pFileFrame->FrameHash;
DataFileOffset += hf->pFrames[i].EncodedSize;
ContentSize += hf->pFrames[i].ContentSize;
FileOffset += hf->pFrames[i].ContentSize;
}
// Save the content size of the file
if(hf->pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
{
hf->pCKeyEntry->ContentSize = ContentSize;
hf->ContentSize = ContentSize;
}
}
}
else
{
// The content size in the file structure must be valid at this point,
// otherwise we don't know the frame content size
if (hf->ContentSize == CASC_INVALID_SIZE)
{
assert(false);
return ERROR_CAN_NOT_COMPLETE;
}
// Save the number of file frames
hf->FrameCount = 1;
// Allocate single "dummy" frame
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, 1);
if (hf->pFrames != NULL)
{
memset(&hf->pFrames->FrameHash, 0, sizeof(CONTENT_KEY));
hf->pFrames->DataFileOffset = (DWORD)DataFileOffset;
hf->pFrames->FileOffset = CASC_INVALID_POS;
hf->pFrames->EncodedSize = (DWORD)(hf->EncodedSize - cbHeaderSize);
hf->pFrames->ContentSize = hf->ContentSize;
}
}
if (hf->pFrames == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
return nError;
}
static int LoadEncodedHeaderAndFileFrames(TCascFile * hf)
{
LPBYTE pbEncodedBuffer;
size_t cbEncodedBuffer = MAX_ENCODED_HEADER;
int nError = ERROR_SUCCESS;
// Should only be called when the file frames are NOT loaded
assert(hf->pFrames == NULL);
assert(hf->FrameCount == 0);
// Allocate the initial buffer for the encoded headers
pbEncodedBuffer = CASC_ALLOC(BYTE, MAX_ENCODED_HEADER);
if (pbEncodedBuffer != NULL)
{
ULONGLONG ReadOffset = hf->ArchiveOffset;
size_t cbTotalHeaderSize;
size_t cbHeaderSize = 0;
// At this point, we expect encoded size to be known
assert(hf->EncodedSize != CASC_INVALID_SIZE);
// Do not read more than encoded size
cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, hf->EncodedSize);
// Load the entire (eventual) header area. This is faster than doing
// two read operations in a row. Read as much as possible. If the file is cut,
// the FileStream will pad it with zeros
if (FileStream_Read(hf->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
{
// Parse the BLTE header
nError = ParseBlteHeader(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
if (nError == ERROR_SUCCESS)
{
// If the headers are larger than the initial read size,
// We read the missing data
cbTotalHeaderSize = cbHeaderSize + (hf->FrameCount * sizeof(BLTE_FRAME));
if (cbTotalHeaderSize > cbEncodedBuffer)
{
pbEncodedBuffer = ReadMissingHeaderData(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize);
if (pbEncodedBuffer == NULL)
nError = GetLastError();
cbEncodedBuffer = cbTotalHeaderSize;
}
// Load the array of frame headers
if (nError == ERROR_SUCCESS)
{
nError = LoadFileFrames(hf, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize);
}
}
}
else
{
nError = ERROR_FILE_CORRUPT;
}
// Free the frame buffer
CASC_FREE(pbEncodedBuffer);
}
else
{
nError = ERROR_NOT_ENOUGH_MEMORY;
}
return nError;
}
static int EnsureFileFramesLoaded(TCascFile * hf)
{
int nError = ERROR_SUCCESS;
// If the encoded frames are not loaded, do it now
if(hf->pFrames == NULL)
{
// We need the data file to be open
nError = EnsureDataStreamIsOpen(hf);
if(nError != ERROR_SUCCESS)
return nError;
// Make sure we have header area loaded
nError = LoadEncodedHeaderAndFileFrames(hf);
}
return nError;
}
static int LoadEncodedFrame(TFileStream * pStream, PCASC_FILE_FRAME pFrame, LPBYTE pbEncodedFrame, bool bVerifyIntegrity)
{
ULONGLONG FileOffset = pFrame->DataFileOffset;
int nError = ERROR_SUCCESS;
// Load the encoded frame to memory
if(FileStream_Read(pStream, &FileOffset, pbEncodedFrame, pFrame->EncodedSize))
{
if (bVerifyIntegrity)
{
if (!CascVerifyDataBlockHash(pbEncodedFrame, pFrame->EncodedSize, pFrame->FrameHash.Value))
nError = ERROR_FILE_CORRUPT;
}
}
else
{
nError = GetLastError();
}
return nError;
}
static int ProcessFileFrame(
TCascStorage * hs,
LPBYTE pbOutBuffer,
DWORD cbOutBuffer,
LPBYTE pbInBuffer,
DWORD cbInBuffer,
DWORD dwFrameIndex)
{
LPBYTE pbWorkBuffer = NULL;
DWORD cbOutBufferExpected = 0;
DWORD cbWorkBuffer = 0;
DWORD dwStepCount = 0;
bool bWorkComplete = false;
int nError = ERROR_SUCCESS;
// Perform the loop
while(bWorkComplete == false)
{
// There should never be a 3rd step
assert(dwStepCount < 2);
// Perform the operation specific by the first byte
switch(pbInBuffer[0])
{
case 'E': // Encrypted files
// The work buffer should not have been allocated by any step
assert(pbWorkBuffer == NULL && cbWorkBuffer == 0);
// Allocate temporary buffer to decrypt into
// Example storage: "2016 - WoW/23420", File: "4ee6bc9c6564227f1748abd0b088e950"
pbWorkBuffer = CASC_ALLOC(BYTE, cbInBuffer - 1);
cbWorkBuffer = cbInBuffer - 1;
if(pbWorkBuffer == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Decrypt the stream to the work buffer
nError = CascDecrypt(hs, pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex);
if(nError != ERROR_SUCCESS)
{
bWorkComplete = true;
break;
}
// When encrypted, there is always one more step after this.
// Setup the work buffer as input buffer for the next operation
pbInBuffer = pbWorkBuffer;
cbInBuffer = cbWorkBuffer;
break;
case 'Z': // ZLIB compressed files
// If we decompressed less than expected, we simply fill the rest with zeros
// Example: INSTALL file from the TACT CASC storage
cbOutBufferExpected = cbOutBuffer;
nError = CascDecompress(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1);
// We exactly know what the output buffer size will be.
// If the uncompressed data is smaller, fill the rest with zeros
if(cbOutBuffer < cbOutBufferExpected)
memset(pbOutBuffer + cbOutBuffer, 0, (cbOutBufferExpected - cbOutBuffer));
bWorkComplete = true;
break;
case 'N': // Normal stored files
nError = CascDirectCopy(pbOutBuffer, &cbOutBuffer, 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;
}
// Increment the step count
dwStepCount++;
}
// Free the temporary buffer
CASC_FREE(pbWorkBuffer);
return nError;
}
static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
{
PCASC_FILE_FULL_INFO pFileInfo;
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
TCascStorage * hs = hf->hs;
// Verify whether we have enough space in the buffer
pFileInfo = (PCASC_FILE_FULL_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_FULL_INFO), pcbLengthNeeded);
if(pFileInfo != NULL)
{
// Reset the entire structure
CopyMemory16(pFileInfo->CKey, pCKeyEntry->CKey);
CopyMemory16(pFileInfo->EKey, pCKeyEntry->EKey);
pFileInfo->FileDataId = CASC_INVALID_ID;
pFileInfo->LocaleFlags = CASC_INVALID_ID;
pFileInfo->ContentFlags = CASC_INVALID_ID;
// Supply information not depending on root
CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->ArchiveIndex);
pFileInfo->StorageOffset = pCKeyEntry->StorageOffset;
pFileInfo->SegmentOffset = hf->ArchiveOffset;
pFileInfo->FileNameHash = 0;
pFileInfo->TagBitMask = pCKeyEntry->TagBitMask;
pFileInfo->SegmentIndex = hf->ArchiveIndex;
pFileInfo->ContentSize = hf->ContentSize;
pFileInfo->EncodedSize = hf->EncodedSize;
// Supply the root-specific information
hs->pRootHandler->GetInfo(pCKeyEntry, pFileInfo);
}
return (pFileInfo != NULL);
}
//-----------------------------------------------------------------------------
// Public functions
bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
{
TCascFile * hf;
LPBYTE pbOutputValue = NULL;
LPBYTE pbInfoValue = NULL;
size_t cbInfoValue = 0;
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// Differentiate between info classes
switch(InfoClass)
{
case CascFileContentKey:
// Do we have content key at all?
if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_CKEY) == 0)
{
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
// Give the content key
pbInfoValue = hf->pCKeyEntry->CKey;
cbInfoValue = CASC_CKEY_SIZE;
break;
case CascFileEncodedKey:
// Do we have content key at all?
if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_EKEY) == 0)
{
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
// Give the content key
pbInfoValue = hf->pCKeyEntry->EKey;
cbInfoValue = CASC_CKEY_SIZE;
break;
case CascFileFullInfo:
return GetFileFullInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
default:
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Sanity check
assert(pbInfoValue != NULL);
assert(cbInfoValue != 0);
// Give the result
pbOutputValue = (LPBYTE)ProbeOutputBuffer(pvFileInfo, cbFileInfo, cbInfoValue, pcbLengthNeeded);
if(pbOutputValue != NULL)
memcpy(pbOutputValue, pbInfoValue, cbInfoValue);
return (pbOutputValue != NULL);
}
//
// THE FILE SIZE PROBLEM
//
// There are members called "FileSize" in many CASC-related structure
// For various files, these variables have different meaning.
//
// Storage FileName FileSize FrameSum HdrArea CKeyEntry EKeyEntry RootEntry
// ----------- -------- ---------- -------- -------- ---------- ---------- ----------
// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 n/a 0x0024BA45 n/a
// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x00193340 0x0010db65 n/a
// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x00001080 0x000008eb 0x00001080
//
// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b n/a 0x030d487b n/a
// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x016a9800 0x0131313d n/a
// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x000007d0 0x00000397 n/a
//
DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
{
TCascFile * hf;
int nError;
CASCLIB_UNUSED(pdwFileSizeHigh);
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return CASC_INVALID_SIZE;
}
// Someone may have provided file content size.
// If yes, do not load the frames, as it's not necessary.
if(hf->ContentSize == CASC_INVALID_SIZE)
{
// Make sure that the file header area is loaded
nError = EnsureFileFramesLoaded(hf);
if(nError != ERROR_SUCCESS)
{
SetLastError(nError);
return CASC_INVALID_SIZE;
}
// The content size should be loaded from the frames
assert(hf->ContentSize != CASC_INVALID_SIZE);
}
// Give the file size to the caller
if(pdwFileSizeHigh != NULL)
*pdwFileSizeHigh = 0;
return hf->ContentSize;
}
DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod)
{
TCascFile * hf;
ULONGLONG FilePosition;
ULONGLONG MoveOffset;
DWORD dwFilePosHi;
// If the hFile is not a valid file handle, return an error.
hf = TCascFile::IsValid(hFile);
if(hf == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return CASC_INVALID_POS;
}
// Get the relative point where to move from
switch(dwMoveMethod)
{
case FILE_BEGIN:
FilePosition = 0;
break;
case FILE_CURRENT:
FilePosition = hf->FilePointer;
break;
case FILE_END:
FilePosition = hf->ContentSize;
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return CASC_INVALID_POS;
}
// Now get the move offset. Note that both values form
// a signed 64-bit value (a file pointer can be moved backwards)
if(plFilePosHigh != NULL)
dwFilePosHi = *plFilePosHigh;
else
dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0;
MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos);
// Now calculate the new file pointer
// Do not allow the file pointer to overflow
FilePosition = ((FilePosition + MoveOffset) >= FilePosition) ? (FilePosition + MoveOffset) : 0;
// CASC files can't be bigger than 4 GB.
// We don't allow to go past 4 GB
if(FilePosition >> 32)
{
SetLastError(ERROR_INVALID_PARAMETER);
return CASC_INVALID_POS;
}
// Change the file position
hf->FilePointer = (DWORD)FilePosition;
// Return the new file position
if(plFilePosHigh != NULL)
*plFilePosHigh = 0;
return hf->FilePointer;
}
bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead)
{
TCascFile * hf;
int nError = ERROR_SUCCESS;
// The buffer must be valid
if(pvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// If the file frames are not loaded yet, do it now
if(nError == ERROR_SUCCESS)
{
nError = EnsureFileFramesLoaded(hf);
}
// If the file position is at or beyond end of file, do nothing
if(nError == ERROR_SUCCESS)
{
// Check the starting position
if(hf->FilePointer >= hf->ContentSize)
{
*pdwBytesRead = 0;
return true;
}
// Check the ending position
if((hf->FilePointer + dwBytesToRead) > hf->ContentSize)
{
dwBytesToRead = hf->ContentSize - hf->FilePointer;
}
}
// Allocate cache buffer for the entire file. This is the fastest approach
// (without reallocations). However, this may consume quite a lot of memory
// (Storage: "2016 - Starcraft II/45364", file: "3d815f40c0413701aa2bd214070d0062"
// needs 0x239a09b3 bytes of memory (~600 MB)
if(nError == ERROR_SUCCESS)
{
if(hf->pbFileCache == NULL)
{
// Allocate buffer
hf->pbFileCache = CASC_ALLOC(BYTE, hf->ContentSize);
hf->cbFileCache = hf->ContentSize;
if(hf->pbFileCache == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
// Load all frames that are not loaded yet
if(nError == ERROR_SUCCESS)
{
PCASC_FILE_FRAME pFrame = hf->pFrames;
DWORD StartFrameOffset = 0;
DWORD StartReadOffset = hf->FilePointer;
DWORD EndReadOffset = hf->FilePointer + dwBytesToRead;
for(DWORD i = 0; (i < hf->FrameCount) && (nError == ERROR_SUCCESS); i++, pFrame++)
{
LPBYTE pbDecodedFrame = hf->pbFileCache + StartFrameOffset;
LPBYTE pbEncodedFrame;
DWORD EndFrameOffset = StartFrameOffset + pFrame->ContentSize;
// Does that frame belong to the range?
if(StartReadOffset < EndFrameOffset && EndReadOffset > StartFrameOffset)
{
// Is the frame already loaded?
if (pFrame->FileOffset == CASC_INVALID_POS)
{
// Allocate space for the encoded frame
pbEncodedFrame = CASC_ALLOC(BYTE, pFrame->EncodedSize);
if (pbEncodedFrame != NULL)
{
// Load the encoded frame data
nError = LoadEncodedFrame(hf->pStream, pFrame, pbEncodedFrame, hf->bVerifyIntegrity);
if (nError == ERROR_SUCCESS)
{
// Decode the frame
nError = ProcessFileFrame(hf->hs,
pbDecodedFrame,
pFrame->ContentSize,
pbEncodedFrame,
pFrame->EncodedSize,
(DWORD)(pFrame - hf->pFrames));
// Some people find it handy to extract data from partially encrypted file,
// even at the cost producing files that are corrupt.
// We overcome missing decryption key by zeroing the encrypted portions
if(nError == ERROR_FILE_ENCRYPTED && hf->bOvercomeEncrypted)
{
memset(pbDecodedFrame, 0, pFrame->ContentSize);
nError = ERROR_SUCCESS;
}
if (nError == ERROR_SUCCESS)
{
// Mark the frame as loaded
pFrame->FileOffset = StartFrameOffset;
}
}
// Free the frame buffer
CASC_FREE(pbEncodedFrame);
}
else
{
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
}
// If the frame start is past the read offset, stop the loop
if ((StartFrameOffset + pFrame->ContentSize) >= EndReadOffset)
break;
StartFrameOffset += pFrame->ContentSize;
}
}
// Now all frames have been loaded into the cache; copy the entire block to the output buffer
if(nError == ERROR_SUCCESS)
{
// Copy the entire data
memcpy(pvBuffer, hf->pbFileCache + hf->FilePointer, dwBytesToRead);
hf->FilePointer += dwBytesToRead;
// Give the number of bytes read
if(pdwBytesRead != NULL)
*pdwBytesRead = dwBytesToRead;
return true;
}
else
{
SetLastError(nError);
return false;
}
}
+889
View File
@@ -0,0 +1,889 @@
/*****************************************************************************/
/* CascRootFile_Diablo3.cpp Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Support for loading Diablo 3 ROOT file */
/* Note: D3 offsets refer to Diablo III.exe 2.2.0.30013 (32-bit) */
/* SHA1: e4f17eca8aad8dde70870bf932ac3f5b85f17a1f */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 04.03.15 1.00 Lad The first version of CascRootFile_Diablo3.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local structures
#define DIABLO3_SUBDIR_SIGNATURE 0xEAF1FE87
#define DIABLO3_PACKAGES_SIGNATURE 0xAABB0002
#define DIABLO3_MAX_SUBDIRS 0x20
#define DIABLO3_MAX_ASSETS 70 // Maximum possible number of assets
#define DIABLO3_MAX_ROOT_FOLDERS 0x20 // Maximum count of root directory named entries
// On-disk structure for a file given by file number
typedef struct _DIABLO3_ASSET_ENTRY
{
CONTENT_KEY CKey; // Content key for the file
DWORD FileIndex; // File index
} DIABLO3_ASSET_ENTRY, *PDIABLO3_ASSET_ENTRY;
// On-disk structure for a file given by file number and suffix
typedef struct _DIABLO3_ASSETIDX_ENTRY
{
CONTENT_KEY CKey; // Content key for the file
DWORD FileIndex; // File index
DWORD SubIndex; // File subindex, like "SoundBank\3D Ambience\0000.smp"
} DIABLO3_ASSETIDX_ENTRY, *PDIABLO3_ASSETIDX_ENTRY;
// In-memory structure of the named entry
typedef struct _DIABLO3_NAMED_ENTRY
{
PCONTENT_KEY pCKey; // Pointer to the content key
const char * szFileName; // Pointer to the zero-terminated file name
const char * szFileEnd; // Position of the zero terminator (aka end of the file name)
} DIABLO3_NAMED_ENTRY, *PDIABLO3_NAMED_ENTRY;
// On-disk structure of CoreToc.dat header
typedef struct _DIABLO3_CORE_TOC_HEADER
{
DWORD EntryCounts[DIABLO3_MAX_ASSETS]; // Array of number of entries (files) for each asset
DWORD EntryOffsets[DIABLO3_MAX_ASSETS]; // Array of offsets of each DIABLO3_CORE_TOC_ENTRY, relative to data after header
DWORD Unknowns[DIABLO3_MAX_ASSETS]; // Unknown
DWORD Alignment;
} DIABLO3_CORE_TOC_HEADER, *PDIABLO3_CORE_TOC_HEADER;
// On-disk structure of the entry in CoreToc.dat
typedef struct _DIABLO3_CORE_TOC_ENTRY
{
DWORD AssetIndex; // Index of the Diablo3 asset (aka directory)
DWORD FileIndex; // File index
DWORD NameOffset; // Offset of the plain file name
} DIABLO3_CORE_TOC_ENTRY, *PDIABLO3_CORE_TOC_ENTRY;
// In-memory structure of parsed directory data
typedef struct _DIABLO3_DIRECTORY
{
LPBYTE pbDirectoryData; // The begin of the directory data block
LPBYTE pbDirectoryEnd; // The end of the directory data block
LPBYTE pbAssetEntries; // Pointer to asset entries without subitem number. Example: "SoundBank\SoundFile.smp"
LPBYTE pbAssetIdxEntries; // Pointer to asset entries with subitem number
LPBYTE pbNamedEntries; // Pointer to named entries. These are for files with arbitrary names, and they do not belong to an asset
DWORD dwAssetEntries; // Number of asset entries without subitem number
DWORD dwAssetIdxEntries;
DWORD dwNamedEntries;
DWORD dwNodeIndex; // Index of file node for this folder
} DIABLO3_DIRECTORY, *PDIABLO3_DIRECTORY;
// Structure for conversion DirectoryID -> Directory name
typedef struct _DIABLO3_ASSET_INFO
{
const char * szDirectoryName; // Directory name
const char * szExtension;
} DIABLO3_ASSET_INFO;
typedef const DIABLO3_ASSET_INFO * PDIABLO3_ASSET_INFO;
//-----------------------------------------------------------------------------
// Local variables
static const DIABLO3_ASSET_INFO Assets[] =
{
// DIR-NAME EXTENSION
// ========== =========
{NULL, NULL}, // 0x00
{"Actor", "acr"}, // 0x01
{"Adventure", "adv"}, // 0x02
{NULL, NULL}, // 0x03
{NULL, NULL}, // 0x04
{"AmbientSound", "ams"}, // 0x05
{"Anim", "ani"}, // 0x06
{"Anim2D", "an2"}, // 0x07
{"AnimSet", "ans"}, // 0x08
{"Appearance", "app"}, // 0x09
{NULL, NULL}, // 0x0A
{"Cloth", "clt"}, // 0x0B
{"Conversation", "cnv"}, // 0x0C
{NULL, NULL}, // 0x0D
{"EffectGroup", "efg"}, // 0x0E
{"Encounter", "enc"}, // 0x0F
{NULL, NULL}, // 0x10
{"Explosion", "xpl"}, // 0x11
{NULL, NULL}, // 0x12
{"Font", "fnt"}, // 0x13
{"GameBalance", "gam"}, // 0x14
{"Globals", "glo"}, // 0x15
{"LevelArea", "lvl"}, // 0x16
{"Light", "lit"}, // 0x17
{"MarkerSet", "mrk"}, // 0x18
{"Monster", "mon"}, // 0x19
{"Observer", "obs"}, // 0x1A
{"Particle", "prt"}, // 0x1B
{"Physics", "phy"}, // 0x1C
{"Power", "pow"}, // 0x1D
{NULL, NULL}, // 0x1E
{"Quest", "qst"}, // 0x1F
{"Rope", "rop"}, // 0x20
{"Scene", "scn"}, // 0x21
{"SceneGroup", "scg"}, // 0x22
{NULL, NULL}, // 0x23
{"ShaderMap", "shm"}, // 0x24
{"Shaders", "shd"}, // 0x25
{"Shakes", "shk"}, // 0x26
{"SkillKit", "skl"}, // 0x27
{"Sound", "snd"}, // 0x28
{"SoundBank", "sbk"}, // 0x29
{"StringList", "stl"}, // 0x2A
{"Surface", "srf"}, // 0x2B
{"Textures", "tex"}, // 0x2C
{"Trail", "trl"}, // 0x2D
{"UI", "ui"}, // 0x2E
{"Weather", "wth"}, // 0x2F
{"Worlds", "wrl"}, // 0x30
{"Recipe", "rcp"}, // 0x31
{NULL, NULL}, // 0x32
{"Condition", "cnd"}, // 0x33
{NULL, NULL}, // 0x34
{NULL, NULL}, // 0x35
{NULL, NULL}, // 0x36
{NULL, NULL}, // 0x37
{"Act", "act"}, // 0x38
{"Material", "mat"}, // 0x39
{"QuestRange", "qsr"}, // 0x3A
{"Lore", "lor"}, // 0x3B
{"Reverb", "rev"}, // 0x3C
{"PhysMesh", "phm"}, // 0x3D
{"Music", "mus"}, // 0x3E
{"Tutorial", "tut"}, // 0x3F
{"BossEncounter", "bos"}, // 0x40
{NULL, NULL}, // 0x41
{"Accolade", "aco"}, // 0x42
};
#define DIABLO3_ASSET_COUNT (sizeof(Assets) / sizeof(Assets[0]))
//-----------------------------------------------------------------------------
// Handler definitions for Diablo3 root file
struct TDiabloRoot : public TFileTreeRoot
{
public:
TDiabloRoot() : TFileTreeRoot(0)
{
memset(RootFolders, 0, sizeof(RootFolders));
pFileIndices = NULL;
pbCoreTocFile = NULL;
pbCoreTocData = NULL;
nFileIndices = 0;
cbCoreTocFile = 0;
// Map for searching a real file extension
memset(&PackagesMap, 0, sizeof(CASC_MAP));
pbPackagesDat = NULL;
cbPackagesDat = 0;
// We have file names and return CKey as result of search
dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY);
}
~TDiabloRoot()
{
FreeLoadingStuff();
}
void AppendBackslashToTotalPath(PATH_BUFFER & PathBuffer)
{
if(PathBuffer.szPtr < PathBuffer.szEnd)
{
PathBuffer.szPtr[0] = '\\';
PathBuffer.szPtr++;
}
}
void AppendPathToTotalPath(PATH_BUFFER & PathBuffer, const char * szFileName, const char * szFileEnd)
{
char * szPathPtr = PathBuffer.szPtr;
size_t nLength = (szFileEnd - szFileName);
// Append the name
if((szPathPtr + nLength) < PathBuffer.szEnd)
{
memcpy(szPathPtr, szFileName, nLength);
szPathPtr += nLength;
}
// Append zero terminator
if(szPathPtr < PathBuffer.szEnd)
szPathPtr[0] = 0;
PathBuffer.szPtr = szPathPtr;
}
PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex)
{
if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL)
return &Assets[dwAssetIndex];
return NULL;
}
char * FindPackageName(const char * szAssetName, const char * szPlainName)
{
char szFileName[MAX_PATH+1];
size_t nLength;
// Construct the name without extension and find it in the map
nLength = CascStrPrintf(szFileName, _countof(szFileName), "%s\\%s", szAssetName, szPlainName);
return (char *)PackagesMap.FindString(szFileName, szFileName + nLength);
}
LPBYTE LoadFileToMemory(TCascStorage * hs, const char * szFileName, DWORD * pcbFileData)
{
PCASC_CKEY_ENTRY pCKeyEntry;
LPBYTE pbFileData = NULL;
// Try to find CKey for the file
pCKeyEntry = GetFile(hs, szFileName);
if(pCKeyEntry != NULL)
pbFileData = LoadInternalFileToMemory(hs, pCKeyEntry, pcbFileData);
return pbFileData;
}
static LPBYTE CaptureDirectoryData(
DIABLO3_DIRECTORY & DirHeader,
LPBYTE pbDirectory,
DWORD cbDirectory)
{
LPBYTE pbDirectoryData = pbDirectory;
LPBYTE pbDataEnd = pbDirectory + cbDirectory;
DWORD Signature = 0;
//
// Structure of a Diablo3 directory header
// 1) Signature (4 bytes)
// 2) Number of DIABLO3_ASSET_ENTRY entries (4 bytes)
// 3) Array of DIABLO3_ASSET_ENTRY entries
// 4) Number of DIABLO3_ASSETIDX_ENTRY entries (4 bytes)
// 5) Array of DIABLO3_ASSETIDX_ENTRY entries
// 6) Number of DIABLO3_NAMED_ENTRY entries (4 bytes)
// 7) Array of DIABLO3_NAMED_ENTRY entries
//
// Prepare the header signature
memset(&DirHeader, 0, sizeof(DIABLO3_DIRECTORY));
// Get the header signature
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &Signature);
if((pbDirectory == NULL) || (Signature != CASC_DIABLO3_ROOT_SIGNATURE && Signature != DIABLO3_SUBDIR_SIGNATURE))
return NULL;
// Subdirectories have extra two arrays
if(Signature == DIABLO3_SUBDIR_SIGNATURE)
{
// Capture the number of DIABLO3_ASSET_ENTRY items
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetEntries);
if(pbDirectory == NULL)
return NULL;
// Capture the array of DIABLO3_ASSET_ENTRY
pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetEntries, DIABLO3_ASSET_ENTRY, DirHeader.dwAssetEntries);
if(pbDirectory == NULL)
return NULL;
// Capture the number of DIABLO3_ASSETIDX_ENTRY items
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetIdxEntries);
if(pbDirectory == NULL)
return NULL;
// Capture the array of DIABLO3_ASSETIDX_ENTRY
pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetIdxEntries, DIABLO3_ASSETIDX_ENTRY, DirHeader.dwAssetIdxEntries);
if(pbDirectory == NULL)
return NULL;
}
// Capture the number of DIABLO3_NAMED_ENTRY array
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwNamedEntries);
if(pbDirectory == NULL)
return NULL;
// Note: Do not capture the array here. We will do that later,
// when we will be parsing the directory
DirHeader.pbNamedEntries = pbDirectory;
// Put the directory range
DirHeader.pbDirectoryData = pbDirectoryData;
DirHeader.pbDirectoryEnd = pbDirectoryData + cbDirectory;
return pbDirectory;
}
LPBYTE CaptureCoreTocHeader(
PDIABLO3_CORE_TOC_HEADER * PtrHeader,
PDWORD PtrMaxIndex,
LPBYTE pbDataPtr,
LPBYTE pbDataEnd)
{
PDIABLO3_CORE_TOC_HEADER pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbDataPtr;
DWORD dwMaxFileIndex = 0;
// Check the space for header
if((pbDataPtr + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbDataEnd)
return NULL;
pbDataPtr += sizeof(DIABLO3_CORE_TOC_HEADER);
// Verify all asset arrays
for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++)
{
PDIABLO3_CORE_TOC_ENTRY pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbDataPtr + pTocHeader->EntryOffsets[i]);
DWORD EntryOffset = pTocHeader->EntryOffsets[i];
DWORD EntryCount = pTocHeader->EntryCounts[i];
// Verify file range
if((pbDataPtr + EntryOffset + EntryCount * sizeof(DIABLO3_CORE_TOC_ENTRY)) > pbDataEnd)
return NULL;
// Find out the entry with the maximum index
for(DWORD n = 0; n < EntryCount; n++)
{
if(pTocEntry->FileIndex >= dwMaxFileIndex)
dwMaxFileIndex = pTocEntry->FileIndex;
pTocEntry++;
}
}
// Give data and return
PtrMaxIndex[0] = dwMaxFileIndex;
PtrHeader[0] = pTocHeader;
return pbDataPtr;
}
LPBYTE CaptureNamedEntry(
LPBYTE pbDataPtr,
LPBYTE pbDataEnd,
PDIABLO3_NAMED_ENTRY pEntry)
{
// Capture the content key
pbDataPtr = CaptureContentKey(pbDataPtr, pbDataEnd, &pEntry->pCKey);
if(pbDataPtr == NULL)
return NULL;
// Capture file name. Must be ASCIIZ file name
pEntry->szFileName = (const char *)pbDataPtr;
while(pbDataPtr < pbDataEnd && pbDataPtr[0] != 0)
pbDataPtr++;
// Did we find a zero char?
if(pbDataPtr < pbDataEnd && pbDataPtr[0] == 0)
{
pEntry->szFileEnd = (const char *)pbDataPtr;
return pbDataPtr + 1;
}
return NULL;
}
int LoadDirectoryFile(TCascStorage * hs, DIABLO3_DIRECTORY & DirHeader, PCASC_CKEY_ENTRY pCKeyEntry)
{
LPBYTE pbData;
DWORD cbData = 0;
// Load the n-th folder
pbData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbData);
if(pbData && cbData)
{
if(CaptureDirectoryData(DirHeader, pbData, cbData) == NULL)
{
// Clear the directory
CASC_FREE(pbData);
return ERROR_BAD_FORMAT;
}
}
return ERROR_SUCCESS;
}
bool CreateAssetFileName(
PATH_BUFFER & PathBuffer,
DWORD FileIndex,
DWORD SubIndex)
{
PDIABLO3_CORE_TOC_ENTRY pTocEntry;
PDIABLO3_ASSET_INFO pAssetInfo;
const char * szPackageName = NULL;
const char * szPlainName;
const char * szFormat;
char * szPathEnd = PathBuffer.szEnd;
char * szPathPtr = PathBuffer.szPtr;
size_t nLength = 0;
// Find and check the entry
pTocEntry = pFileIndices + FileIndex;
if(pTocEntry->FileIndex == FileIndex)
{
// Retrieve the asset information
pAssetInfo = GetAssetInfo(pTocEntry->AssetIndex);
// Either use the asset info for getting the folder name or supply "Asset##"
if(pAssetInfo != NULL)
{
CascStrCopy(szPathPtr, (szPathEnd - szPathPtr), pAssetInfo->szDirectoryName);
szPathPtr += strlen(szPathPtr);
}
else
{
szPathPtr[0] = 'A';
szPathPtr[1] = 's';
szPathPtr[2] = 's';
szPathPtr[3] = 'e';
szPathPtr[4] = 't';
szPathPtr[5] = (char)('0' + (pTocEntry->AssetIndex / 10));
szPathPtr[6] = (char)('0' + (pTocEntry->AssetIndex % 10));
szPathPtr += 7;
}
// Put the backslash
if(szPathPtr < PathBuffer.szEnd)
*szPathPtr++ = '\\';
// Construct the file name, up to the extension. Don't include the '.'
szPlainName = (const char *)(pbCoreTocData + pTocEntry->NameOffset);
szFormat = (SubIndex != CASC_INVALID_INDEX) ? "%s\\%04u" : "%s";
nLength = CascStrPrintf(szPathPtr, (szPathEnd - szPathPtr), szFormat, szPlainName, SubIndex);
// Try to fixup the file extension from the package name.
// File extensions are not predictable because for subitems,
// they are not always equal to the main items:
//
// SoundBank\3D Ambience.sbk
// SoundBank\3D Ambience\0000.smp
// SoundBank\3D Ambience\0002.smp
// ...
// SoundBank\Angel.sbk
// SoundBank\Angel\0000.fsb
// SoundBank\Angel\0002.fsb
//
// We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible
//
if(PackagesMap.IsInitialized() && pAssetInfo != NULL)
{
// Retrieve the asset name
szPackageName = FindPackageName(pAssetInfo->szDirectoryName, szPathPtr);
if(szPackageName != NULL)
{
CascStrCopy(PathBuffer.szPtr, (PathBuffer.szEnd - PathBuffer.szPtr), szPackageName);
return true;
}
}
// Use the extension from the AssetInfo, if we have any
if(pAssetInfo != NULL && pAssetInfo->szExtension != NULL)
{
szPathPtr[nLength++] = '.';
CascStrCopy(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), pAssetInfo->szExtension);
return true;
}
// Otherwise, supply "a##"
CascStrPrintf(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), ".a%02u", pTocEntry->AssetIndex);
return true;
}
return false;
}
// Parse the asset entries
int ParseAssetEntries(
TCascStorage * hs,
DIABLO3_DIRECTORY & Directory,
PATH_BUFFER & PathBuffer)
{
PDIABLO3_ASSET_ENTRY pEntry = (PDIABLO3_ASSET_ENTRY)Directory.pbAssetEntries;
PCASC_CKEY_ENTRY pCKeyEntry;
DWORD dwEntries = Directory.dwAssetEntries;
// Do nothing if there is no entries
if(pEntry != NULL && dwEntries != 0)
{
// Insert all asset entries to the file tree
for(DWORD i = 0; i < dwEntries; i++, pEntry++)
{
pCKeyEntry = FindCKeyEntry_CKey(hs, pEntry->CKey.Value);
if(pCKeyEntry != NULL)
{
// Construct the full path name of the entry
if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, CASC_INVALID_INDEX))
{
// Insert the entry to the file tree
FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
}
}
}
}
return ERROR_SUCCESS;
}
int ParseAssetAndIdxEntries(
TCascStorage * hs,
DIABLO3_DIRECTORY & Directory,
PATH_BUFFER & PathBuffer)
{
PDIABLO3_ASSETIDX_ENTRY pEntry = (PDIABLO3_ASSETIDX_ENTRY)Directory.pbAssetIdxEntries;
PCASC_CKEY_ENTRY pCKeyEntry;
DWORD dwEntries = Directory.dwAssetIdxEntries;
// Do nothing if there is no entries
if(pEntry != NULL && dwEntries != 0)
{
// Insert all asset entries to the file tree
for(DWORD i = 0; i < dwEntries; i++, pEntry++)
{
pCKeyEntry = FindCKeyEntry_CKey(hs, pEntry->CKey.Value);
if(pCKeyEntry != NULL)
{
// Construct the full path name of the entry
if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, pEntry->SubIndex))
{
// Insert the entry to the file tree
// fprintf(fp, "%08u %04u %s\n", pEntry->FileIndex, pEntry->SubIndex, PathBuffer.szBegin);
FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
}
}
}
}
return ERROR_SUCCESS;
}
// Parse the named entries of all folders
int ParseDirectory_Phase1(
TCascStorage * hs,
DIABLO3_DIRECTORY & Directory,
PATH_BUFFER & PathBuffer,
bool bIsRootDirectory)
{
DIABLO3_NAMED_ENTRY NamedEntry;
char * szSavePtr = PathBuffer.szPtr;
size_t nFolderIndex = 0;
int nError = ERROR_SUCCESS;
// Do nothing if there is no named headers
if(Directory.pbNamedEntries && Directory.dwNamedEntries)
{
PCASC_CKEY_ENTRY pCKeyEntry;
PCASC_FILE_NODE pFileNode;
LPBYTE pbDataPtr = Directory.pbNamedEntries;
LPBYTE pbDataEnd = Directory.pbDirectoryEnd;
DWORD dwNodeIndex;
// Parse all entries
while(pbDataPtr < pbDataEnd)
{
// Capture the named entry
pbDataPtr = CaptureNamedEntry(pbDataPtr, pbDataEnd, &NamedEntry);
if(pbDataPtr == NULL)
return ERROR_BAD_FORMAT;
// Append the path fragment to the total path
AppendPathToTotalPath(PathBuffer, NamedEntry.szFileName, NamedEntry.szFileEnd);
// Check whether the file exists in the storage
pCKeyEntry = FindCKeyEntry_CKey(hs, NamedEntry.pCKey->Value);
if(pCKeyEntry != NULL)
{
// Create file node belonging to this folder
pFileNode = FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
dwNodeIndex = (DWORD)FileTree.IndexOf(pFileNode);
// If we are parsing root folder, we also need to load the data of the sub-folder file
if(bIsRootDirectory)
{
// Mark the node as directory
AppendBackslashToTotalPath(PathBuffer);
pCKeyEntry->Flags |= CASC_CE_FOLDER_ENTRY;
pFileNode->Flags |= CFN_FLAG_FOLDER;
// Load the sub-directory file
nError = LoadDirectoryFile(hs, RootFolders[nFolderIndex], pCKeyEntry);
if(nError != ERROR_SUCCESS)
return nError;
// Parse the sub-directory file
nError = ParseDirectory_Phase1(hs, RootFolders[nFolderIndex], PathBuffer, false);
if(nError != ERROR_SUCCESS)
return nError;
// Also save the item pointer and increment the folder index
RootFolders[nFolderIndex].dwNodeIndex = dwNodeIndex;
nFolderIndex++;
}
// Restore the path pointer
PathBuffer.szPtr = szSavePtr;
szSavePtr[0] = 0;
}
}
}
return nError;
}
// Parse the nameless entries of all folders
int ParseDirectory_Phase2(TCascStorage * hs)
{
PATH_BUFFER PathBuffer;
char szPathBuffer[MAX_PATH];
// Parse each root subdirectory
for(size_t i = 0; i < DIABLO3_MAX_ROOT_FOLDERS; i++)
{
// Is this root folder loaded?
if(RootFolders[i].pbDirectoryData != NULL)
{
PathBuffer.szBegin = szPathBuffer;
PathBuffer.szPtr = szPathBuffer;
PathBuffer.szEnd = szPathBuffer + MAX_PATH - 1;
szPathBuffer[0] = 0;
// Retrieve the parent name
if(RootFolders[i].dwNodeIndex != 0)
{
FileTree.PathAt(szPathBuffer, MAX_PATH, RootFolders[i].dwNodeIndex);
PathBuffer.szPtr = PathBuffer.szBegin + strlen(szPathBuffer);
}
// Array of DIABLO3_ASSET_ENTRY entries.
// These are for files belonging to an asset, without subitem number.
// Example: "SoundBank\SoundFile.smp"
ParseAssetEntries(hs, RootFolders[i], PathBuffer);
// Array of DIABLO3_ASSETIDX_ENTRY entries.
// These are for files belonging to an asset, with a subitem number.
// Example: "SoundBank\SoundFile\0001.smp"
ParseAssetAndIdxEntries(hs, RootFolders[i], PathBuffer);
}
}
return ERROR_SUCCESS;
}
// Creates an array of DIABLO3_CORE_TOC_ENTRY entries indexed by FileIndex
// Used as lookup table when we have FileIndex and need Asset+PlainName
int CreateMapOfFileIndices(TCascStorage * hs, const char * szFileName)
{
PDIABLO3_CORE_TOC_HEADER pTocHeader = NULL;
LPBYTE pbCoreTocPtr = pbCoreTocFile;
DWORD dwMaxFileIndex = 0;
int nError = ERROR_CAN_NOT_COMPLETE;
// Load the entire file to memory
pbCoreTocFile = pbCoreTocPtr = LoadFileToMemory(hs, szFileName, &cbCoreTocFile);
if(pbCoreTocFile && cbCoreTocFile)
{
LPBYTE pbCoreTocEnd = pbCoreTocFile + cbCoreTocFile;
// Capture the header
if((pbCoreTocPtr = CaptureCoreTocHeader(&pTocHeader, &dwMaxFileIndex, pbCoreTocPtr, pbCoreTocEnd)) == NULL)
return ERROR_BAD_FORMAT;
// If there are no indices, return NULL
if(dwMaxFileIndex == 0)
return ERROR_SUCCESS;
// Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs
pFileIndices = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwMaxFileIndex + 1);
if(pFileIndices != NULL)
{
// Initialize all entries to invalid
memset(pFileIndices, 0xFF, (dwMaxFileIndex + 1) * sizeof(DIABLO3_CORE_TOC_ENTRY));
// Populate the linear array with the file indices
for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++)
{
PDIABLO3_CORE_TOC_ENTRY pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocPtr + pTocHeader->EntryOffsets[i]);
LPBYTE pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]);
// Setup the entries
for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++)
{
DWORD dwFileIndex = pTocEntry->FileIndex;
pFileIndices[dwFileIndex].AssetIndex = pTocEntry->AssetIndex;
pFileIndices[dwFileIndex].FileIndex = pTocEntry->FileIndex;
pFileIndices[dwFileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocPtr) + pTocEntry->NameOffset;
pTocEntry++;
}
}
// Save the file to the root handler
pbCoreTocData = pbCoreTocPtr;
nFileIndices = dwMaxFileIndex;
nError = ERROR_SUCCESS;
}
}
return nError;
}
// Packages.dat contains a list of full file names (without locale prefix).
// They are not sorted, nor they correspond to file IDs.
// Does the sort order mean something? Perhaps we could use them as listfile?
int CreateMapOfRealNames(TCascStorage * hs, const char * szFileName)
{
DWORD Signature = 0;
DWORD NumberOfNames = 0;
// Load the entire file to memory
pbPackagesDat = LoadFileToMemory(hs, szFileName, &cbPackagesDat);
if(pbPackagesDat && cbPackagesDat)
{
LPBYTE pbPackagesPtr = pbPackagesDat;
LPBYTE pbPackagesEnd = pbPackagesDat + cbPackagesDat;
/*
LPBYTE pbPackagesPtr = pbPackagesDat + 8;
FILE * fp = fopen("E:\\Packages.dat", "wt");
if(fp != NULL)
{
while(pbPackagesPtr < pbPackagesEnd)
{
fprintf(fp, "%s\n", pbPackagesPtr);
pbPackagesPtr = pbPackagesPtr + strlen((char *)pbPackagesPtr) + 1;
}
fclose(fp);
}
*/
// Get the header. There is just Signature + NumberOfNames
if((pbPackagesPtr = CaptureInteger32(pbPackagesPtr, pbPackagesEnd, &Signature)) == NULL)
return ERROR_BAD_FORMAT;
if((pbPackagesPtr = CaptureInteger32(pbPackagesPtr, pbPackagesEnd, &NumberOfNames)) == NULL)
return ERROR_BAD_FORMAT;
if(Signature != DIABLO3_PACKAGES_SIGNATURE || NumberOfNames == 0)
return ERROR_BAD_FORMAT;
// Create the map for fast search of the file name
if(PackagesMap.Create(NumberOfNames, 0, 0, KeyIsString) == ERROR_SUCCESS)
{
const char * szPackageName = (const char *)pbPackagesPtr;
// Go as long as there is something
for(DWORD i = 0; i < NumberOfNames; i++)
{
// Get the file extension
if((LPBYTE)szPackageName >= pbPackagesEnd)
break;
// Insert the file name to the map. The file extension is not included
PackagesMap.InsertString(szPackageName, true);
szPackageName = szPackageName + strlen(szPackageName) + 1;
}
}
}
return ERROR_SUCCESS;
}
int Load(TCascStorage * hs, DIABLO3_DIRECTORY & RootDirectory)
{
PATH_BUFFER PathBuffer;
char szPathBuffer[MAX_PATH];
int nError;
// Initialize path buffer and go parse the directory
PathBuffer.szBegin = szPathBuffer;
PathBuffer.szPtr = szPathBuffer;
PathBuffer.szEnd = szPathBuffer + MAX_PATH;
szPathBuffer[0] = 0;
// Always parse the named entries first. They always point to a file.
// These are entries with arbitrary names, and they do not belong to an asset
nError = ParseDirectory_Phase1(hs, RootDirectory, PathBuffer, true);
if(nError == ERROR_SUCCESS)
{
// The asset entries in the ROOT file don't contain file names, but indices.
// To convert a file index to a file name, we need to load and parse the "Base\\CoreTOC.dat" file.
nError = CreateMapOfFileIndices(hs, "Base\\CoreTOC.dat");
if(nError == ERROR_SUCCESS)
{
// The file "Base\Data_D3\PC\Misc\Packages.dat" contains the file names
// (without level-0 and level-1 directory).
// We can use these names for supplying the missing extensions
CreateMapOfRealNames(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat");
// Now parse all folders and resolve the full names
ParseDirectory_Phase2(hs);
}
// Free all stuff that was used during loading of the ROOT file
FreeLoadingStuff();
}
return nError;
}
void FreeLoadingStuff()
{
// Free the captured root sub-directories
for(size_t i = 0; i < DIABLO3_MAX_SUBDIRS; i++)
CASC_FREE(RootFolders[i].pbDirectoryData);
// Free the package map
PackagesMap.Free();
// Free the array of file indices
CASC_FREE(pFileIndices);
// Free the loaded CoreTOC.dat file
CASC_FREE(pbCoreTocFile);
// Free the loaded Packages.dat file
CASC_FREE(pbPackagesDat);
}
// Array of root directory subdirectories
DIABLO3_DIRECTORY RootFolders[DIABLO3_MAX_ROOT_FOLDERS];
// Array of DIABLO3_TOC_ENTRY structures, sorted by the file index
// Used for converting FileIndex -> Asset+PlainName during loading
PDIABLO3_CORE_TOC_ENTRY pFileIndices;
LPBYTE pbCoreTocFile;
LPBYTE pbCoreTocData;
size_t nFileIndices;
DWORD cbCoreTocFile;
// Map for searching a real file extension
CASC_MAP PackagesMap;
LPBYTE pbPackagesDat;
DWORD cbPackagesDat;
};
//-----------------------------------------------------------------------------
// Public functions
int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TDiabloRoot * pRootHandler = NULL;
DIABLO3_DIRECTORY RootDirectory;
int nError = ERROR_BAD_FORMAT;
// Verify the header of the ROOT file
if(TDiabloRoot::CaptureDirectoryData(RootDirectory, pbRootFile, cbRootFile) != NULL)
{
// Allocate the root handler object
pRootHandler = new TDiabloRoot();
if(pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
nError = pRootHandler->Load(hs, RootDirectory);
if(nError != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return nError;
}
+121
View File
@@ -0,0 +1,121 @@
/*****************************************************************************/
/* CascRootFile_Install.cpp Copyright (c) Ladislav Zezula 2018 */
/*---------------------------------------------------------------------------*/
/* Support for ROOT handler based on INSTALL manifest */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 21.05.19 1.00 Lad The first version of CascRootFile_Install.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Handler definitions for Starcraft I root file
struct TRootHandler_Install : public TFileTreeRoot
{
public:
TRootHandler_Install() : TFileTreeRoot(0)
{
// We have file names and return CKey as result of search
dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY);
}
static int CaptureInstallHeader(CASC_INSTALL_HEADER & InHeader, LPBYTE pbFileData, size_t cbFileData)
{
PFILE_INSTALL_HEADER pFileHeader = (PFILE_INSTALL_HEADER)pbFileData;
// Check the signature ('DL') and version
if (cbFileData < sizeof(FILE_INSTALL_HEADER) || pFileHeader->Magic != FILE_MAGIC_INSTALL || pFileHeader->Version != 1)
return ERROR_BAD_FORMAT;
// Note that we don't support CKey sizes greater than 0x10 in the INSTALL file
if (pFileHeader->EKeyLength > MD5_HASH_SIZE)
return ERROR_BAD_FORMAT;
// Capture the header version 1
memset(&InHeader, 0, sizeof(CASC_INSTALL_HEADER));
InHeader.Magic = pFileHeader->Magic;
InHeader.Version = pFileHeader->Version;
InHeader.EKeyLength = pFileHeader->EKeyLength;
InHeader.TagCount = ConvertBytesToInteger_2(pFileHeader->TagCount);
InHeader.EntryCount = ConvertBytesToInteger_4(pFileHeader->EntryCount);
InHeader.HeaderLength = sizeof(FILE_INSTALL_HEADER);
return ERROR_SUCCESS;
}
int Load(TCascStorage * hs, CASC_INSTALL_HEADER InHeader, LPBYTE pbInstallFile, LPBYTE pbInstallEnd)
{
PCASC_CKEY_ENTRY pCKeyEntry;
const char * szString;
size_t nBitmapLength;
size_t nFileCount = InHeader.EntryCount;
// Skip the header
pbInstallFile += InHeader.HeaderLength;
// Skip the tags
for (DWORD i = 0; i < InHeader.TagCount; i++)
{
szString = (const char *)pbInstallFile;
nBitmapLength = GetTagBitmapLength(pbInstallFile, pbInstallEnd, InHeader.EntryCount);
pbInstallFile = pbInstallFile + strlen(szString) + 1 + sizeof(USHORT) + nBitmapLength;
}
// Load the names and insert them to the root handler
while(nFileCount > 0 && pbInstallFile < pbInstallEnd)
{
// File Name and CKey
szString = (const char *)pbInstallFile;
pbInstallFile += strlen(szString) + 1;
// Verify whether it is a known entry
pCKeyEntry = FindCKeyEntry_CKey(hs, pbInstallFile);
pbInstallFile += MD5_HASH_SIZE + sizeof(DWORD);
// Insert the FileName+CKey to the file tree
if (pCKeyEntry != NULL)
FileTree.InsertByName(pCKeyEntry, szString);
nFileCount--;
}
return ERROR_SUCCESS;
}
};
//-----------------------------------------------------------------------------
// Public functions
//
// Starcraft ROOT file is a text file with the following format:
// HD2/portraits/NBluCrit/NLCFID01.webm|c2795b120592355d45eba9cdc37f691e
// locales/enUS/Assets/campaign/EXPZerg/Zerg08/staredit/wav/zovtra01.ogg|316b0274bf2dabaa8db60c3ff1270c85
// locales/zhCN/Assets/sound/terran/ghost/tghdth01.wav|6637ed776bd22089e083b8b0b2c0374c
//
int RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbInstallFile, DWORD cbInstallFile)
{
CASC_INSTALL_HEADER InHeader;
TRootHandler_Install * pRootHandler = NULL;
int nError = ERROR_BAD_FORMAT;
// Capture the header of the DOWNLOAD file
nError = TRootHandler_Install::CaptureInstallHeader(InHeader, pbInstallFile, cbInstallFile);
if (nError == ERROR_SUCCESS)
{
// Allocate the root handler object
pRootHandler = new TRootHandler_Install();
if (pRootHandler != NULL)
{
// Parse the entire install manifest
nError = pRootHandler->Load(hs, InHeader, pbInstallFile, pbInstallFile + cbInstallFile);
hs->pRootHandler = pRootHandler;
}
}
return nError;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+605
View File
@@ -0,0 +1,605 @@
/*****************************************************************************/
/* CascRootFile_Text.cpp Copyright (c) Ladislav Zezula 2017 */
/*---------------------------------------------------------------------------*/
/* Support for loading ROOT files in plain text */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 28.10.15 1.00 Lad The first version of CascRootFile_Text.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Structure definitions for CMF files
#define MAX_LINE_ELEMENTS 8
typedef struct _CMF_HEADER_V3
{
DWORD BuildVersion;
DWORD Unknown0;
DWORD Unknown1;
DWORD Unknown2;
DWORD Unknown3;
DWORD DataCount;
DWORD Unknown4;
DWORD EntryCount;
DWORD Magic;
} CMF_HEADER_V3, *PCMF_HEADER_V3;
typedef struct _CMF_HEADER_V2
{
DWORD BuildVersion;
DWORD Unknown0;
DWORD Unknown1;
DWORD Unknown2;
DWORD DataCount;
DWORD Unknown3;
DWORD EntryCount;
DWORD Magic;
} CMF_HEADER_V2, *PCMF_HEADER_V2;
typedef struct _CMF_HEADER_V1
{
DWORD BuildVersion;
DWORD Unknown0;
DWORD DataCount;
DWORD Unknown1;
DWORD EntryCount;
DWORD Magic;
} CMF_HEADER_V1, *PCMF_HEADER_V1;
//-----------------------------------------------------------------------------
// Structure definitions for APM files
// In-memory format
typedef struct _APM_ENTRY
{
DWORD Index;
ULONGLONG HashA;
ULONGLONG HashB;
} APM_ENTRY, *PAPM_ENTRY;
// On-disk format, size = 0x14
typedef struct _APM_ENTRY_V2
{
DWORD Index;
DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned
DWORD HashA_Hi;
DWORD HashB_Lo;
DWORD HashB_Hi;
} APM_ENTRY_V2, *PAPM_ENTRY_V2;
// On-disk format, size = 0x0C
typedef struct _APM_ENTRY_V1
{
DWORD Index;
DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned
DWORD HashA_Hi;
} APM_ENTRY_V1, *PAPM_ENTRY_V1;
// In-memory format
typedef struct _APM_PACKAGE_ENTRY
{
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
DWORD Unknown3;
ULONGLONG Unknown4;
} APM_PACKAGE_ENTRY, *PAPM_PACKAGE_ENTRY;
// On-disk format
typedef struct _APM_PACKAGE_ENTRY_V2
{
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
DWORD Unknown3;
ULONGLONG Unknown4;
} APM_PACKAGE_ENTRY_V2, *PAPM_PACKAGE_ENTRY_V2;
// On-disk format
typedef struct _APM_PACKAGE_ENTRY_V1
{
ULONGLONG EntryPointGUID; // virtual most likely
ULONGLONG PrimaryGUID; // real
ULONGLONG SecondaryGUID; // real
ULONGLONG Key; // encryption
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
} APM_PACKAGE_ENTRY_V1, *PAPM_PACKAGE_ENTRY_V1;
typedef struct _APM_HEADER_V3
{
ULONGLONG BuildNumber; // Build number of the game
ULONGLONG ZeroValue1;
DWORD ZeroValue2;
DWORD PackageCount;
DWORD ZeroValue3;
DWORD EntryCount;
DWORD Checksum;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V3, *PAPM_HEADER_V3;
typedef struct _APM_HEADER_V2
{
ULONGLONG BuildNumber; // Build number of the game
ULONGLONG ZeroValue1;
DWORD PackageCount;
DWORD ZeroValue2;
DWORD EntryCount;
DWORD Checksum;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V2, *PAPM_HEADER_V2;
typedef struct _APM_HEADER_V1
{
ULONGLONG BuildNumber; // Build number of the game
DWORD BuildVersion;
DWORD PackageCount;
DWORD EntryCount;
DWORD Checksum;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V1, *PAPM_HEADER_V1;
//-----------------------------------------------------------------------------
// Handler classes
/*
struct TCmfFile
{
TCmfFile()
{
memset(this, 0, sizeof(TCmfFile));
}
LPBYTE CaptureHeader(LPBYTE pbCmfData, LPBYTE pbCmfEnd)
{
DWORD BuildNumber = *(PDWORD)pbCmfData;
// Check the newest header version
if(BuildNumber >= 45104 && BuildNumber != 45214)
{
PCMF_HEADER_V3 pHeader3 = (PCMF_HEADER_V3)pbCmfData;
if ((LPBYTE)(pHeader3 + 1) > pbCmfEnd)
return NULL;
BuildVersion = pHeader3->BuildVersion;
DataCount = pHeader3->DataCount;
EntryCount = pHeader3->EntryCount;
Magic = pHeader3->Magic;
return (LPBYTE)(pHeader3 + 1);
}
else if(BuildNumber >= 39028)
{
// TODO
assert(false);
return NULL;
}
else
{
// TODO
assert(false);
return NULL;
}
}
DWORD BuildVersion;
DWORD DataCount;
DWORD EntryCount;
DWORD Magic;
};
struct TApmFile
{
TApmFile()
{
memset(this, 0, sizeof(TApmFile));
}
~TApmFile()
{
CASC_FREE(pApmPackages);
CASC_FREE(pApmEntries);
}
LPBYTE CaptureHeader(LPBYTE pbApmData, LPBYTE pbApmEnd)
{
// Check the data size for the largest possible header size
if((pbApmData + sizeof(APM_HEADER_V3)) < pbApmEnd)
{
// Try the version 3
PAPM_HEADER_V3 pApmFile3 = (PAPM_HEADER_V3)(pbApmData);
if(pApmFile3->ZeroValue1 == 0 && pApmFile3->ZeroValue2 == 0 && pApmFile3->PackageCount && pApmFile3->EntryCount && pApmFile3->Checksum)
{
BuildNumber = pApmFile3->BuildNumber;
PackageCount = pApmFile3->PackageCount;
EntryCount = pApmFile3->EntryCount;
Checksum = pApmFile3->Checksum;
return pbApmData + 0x24;
}
// Try the version 2
PAPM_HEADER_V2 pApmFile2 = (PAPM_HEADER_V2)(pbApmData);
if(pApmFile2->ZeroValue1 == 0 && pApmFile2->PackageCount && pApmFile2->EntryCount && pApmFile2->Checksum)
{
BuildNumber = pApmFile2->BuildNumber;
PackageCount = pApmFile2->PackageCount;
EntryCount = pApmFile2->EntryCount;
Checksum = pApmFile2->Checksum;
return pbApmData + 0x20;
}
// Try the version 1 (build 24919)
PAPM_HEADER_V1 pApmHeader1 = (PAPM_HEADER_V1)(pbApmData);
if(pApmHeader1->BuildVersion != 0 && pApmHeader1->PackageCount && pApmHeader1->EntryCount && pApmHeader1->Checksum)
{
BuildNumber = pApmHeader1->BuildNumber;
PackageCount = pApmHeader1->PackageCount;
EntryCount = pApmHeader1->EntryCount;
Checksum = pApmHeader1->Checksum;
return pbApmData + 0x18;
}
}
return NULL;
}
LPBYTE CaptureArrayOfEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd)
{
// Allocate array of entries
pApmEntries = CASC_ALLOC(APM_ENTRY, EntryCount);
if(pApmEntries != NULL)
{
// The newest format
if(BuildNumber > 45104 && BuildNumber != 45214)
{
PAPM_ENTRY_V2 pEntry2 = (PAPM_ENTRY_V2)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry2 + EntryCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < EntryCount; i++)
{
pApmEntries[i].Index = pEntry2->Index;
pApmEntries[i].HashA = MAKE_OFFSET64(pEntry2->HashA_Hi, pEntry2->HashA_Lo);
pApmEntries[i].HashB = MAKE_OFFSET64(pEntry2->HashB_Hi, pEntry2->HashB_Lo);
}
return pbEntriesEnd;
}
}
else
{
PAPM_ENTRY_V1 pEntry1 = (PAPM_ENTRY_V1)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry1 + EntryCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < EntryCount; i++)
{
pApmEntries[i].Index = pEntry1->Index;
pApmEntries[i].HashA = MAKE_OFFSET64(pEntry1->HashA_Hi, pEntry1->HashA_Lo);
pApmEntries[i].HashB = 0;
}
return pbEntriesEnd;
}
}
}
return NULL;
}
LPBYTE CapturePackageEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd)
{
// Allocate array of entries
pApmPackages = CASC_ALLOC(APM_PACKAGE_ENTRY, PackageCount);
if(pApmPackages != NULL)
{
// Zero the entire array
memset(pApmPackages, 0, PackageCount * sizeof(APM_PACKAGE_ENTRY));
// The newest format
if(BuildNumber > 45104 && BuildNumber != 45214)
{
PAPM_PACKAGE_ENTRY_V2 pEntry2 = (PAPM_PACKAGE_ENTRY_V2)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry2 + PackageCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < PackageCount; i++)
{
pApmPackages[i].PackageGUID = pEntry2[i].PackageGUID;
pApmPackages[i].Unknown1 = pEntry2[i].Unknown1;
pApmPackages[i].Unknown2 = pEntry2[i].Unknown2;
pApmPackages[i].Unknown3 = pEntry2[i].Unknown3;
pApmPackages[i].Unknown4 = pEntry2[i].Unknown4;
}
return pbEntriesEnd;
}
}
else
{
PAPM_PACKAGE_ENTRY_V1 pEntry1 = (PAPM_PACKAGE_ENTRY_V1)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry1 + PackageCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < PackageCount; i++)
{
// TODO!!!
pApmPackages[i].PackageGUID = pEntry1->PackageGUID;
}
return pbEntriesEnd;
}
}
}
return NULL;
}
PAPM_ENTRY pApmEntries;
PAPM_PACKAGE_ENTRY pApmPackages;
ULONGLONG BuildNumber;
DWORD PackageCount;
DWORD EntryCount;
DWORD Checksum;
size_t HeaderSize;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
};
*/
//-----------------------------------------------------------------------------
// Handler definition for OVERWATCH root file
//
// -------------------------------------
// Overwatch ROOT file (build 24919):
// -------------------------------------
// #MD5|CHUNK_ID|FILENAME|INSTALLPATH
// FE3AD8A77EEF77B383DF4929AED816FD|0|RetailClient/GameClientApp.exe|GameClientApp.exe
// 5EDDEFECA544B6472C5CD52BE63BC02F|0|RetailClient/Overwatch Launcher.exe|Overwatch Launcher.exe
// 6DE09F0A67F33F874F2DD8E2AA3B7AAC|0|RetailClient/ca-bundle.crt|ca-bundle.crt
// 99FE9EB6A4BB20209202F8C7884859D9|0|RetailClient/ortp_x64.dll|ortp_x64.dll
//
// -------------------------------------
// Overwatch ROOT file (build 47161):
// -------------------------------------
// #FILEID|MD5|CHUNK_ID|PRIORITY|MPRIORITY|FILENAME|INSTALLPATH
// RetailClient/Overwatch.exe|807F96661280C07E762A8C129FEBDA6F|0|0|255|RetailClient/Overwatch.exe|Overwatch.exe
// RetailClient/Overwatch Launcher.exe|5EDDEFECA544B6472C5CD52BE63BC02F|0|0|255|RetailClient/Overwatch Launcher.exe|Overwatch Launcher.exe
// RetailClient/ortp_x64.dll|7D1B5DEC267480F3E8DAD6B95143A59C|0|0|255|RetailClient/ortp_x64.dll|ortp_x64.dll
//
struct TRootHandler_OW : public TFileTreeRoot
{
TRootHandler_OW() : TFileTreeRoot(0)
{
// We have file names and return CKey as result of search
dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY);
}
/*
bool IsManifestFolderName(const char * szFileName, const char * szManifestFolder, size_t nLength)
{
if(!_strnicmp(szFileName, szManifestFolder, nLength))
{
return (szFileName[nLength] == '\\' || szFileName[nLength] == '/');
}
return false;
}
bool IsApmFileName(const char * szFileName)
{
const char * szExtension;
if(IsManifestFolderName(szFileName, "Manifest", 8) || IsManifestFolderName(szFileName, "TactManifest", 12))
{
szExtension = GetFileExtension(szFileName);
if(!_stricmp(szExtension, ".apm"))
{
return true;
}
}
return false;
}
int LoadApmFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName)
{
TApmFile ApmFile;
LPBYTE pbApmData;
DWORD cbApmData = 0;
int nError = ERROR_BAD_FORMAT;
pbApmData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbApmData);
if(pbApmData != NULL)
{
LPBYTE pbApmEnd = pbApmData + cbApmData;
LPBYTE pbApmPtr = pbApmData;
pbApmPtr = ApmFile.CaptureHeader(pbApmPtr, pbApmEnd);
if(pbApmPtr == NULL)
return ERROR_BAD_FORMAT;
// Read the array of entries
pbApmPtr = ApmFile.CaptureArrayOfEntries(pbApmPtr, pbApmEnd);
if(pbApmPtr == NULL)
return ERROR_BAD_FORMAT;
// Read the array of package entries
pbApmPtr = ApmFile.CapturePackageEntries(pbApmPtr, pbApmEnd);
if(pbApmPtr == NULL)
return ERROR_BAD_FORMAT;
CASC_FREE(pbApmData);
}
return nError;
}
static int LoadCmfFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName)
{
TCmfFile CmfFile;
LPBYTE pbCmfData;
DWORD cbCmfData = 0;
int nError = ERROR_BAD_FORMAT;
pbCmfData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbCmfData);
if(pbCmfData != NULL)
{
LPBYTE pbCmfEnd = pbCmfData + cbCmfData;
LPBYTE pbCmfPtr = pbCmfData;
// Capture the CMF header
pbCmfPtr = CmfFile.CaptureHeader(pbCmfPtr, pbCmfEnd);
if(pbCmfPtr == NULL)
return ERROR_BAD_FORMAT;
// if(CmfFile.Magic >= 0x636D6614)
// DecryptCmfFile(
CASC_FREE(pbCmfData);
}
return nError;
}
*/
int Load(TCascStorage * hs, CASC_CSV & Csv, size_t nFileNameIndex, size_t nCKeyIndex)
{
PCASC_CKEY_ENTRY pCKeyEntry;
// size_t ApmFiles[0x80];
// size_t nApmFiles = 0;
BYTE CKey[MD5_HASH_SIZE];
CASCLIB_UNUSED(hs);
// Keep loading every line until there is something
while(Csv.LoadNextLine())
{
const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][nFileNameIndex];
const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][nCKeyIndex];
// Retrieve the file name and the content key
if(FileName.szValue && CKeyStr.szValue && CKeyStr.nLength == MD5_STRING_SIZE)
{
// Convert the string CKey to binary
if(ConvertStringToBinary(CKeyStr.szValue, MD5_STRING_SIZE, CKey) == ERROR_SUCCESS)
{
// Find the item in the tree
if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKey)) != NULL)
{
// Insert the file name and the CKey into the tree
FileTree.InsertByName(pCKeyEntry, FileName.szValue);
// If the file name is actually an asset, we need to parse that asset and load files in it
// if(IsApmFileName(szFileName))
// {
// ApmFiles[nApmFiles++] = FileTree_IndexOf(&pRootHandler->FileTree, pFileNode1);
// }
}
}
}
}
/*
// Load all CMF+APM files
if(nError == ERROR_SUCCESS)
{
for(size_t i = 0; i < nApmFiles; i++)
{
char szApmFile[MAX_PATH + 1];
char szCmfFile[MAX_PATH + 1];
// Get the n-th item and its name
pFileNode1 = (PCASC_FILE_NODE)FileTree_PathAt(&pRootHandler->FileTree, szApmFile, MAX_PATH, ApmFiles[i]);
if(pFileNode1 == NULL)
break;
if(strcmp(szApmFile, "TactManifest\\Win_SPWin_RDEV_LenUS_EExt.apm"))
continue;
// Get the name of thew CMF file
CascStrCopy(szCmfFile, _countof(szCmfFile), szApmFile);
CascStrCopy((char *)GetFileExtension(szCmfFile), 5, ".cmf");
pFileNode2 = (PCASC_FILE_NODE)FileTree_Find(&pRootHandler->FileTree, szCmfFile);
if(pFileNode2 == NULL)
break;
// Create the map of CMF entries
nError = LoadCmfFile(hs, pFileNode2->CKey, szCmfFile);
if(nError != ERROR_SUCCESS)
break;
}
}
*/
return ERROR_SUCCESS;
}
};
//-----------------------------------------------------------------------------
// Public functions
// TODO: There is way more files in the Overwatch CASC storage than present in the ROOT file.
int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TRootHandler_OW * pRootHandler = NULL;
CASC_CSV Csv(0, true);
size_t Indices[2];
int nError;
// Load the ROOT file
nError = Csv.Load(pbRootFile, cbRootFile);
if(nError == ERROR_SUCCESS)
{
// Retrieve the indices of the file name and MD5 columns
Indices[0] = Csv.GetColumnIndex("FILENAME");
Indices[1] = Csv.GetColumnIndex("MD5");
// If both indices were found OK, then load the root file
if(Indices[0] != CSV_INVALID_INDEX && Indices[1] != CSV_INVALID_INDEX)
{
pRootHandler = new TRootHandler_OW();
if (pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
nError = pRootHandler->Load(hs, Csv, Indices[0], Indices[1]);
if (nError != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
else
{
nError = ERROR_BAD_FORMAT;
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return nError;
}
+634
View File
@@ -0,0 +1,634 @@
/*****************************************************************************/
/* CascRootFile_TVFS.cpp Copyright (c) Ladislav Zezula 2018 */
/*---------------------------------------------------------------------------*/
/* ROOT handler for TACT VFS manifest format (root) */
/* Note: TACT = Trusted Application Content Transfer */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 24.05.18 1.00 Lad The first version of CascRootFile_TVFS.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local defines
#define TVFS_FLAG_INCLUDE_CKEY 0x0001 // Include C-key in content file record
#define TVFS_FLAG_WRITE_SUPPORT 0x0002 // Write support. Include a table of encoding specifiers. This is required for writing files to the underlying storage. This bit is implied by the patch-support bit
#define TVFS_FLAG_PATCH_SUPPORT 0x0004 // Patch support. Include patch records in the content file records.
#define TVFS_FLAG_LOWERCASE_MANIFEST 0x0008 // Lowercase manifest. All paths in the path table have been converted to ASCII lowercase (i.e. [A-Z] converted to [a-z])
#define TVFS_PTE_PATH_SEPARATOR_PRE 0x0001 // There is path separator before the name
#define TVFS_PTE_PATH_SEPARATOR_POST 0x0002 // There is path separator after the name
#define TVFS_PTE_NODE_VALUE 0x0004 // The NodeValue in path table entry is valid
#define TVFS_FOLDER_NODE 0x80000000 // Highest bit is set if a file node is a folder
#define TVFS_FOLDER_SIZE_MASK 0x7FFFFFFF // Mask to get length of the folder
//-----------------------------------------------------------------------------
// Local structures
// In-memory layout of the TVFS file header
typedef struct _TVFS_DIRECTORY_HEADER
{
DWORD Signature; // Must be CASC_TVFS_ROOT_SIGNATURE
BYTE FormatVersion; // Version of the format. Should be 1.
BYTE HeaderSize; // Size of the header, in bytes
BYTE EKeySize; // Size of an E-Key. TACT uses 9-byte E-keys
BYTE PatchKeySize; // Size of a patch key. TACT uses 9-byte P-keys
DWORD Flags; // Flags. See TVFS_FLAG_XXX
// Followed by the offset table (variable length)
DWORD PathTableOffset; // Offset of the path table
DWORD PathTableSize; // Size of the path table
DWORD VfsTableOffset; // Offset of the VFS table
DWORD VfsTableSize; // Size of the VFS table
DWORD CftTableOffset; // Offset of the container file table
DWORD CftTableSize; // Size of the container file table
USHORT MaxDepth; // The maximum depth of the path prefix tree stored in the path table
DWORD EstTableOffset; // The offset of the encoding specifier table. Only if the write-support bit is set in the header flag
DWORD EstTableSize; // The size of the encoding specifier table. Only if the write-support bit is set in the header flag
DWORD CftOffsSize; // Byte length of the offset in the Content File Table entry
DWORD EstOffsSize; // Byte length of the offset in the Encoding Specifier Table entry
LPBYTE pbDirectoryData; // Pointer to the begin of directory data
LPBYTE pbDirectoryEnd; // Pointer to the end of directory data
// LPBYTE pbPathFileTable; // Begin and end of the path table
// LPBYTE pbPathTableEnd;
// LPBYTE pbVfsFileTable; // Begin and end of the VFS file table
// LPBYTE pbVfsTableEnd;
// LPBYTE pbCftFileTable; // Begin and end of the content file table
// LPBYTE pbCftTableEnd;
} TVFS_DIRECTORY_HEADER, *PTVFS_DIRECTORY_HEADER;
/*
// Minimum size of a valid path table entry. 1 byte + 1-byte name + 1 byte + DWORD
#define TVFS_HEADER_LENGTH FIELD_OFFSET(TVFS_DIRECTORY_HEADER, CftOffsSize)
// Minimum size of a valid path table entry. 1 byte + 1-byte name + 1 byte + DWORD
#define TVFS_MIN_PATH_ENTRY (1 + 1 + 1 + sizeof(DWORD))
// Minimum size of the VFS entry (SpanCount + FileOffset + SpanLength + CftOffset)
#define TVFS_MIN_VFS_ENTRY (1 + sizeof(DWORD) + sizeof(DWORD) + 1)
// Minimum size of the Content File Table entry (CASC_EKEY_SIZE + EncodedSize + ContentSize)
#define TVFS_MIN_CFT_ENTRY (CASC_EKEY_SIZE + sizeof(DWORD) + sizeof(DWORD))
// Minimum size of the TVFS folder data
#define TVFS_MIN_FILE_SIZE (TVFS_HEADER_LENGTH + TVFS_MIN_PATH_ENTRY + TVFS_MIN_VFS_ENTRY + TVFS_MIN_CFT_ENTRY)
// Maximum estimated file table. Empirically set to 8 MB, increase if needed.
#define TVFS_MAX_FILE_SIZE 0x00800000
*/
// In-memory layout of the path table entry
typedef struct _TVFS_PATH_TABLE_ENTRY
{
LPBYTE pbNamePtr; // Pointer to the begin of the node name
LPBYTE pbNameEnd; // Pointer to the end of the file name
DWORD NodeFlags; // TVFS_PTE_XXX
DWORD NodeValue; // Node value
} TVFS_PATH_TABLE_ENTRY, *PTVFS_PATH_TABLE_ENTRY;
//-----------------------------------------------------------------------------
// Handler definition for TVFS root file
//static FILE * fp = NULL;
// Structure for the root handler
struct TRootHandler_TVFS : public TFileTreeRoot
{
public:
TRootHandler_TVFS() : TFileTreeRoot(0)
{
// TVFS supports file names, but DOESN'T support CKeys.
dwFeatures |= CASC_FEATURE_FILE_NAMES;
dwNestLevel = 0;
}
// Returns size of "container file table offset" fiels in the VFS.
// - If the container file table is larger than 0xffffff bytes, it's 4 bytes
// - If the container file table is larger than 0xffff bytes, it's 3 bytes
// - If the container file table is larger than 0xff bytes, it's 2 bytes
// - If the container file table is smaller than 0xff bytes, it's 1 byte
static DWORD GetOffsetFieldSize(DWORD dwTableSize)
{
if(dwTableSize > 0xffffff)
return 4;
if(dwTableSize > 0xffff)
return 3;
if(dwTableSize > 0xff)
return 2;
return 1;
}
bool PathBuffer_AddChar(PATH_BUFFER & PathBuffer, char chOneChar)
{
if(PathBuffer.szPtr >= PathBuffer.szEnd)
return false;
*PathBuffer.szPtr++ = chOneChar;
*PathBuffer.szPtr = 0;
return true;
}
bool PathBuffer_AddString(PATH_BUFFER & PathBuffer, LPBYTE pbNamePtr, LPBYTE pbNameEnd)
{
size_t nLength = (pbNameEnd - pbNamePtr);
// Check whether we have enough space
if ((PathBuffer.szPtr + nLength) > PathBuffer.szEnd)
return false;
// Copy the node name
memcpy(PathBuffer.szPtr, pbNamePtr, nLength);
PathBuffer.szPtr += nLength;
return true;
}
bool PathBuffer_AppendNode(PATH_BUFFER & PathBuffer, TVFS_PATH_TABLE_ENTRY & PathEntry)
{
// Append the prefix separator, if needed
if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE)
PathBuffer_AddChar(PathBuffer, '/');
// Append the name fragment, if any
if (PathEntry.pbNameEnd > PathEntry.pbNamePtr)
PathBuffer_AddString(PathBuffer, PathEntry.pbNamePtr, PathEntry.pbNameEnd);
// Append the postfix separator, if needed
if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST)
PathBuffer_AddChar(PathBuffer, '/');
// Always end the buffer with zero
PathBuffer.szPtr[0] = 0;
return true;
}
static int CaptureDirectoryHeader(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd)
{
// Fill the header structure with zeros
memset(&DirHeader, 0, sizeof(TVFS_DIRECTORY_HEADER));
DirHeader.pbDirectoryData = pbDataPtr;
DirHeader.pbDirectoryEnd = pbDataEnd;
// Capture the signature
pbDataPtr = CaptureInteger32(pbDataPtr, pbDataEnd, &DirHeader.Signature);
if(pbDataPtr == NULL || DirHeader.Signature != CASC_TVFS_ROOT_SIGNATURE)
return ERROR_BAD_FORMAT;
// Capture the other four integers
pbDataPtr = CaptureByteArray(pbDataPtr, pbDataEnd, 4, &DirHeader.FormatVersion);
if(pbDataPtr == NULL || DirHeader.FormatVersion != 1 || DirHeader.EKeySize != 9 || DirHeader.PatchKeySize != 9 || DirHeader.HeaderSize < 8)
return ERROR_BAD_FORMAT;
// Capture the rest
pbDataPtr = CaptureByteArray(pbDataPtr, pbDataEnd, DirHeader.HeaderSize - FIELD_OFFSET(TVFS_DIRECTORY_HEADER, Flags), (LPBYTE)(&DirHeader.Flags));
if(pbDataPtr == NULL)
return ERROR_BAD_FORMAT;
// Swap the header values
DirHeader.Flags = ConvertBytesToInteger_4_LE((LPBYTE)(&DirHeader.Flags));
// Swap the offset table values
DirHeader.PathTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.PathTableOffset));
DirHeader.PathTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.PathTableSize));
DirHeader.VfsTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.VfsTableOffset));
DirHeader.VfsTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.VfsTableSize));
DirHeader.CftTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.CftTableOffset));
DirHeader.CftTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.CftTableSize));
DirHeader.MaxDepth = (USHORT)ConvertBytesToInteger_2((LPBYTE)(&DirHeader.MaxDepth));
DirHeader.EstTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.EstTableOffset));
DirHeader.EstTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.EstTableSize));
// Determine size of file table offsets
DirHeader.CftOffsSize = GetOffsetFieldSize(DirHeader.CftTableSize);
DirHeader.EstOffsSize = GetOffsetFieldSize(DirHeader.EstTableSize);
// Capture the path table
// DirHeader.pbPathFileTable = pbDirectory + DirHeader.PathTableOffset;
// DirHeader.pbPathTableEnd = pbDirectory + DirHeader.PathTableOffset + DirHeader.PathTableSize;
// if(DirHeader.pbPathTableEnd > pbDataEnd)
// return ERROR_BAD_FORMAT;
// Capture the VFS file table
// DirHeader.pbVfsFileTable = pbDirectory + DirHeader.VfsTableOffset;
// DirHeader.pbVfsTableEnd = pbDirectory + DirHeader.VfsTableOffset + DirHeader.VfsTableSize;
// if(DirHeader.pbVfsTableEnd > pbDataEnd)
// return ERROR_BAD_FORMAT;
// Capture the container file table
// DirHeader.pbCftFileTable = pbDirectory + DirHeader.CftTableOffset;
// DirHeader.pbCftTableEnd = pbDirectory + DirHeader.CftTableOffset + DirHeader.CftTableSize;
// if(DirHeader.pbCftTableEnd > pbDataEnd)
// return ERROR_BAD_FORMAT;
return ERROR_SUCCESS;
}
int CaptureVfsSpanEntries(TVFS_DIRECTORY_HEADER & DirHeader, PENCODED_KEY pEKey, PDWORD PtrSpanSize, DWORD dwVfsOffset)
{
LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset;
LPBYTE pbVfsFileEntry = pbVfsFileTable + dwVfsOffset;
LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize;
LPBYTE pbCftFileTable;
LPBYTE pbCftFileEntry;
LPBYTE pbCftFileEnd;
LPBYTE pbTemp = NULL;
size_t ItemSize = sizeof(DWORD) + sizeof(DWORD) + DirHeader.CftOffsSize;
DWORD dwCftOffset;
DWORD dwSpanCount;
DWORD dwSpanSize;
// Get the number of span entries
if(!(pbVfsFileTable <= pbVfsFileEntry && pbVfsFileEntry <= pbVfsFileEnd))
return ERROR_INVALID_PARAMETER;
dwSpanCount = *pbVfsFileEntry++;
// 1 - 224 = valid file, 225-254 = other file, 255 = deleted file
// We will ignore all files with unsupported span count
if(dwSpanCount == 0 || dwSpanCount > 224)
return ERROR_BAD_FORMAT;
// So far we've only saw entries with 1 span.
// Need to test files with multiple spans. Ignore such files for now.
assert(dwSpanCount == 1);
// Capture the array of span items
if(CaptureArray_(pbVfsFileEntry, pbVfsFileEnd, &pbTemp, ItemSize, dwSpanCount) == NULL)
return ERROR_BAD_FORMAT;
//
// Structure of the span entry:
// (4bytes): Offset into the referenced file (big endian)
// (4bytes): Size of the span (big endian)
// (?bytes): Offset into Container File Table. Length depends on container file table size
//
// Get the offset to the Container File Table
dwCftOffset = ConvertBytesToInteger_X(pbVfsFileEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize);
dwSpanSize = ConvertBytesToInteger_4(pbVfsFileEntry + sizeof(DWORD));
// Go to the Container File Table and fetch the EKey from there
pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset;
pbCftFileEntry = pbCftFileTable + dwCftOffset;
pbCftFileEnd = pbCftFileTable + DirHeader.CftTableSize;
if((pbCftFileEntry + DirHeader.EKeySize) > pbCftFileEnd)
return ERROR_BAD_FORMAT;
// Give the pointer to the EKey
memcpy(pEKey->Value, pbCftFileEntry, DirHeader.EKeySize);
PtrSpanSize[0] = dwSpanSize;
return ERROR_SUCCESS;
}
//
// Structure of the path table entry:
// (1byte) 0x00 (optional) - means that there will be prefix path separator
// (1byte) File name length
// (?byte) File name
// (1byte) 0x00 (optional) - means that there will be postfix path separator
// (1byte) 0xFF (optional) - node value identifier
// (4byte) - node value
//
// Note: The path "data\archive\maps\file.bmp" could be cut into nodes like:
// data\0 (or data with subdirectory)
// arc
// hive\0
// maps\0 (or folder data)
// file.bmp
//
LPBYTE CapturePathEntry(TVFS_PATH_TABLE_ENTRY & PathEntry, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd)
{
// Reset the path entry structure
PathEntry.pbNamePtr = pbPathTablePtr;
PathEntry.pbNameEnd = pbPathTablePtr;
PathEntry.NodeFlags = 0;
PathEntry.NodeValue = 0;
// Zero before the name means prefix path separator
if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_PRE;
pbPathTablePtr++;
}
// Capture the length of the name fragment
if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] != 0xFF)
{
// Capture length of the name fragment
size_t nLength = *pbPathTablePtr++;
if ((pbPathTablePtr + nLength) > pbPathTableEnd)
return NULL;
PathEntry.pbNamePtr = pbPathTablePtr;
PathEntry.pbNameEnd = pbPathTablePtr + nLength;
pbPathTablePtr += nLength;
}
// Zero after the name means postfix path separator
if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST;
pbPathTablePtr++;
}
if (pbPathTablePtr < pbPathTableEnd)
{
// Check for node value
if (pbPathTablePtr[0] == 0xFF)
{
if ((pbPathTablePtr + 1 + sizeof(DWORD)) > pbPathTableEnd)
return NULL;
PathEntry.NodeValue = ConvertBytesToInteger_4(pbPathTablePtr + 1);
PathEntry.NodeFlags |= TVFS_PTE_NODE_VALUE;
pbPathTablePtr = pbPathTablePtr + 1 + sizeof(DWORD);
}
// Non-0xFF after the name means path separator after
else
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST;
assert(pbPathTablePtr[0] != 0);
}
}
return pbPathTablePtr;
}
bool IsVfsFileEKey(TCascStorage * hs, ENCODED_KEY & EKey, size_t EKeyLength)
{
PCASC_CKEY_ENTRY pCKeyEntry;
size_t ItemCount = hs->VfsRootList.ItemCount();
// Search the array
for (size_t i = 0; i < ItemCount; i++)
{
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i);
if (pCKeyEntry != NULL)
{
if (!memcmp(pCKeyEntry->EKey, EKey.Value, EKeyLength))
return true;
}
}
// Not found in the VFS list
return false;
}
// This function verifies whether a file is actually a sub-directory.
// If yes, it contains just another "TVFS" virtual file system, just like the ROOT file.
int IsVfsSubDirectory(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, TVFS_DIRECTORY_HEADER & SubHeader, ENCODED_KEY & EKey, DWORD dwFileSize)
{
PCASC_CKEY_ENTRY pCKeyEntry;
LPBYTE pbVfsData = NULL;
DWORD cbVfsData = dwFileSize;
int nError = ERROR_BAD_FORMAT;
// Verify whether the EKey is in the list of VFS root files
if(IsVfsFileEKey(hs, EKey, DirHeader.EKeySize))
{
// Locate the CKey entry
if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL)
{
// Load the entire file into memory
pbVfsData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbVfsData);
if (pbVfsData && cbVfsData)
{
// Capture the file folder. This also serves as test
nError = CaptureDirectoryHeader(SubHeader, pbVfsData, pbVfsData + cbVfsData);
if (nError == ERROR_SUCCESS)
return nError;
// Clear the captured header
memset(&SubHeader, 0, sizeof(TVFS_DIRECTORY_HEADER));
CASC_FREE(pbVfsData);
}
}
}
return nError;
}
void InsertRootVfsEntry(TCascStorage * hs, LPBYTE pbCKey, const char * szFormat, size_t nIndex)
{
PCASC_CKEY_ENTRY pCKeyEntry;
char szFileName[0x20];
// The CKey entry must exist
if((pCKeyEntry = FindCKeyEntry_CKey(hs, pbCKey)) != NULL)
{
CascStrPrintf(szFileName, _countof(szFileName), szFormat, nIndex);
Insert(szFileName, pCKeyEntry);
}
}
DWORD ParsePathFileTable(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd)
{
TVFS_DIRECTORY_HEADER SubHeader;
TVFS_PATH_TABLE_ENTRY PathEntry;
PCASC_CKEY_ENTRY pCKeyEntry;
ENCODED_KEY EKey;
char * szSavePathPtr = PathBuffer.szPtr;
DWORD dwSpanSize = 0;
int nError;
// Prepare the EKey structure to be filled with zeros
memset(&EKey, 0, sizeof(ENCODED_KEY));
// Parse the file table
while(pbPathTablePtr < pbPathTableEnd)
{
// Capture the single path table entry
pbPathTablePtr = CapturePathEntry(PathEntry, pbPathTablePtr, pbPathTableEnd);
if(pbPathTablePtr == NULL)
return ERROR_BAD_FORMAT;
// Append the node name to the total path. Also add backslash, if it's a folder
PathBuffer_AppendNode(PathBuffer, PathEntry);
// Folder component
if (PathEntry.NodeFlags & TVFS_PTE_NODE_VALUE)
{
// If the TVFS_FOLDER_NODE is set, then the path node is a directory,
// with its data immediately following the path node. Lower 31 bits of NodeValue
// contain the length of the directory (including the NodeValue!)
if (PathEntry.NodeValue & TVFS_FOLDER_NODE)
{
LPBYTE pbDirectoryEnd = pbPathTablePtr + (PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) - sizeof(DWORD);
// Check the available data
assert((PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) >= sizeof(DWORD));
// Recursively call the folder parser on the same file
nError = ParsePathFileTable(hs, DirHeader, PathBuffer, pbPathTablePtr, pbDirectoryEnd);
if (nError != ERROR_SUCCESS)
return nError;
// Skip the directory data
pbPathTablePtr = pbDirectoryEnd;
}
else
{
// Capture the VFS and Container Table Entry in order to get the file EKey
nError = CaptureVfsSpanEntries(DirHeader, &EKey, &dwSpanSize, PathEntry.NodeValue);
if (nError != ERROR_SUCCESS)
return nError;
// We need to check whether this is another TVFS directory file
if (IsVfsSubDirectory(hs, DirHeader, SubHeader, EKey, dwSpanSize) == ERROR_SUCCESS)
{
// Add colon (':')
PathBuffer_AddChar(PathBuffer, ':');
// Insert the file to the file tree
if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL)
{
// The file content size should already be there
assert(pCKeyEntry->ContentSize == dwSpanSize);
FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
}
ParseDirectoryData(hs, SubHeader, PathBuffer);
CASC_FREE(SubHeader.pbDirectoryData);
}
else
{
// if (fp != NULL)
// {
// fwrite(PathBuffer.szBegin, 1, (PathBuffer.szPtr - PathBuffer.szBegin), fp);
// fprintf(fp, "\n");
// }
// Insert the file to the file tree
if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL)
{
// If the file content is not there, supply it now
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
pCKeyEntry->ContentSize = dwSpanSize;
FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
}
}
}
// Reset the position of the path buffer
PathBuffer.szPtr = szSavePathPtr;
PathBuffer.szPtr[0] = 0;
}
}
// Return the total number of entries
return ERROR_SUCCESS;
}
int ParseDirectoryData(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer)
{
LPBYTE pbRootDirectory = DirHeader.pbDirectoryData + DirHeader.PathTableOffset;
LPBYTE pbRootDirPtr = pbRootDirectory;
LPBYTE pbRootDirEnd = pbRootDirPtr + DirHeader.PathTableSize;
DWORD dwNodeValue = 0;
// Most usually, there is a root directory in the folder
if((pbRootDirPtr + 1 + sizeof(DWORD)) < pbRootDirEnd)
{
//
// The structure of the root directory
// -----------------------------------
// 1byte 0xFF
// 4bytes NodeValue (BigEndian). The most significant bit is set
// - Lower 31 bits contain length of the directory data, including NodeValue
//
if(pbRootDirPtr[0] == 0xFF)
{
// Get the NodeValue and check its highest bit
if(CaptureInteger32_BE(pbRootDirPtr + 1, pbRootDirEnd, &dwNodeValue) == NULL || (dwNodeValue & TVFS_FOLDER_NODE) == 0)
return ERROR_BAD_FORMAT;
// Get the range of the root directory
pbRootDirEnd = pbRootDirPtr + 1 + (dwNodeValue & TVFS_FOLDER_SIZE_MASK);
pbRootDirPtr = pbRootDirPtr + 1 + sizeof(DWORD);
// Check the directory
if(pbRootDirEnd > (pbRootDirectory + DirHeader.PathTableSize))
return ERROR_BAD_FORMAT;
}
}
// Now go parse the path file table
return ParsePathFileTable(hs, DirHeader, PathBuffer, pbRootDirPtr, pbRootDirEnd);
}
int Load(TCascStorage * hs, TVFS_DIRECTORY_HEADER & RootHeader)
{
// PCASC_CKEY_ENTRY pCKeyEntry;
PATH_BUFFER PathBuffer;
char szPathBuffer[MAX_PATH];
// Initialize the path buffer
memset(szPathBuffer, 0, sizeof(szPathBuffer));
PathBuffer.szBegin =
PathBuffer.szPtr = szPathBuffer;
PathBuffer.szEnd = szPathBuffer + MAX_PATH;
// Save the length of the key
FileTree.SetKeyLength(RootHeader.EKeySize);
// Insert the main VFS root file as named entry
InsertRootVfsEntry(hs, hs->VfsRoot.CKey, "vfs-root", 0);
// Insert all VFS roots folders as files
//for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++)
//{
// pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i);
// InsertRootVfsEntry(hs, pCKeyEntry->CKey, "vfs-%u", i+1);
//}
// Parse the entire directory data
return ParseDirectoryData(hs, RootHeader, PathBuffer);
}
DWORD dwNestLevel;
};
//-----------------------------------------------------------------------------
// Public functions - TVFS root
int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TRootHandler_TVFS * pRootHandler = NULL;
TVFS_DIRECTORY_HEADER RootHeader;
int nError;
// Capture the entire root directory
nError = TRootHandler_TVFS::CaptureDirectoryHeader(RootHeader, pbRootFile, pbRootFile + cbRootFile);
if(nError == ERROR_SUCCESS)
{
// Allocate the root handler object
pRootHandler = new TRootHandler_TVFS();
if(pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
nError = pRootHandler->Load(hs, RootHeader);
if(nError != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return nError;
}
+121
View File
@@ -0,0 +1,121 @@
/*****************************************************************************/
/* CascRootFile_Text.cpp Copyright (c) Ladislav Zezula 2017 */
/*---------------------------------------------------------------------------*/
/* Support for generic ROOT handler with mapping of FileName -> CKey */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 28.10.15 1.00 Lad The first version of CascRootFile_Text.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Handler definitions for Starcraft I root file
struct TRootHandler_SC1 : public TFileTreeRoot
{
public:
TRootHandler_SC1() : TFileTreeRoot(0)
{
// We have file names and return CKey as result of search
dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY);
}
static bool IsRootFile(LPBYTE pbRootFile, DWORD cbRootFile)
{
CASC_CSV Csv(1, false);
size_t nColumns;
bool bResult = false;
// Get the first line from the listfile
if(Csv.Load(pbRootFile, cbRootFile) == ERROR_SUCCESS)
{
// There must be 2 or 3 elements
nColumns = Csv[CSV_ZERO].GetColumnCount();
if (nColumns == 2 || nColumns == 3)
{
const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][CSV_ZERO];
const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][1];
bResult = (FileName.szValue && CKeyStr.szValue && CKeyStr.nLength == MD5_STRING_SIZE);
}
}
// We need to reset the listfile to the begin position
return bResult;
}
int Load(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
PCASC_CKEY_ENTRY pCKeyEntry;
CASC_CSV Csv(0, false);
BYTE CKey[MD5_HASH_SIZE];
int nError;
// Parse the ROOT file first in order to see whether we have the correct format
nError = Csv.Load(pbRootFile, cbRootFile);
if(nError == ERROR_SUCCESS)
{
// Parse all lines
while(Csv.LoadNextLine())
{
const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][CSV_ZERO];
const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][1];
// Convert the CKey to binary
if(ConvertStringToBinary(CKeyStr.szValue, MD5_STRING_SIZE, CKey) == ERROR_SUCCESS)
{
// Verify whether it is a known entry
if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKey)) != NULL)
{
// Insert the FileName+CKey to the file tree
FileTree.InsertByName(pCKeyEntry, FileName.szValue);
}
}
}
}
return nError;
}
};
//-----------------------------------------------------------------------------
// Public functions
//
// Starcraft ROOT file is a text file with the following format:
// HD2/portraits/NBluCrit/NLCFID01.webm|c2795b120592355d45eba9cdc37f691e
// locales/enUS/Assets/campaign/EXPZerg/Zerg08/staredit/wav/zovtra01.ogg|316b0274bf2dabaa8db60c3ff1270c85
// locales/zhCN/Assets/sound/terran/ghost/tghdth01.wav|6637ed776bd22089e083b8b0b2c0374c
//
int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TRootHandler_SC1 * pRootHandler = NULL;
int nError = ERROR_BAD_FORMAT;
// Verify whether this looks like a Starcraft I root file
if(TRootHandler_SC1::IsRootFile(pbRootFile, cbRootFile))
{
// Allocate the root handler object
pRootHandler = new TRootHandler_SC1();
if(pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
nError = pRootHandler->Load(hs, pbRootFile, cbRootFile);
if(nError != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return nError;
}
+481
View File
@@ -0,0 +1,481 @@
/*****************************************************************************/
/* CascRootFile_WoW.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Storage functions for CASC */
/* Note: WoW 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 CascRootFile_WoW.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 3
// Known dwRegion values returned from sub_661316 (7.0.3.22210 x86 win), also referred by lua GetCurrentRegion
#define WOW_REGION_US 0x01
#define WOW_REGION_KR 0x02
#define WOW_REGION_EU 0x03
#define WOW_REGION_TW 0x04
#define WOW_REGION_CN 0x05
typedef enum _ROOT_FORMAT
{
RootFormatWoW6x, // WoW 6.x - 8.1.x
RootFormatWoW82 // WoW 8.2 or newer
} ROOT_FORMAT, *PROOT_FORMAT;
// ROOT file header, since 8.2
typedef struct _FILE_ROOT_HEADER_82
{
DWORD Signature; // Must be CASC_WOW82_ROOT_SIGNATURE
DWORD TotalFiles;
DWORD FilesWithNameHash;
} FILE_ROOT_HEADER_82, *PFILE_ROOT_HEADER_82;
// On-disk version of root group. A root group contains a group of file
// with the same locale and file flags
typedef struct _FILE_ROOT_GROUP_HEADER
{
DWORD NumberOfFiles; // Number of entries
DWORD ContentFlags;
DWORD LocaleFlags; // File locale mask (CASC_LOCALE_XXX)
// Followed by a block of file data IDs (count: NumberOfFiles)
// Followed by the MD5 and file name hash (count: NumberOfFiles)
} FILE_ROOT_GROUP_HEADER, *PFILE_ROOT_GROUP_HEADER;
// On-disk version of root entry. Only present in versions 6.x - 8.1.xx
// Each root entry represents one file in the CASC storage
// In WoW 8.2 and newer, CKey and FileNameHash are split into separate arrays
// and FileNameHash is optional
typedef struct _FILE_ROOT_ENTRY
{
CONTENT_KEY CKey; // MD5 of the file
ULONGLONG FileNameHash; // Jenkins hash of the file name
} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
typedef struct _FILE_ROOT_GROUP
{
FILE_ROOT_GROUP_HEADER Header;
PDWORD FileDataIds; // Pointer to the array of File Data IDs
PFILE_ROOT_ENTRY pRootEntries; // Valid for WoW 6.x - 8.1.x
PCONTENT_KEY pCKeyEntries; // Valid for WoW 8.2 or newer
PULONGLONG pHashes; // Valid for WoW 8.2 or newer (optional)
} FILE_ROOT_GROUP, *PFILE_ROOT_GROUP;
//-----------------------------------------------------------------------------
// TRootHandler_WoW interface / implementation
#define FTREE_FLAGS_WOW (FTREE_FLAG_USE_DATA_ID | FTREE_FLAG_USE_LOCALE_FLAGS | FTREE_FLAG_USE_CONTENT_FLAGS)
struct TRootHandler_WoW : public TFileTreeRoot
{
public:
TRootHandler_WoW(ROOT_FORMAT RFormat, DWORD HashlessFileCount) : TFileTreeRoot(FTREE_FLAGS_WOW)
{
// Turn off the "we know file names" bit
FileCounterHashless = HashlessFileCount;
FileCounter = 0;
RootFormat = RFormat;
// Update the flags based on format
switch(RootFormat)
{
case RootFormatWoW6x:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_FNAME_HASHES | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS;
break;
case RootFormatWoW82:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_FNAME_HASHES_OPTIONAL | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS;
break;
}
}
static LPBYTE CaptureRootHeader(FILE_ROOT_HEADER_82 & RootHeader, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
{
// Validate the root file header
if((pbRootPtr + sizeof(FILE_ROOT_HEADER_82)) >= pbRootEnd)
return NULL;
memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_82));
// Verify the root file header
if(RootHeader.Signature != CASC_WOW82_ROOT_SIGNATURE)
return NULL;
if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
return NULL;
return pbRootPtr + sizeof(FILE_ROOT_HEADER_82);
}
LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
{
// Reset the entire root group structure
memset(&RootGroup, 0, sizeof(FILE_ROOT_GROUP));
// Validate the locale block header
if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER)) >= pbRootEnd)
return NULL;
memcpy(&RootGroup.Header, pbRootPtr, sizeof(FILE_ROOT_GROUP_HEADER));
pbRootPtr = pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER);
// Validate the array of file data IDs
if((pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles)) >= pbRootEnd)
return NULL;
RootGroup.FileDataIds = (PDWORD)pbRootPtr;
pbRootPtr = pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles);
// Add the number of files in this block to the number of files loaded
FileCounter += RootGroup.Header.NumberOfFiles;
// Validate the array of root entries
switch(RootFormat)
{
case RootFormatWoW6x:
if((pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;
RootGroup.pRootEntries = (PFILE_ROOT_ENTRY)pbRootPtr;
// Return the position of the next block
return pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles);
case RootFormatWoW82:
// Verify the position of array of CONTENT_KEY
if((pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;
RootGroup.pCKeyEntries = (PCONTENT_KEY)pbRootPtr;
pbRootPtr = pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles);
// Also include array of file hashes
if(FileCounter > FileCounterHashless)
{
if((pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;
RootGroup.pHashes = (PULONGLONG)pbRootPtr;
pbRootPtr = pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles);
}
return pbRootPtr;
default:
return NULL;
}
}
int ParseWowRootFile_AddFiles_6x(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
{
PFILE_ROOT_ENTRY pRootEntry = RootGroup.pRootEntries;
PCASC_CKEY_ENTRY pCKeyEntry;
DWORD FileDataId = 0;
// Sanity check
assert(RootGroup.pRootEntries != NULL);
// WoW.exe (build 19116): Blocks with zero files are skipped
for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pRootEntry++)
{
// Set the file data ID
FileDataId = FileDataId + RootGroup.FileDataIds[i];
// BREAKIF(FileDataId == 2823765);
// Find the item in the central storage. Insert it to the tree
if((pCKeyEntry = FindCKeyEntry_CKey(hs, pRootEntry->CKey.Value)) != NULL)
{
if(pRootEntry->FileNameHash != 0)
{
FileTree.InsertByHash(pCKeyEntry, pRootEntry->FileNameHash, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
}
else
{
FileTree.InsertById(pCKeyEntry, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
}
}
// Update the file data ID
assert((FileDataId + 1) > FileDataId);
FileDataId++;
}
return ERROR_SUCCESS;
}
int ParseWowRootFile_AddFiles_82(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
{
PCASC_CKEY_ENTRY pCKeyEntry;
PCONTENT_KEY pCKey = RootGroup.pCKeyEntries;
DWORD FileDataId = 0;
// Sanity check
assert(RootGroup.pCKeyEntries != NULL);
// WoW.exe (build 19116): Blocks with zero files are skipped
for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pCKey++)
{
// Set the file data ID
FileDataId = FileDataId + RootGroup.FileDataIds[i];
// Find the item in the central storage. Insert it to the tree
if((pCKeyEntry = FindCKeyEntry_CKey(hs, pCKey->Value)) != NULL)
{
// If we know the file name hash, we're gonna insert it by hash AND file data id.
// If we don't know the hash, we're gonna insert it just by file data id.
if(RootGroup.pHashes != NULL && RootGroup.pHashes[i] != 0)
{
FileTree.InsertByHash(pCKeyEntry, RootGroup.pHashes[i], FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
}
else
{
FileTree.InsertById(pCKeyEntry, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
}
}
// Update the file data ID
assert((FileDataId + 1) > FileDataId);
FileDataId++;
}
return ERROR_SUCCESS;
}
int ParseWowRootFile_Level2(
TCascStorage * hs,
LPBYTE pbRootPtr,
LPBYTE pbRootEnd,
DWORD dwLocaleMask,
BYTE bOverrideLowViolence,
BYTE bAudioLocale)
{
FILE_ROOT_GROUP RootBlock;
// Reset the total file counter
FileCounter = 0;
// Now parse the root file
while(pbRootPtr < pbRootEnd)
{
// Validate the file locale block
pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd);
if(pbRootPtr == NULL)
return ERROR_BAD_FORMAT;
// WoW.exe (build 19116): Entries with flag 0x100 set are skipped
if(RootBlock.Header.ContentFlags & CASC_CFLAG_DONT_LOAD)
continue;
// WoW.exe (build 19116): Entries with flag 0x80 set are skipped if overrideArchive CVAR is set to FALSE (which is by default in non-chinese clients)
if((RootBlock.Header.ContentFlags & CASC_CFLAG_LOW_VIOLENCE) && bOverrideLowViolence == 0)
continue;
// WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to bAudioLocale are skipped
if((RootBlock.Header.ContentFlags >> 0x1F) != bAudioLocale)
continue;
// WoW.exe (build 19116): Locales other than defined mask are skipped too
if((RootBlock.Header.LocaleFlags & dwLocaleMask) == 0)
continue;
// Now call the custom function
switch(RootFormat)
{
case RootFormatWoW82:
ParseWowRootFile_AddFiles_82(hs, RootBlock);
break;
case RootFormatWoW6x:
ParseWowRootFile_AddFiles_6x(hs, RootBlock);
break;
default:
return ERROR_NOT_SUPPORTED;
}
}
return ERROR_SUCCESS;
}
/*
#define CASC_LOCALE_BIT_ENUS 0x01
#define CASC_LOCALE_BIT_KOKR 0x02
#define CASC_LOCALE_BIT_RESERVED 0x03
#define CASC_LOCALE_BIT_FRFR 0x04
#define CASC_LOCALE_BIT_DEDE 0x05
#define CASC_LOCALE_BIT_ZHCN 0x06
#define CASC_LOCALE_BIT_ESES 0x07
#define CASC_LOCALE_BIT_ZHTW 0x08
#define CASC_LOCALE_BIT_ENGB 0x09
#define CASC_LOCALE_BIT_ENCN 0x0A
#define CASC_LOCALE_BIT_ENTW 0x0B
#define CASC_LOCALE_BIT_ESMX 0x0C
#define CASC_LOCALE_BIT_RURU 0x0D
#define CASC_LOCALE_BIT_PTBR 0x0E
#define CASC_LOCALE_BIT_ITIT 0x0F
#define CASC_LOCALE_BIT_PTPT 0x10
// dwLocale is obtained from a WOW_LOCALE_* to CASC_LOCALE_BIT_* mapping (sub_6615D0 in 7.0.3.22210 x86 win)
// because (ENUS, ENGB) and (PTBR, PTPT) pairs share the same value on WOW_LOCALE_* enum
// dwRegion is used to distinguish them
if(dwRegion == WOW_REGION_EU)
{
// Is this english version of WoW?
if(dwLocale == CASC_LOCALE_BIT_ENUS)
{
LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_ENGB, bOverrideArchive, bAudioLocale);
LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_ENUS, bOverrideArchive, bAudioLocale);
return ERROR_SUCCESS;
}
// Is this portuguese version of WoW?
if(dwLocale == CASC_LOCALE_BIT_PTBR)
{
LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_PTPT, bOverrideArchive, bAudioLocale);
LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_PTBR, bOverrideArchive, bAudioLocale);
}
}
else
LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, (1 << dwLocale), bOverrideArchive, bAudioLocale);
*/
int ParseWowRootFile_Level1(
TCascStorage * hs,
LPBYTE pbRootPtr,
LPBYTE pbRootEnd,
DWORD dwLocaleMask,
BYTE bAudioLocale)
{
int nError;
// Load the locale as-is
nError = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale);
if (nError != ERROR_SUCCESS)
return nError;
// If we wanted enGB, we also load enUS for the missing files
if(dwLocaleMask == CASC_LOCALE_ENGB)
ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_ENUS, false, bAudioLocale);
if(dwLocaleMask == CASC_LOCALE_PTPT)
ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_PTBR, false, bAudioLocale);
return ERROR_SUCCESS;
}
// WoW.exe: 004146C7 (BuildManifest::Load)
int Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask)
{
int nError;
nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0);
if (nError == ERROR_SUCCESS)
nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1);
return nError;
}
// Search for files
PCASC_CKEY_ENTRY Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
// If we have a listfile, we'll feed the listfile entries to the file tree
if(pSearch->pCache != NULL && pSearch->bListFileUsed == false)
{
PCASC_FILE_NODE pFileNode;
ULONGLONG FileNameHash;
DWORD FileDataId = CASC_INVALID_ID;
char szFileName[MAX_PATH];
if(RootFormat == RootFormatWoW82)
{
// Keep going through the listfile
while(ListFile_GetNext(pSearch->pCache, szFileName, _countof(szFileName), &FileDataId))
{
// Try to find the file node by file data id
pFileNode = FileTree.FindById(FileDataId);
if(pFileNode != NULL && pFileNode->NameLength == 0)
{
FileTree.SetNodeFileName(pFileNode, szFileName);
}
}
}
else
{
// Keep going through the listfile
while(ListFile_GetNextLine(pSearch->pCache, szFileName, MAX_PATH))
{
// Calculate the hash of the file name
FileNameHash = CalcFileNameHash(szFileName);
// Try to find the file node by file name hash
pFileNode = FileTree.Find(FileNameHash);
if(pFileNode != NULL && pFileNode->NameLength == 0)
{
FileTree.SetNodeFileName(pFileNode, szFileName);
}
}
}
pSearch->bListFileUsed = true;
}
// Let the file tree root give us the file names
return TFileTreeRoot::Search(pSearch, pFindData);
}
ROOT_FORMAT RootFormat; // Root file format
DWORD FileCounterHashless; // Number of files for which we don't have hash. Meaningless for WoW before 8.2
DWORD FileCounter; // Counter of loaded files. Only used during loading of ROOT file
};
//-----------------------------------------------------------------------------
// Public functions
int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask)
{
TRootHandler_WoW * pRootHandler = NULL;
FILE_ROOT_HEADER_82 RootHeader;
ROOT_FORMAT RootFormat = RootFormatWoW6x;
LPBYTE pbRootEnd = pbRootFile + cbRootFile;
LPBYTE pbRootPtr;
DWORD FileCounterHashless = 0;
int nError = ERROR_BAD_FORMAT;
// Check for the new format (World of Warcraft 8.2, build 30170)
pbRootPtr = TRootHandler_WoW::CaptureRootHeader(RootHeader, pbRootFile, pbRootEnd);
if(pbRootPtr != NULL)
{
FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
RootFormat = RootFormatWoW82;
pbRootFile = pbRootPtr;
}
// Create the WOW handler
pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless);
if(pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
nError = pRootHandler->Load(hs, pbRootFile, pbRootEnd, dwLocaleMask);
if(nError != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return nError;
}
+255
View File
@@ -0,0 +1,255 @@
/*****************************************************************************/
/* CascStructs.h Copyright (c) Ladislav Zezula 2019 */
/*---------------------------------------------------------------------------*/
/* On-disk structures for CASC storages */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 28.04.19 1.00 Lad The first version of CascStructs.h */
/*****************************************************************************/
#ifndef __CASC_STRUCTS_H__
#define __CASC_STRUCTS_H__
//-----------------------------------------------------------------------------
// Common definitions
#define CASC_INDEX_COUNT 0x10 // Number of index files
#define CASC_CKEY_SIZE 0x10 // Size of the content key
#define CASC_EKEY_SIZE 0x09 // Size of the encoded key
#define CASC_MAX_DATA_FILES 0x100 // Maximum number of data files
//-----------------------------------------------------------------------------
// The index files structures
#define FILE_INDEX_PAGE_SIZE 0x200 // Number of bytes in one page of EKey items
// Structure describing the 32-bit block size and 32-bit Jenkins hash of the block
typedef struct _BLOCK_SIZE_AND_HASH
{
DWORD cbBlockSize;
DWORD dwBlockHash;
} BLOCK_SIZE_AND_HASH, *PBLOCK_SIZE_AND_HASH;
// Checksum describing a block in the index file (v2)
typedef struct _FILE_INDEX_GUARDED_BLOCK
{
DWORD BlockSize;
DWORD BlockHash;
} FILE_INDEX_GUARDED_BLOCK, *PFILE_INDEX_GUARDED_BLOCK;
// Structure of the header of the index files version 1
typedef struct _FILE_INDEX_HEADER_V1
{
USHORT IndexVersion; // Must be 0x05
BYTE BucketIndex; // The bucket index of this file; should be the same as the first byte of the hex filename.
BYTE align_3;
DWORD field_4;
ULONGLONG field_8;
ULONGLONG SegmentSize; // Size of one data segment (aka data.### file)
BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry
BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry
BYTE EKeyLength; // Length of the encoded key (bytes)
BYTE FileOffsetBits; // Number of bits of the archive file offset in StorageOffset field. Rest is data segment index
DWORD EKeyCount1;
DWORD EKeyCount2;
DWORD KeysHash1;
DWORD KeysHash2;
DWORD HeaderHash;
} FILE_INDEX_HEADER_V1, *PFILE_INDEX_HEADER_V1;
typedef struct _FILE_INDEX_HEADER_V2
{
USHORT IndexVersion; // Must be 0x07
BYTE BucketIndex; // The bucket index of this file; should be the same as the first byte of the hex filename.
BYTE ExtraBytes; // Unknown; must be 0
BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry
BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry
BYTE EKeyLength; // Length of the encoded key (bytes)
BYTE FileOffsetBits; // Number of bits of the archive file offset in StorageOffset field. Rest is data segment index
ULONGLONG SegmentSize; // Size of one data segment (aka data.### file)
} FILE_INDEX_HEADER_V2, *PFILE_INDEX_HEADER_V2;
// The EKey entry from the ".idx" files. Note that the lengths are optional and controlled by the FILE_INDEX_HEADER_Vx
typedef struct _FILE_EKEY_ENTRY
{
BYTE EKey[CASC_EKEY_SIZE]; // The first 9 bytes of the encoded key
BYTE FileOffsetBE[5]; // Index of data file and offset within (big endian).
BYTE EncodedSize[4]; // Encoded size (big endian). This is the size of encoded header, all file frame headers and all file frames
} FILE_EKEY_ENTRY, *PFILE_EKEY_ENTRY;
//-----------------------------------------------------------------------------
// The archive index (md5.index) files structures
// https://wowdev.wiki/TACT#CDN_File_Organization
template <int CHKSUM_LENGTH>
struct FILE_INDEX_FOOTER
{
BYTE TocHash[MD5_HASH_SIZE]; // Client tries to read with 0x10, then backs off in size when smaller
BYTE Version; // Version of the index header
BYTE Reserved[2]; // Length, in bytes, of the file offset field
BYTE PageSizeKB; // Length, in kilobytes, of the index page
BYTE OffsetBytes; // Normally 4 for archive indices, 6 for group indices, and 0 for loose file indices
BYTE SizeBytes; // Normally 4
BYTE EKeySizeBytes; // Normally 16
BYTE FooterHashBytes; // Normally 8, <= 0x10
BYTE ElementCount[4]; // BigEndian in _old_ versions (e.g. 18179)
BYTE FooterHash[CHKSUM_LENGTH];
};
//-----------------------------------------------------------------------------
// The ENCODING manifest structures
//
// The ENCODING file is in the form of:
// * File header. Fixed length.
// * Encoding Specification (ESpec) in the string form. Length is stored in FILE_ENCODING_HEADER::ESpecBlockSize
// https://wowdev.wiki/CASC#Encoding
// https://wowdev.wiki/TACT#Encoding_table
//
#define FILE_MAGIC_ENCODING 0x4E45 // 'EN'
// File header of the ENCODING manifest
typedef struct _FILE_ENCODING_HEADER
{
USHORT Magic; // FILE_MAGIC_ENCODING ('EN')
BYTE Version; // Expected to be 1 by CascLib
BYTE CKeyLength; // The content key length in ENCODING file. Usually 0x10
BYTE EKeyLength; // The encoded key length in ENCODING file. Usually 0x10
BYTE CKeyPageSize[2]; // Size of the CKey page, in KB (big-endian)
BYTE EKeyPageSize[2]; // Size of the EKey page, in KB (big-endian)
BYTE CKeyPageCount[4]; // Number of CKey pages in the table (big endian)
BYTE EKeyPageCount[4]; // Number of EKey pages in the table (big endian)
BYTE field_11; // Asserted to be zero by the agent
BYTE ESpecBlockSize[4]; // Size of the ESpec string block
} FILE_ENCODING_HEADER, *PFILE_ENCODING_HEADER;
// Page header of the ENCODING manifest
typedef struct _FILE_CKEY_PAGE
{
BYTE FirstKey[MD5_HASH_SIZE]; // The first CKey/EKey in the segment
BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
} FILE_CKEY_PAGE, *PFILE_CKEY_PAGE;
// Single entry in the page
typedef struct _FILE_CKEY_ENTRY
{
USHORT EKeyCount; // Number of EKeys
BYTE ContentSize[4]; // Content file size (big endian)
BYTE CKey[CASC_CKEY_SIZE]; // Content key. This is MD5 of the file content
BYTE EKey[CASC_CKEY_SIZE]; // Encoded key. This is (trimmed) MD5 hash of the file header, containing MD5 hashes of all the logical blocks of the file
} FILE_CKEY_ENTRY, *PFILE_CKEY_ENTRY;
typedef struct _FILE_ESPEC_ENTRY
{
BYTE ESpecKey[MD5_HASH_SIZE]; // The ESpec key of the file
BYTE ESpecIndexBE[4]; // Index of ESPEC entry, assuming zero-terminated strings (big endian)
BYTE FileSizeBE[5]; // Size of the encoded version of the file (big endian)
} FILE_ESPEC_ENTRY, *PFILE_ESPEC_ENTRY;
//-----------------------------------------------------------------------------
// The DOWNLOAD manifest structures
//
// See https://wowdev.wiki/TACT#Download_manifest
//
#define FILE_MAGIC_DOWNLOAD 0x4C44 // 'DL'
// File header of the DOWNLOAD manifest
typedef struct _FILE_DOWNLOAD_HEADER
{
USHORT Magic; // FILE_MAGIC_DOWNLOAD ('DL')
BYTE Version; // Expected to be 1 by CascLib
BYTE EKeyLength; // The content key length in DOWNLOAD file. Expected to be 0x10
BYTE EntryHasChecksum; // If nonzero, then the entry has checksum.
BYTE EntryCount[4]; // Number of entries (big-endian)
BYTE TagCount[2]; // Number of tag entries (big endian)
// Version 2 or newer
BYTE FlagByteSize; // Number of flag bytes
// Verion 3 or newer
BYTE BasePriority;
BYTE Unknown2[3];
} FILE_DOWNLOAD_HEADER, *PFILE_DOWNLOAD_HEADER;
typedef struct _FILE_DOWNLOAD_ENTRY
{
BYTE EKey[MD5_HASH_SIZE]; // Encoding key (variable length)
BYTE FileSize[5]; // File size
BYTE Priority;
// DWORD Checksum [optional]
// BYTE Flags;
} FILE_DOWNLOAD_ENTRY, *PFILE_DOWNLOAD_ENTRY;
//-----------------------------------------------------------------------------
// The INSTALL manifest structures
//
// See https://wowdev.wiki/TACT#Install_manifest
//
#define FILE_MAGIC_INSTALL 0x4E49 // 'IN'
// File header of the INSTALL manifest
typedef struct _FILE_INSTALL_HEADER
{
USHORT Magic; // FILE_MAGIC_INSTALL ('DL')
BYTE Version; // Expected to be 1 by CascLib
BYTE EKeyLength; // The content key length in INSTALL file. Expected to be 0x10
BYTE TagCount[2]; // Number of tag entries (big endian)
BYTE EntryCount[4]; // Number of entries (big-endian)
} FILE_INSTALL_HEADER, *PFILE_INSTALL_HEADER;
//-----------------------------------------------------------------------------
// Data file structures
#define BLTE_HEADER_SIGNATURE 0x45544C42 // 'BLTE' header in the data files
#define BLTE_HEADER_DELTA 0x1E // Distance of BLTE header from begin of the header area
#define MAX_ENCODED_HEADER 0x1000 // Starting size for the frame headers
typedef struct _BLTE_HEADER
{
BYTE Signature[4]; // Must be "BLTE"
BYTE HeaderSize[4]; // Header size in bytes (big endian)
BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSize != 0
BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSize != 0
} BLTE_HEADER, *PBLTE_HEADER;
typedef struct _BLTE_ENCODED_HEADER
{
// Header span.
ENCODED_KEY EKey; // Encoded key of the data beginning with "BLTE" (byte-reversed)
DWORD EncodedSize; // Encoded size of the data data beginning with "BLTE" (little endian)
BYTE field_14; // Seems to be 1 if the header span has no data
BYTE field_15; // Hardcoded to zero (Agent.exe 2.15.0.6296: 01370000->0148E2AA)
BYTE JenkinsHash[4]; // Jenkins hash (hashlittle2) of the preceding fields (EKey + EncodedSize + field_14 + field_15) (little endian)
BYTE Checksum[4]; // Checksum of the previous part. See "VerifyHeaderSpan()" for more information.
// BLTE header. Always present.
BYTE Signature[4]; // Must be "BLTE"
BYTE HeaderSize[4]; // Header size in bytes (big endian)
BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSize != 0
BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSize != 0
} BLTE_ENCODED_HEADER, *PBLTE_ENCODED_HEADER;
typedef struct _BLTE_FRAME
{
BYTE EncodedSize[4]; // Encoded frame size (big endian)
BYTE ContentSize[4]; // Content frame size (big endian)
CONTENT_KEY FrameHash; // Hash of the encoded frame
} BLTE_FRAME, *PBLTE_FRAME;
#endif // __CASC_STRUCTS_H__
+24
View File
@@ -0,0 +1,24 @@
/*****************************************************************************/
/* DllMain.c Copyright (c) Ladislav Zezula 2015 */
/*---------------------------------------------------------------------------*/
/* Description: DllMain for the CascLib.dll library */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 26.10.15 1.00 Lad The first version of DllMain.c */
/*****************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
//-----------------------------------------------------------------------------
// DllMain
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved)
{
UNREFERENCED_PARAMETER(hInst);
UNREFERENCED_PARAMETER(dwReason);
UNREFERENCED_PARAMETER(lpReserved);
return TRUE;
}
+208
View File
@@ -0,0 +1,208 @@
/*****************************************************************************/
/* Array.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 */
/* 10.08.18 1.00 Lad CLASS-ified, renamed to Array.h */
/*****************************************************************************/
#ifndef __CASC_ARRAY_H__
#define __CASC_ARRAY_H__
//-----------------------------------------------------------------------------
// Structures
class CASC_ARRAY
{
public:
CASC_ARRAY()
{
m_pItemArray = NULL;
m_ItemCountMax = 0;
m_ItemCount = 0;
m_ItemSize = 0;
}
~CASC_ARRAY()
{
Free();
}
// Creates an array with a custom element type
template<typename TYPE>
int Create(size_t ItemCountMax)
{
return Create(sizeof(TYPE), ItemCountMax);
}
// Creates an array with a custom element size
int Create(size_t ItemSize, size_t ItemCountMax)
{
// Create the array
if ((m_pItemArray = CASC_ALLOC(BYTE, ItemSize * ItemCountMax)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
m_ItemCountMax = ItemCountMax;
m_ItemCount = 0;
m_ItemSize = ItemSize;
return ERROR_SUCCESS;
}
// Inserts one or more items; returns pointer to the first inserted item
void * Insert(size_t NewItemCount)
{
void * pNewItems;
// Try to enlarge the buffer, if needed
if (!EnlargeArray(m_ItemCount + NewItemCount))
return NULL;
pNewItems = m_pItemArray + (m_ItemCount * m_ItemSize);
// Increment the size of the array
m_ItemCount += NewItemCount;
// Return pointer to the new item
return pNewItems;
}
// Inserts one or more items; returns pointer to the first inserted item
void * Insert(const void * NewItems, size_t NewItemCount)
{
void * pNewItem = Insert(NewItemCount);
// Copy the item(s) to the array, if any
if (pNewItem && NewItems)
memcpy(pNewItem, NewItems, (NewItemCount * m_ItemSize));
return pNewItem;
}
// Returns an item at a given index
void * ItemAt(size_t ItemIndex)
{
return (ItemIndex < m_ItemCount) ? (m_pItemArray + (ItemIndex * m_ItemSize)) : NULL;
}
void * LastItem()
{
return m_pItemArray + (m_ItemCount * m_ItemSize);
}
// Inserts an item at a given index. If there is not enough items in the array,
// the array will be enlarged. Should any gaps to be created, the function will zero them
void * InsertAt(size_t ItemIndex)
{
LPBYTE pbLastItem;
LPBYTE pbNewItem;
// Make sure we have array large enough
if(!EnlargeArray(ItemIndex + 1))
return NULL;
// Get the items range
pbLastItem = m_pItemArray + (m_ItemCount * m_ItemSize);
pbNewItem = m_pItemArray + (ItemIndex * m_ItemSize);
m_ItemCount = CASCLIB_MAX(m_ItemCount, ItemIndex+1);
// If we inserted an item past the current end, we need to clear the items in-between
if (pbNewItem > pbLastItem)
{
memset(pbLastItem, 0, (pbNewItem - pbLastItem));
m_ItemCount = ItemIndex + 1;
}
return pbNewItem;
}
// Returns index of an item
size_t IndexOf(const void * pItem)
{
LPBYTE pbItem = (LPBYTE)pItem;
assert((m_pItemArray <= pbItem) && (pbItem <= m_pItemArray + (m_ItemCount * m_ItemSize)));
assert(((pbItem - m_pItemArray) % m_ItemSize) == 0);
return ((pbItem - m_pItemArray) / m_ItemSize);
}
void * ItemArray()
{
return m_pItemArray;
}
size_t ItemCount()
{
return m_ItemCount;
}
size_t ItemCountMax()
{
return m_ItemCountMax;
}
size_t ItemSize()
{
return m_ItemSize;
}
bool IsInitialized()
{
return (m_pItemArray && m_ItemCountMax);
}
// Invalidates the entire array, but keeps memory allocated
void Reset()
{
memset(m_pItemArray, 0, m_ItemCountMax * m_ItemSize);
m_ItemCount = 0;
}
// Frees the array
void Free()
{
CASC_FREE(m_pItemArray);
m_ItemCountMax = m_ItemCount = m_ItemSize = 0;
}
protected:
bool EnlargeArray(size_t NewItemCount)
{
LPBYTE NewItemArray;
size_t ItemCountMax;
// We expect the array to be already allocated
assert(m_pItemArray != NULL);
assert(m_ItemCountMax != 0);
// Shall we enlarge the table?
if (NewItemCount > m_ItemCountMax)
{
// Calculate new table size
ItemCountMax = m_ItemCountMax;
while (ItemCountMax < NewItemCount)
ItemCountMax = ItemCountMax << 1;
// Allocate new table
NewItemArray = CASC_REALLOC(BYTE, m_pItemArray, (ItemCountMax * m_ItemSize));
if (NewItemArray == NULL)
return false;
// Set the new table size
m_ItemCountMax = ItemCountMax;
m_pItemArray = NewItemArray;
}
return true;
}
LPBYTE m_pItemArray; // Pointer to item array
size_t m_ItemCountMax; // Maximum item count
size_t m_ItemCount; // Current item count
size_t m_ItemSize; // Size of an item
};
#endif // __CASC_ARRAY__
+743
View File
@@ -0,0 +1,743 @@
/*****************************************************************************/
/* CascCommon.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Common functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascCommon.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Conversion to uppercase/lowercase
// Converts ASCII characters to lowercase
// Converts backslash (0x5C) to normal slash (0x2F)
unsigned char AsciiToLowerTable_Slash[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x2F, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
// Converts ASCII characters to uppercase
// Converts slash (0x2F) to backslash (0x5C)
unsigned char AsciiToUpperTable_BkSlash[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
unsigned char IntToHexChar[] = "0123456789abcdef";
//-----------------------------------------------------------------------------
// GetLastError/SetLastError support for non-Windows platform
#ifndef PLATFORM_WINDOWS
static DWORD dwLastError = ERROR_SUCCESS;
DWORD GetLastError()
{
return dwLastError;
}
void SetLastError(DWORD dwErrCode)
{
dwLastError = dwErrCode;
}
#endif
//-----------------------------------------------------------------------------
// Linear data stream manipulation
LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue)
{
// Is there enough data?
if((pbDataPtr + sizeof(DWORD)) > pbDataEnd)
return NULL;
// Give data
PtrValue[0] = *(PDWORD)pbDataPtr;
// Return the pointer to data following after the integer
return pbDataPtr + sizeof(DWORD);
}
LPBYTE CaptureInteger32_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue)
{
// Is there enough data?
if((pbDataPtr + sizeof(DWORD)) > pbDataEnd)
return NULL;
// Convert data from Little endian to
PtrValue[0] = ConvertBytesToInteger_4(pbDataPtr);
// Return the pointer to data following after the integer
return pbDataPtr + sizeof(DWORD);
}
LPBYTE CaptureByteArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nLength, LPBYTE pbOutput)
{
// Is there enough data?
if((pbDataPtr + nLength) > pbDataEnd)
return NULL;
// Give data
memcpy(pbOutput, pbDataPtr, nLength);
// Return the pointer to data following after the integer
return pbDataPtr + nLength;
}
LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrCKey)
{
// Is there enough data?
if((pbDataPtr + sizeof(CONTENT_KEY)) > pbDataEnd)
return NULL;
// Give data
PtrCKey[0] = (PCONTENT_KEY)pbDataPtr;
// Return the pointer to data following after the integer
return pbDataPtr + sizeof(CONTENT_KEY);
}
LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount)
{
size_t ArraySize = ItemSize * ItemCount;
// Is there enough data?
if((pbDataPtr + ArraySize) > pbDataEnd)
return NULL;
// Give data
PtrArray[0] = pbDataPtr;
// Return the pointer to data following after the array
return pbDataPtr + ArraySize;
}
//-----------------------------------------------------------------------------
// String copying and conversion
void CascStrCopy(char * szTarget, size_t cchTarget, const char * szSource, size_t cchSource)
{
size_t cchToCopy;
if (cchTarget > 0)
{
// Make sure we know the length
if (cchSource == -1)
cchSource = strlen(szSource);
cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource);
// Copy the string
memcpy(szTarget, szSource, cchToCopy);
szTarget[cchToCopy] = 0;
}
}
void CascStrCopy(char * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource)
{
size_t cchToCopy;
if (cchTarget > 0)
{
// Make sure we know the length
if (cchSource == -1)
cchSource = wcslen(szSource);
cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource);
wcstombs(szTarget, szSource, cchToCopy);
szTarget[cchToCopy] = 0;
}
}
void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const char * szSource, size_t cchSource)
{
size_t cchToCopy;
if (cchTarget > 0)
{
// Make sure we know the length
if (cchSource == -1)
cchSource = strlen(szSource);
cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource);
mbstowcs(szTarget, szSource, cchToCopy);
szTarget[cchToCopy] = 0;
}
}
void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource)
{
size_t cchToCopy;
if (cchTarget > 0)
{
// Make sure we know the length
if (cchSource == -1)
cchSource = wcslen(szSource);
cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource);
memcpy(szTarget, szSource, cchToCopy * sizeof(wchar_t));
szTarget[cchToCopy] = 0;
}
}
//-----------------------------------------------------------------------------
// Safe version of s(w)printf
size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...)
{
char * buffend;
va_list argList;
// Start the argument list
va_start(argList, format);
#ifdef PLATFORM_WINDOWS
StringCchVPrintfExA(buffer, nCount, &buffend, NULL, 0, format, argList);
#else
buffend = buffer + vsnprintf(buffer, nCount, format, argList);
#endif
// End the argument list
va_end(argList);
return (buffend - buffer);
}
size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, ...)
{
wchar_t * buffend;
va_list argList;
// Start the argument list
va_start(argList, format);
#ifdef PLATFORM_WINDOWS
StringCchVPrintfExW(buffer, nCount, &buffend, NULL, 0, format, argList);
#else
buffend = buffer + vswprintf(buffer, nCount, format, argList);
#endif
// End the argument list
va_end(argList);
return (buffend - buffer);
}
//-----------------------------------------------------------------------------
// String allocation
char * CascNewStr(const char * szString, size_t nCharsToReserve)
{
char * szNewString = NULL;
size_t nLength;
if(szString != NULL)
{
nLength = strlen(szString);
szNewString = CASC_ALLOC(char, nLength + nCharsToReserve + 1);
if(szNewString != NULL)
{
memcpy(szNewString, szString, nLength);
szNewString[nLength] = 0;
}
}
return szNewString;
}
wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve)
{
wchar_t * szNewString = NULL;
size_t nLength;
if(szString != NULL)
{
nLength = wcslen(szString);
szNewString = CASC_ALLOC(wchar_t, nLength + nCharsToReserve + 1);
if(szNewString != NULL)
{
memcpy(szNewString, szString, nLength * sizeof(wchar_t));
szNewString[nLength] = 0;
}
}
return szNewString;
}
template <typename XCHAR>
TCHAR * AppendPathFragment(TCHAR * szBuffer, TCHAR * szBufferEnd, const XCHAR * szPath, char chSeparator, bool bFirstFragment = false)
{
// The "Path" must not be empty
if(szPath && szPath[0])
{
// Append the path separator after the first fragment
if(szBuffer < szBufferEnd && bFirstFragment == false)
{
if(szBuffer[-1] != chSeparator)
{
*szBuffer++ = chSeparator;
}
}
// Copy the sub path
while(szBuffer < szBufferEnd && szPath[0] != 0)
{
// If there is a path separator, we skip it (all of them) and put single separator there
if(szPath[0] == '\\' || szPath[0] == '/')
{
while(szPath[0] == '\\' || szPath[0] == '/')
szPath++;
*szBuffer++ = chSeparator;
}
else
{
*szBuffer++ = *szPath++;
}
}
// Append end of string
szBuffer[0] = 0;
}
return szBuffer;
}
TCHAR * GetLastPathPart(TCHAR * szWorkPath)
{
size_t nLength = _tcslen(szWorkPath);
// Go one character back
if(nLength > 0)
nLength--;
// Cut ending (back)slashes, if any
while(nLength > 0 && (szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/')))
nLength--;
// Cut the last path part
while(nLength > 0)
{
// End of path?
if(szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/'))
{
return szWorkPath + nLength;
}
// Go one character back
nLength--;
}
return NULL;
}
bool CutLastPathPart(TCHAR * szWorkPath)
{
// Get the last part of the path
szWorkPath = GetLastPathPart(szWorkPath);
if(szWorkPath == NULL)
return false;
szWorkPath[0] = 0;
return true;
}
TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir)
{
TCHAR * szFullPathEnd;
TCHAR * szFullPath = NULL;
TCHAR * szPathPtr;
size_t nLength1 = (szDirectory != NULL) ? _tcslen(szDirectory) : 0;
size_t nLength2 = (szSubDir != NULL) ? _tcslen(szSubDir) : 0;
// Allocate the entire buffer
szFullPath = szPathPtr = CASC_ALLOC(TCHAR, nLength1 + nLength2 + 2);
szFullPathEnd = szFullPath + nLength1 + nLength2 + 1;
if(szFullPath != NULL)
{
szPathPtr = AppendPathFragment(szPathPtr, szFullPathEnd, szDirectory, PATH_SEP_CHAR, true);
szPathPtr = AppendPathFragment(szPathPtr, szFullPathEnd, szSubDir, PATH_SEP_CHAR);
}
return szFullPath;
}
size_t CombineFilePath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szPath, const TCHAR * szSubPath1, const TCHAR * szSubPath2)
{
TCHAR * szSaveBuffer = szBuffer;
TCHAR * szBufferEnd = szBuffer + nMaxChars - 1;
// Append all three parts and return length
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szPath, PATH_SEP_CHAR, true);
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath1, PATH_SEP_CHAR);
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath2, PATH_SEP_CHAR);
return (szBuffer - szSaveBuffer);
}
size_t CombineUrlPath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szHost, const TCHAR * szSubPath1, const TCHAR * szSubPath2)
{
TCHAR * szSaveBuffer = szBuffer;
TCHAR * szBufferEnd = szBuffer + nMaxChars - 1;
// Append all three parts and return length
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHost, '/', true);
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath1, '/');
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath2, '/');
return (szBuffer - szSaveBuffer);
}
size_t CreateCascSubdirectoryName(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szSubDir, const TCHAR * szExtension, LPBYTE pbEKey)
{
TCHAR * szSaveBuffer = szBuffer;
TCHAR * szBufferEnd = szBuffer + nMaxChars - 1;
char szHashSubPath[0x80];
char szHashText[MD5_STRING_SIZE+1];
// Prepare the subpath
StringFromBinary(pbEKey, MD5_HASH_SIZE, szHashText);
CascStrPrintf(szHashSubPath, _countof(szHashSubPath), "%02x/%02x/%s", pbEKey[0], pbEKey[1], szHashText);
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubDir, '/', true);
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHashSubPath, '/');
szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szExtension, '/', true);
return (szBuffer - szSaveBuffer);
}
size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars)
{
char * szNormNameEnd = szNormName + cchMaxChars;
size_t i;
// Normalize the file name: ToLower + BackSlashToSlash
for(i = 0; szFileName[0] != 0 && szNormName < szNormNameEnd; i++)
*szNormName++ = NormTable[*szFileName++];
// 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 CalcNormNameHash(const char * szNormName, size_t nLength)
{
uint32_t dwHashHigh = 0;
uint32_t dwHashLow = 0;
// Calculate the HASH value of the normalized file name
hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow);
return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
}
ULONGLONG CalcFileNameHash(const char * szFileName)
{
char szNormName[MAX_PATH+1];
size_t nLength;
// Normalize the file name - convert to uppercase, slashes to backslashes
nLength = NormalizeFileName_UpperBkSlash(szNormName, szFileName, MAX_PATH);
// Calculate hash from the normalized name
return CalcNormNameHash(szNormName, nLength);
}
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue)
{
BYTE Digit;
Digit = (BYTE)(AsciiToUpperTable_BkSlash[szString[0]] - _T('0'));
if(Digit > 9)
Digit -= 'A' - '9' - 1;
PtrValue[0] = Digit;
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
assert((nMaxDigits & 0x01) == 0);
assert(nMaxDigits <= 8);
// Prepare the variables
PtrValue[0] = 0;
nMaxDigits >>= 1;
// Convert the string up to the number of digits
for(size_t i = 0; i < nMaxDigits; i++)
{
BYTE DigitOne;
BYTE DigitTwo;
DigitOne = (BYTE)(AsciiToUpperTable_BkSlash[szString[0]] - _T('0'));
if(DigitOne > 9)
DigitOne -= 'A' - '9' - 1;
DigitTwo = (BYTE)(AsciiToUpperTable_BkSlash[szString[1]] - _T('0'));
if(DigitTwo > 9)
DigitTwo -= 'A' - '9' - 1;
if(DigitOne > 0x0F || DigitTwo > 0x0F)
return ERROR_BAD_FORMAT;
PtrValue[0] = (PtrValue[0] << 0x08) | (DigitOne << 0x04) | DigitTwo;
szString += 2;
}
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;
// Verify the binary pointer
if(pbBinary && cbBinary)
{
// Convert the string to the array of MD5
// Copy the blob data as text
for(size_t i = 0; i < cbBinary; i++)
{
*szBuffer++ = IntToHexChar[pbBinary[i] >> 0x04];
*szBuffer++ = IntToHexChar[pbBinary[i] & 0x0F];
}
}
// Terminate the string
*szBuffer = 0;
return szSaveBuffer;
}
char * StringFromMD5(LPBYTE md5, char * szBuffer)
{
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
}
//-----------------------------------------------------------------------------
// File name utilities
bool IsFileDataIdName(const char * szFileName, DWORD & FileDataId)
{
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
if(szFileName[0x0C] == 0 || szFileName[0x0C] == '.')
{
FileDataId = ConvertBytesToInteger_4(BinaryValue);
return (FileDataId != CASC_INVALID_ID);
}
}
}
return false;
}
bool IsFileCKeyEKeyName(const char * szFileName, LPBYTE PtrKeyBuffer)
{
size_t nLength = strlen(szFileName);
if(nLength == MD5_STRING_SIZE)
{
if(ConvertStringToBinary(szFileName, MD5_STRING_SIZE, PtrKeyBuffer) == ERROR_SUCCESS)
{
return true;
}
}
return false;
}
bool CascCheckWildCard(const char * szString, const char * szWildCard)
{
const char * szWildCardPtr;
while(szWildCard && szWildCard[0])
{
// If there is '?' in the wildcard, we skip one char
while(szWildCard[0] == '?')
{
if(szString[0] == 0)
return false;
szWildCard++;
szString++;
}
// Handle '*'
szWildCardPtr = szWildCard;
if(szWildCardPtr[0] != 0)
{
if(szWildCardPtr[0] == '*')
{
szWildCardPtr++;
if(szWildCardPtr[0] == '*')
continue;
if(szWildCardPtr[0] == 0)
return true;
if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] == AsciiToUpperTable_BkSlash[szString[0]])
{
if(CascCheckWildCard(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
{
return (szString[0] == 0) ? true : false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Hashing functions
bool CascIsValidMD5(LPBYTE pbMd5)
{
PDWORD Int32Array = (PDWORD)pbMd5;
// The MD5 is considered invalid if it is zeroed
return (Int32Array[0] | Int32Array[1] | Int32Array[2] | Int32Array[3]) ? true : false;
}
bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
{
MD5_CTX md5_ctx;
BYTE md5_digest[MD5_HASH_SIZE];
// Don't verify the block if the MD5 is not valid.
if(!CascIsValidMD5(expected_md5))
return true;
// Calculate the MD5 of the data block
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock);
MD5_Final(md5_digest, &md5_ctx);
// Does the MD5's match?
return (memcmp(md5_digest, expected_md5, MD5_HASH_SIZE) == 0);
}
void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
{
MD5_CTX md5_ctx;
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock);
MD5_Final(md5_hash, &md5_ctx);
}
+386
View File
@@ -0,0 +1,386 @@
/*****************************************************************************/
/* CascCommon.h Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Common functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of CascCommon.h */
/*****************************************************************************/
#ifndef __COMMON_H__
#define __COMMON_H__
//-----------------------------------------------------------------------------
// Common macros
// Macro for building 64-bit file offset from two 32-bit
#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo)
#ifndef ALIGN_TO_SIZE
#define ALIGN_TO_SIZE(x, a) (((x) + (a)-1) & ~((a)-1))
#endif
// Prevent problems with CRT "min" and "max" functions,
// as they are not defined on all platforms
#define CASCLIB_MIN(a, b) ((a < b) ? a : b)
#define CASCLIB_MAX(a, b) ((a > b) ? a : b)
#define CASCLIB_UNUSED(p) ((void)(p))
//-----------------------------------------------------------------------------
// Common structures
// Structure for static content key (CKey) and encoded key (EKey)
// The CKey is a MD5 hash of the file data.
// The EKey is (shortened) MD5 hash of the file header, which contains MD5 hashes of all the logical blocks of the file.
typedef struct _CONTENT_KEY
{
BYTE Value[MD5_HASH_SIZE]; // MD5 of the file
} CONTENT_KEY, *PCONTENT_KEY, ENCODED_KEY, *PENCODED_KEY;
// Helper structure for merging file paths
typedef struct _PATH_BUFFER
{
char * szBegin;
char * szPtr;
char * szEnd;
} PATH_BUFFER, *PPATH_BUFFER;
//-----------------------------------------------------------------------------
// Basic structure used by all CascLib objects to describe a single entry
// in the CASC storage. Each entry represents one physical file
// in the storage. Note that the file may be present under several file names.
// Flags for CASC_CKEY_ENTRY::Flags
#define CASC_CE_FILE_IS_LOCAL 0x00000001 // The file is available locally. Keep this flag to have value of 1
#define CASC_CE_HAS_CKEY 0x00000002 // The CKey is present in the entry
#define CASC_CE_HAS_EKEY 0x00000004 // The EKey is present, at least partial one
#define CASC_CE_HAS_EKEY_PARTIAL 0x00000008 // The EKey is only partial, padded by zeros. Always used with CASC_CE_HAS_EKEY
#define CASC_CE_IN_ENCODING 0x00000010 // Present in the ENCODING manifest
#define CASC_CE_IN_DOWNLOAD 0x00000020 // Present in the DOWNLOAD manifest
#define CASC_CE_FOLDER_ENTRY 0x00000040 // This CKey entry is a folder
// In-memory representation of a single entry.
struct CASC_CKEY_ENTRY
{
CASC_CKEY_ENTRY()
{
Init();
}
void Init(void)
{
memset(this, 0, sizeof(CASC_CKEY_ENTRY));
StorageOffset = CASC_INVALID_OFFS64;
EncodedSize = CASC_INVALID_SIZE;
ContentSize = CASC_INVALID_SIZE;
}
BYTE CKey[MD5_HASH_SIZE]; // Content key of the full length
BYTE EKey[MD5_HASH_SIZE]; // Encoded key of the full length
ULONGLONG StorageOffset; // Linear offset over the entire storage. 0 if not present
ULONGLONG TagBitMask; // Bitmap for the tags. 0 ig tags are not supported
DWORD EncodedSize; // Encoded size of the file. 0 if not supported
DWORD ContentSize; // Content size of the file. 0 if not supported
DWORD Flags; // See CASC_CE_XXX
USHORT RefCount; // This is the number of file names referencing this entry
BYTE Priority; // Download priority
BYTE Alignment;
};
typedef CASC_CKEY_ENTRY *PCASC_CKEY_ENTRY;
//-----------------------------------------------------------------------------
// Conversion tables
extern unsigned char AsciiToLowerTable_Slash[256];
extern unsigned char AsciiToUpperTable_BkSlash[256];
extern unsigned char IntToHexChar[];
//-----------------------------------------------------------------------------
// Memory management
//
// We use our own macros for allocating/freeing memory. If you want
// to redefine them, please keep the following rules:
//
// - The memory allocation must return NULL if not enough memory
// (i.e not to throw exception)
// - The allocating function does not need to fill the allocated buffer with zeros
// - The reallocating function must support NULL as the previous block
// - Memory freeing function must check for NULL pointer and do nothing if so
//
#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type))
#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type))
template <typename T>
void CASC_FREE(T *& ptr)
{
if (ptr != NULL)
free(ptr);
ptr = NULL;
}
//-----------------------------------------------------------------------------
// Big endian number manipulation
inline DWORD ConvertBytesToInteger_2(LPBYTE ValueAsBytes)
{
USHORT Value = 0;
Value = (Value << 0x08) | ValueAsBytes[0];
Value = (Value << 0x08) | ValueAsBytes[1];
return Value;
}
inline DWORD ConvertBytesToInteger_3(LPBYTE ValueAsBytes)
{
DWORD Value = 0;
Value = (Value << 0x08) | ValueAsBytes[0];
Value = (Value << 0x08) | ValueAsBytes[1];
Value = (Value << 0x08) | ValueAsBytes[2];
return Value;
}
inline DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes)
{
DWORD Value = 0;
Value = (Value << 0x08) | ValueAsBytes[0];
Value = (Value << 0x08) | ValueAsBytes[1];
Value = (Value << 0x08) | ValueAsBytes[2];
Value = (Value << 0x08) | ValueAsBytes[3];
return Value;
}
// Converts the variable-size big-endian into integer
inline DWORD ConvertBytesToInteger_X(LPBYTE ValueAsBytes, DWORD dwByteSize)
{
DWORD Value = 0;
if(dwByteSize > 0)
Value = (Value << 0x08) | ValueAsBytes[0];
if(dwByteSize > 1)
Value = (Value << 0x08) | ValueAsBytes[1];
if(dwByteSize > 2)
Value = (Value << 0x08) | ValueAsBytes[2];
if(dwByteSize > 3)
Value = (Value << 0x08) | ValueAsBytes[3];
return Value;
}
inline DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes)
{
DWORD Value = 0;
Value = (Value << 0x08) | ValueAsBytes[3];
Value = (Value << 0x08) | ValueAsBytes[2];
Value = (Value << 0x08) | ValueAsBytes[1];
Value = (Value << 0x08) | ValueAsBytes[0];
return Value;
}
// Read the 40-bit big-endian offset into ULONGLONG
inline ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes)
{
ULONGLONG Value = 0;
Value = (Value << 0x08) | ValueAsBytes[0];
Value = (Value << 0x08) | ValueAsBytes[1];
Value = (Value << 0x08) | ValueAsBytes[2];
Value = (Value << 0x08) | ValueAsBytes[3];
Value = (Value << 0x08) | ValueAsBytes[4];
return Value;
}
inline 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;
}
inline void ConvertIntegerToBytes_4_LE(DWORD Value, LPBYTE ValueAsBytes)
{
ValueAsBytes[0] = (Value >> 0x00) & 0xFF;
ValueAsBytes[1] = (Value >> 0x08) & 0xFF;
ValueAsBytes[2] = (Value >> 0x10) & 0xFF;
ValueAsBytes[3] = (Value >> 0x18) & 0xFF;
}
// Faster than memset(Buffer, 0, 0x10)
inline void ZeroMemory16(void * Buffer)
{
PDWORD PtrBuffer = (PDWORD)Buffer;
PtrBuffer[0] = 0;
PtrBuffer[1] = 0;
PtrBuffer[2] = 0;
PtrBuffer[3] = 0;
}
// Faster than memcpy(Target, Source, 0x10)
inline void CopyMemory16(void * Target, void * Source)
{
PDWORD PtrTarget = (PDWORD)Target;
PDWORD PtrSource = (PDWORD)Source;
PtrTarget[0] = PtrSource[0];
PtrTarget[1] = PtrSource[1];
PtrTarget[2] = PtrSource[2];
PtrTarget[3] = PtrSource[3];
}
//-----------------------------------------------------------------------------
// Linear data stream manipulation
LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue);
LPBYTE CaptureInteger32_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue);
LPBYTE CaptureByteArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nLength, LPBYTE pbOutput);
LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrCKey);
LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount);
#define CaptureArray(pbDataPtr, pbDataEnd, PtrArray, type, count) CaptureArray_(pbDataPtr, pbDataEnd, PtrArray, sizeof(type), count)
//-----------------------------------------------------------------------------
// String copying and conversion
void CascStrCopy(char * szTarget, size_t cchTarget, const char * szSource, size_t cchSource = -1);
void CascStrCopy(char * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource = -1);
void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const char * szSource, size_t cchSource = -1);
void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource = -1);
//-----------------------------------------------------------------------------
// Safe version of s(w)printf
size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...);
size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, ...);
//-----------------------------------------------------------------------------
// String allocation
char * CascNewStr(const char * szString, size_t nCharsToReserve = 0);
wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve = 0);
TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir);
size_t CombineFilePath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szPath, const TCHAR * szSubPath1, const TCHAR * szSubPath2 = NULL);
size_t CombineUrlPath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szHost, const TCHAR * szSubPath1, const TCHAR * szSubPath2 = NULL);
TCHAR * GetLastPathPart(TCHAR * szWorkPath);
bool CutLastPathPart(TCHAR * szWorkPath);
size_t CreateCascSubdirectoryName(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szSubDir, const TCHAR * szExtension, LPBYTE pbEKey);
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 CalcNormNameHash(const char * szNormName, size_t nLength);
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);
//-----------------------------------------------------------------------------
// Structure query key
struct QUERY_KEY
{
QUERY_KEY()
{
pbData = NULL;
cbData = 0;
}
~QUERY_KEY()
{
CASC_FREE(pbData);
cbData = 0;
}
LPBYTE pbData;
size_t cbData;
};
typedef QUERY_KEY *PQUERY_KEY;
//-----------------------------------------------------------------------------
// File name utilities
// Retrieves the pointer to plain name
template <typename XCHAR>
const XCHAR * GetPlainFileName(const XCHAR * szFileName)
{
const XCHAR * szPlainName = szFileName;
while(*szFileName != 0)
{
if(*szFileName == '\\' || *szFileName == '/')
szPlainName = szFileName + 1;
szFileName++;
}
return szPlainName;
}
// Retrieves the pointer to file extension
template <typename XCHAR>
const XCHAR * GetFileExtension(const XCHAR * szFileName)
{
const XCHAR * szExtension = NULL;
// We need to start searching from the plain name
// Avoid: C:\$RECYCLE.BIN\File.ext
szFileName = GetPlainFileName(szFileName);
// Find the last dot in the plain file name
while(szFileName[0] != 0)
{
if(szFileName[0] == '.')
szExtension = szFileName;
szFileName++;
}
// If not found, return the end of the file name
return (XCHAR *)((szExtension != NULL) ? szExtension : szFileName);
}
bool IsFileDataIdName(const char * szFileName, DWORD & FileDataId);
bool IsFileCKeyEKeyName(const char * szFileName, LPBYTE PtrKeyBuffer);
bool CascCheckWildCard(const char * szString, const char * szWildCard);
//-----------------------------------------------------------------------------
// Hashing functions
ULONGLONG HashStringJenkins(const char * szFileName);
bool CascIsValidMD5(LPBYTE pbMd5);
void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash);
bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5);
//-----------------------------------------------------------------------------
// 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 // __COMMON_H__
+370
View File
@@ -0,0 +1,370 @@
/*****************************************************************************/
/* Csv.cpp Copyright (c) Ladislav Zezula 2019 */
/*---------------------------------------------------------------------------*/
/* Implementation for the CSV handler class */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 24.05.19 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Local variables
static const CASC_CSV_LINE NullLine;
#define NullColumn NullLine.Columns[0];
//-----------------------------------------------------------------------------
// Local functions
static char * NextLine(char * szLine)
{
// Find the end of the line
while(szLine[0] != 0 && szLine[0] != 0x0A && szLine[0] != 0x0D)
szLine++;
// Terminate the line
while(szLine[0] == 0x0A || szLine[0] == 0x0D)
*szLine++ = 0;
// If there's an end-of-string, it's over
return (szLine[0] != 0) ? szLine : NULL;
}
static char * NextColumn(char * szColumn)
{
// Find the end of the column
while(szColumn[0] != 0 && szColumn[0] != '|')
szColumn++;
// Terminate the column
if (szColumn[0] == '|')
{
*szColumn++ = 0;
return szColumn;
}
// If there's an end-of-string, it's over
return NULL;
}
static size_t CalcHashValue(const char * szString)
{
size_t dwHash = 0x7EEE7EEE;
// Hash the string itself
while(szString[0] != 0)
{
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ szString[0];
szString++;
}
// Return the hash limited by the table size
return dwHash;
}
static BYTE HashToIndex(size_t dwHashValue)
{
return (BYTE)(dwHashValue & (CSV_HASH_TABLE_SIZE - 1));
}
//-----------------------------------------------------------------------------
// Class for CSV line
CASC_CSV_LINE::CASC_CSV_LINE()
{
m_pParent = NULL;
m_nColumns = 0;
}
CASC_CSV_LINE::~CASC_CSV_LINE()
{}
bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine)
{
char * szCsvColumn;
size_t nHdrColumns = 0;
size_t nColumns = 0;
// Reset the number of column to zero
m_pParent = pParent;
m_nColumns = 0;
// Parse each column
while(szCsvLine != NULL && nColumns < CSV_MAX_COLUMNS)
{
// Get current line and the next one
szCsvColumn = szCsvLine;
szCsvLine = NextColumn(szCsvLine);
// Save the current line
Columns[nColumns].szValue = szCsvColumn;
Columns[nColumns].nLength = strlen(szCsvColumn);
nColumns++;
}
// Columns overflow
if(nColumns >= CSV_MAX_COLUMNS)
{
assert(false);
return false;
}
// If the parent has header lines, then the number of columns must match
// In the case of mismatched column count, ignore the line
nHdrColumns = pParent->GetHeaderColumns();
if(nHdrColumns != 0 && nColumns != nHdrColumns)
return false;
// All OK
m_nColumns = nColumns;
return true;
}
const CASC_CSV_COLUMN & CASC_CSV_LINE::operator[](const char * szColumnName) const
{
size_t nIndex;
// The column must have a hash table
if(m_pParent != NULL)
{
nIndex = m_pParent->GetColumnIndex(szColumnName);
if(nIndex != CSV_INVALID_INDEX && nIndex < m_nColumns)
{
return Columns[nIndex];
}
}
return NullColumn;
}
const CASC_CSV_COLUMN & CASC_CSV_LINE::operator[](size_t nIndex) const
{
return (nIndex < m_nColumns) ? Columns[nIndex] : NullColumn;
}
//-----------------------------------------------------------------------------
// Class for CSV object
CASC_CSV::CASC_CSV(size_t nLinesMax, bool bHasHeader)
{
// Initialize the class variables
memset(HashTable, 0xFF, sizeof(HashTable));
m_szCsvFile = NULL;
m_szCsvPtr = NULL;
m_nCsvFile = 0;
m_nLines = 0;
m_bHasHeader = bHasHeader;
// Nonzero number of lines means a finite CSV handler. The CSV will be loaded at once.
// Zero means that the user needs to call LoadNextLine() and then the line data
if(nLinesMax != 0)
{
m_pLines = new CASC_CSV_LINE[nLinesMax];
m_nLinesMax = nLinesMax;
m_bHasAllLines = true;
}
else
{
m_pLines = new CASC_CSV_LINE[1];
m_nLinesMax = 1;
m_bHasAllLines = false;
}
}
CASC_CSV::~CASC_CSV()
{
if(m_pLines != NULL)
delete[] m_pLines;
if(m_szCsvFile != NULL)
delete [] m_szCsvFile;
m_szCsvFile = NULL;
}
int CASC_CSV::Load(const TCHAR * szFileName)
{
DWORD cbFileData = 0;
int nError = ERROR_SUCCESS;
m_szCsvFile = (char *)LoadFileToMemory(szFileName, &cbFileData);
if (m_szCsvFile != NULL)
{
// There is one extra byte reserved by LoadFileToMemory, so we can put zero there
m_szCsvFile[cbFileData] = 0;
m_szCsvPtr = m_szCsvFile;
m_nCsvFile = cbFileData;
// Parse the data
nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
}
else
{
nError = GetLastError();
}
return nError;
}
int CASC_CSV::Load(LPBYTE pbData, size_t cbData)
{
int nError = ERROR_NOT_ENOUGH_MEMORY;
m_szCsvFile = new char[cbData + 1];
if (m_szCsvFile != NULL)
{
// Copy the entire data and terminate them with zero
memcpy(m_szCsvFile, pbData, cbData);
m_szCsvFile[cbData] = 0;
m_szCsvPtr = m_szCsvFile;
m_nCsvFile = cbData;
// Parse the data
nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
}
return nError;
}
bool CASC_CSV::LoadNextLine()
{
bool bResult = false;
// Only endless CSV handler can do this
if(m_bHasAllLines == false)
{
// A few checks
assert(m_pLines != NULL);
assert(m_nLinesMax == 1);
// Reset the current line and load it
bResult = LoadNextLine(m_pLines[0]);
m_nLines = (bResult) ? 1 : 0;
}
return bResult;
}
bool CASC_CSV::InitializeHashTable()
{
// Create the hash table of HeaderText -> ColumnIndex
for(size_t i = 0; i < Header.GetColumnCount(); i++)
{
// Calculate the start slot and the current slot
size_t nStartIndex = HashToIndex(CalcHashValue(Header[i].szValue));
size_t nHashIndex = nStartIndex;
// Go as long as there is not a free space
while(HashTable[nHashIndex] != 0xFF)
{
nHashIndex = HashToIndex(nHashIndex + 1);
}
// Set the slot
HashTable[nHashIndex] = (BYTE)i;
}
return true;
}
bool CASC_CSV::LoadNextLine(CASC_CSV_LINE & Line)
{
// Overwatch ROOT's header begins with "#"
if(m_szCsvPtr == m_szCsvFile && m_szCsvPtr[0] == '#')
m_szCsvPtr++;
// Parse the entire line
while(m_szCsvPtr != NULL && m_szCsvPtr[0] != 0)
{
char * szCsvLine = m_szCsvPtr;
// Get the next line
m_szCsvPtr = NextLine(m_szCsvPtr);
// Initialize the line
if (Line.SetLine(this, szCsvLine))
return true;
}
// End-of-file found
return false;
}
bool CASC_CSV::ParseCsvData()
{
// Sanity checks
assert(m_nLines == 0);
// If we have header, then parse it and set the pointer to the next line
if(m_bHasHeader)
{
// Load the current line to the header
if (!LoadNextLine(Header))
return false;
// Initialize the hash table
if(!InitializeHashTable())
return false;
}
// Are we supposed to load all lines?
if(m_bHasAllLines)
{
// Parse each line
for(size_t i = 0; i < m_nLinesMax; i++)
{
if(!LoadNextLine(m_pLines[i]))
break;
m_nLines++;
}
}
return true;
}
const CASC_CSV_COLUMN & CASC_CSV::operator[](const char * szColumnName) const
{
if (m_pLines == NULL || m_nLines == 0)
return NullColumn;
return m_pLines[0][GetColumnIndex(szColumnName)];
}
const CASC_CSV_LINE & CASC_CSV::operator[](size_t nIndex) const
{
if (m_pLines == NULL || nIndex >= m_nLines)
return NullLine;
return m_pLines[nIndex];
}
size_t CASC_CSV::GetHeaderColumns() const
{
return (m_bHasHeader) ? Header.GetColumnCount() : 0;
}
size_t CASC_CSV::GetColumnIndex(const char * szColumnName) const
{
if(m_bHasHeader)
{
// Calculate the start slot and the current slot
size_t nStartIndex = HashToIndex(CalcHashValue(szColumnName));
size_t nHashIndex = nStartIndex;
size_t nIndex;
// Go as long as there is not a free space
while((nIndex = HashTable[nHashIndex]) != 0xFF)
{
// Compare the string
if(!strcmp(Header[nIndex].szValue, szColumnName))
return nIndex;
// Move to the next column
nHashIndex = HashToIndex(nHashIndex + 1);
}
}
return CSV_INVALID_INDEX;
}
+105
View File
@@ -0,0 +1,105 @@
/*****************************************************************************/
/* Csv.h Copyright (c) Ladislav Zezula 2019 */
/*---------------------------------------------------------------------------*/
/* Interface for the CSV handler class */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 24.05.19 1.00 Lad Created */
/*****************************************************************************/
#ifndef __CSV_H__
#define __CSV_H__
//-----------------------------------------------------------------------------
// Defines
#define CSV_INVALID_INDEX ((size_t)(-1))
#define CSV_ZERO ((size_t)(0)) // Use Csv[0][CSV_ZERO] instead of ambiguous Csv[0][0]
#define CSV_MAX_COLUMNS 0x20
#define CSV_HASH_TABLE_SIZE 0x80
//-----------------------------------------------------------------------------
// Class for CSV line
class CASC_CSV;
struct CASC_CSV_COLUMN
{
CASC_CSV_COLUMN()
{
szValue = NULL;
nLength = 0;
}
const char * szValue;
size_t nLength;
};
class CASC_CSV_LINE
{
public:
CASC_CSV_LINE();
~CASC_CSV_LINE();
bool SetLine(CASC_CSV * pParent, char * szLine);
const CASC_CSV_COLUMN & operator[](const char * szColumnName) const;
const CASC_CSV_COLUMN & operator[](size_t nIndex) const;
size_t GetColumnCount() const
{
return m_nColumns;
}
protected:
friend class CASC_CSV;
CASC_CSV * m_pParent;
CASC_CSV_COLUMN Columns[CSV_MAX_COLUMNS];
size_t m_nColumns;
};
class CASC_CSV
{
public:
CASC_CSV(size_t nLinesMax, bool bHasHeader);
~CASC_CSV();
int Load(LPBYTE pbData, size_t cbData);
int Load(const TCHAR * szFileName);
bool LoadNextLine();
const CASC_CSV_COLUMN & operator[](const char * szColumnName) const;
const CASC_CSV_LINE & operator[](size_t nIndex) const;
size_t GetHeaderColumns() const;
size_t GetColumnIndex(const char * szColumnName) const;
size_t GetLineCount() const
{
return m_nLines;
}
protected:
bool InitializeHashTable();
bool LoadNextLine(CASC_CSV_LINE & Line);
bool ParseCsvData();
CASC_CSV_LINE * m_pLines;
CASC_CSV_LINE Header;
BYTE HashTable[CSV_HASH_TABLE_SIZE];
char * m_szCsvFile;
char * m_szCsvPtr;
size_t m_nCsvFile;
size_t m_nLinesMax;
size_t m_nLines;
bool m_bHasHeader;
bool m_bHasAllLines;
};
#endif // __CSV_H__
+116
View File
@@ -0,0 +1,116 @@
/*****************************************************************************/
/* Directory.cpp Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* System-dependent directory functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.04.14 1.00 Lad The first version of Directory.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Public functions
bool DirectoryExists(const TCHAR * szDirectory)
{
#ifdef PLATFORM_WINDOWS
DWORD dwAttributes = GetFileAttributes(szDirectory);
if((dwAttributes != INVALID_FILE_ATTRIBUTES) && (dwAttributes & FILE_ATTRIBUTE_DIRECTORY))
return true;
#else // PLATFORM_WINDOWS
DIR * dir = opendir(szDirectory);
if(dir != NULL)
{
closedir(dir);
return true;
}
#endif
return false;
}
bool MakeDirectory(const TCHAR * szDirectory)
{
#ifdef PLATFORM_WINDOWS
BOOL bResult = CreateDirectory(szDirectory, NULL);
return (bResult) ? true : false;
#else
return (mkdir(szDirectory, 0755) == 0);
#endif
}
int ScanIndexDirectory(
const TCHAR * szIndexPath,
INDEX_FILE_FOUND pfnOnFileFound,
PDWORD MainIndexes,
PDWORD OldIndexArray,
void * pvContext)
{
#ifdef PLATFORM_WINDOWS
WIN32_FIND_DATA wf;
TCHAR * szSearchMask;
HANDLE hFind;
// Prepare the search mask
szSearchMask = CombinePath(szIndexPath, _T("*"));
if(szSearchMask == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Prepare directory search
hFind = FindFirstFile(szSearchMask, &wf);
if(hFind != INVALID_HANDLE_VALUE)
{
// Skip the first file as it's always just "." or ".."
while(FindNextFile(hFind, &wf))
{
// If the found object is a file, pass it to the handler
if(!(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// Let the callback scan the file name
pfnOnFileFound(wf.cFileName, MainIndexes, OldIndexArray, pvContext);
}
}
// Close the search handle
FindClose(hFind);
}
CASC_FREE(szSearchMask);
#else // PLATFORM_WINDOWS
struct dirent * dir_entry;
DIR * dir;
dir = opendir(szIndexPath);
if(dir != NULL)
{
while((dir_entry = readdir(dir)) != NULL)
{
if(dir_entry->d_type != DT_DIR)
{
pfnOnFileFound(dir_entry->d_name, MainIndexes, OldIndexArray, pvContext);
}
}
closedir(dir);
}
#endif
return ERROR_SUCCESS;
}
+31
View File
@@ -0,0 +1,31 @@
/*****************************************************************************/
/* 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);
bool MakeDirectory(const TCHAR * szDirectory);
int ScanIndexDirectory(
const TCHAR * szIndexPath,
INDEX_FILE_FOUND pfnOnFileFound,
PDWORD IndexArray,
PDWORD OldIndexArray,
void * pvContext
);
#endif // __DIRECTORY_H__
File diff suppressed because it is too large Load Diff
+268
View File
@@ -0,0 +1,268 @@
/*****************************************************************************/
/* FileStream.h Copyright (c) Ladislav Zezula 2012 */
/*---------------------------------------------------------------------------*/
/* Description: Definitions for FileStream object */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 14.04.12 1.00 Lad The first version of FileStream.h */
/*****************************************************************************/
#ifndef __FILESTREAM_H__
#define __FILESTREAM_H__
//-----------------------------------------------------------------------------
// Flags for file stream
#define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file
#define BASE_PROVIDER_MAP 0x00000001 // Base data source is memory-mapped file
#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server
#define BASE_PROVIDER_MASK 0x0000000F // Mask for base provider value
#define STREAM_PROVIDER_FLAT 0x00000000 // Stream is linear with no offset mapping
#define STREAM_PROVIDER_PARTIAL 0x00000010 // Stream is partial file (.part)
#define STREAM_PROVIDER_ENCRYPTED 0x00000020 // Stream is an encrypted archive
#define STREAM_PROVIDER_BLOCK4 0x00000030 // 0x4000 per block, text MD5 after each block, max 0x2000 blocks per file
#define STREAM_PROVIDER_MASK 0x000000F0 // Mask for stream provider value
#define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only
#define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write
#define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it
#define STREAM_FLAG_FILL_MISSING 0x00000800 // If less than expected was read from the file, fill the missing part with zeros
#define STREAM_FLAG_USE_PORT_1119 0x00001000 // For HTTP streams, use port 1119
#define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options
#define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers
#define STREAM_FLAGS_MASK 0x0000FFFF // Mask for all stream flags (providers+options)
//-----------------------------------------------------------------------------
// Function prototypes
typedef void (*STREAM_INIT)(
struct TFileStream * pStream // Pointer to an unopened stream
);
typedef bool (*STREAM_CREATE)(
struct TFileStream * pStream // Pointer to an unopened stream
);
typedef bool (*STREAM_OPEN)(
struct TFileStream * pStream, // Pointer to an unopened stream
const TCHAR * szFileName, // Pointer to file name to be open
DWORD dwStreamFlags // Stream flags
);
typedef bool (*STREAM_READ)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
void * pvBuffer, // Pointer to data to be read
DWORD dwBytesToRead // Number of bytes to read from the file
);
typedef bool (*STREAM_WRITE)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it writes to the current position
const void * pvBuffer, // Pointer to data to be written
DWORD dwBytesToWrite // Number of bytes to read from the file
);
typedef bool (*STREAM_RESIZE)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG FileSize // New size for the file, in bytes
);
typedef bool (*STREAM_GETSIZE)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pFileSize // Receives the file size, in bytes
);
typedef bool (*STREAM_GETPOS)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset // Pointer to store current file position
);
typedef void (*STREAM_CLOSE)(
struct TFileStream * pStream // Pointer to an open stream
);
typedef bool (*BLOCK_READ)(
struct TFileStream * pStream, // Pointer to a block-oriented stream
ULONGLONG StartOffset, // Byte offset of start of the block array
ULONGLONG EndOffset, // End offset (either end of the block or end of the file)
LPBYTE BlockBuffer, // Pointer to block-aligned buffer
DWORD BytesNeeded, // Number of bytes that are really needed
bool bAvailable // true if the block is available
);
typedef bool (*BLOCK_CHECK)(
struct TFileStream * pStream, // Pointer to a block-oriented stream
ULONGLONG BlockOffset // Offset of the file to check
);
typedef void (*BLOCK_SAVEMAP)(
struct TFileStream * pStream // Pointer to a block-oriented stream
);
typedef void (WINAPI * STREAM_DOWNLOAD_CALLBACK)(
void * pvUserData,
ULONGLONG ByteOffset,
DWORD dwTotalBytes
);
//-----------------------------------------------------------------------------
// Local structures - partial file structure and bitmap footer
#define ID_FILE_BITMAP_FOOTER 0x33767470 // Signature of the file bitmap footer ('ptv3')
#define DEFAULT_BLOCK_SIZE 0x00004000 // Default size of the stream block
#define DEFAULT_BUILD_NUMBER 10958 // Build number for newly created partial MPQs
typedef struct _PART_FILE_HEADER
{
DWORD PartialVersion; // Always set to 2
char GameBuildNumber[0x20]; // Minimum build number of the game that can use this MPQ
DWORD Flags; // Flags (details unknown)
DWORD FileSizeLo; // Low 32 bits of the contained file size
DWORD FileSizeHi; // High 32 bits of the contained file size
DWORD BlockSize; // Size of one file block, in bytes
} PART_FILE_HEADER, *PPART_FILE_HEADER;
// Structure describing the block-to-file map entry
typedef struct _PART_FILE_MAP_ENTRY
{
DWORD Flags; // 3 = the block is present in the file
DWORD BlockOffsLo; // Low 32 bits of the block position in the file
DWORD BlockOffsHi; // High 32 bits of the block position in the file
DWORD LargeValueLo; // 64-bit value, meaning is unknown
DWORD LargeValueHi;
} PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY;
typedef struct _FILE_BITMAP_FOOTER
{
DWORD Signature; // 'ptv3' (ID_FILE_BITMAP_FOOTER)
DWORD Version; // Unknown, seems to always have value of 3 (version?)
DWORD BuildNumber; // Game build number for that MPQ
DWORD MapOffsetLo; // Low 32-bits of the offset of the bit map
DWORD MapOffsetHi; // High 32-bits of the offset of the bit map
DWORD BlockSize; // Size of one block (usually 0x4000 bytes)
} FILE_BITMAP_FOOTER, *PFILE_BITMAP_FOOTER;
//-----------------------------------------------------------------------------
// Structure for file stream
union TBaseProviderData
{
struct
{
ULONGLONG FileSize; // Size of the file
ULONGLONG FilePos; // Current file position
ULONGLONG FileTime; // Last write time
HANDLE hFile; // File handle
} File;
struct
{
ULONGLONG FileSize; // Size of the file
ULONGLONG FilePos; // Current file position
ULONGLONG FileTime; // Last write time
LPBYTE pbFile; // Pointer to mapped view
} Map;
struct
{
ULONGLONG FileSize; // Size of the file
ULONGLONG FilePos; // Current file position
ULONGLONG FileTime; // Last write time
HANDLE hInternet; // Internet handle
HANDLE hConnect; // Connection to the internet server
} Http;
};
struct TFileStream
{
// Stream provider functions
STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly.
STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly.
STREAM_RESIZE StreamResize; // Pointer to function changing file size
STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size
STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position
STREAM_CLOSE StreamClose; // Pointer to function closing the stream
// Block-oriented functions
BLOCK_READ BlockRead; // Pointer to function reading one or more blocks
BLOCK_CHECK BlockCheck; // Pointer to function checking whether the block is present
// Base provider functions
STREAM_CREATE BaseCreate; // Pointer to base create function
STREAM_OPEN BaseOpen; // Pointer to base open function
STREAM_READ BaseRead; // Read from the stream
STREAM_WRITE BaseWrite; // Write to the stream
STREAM_RESIZE BaseResize; // Pointer to function changing file size
STREAM_GETSIZE BaseGetSize; // Pointer to function returning file size
STREAM_GETPOS BaseGetPos; // Pointer to function that returns current file position
STREAM_CLOSE BaseClose; // Pointer to function closing the stream
// Base provider data (file size, file position)
TBaseProviderData Base;
// Stream provider data
TFileStream * pMaster; // Master stream (e.g. MPQ on a web server)
TCHAR * szFileName; // File name (self-relative pointer)
ULONGLONG StreamSize; // Stream size (can be less than file size)
ULONGLONG StreamPos; // Stream position
DWORD BuildNumber; // Game build number
DWORD dwFlags; // Stream flags
// Followed by stream provider data, with variable length
};
//-----------------------------------------------------------------------------
// Structures for block-oriented stream
struct TBlockStream : public TFileStream
{
STREAM_DOWNLOAD_CALLBACK pfnCallback; // Callback for downloading
void * FileBitmap; // Array of bits for file blocks
void * UserData; // User data to be passed to the download callback
DWORD BitmapSize; // Size of the file bitmap (in bytes)
DWORD BlockSize; // Size of one block, in bytes
DWORD BlockCount; // Number of data blocks in the file
DWORD IsComplete; // If nonzero, no blocks are missing
DWORD IsModified; // nonzero if the bitmap has been modified
};
//-----------------------------------------------------------------------------
// Structure for encrypted stream
#define ENCRYPTED_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted
struct TEncryptedStream : public TBlockStream
{
BYTE Key[ENCRYPTED_CHUNK_SIZE]; // File key
};
//-----------------------------------------------------------------------------
// Public functions for file stream
TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags);
TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags);
const TCHAR * FileStream_GetFileName(TFileStream * pStream);
size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider);
bool FileStream_SetCallback(TFileStream * pStream, STREAM_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData);
bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead);
bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite);
bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize);
bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize);
bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset);
bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFT);
bool FileStream_GetFlags(TFileStream * pStream, PDWORD pdwStreamFlags);
bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream);
void FileStream_Close(TFileStream * pStream);
#endif // __FILESTREAM_H__
+684
View File
@@ -0,0 +1,684 @@
/*****************************************************************************/
/* FileTree.cpp Copyright (c) Ladislav Zezula 2018 */
/*---------------------------------------------------------------------------*/
/* Common implementation of a file tree object for various ROOt file formats */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.05.18 1.00 Lad The first version of FileTree.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Local defines
#define START_ITEM_COUNT 0x4000
inline DWORD GET_NODE_INT32(void * node, size_t offset)
{
PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset);
return PtrValue[0];
}
inline void SET_NODE_INT32(void * node, size_t offset, DWORD value)
{
PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset);
PtrValue[0] = value;
}
/*
static bool CompareFileNode(void * pvObject, void * pvUserData)
{
PCASC_COMPARE_CONTEXT pCtx = (PCASC_COMPARE_CONTEXT)pvUserData;
PCASC_FILE_TREE pFileTree = (PCASC_FILE_TREE)pCtx->pThis;
PCASC_FILE_NODE pFileNode = (PCASC_FILE_NODE)pvObject;
char szFullPath[MAX_PATH];
// First of all, the name hash must match
if(pFileNode->FileNameHash == pCtx->FileNameHash)
{
// Then also compare the full path name
pFileTree->PathAt(szFullPath, _countof(szFullPath), pFileNode);
if(!_stricmp(szFullPath, pCtx->szFileName))
{
return true;
}
}
return false;
}
*/
//-----------------------------------------------------------------------------
// Protected functions
// Inserts a new file node to the file tree.
// If the pointer to file node array changes, the function also rebuilds all maps
PCASC_FILE_NODE CASC_FILE_TREE::InsertNew(PCASC_CKEY_ENTRY pCKeyEntry)
{
PCASC_FILE_NODE pFileNode;
// Create a brand new node
pFileNode = InsertNew();
if(pFileNode != NULL)
{
// Initialize the file node's CKeyEntry
pFileNode->pCKeyEntry = pCKeyEntry;
// Don't insert the node into any of the arrays here.
// That is the caller's responsibility
}
return pFileNode;
}
PCASC_FILE_NODE CASC_FILE_TREE::InsertNew()
{
PCASC_FILE_NODE pFileNode;
void * SaveItemArray = NodeTable.ItemArray(); // We need to save the array pointers. If it changes, we must rebuild both maps
// Create a brand new node
pFileNode = (PCASC_FILE_NODE)NodeTable.Insert(1);
if(pFileNode != NULL)
{
// Initialize the file node
pFileNode->FileNameHash = 0;
pFileNode->pCKeyEntry = NULL;
pFileNode->Parent = 0;
pFileNode->NameIndex = 0;
pFileNode->NameLength = 0;
pFileNode->Flags = 0;
// We need to supply a file data id for the new entry, otherwise the rebuilding function
// will use the uninitialized one
SetExtras(pFileNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID);
// If the array pointer changed or we are close to the size of the array, we need to rebuild the maps
if(NodeTable.ItemArray() != SaveItemArray || (NodeTable.ItemCount() * 3 / 2) > NameMap.HashTableSize())
{
// Rebuild both maps. Note that rebuilding also inserts all items to the maps, so no need to insert them here
if(!RebuildNameMaps())
{
pFileNode = NULL;
assert(false);
}
}
}
return pFileNode;
}
// Insert the node to the map of FileNameHash -> CASC_FILE_NODE
bool CASC_FILE_TREE::InsertToHashTable(PCASC_FILE_NODE pFileNode)
{
bool bResult = false;
// Insert the file node to the table
if(pFileNode->FileNameHash != 0)
bResult = NameMap.InsertObject(pFileNode, &pFileNode->FileNameHash);
return bResult;
}
// Inserts the file node to the array of file data ids
bool CASC_FILE_TREE::InsertToIdTable(PCASC_FILE_NODE pFileNode)
{
PCASC_FILE_NODE * RefElement;
DWORD FileDataId = CASC_INVALID_ID;
if(FileDataIds.IsInitialized())
{
// Retrieve the file data id
GetExtras(pFileNode, &FileDataId, NULL, NULL);
if(FileDataId != CASC_INVALID_ID)
{
// Sanity check
assert(FileDataId < 0x10000000);
// Insert the element to the array
RefElement = (PCASC_FILE_NODE *)FileDataIds.InsertAt(FileDataId);
if(RefElement != NULL)
{
RefElement[0] = pFileNode;
return true;
}
}
}
return false;
}
bool CASC_FILE_TREE::SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd)
{
char * szNodeName;
size_t nLength = (szPlainNameEnd - szPlainName);
// Insert all chars to the name array
szNodeName = (char *)NameTable.Insert(nLength);
if(szNodeName != NULL)
{
// Copy the plain name to the node. Do not include the string terminator
memcpy(szNodeName, szPlainName, nLength);
// Supply the file name to the file node
pFileNode->NameIndex = (DWORD)NameTable.IndexOf(szNodeName);
pFileNode->NameLength = (USHORT)nLength;
return true;
}
return false;
}
bool CASC_FILE_TREE::SetKeyLength(DWORD aKeyLength)
{
if(aKeyLength > MD5_HASH_SIZE)
return false;
KeyLength = aKeyLength;
return true;
}
DWORD CASC_FILE_TREE::GetNextFileDataId()
{
if(FileDataIds.IsInitialized())
return (DWORD)(FileDataIds.ItemCount() + 1);
return CASC_INVALID_ID;
}
bool CASC_FILE_TREE::RebuildNameMaps()
{
PCASC_FILE_NODE pFileNode;
size_t nMaxItems = NodeTable.ItemCountMax();
// Free the map of "FullName -> CASC_FILE_NODE"
NameMap.Free();
// Create new map map "FullName -> CASC_FILE_NODE"
if(NameMap.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_NODE, FileNameHash)) != ERROR_SUCCESS)
return false;
// Reset the entire array, but keep the buffer allocated
FileDataIds.Reset();
// Parse all items and insert them to the map
for(size_t i = 0; i < NodeTable.ItemCount(); i++)
{
// Retrieve the n-th object
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i);
if(pFileNode != NULL)
{
// Insert it to the map "FileNameHash -> CASC_FILE_NODE"
if(pFileNode->FileNameHash != 0)
InsertToHashTable(pFileNode);
// Insert it to the array "FileDataId -> CASC_FILE_NODE"
if(FileDataIds.IsInitialized())
InsertToIdTable(pFileNode);
}
}
return true;
}
//-----------------------------------------------------------------------------
// Public functions
int CASC_FILE_TREE::Create(DWORD Flags)
{
PCASC_FILE_NODE pRootNode;
size_t FileNodeSize = FIELD_OFFSET(CASC_FILE_NODE, ExtraValues);
int nError;
// Initialize the file tree
memset(this, 0, sizeof(CASC_FILE_TREE));
KeyLength = MD5_HASH_SIZE;
// Shall we use the data ID in the tree node?
if(Flags & FTREE_FLAG_USE_DATA_ID)
{
// Set the offset of the file data id in the entry
FileDataIdOffset = FileNodeSize;
FileNodeSize += sizeof(DWORD);
// Create the array for FileDataId -> CASC_FILE_NODE
nError = FileDataIds.Create<PCASC_FILE_NODE>(START_ITEM_COUNT);
if(nError != ERROR_SUCCESS)
return nError;
}
// Shall we use the locale ID in the tree node?
if(Flags & FTREE_FLAG_USE_LOCALE_FLAGS)
{
LocaleFlagsOffset = FileNodeSize;
FileNodeSize += sizeof(DWORD);
}
if(Flags & FTREE_FLAG_USE_CONTENT_FLAGS)
{
ContentFlagsOffset = FileNodeSize;
FileNodeSize += sizeof(DWORD);
}
// Align the file node size to 8 bytes
FileNodeSize = ALIGN_TO_SIZE(FileNodeSize, 8);
// Initialize the dynamic array
nError = NodeTable.Create(FileNodeSize, START_ITEM_COUNT);
if(nError == ERROR_SUCCESS)
{
// Create the dynamic array that will hold the node names
nError = NameTable.Create<char>(START_ITEM_COUNT);
if(nError == ERROR_SUCCESS)
{
// Insert the first "root" node, without name
pRootNode = (PCASC_FILE_NODE)NodeTable.Insert(1);
if(pRootNode != NULL)
{
// Initialize the node
memset(pRootNode, 0, NodeTable.ItemSize());
pRootNode->Parent = CASC_INVALID_INDEX;
pRootNode->NameIndex = CASC_INVALID_INDEX;
pRootNode->Flags = CFN_FLAG_FOLDER;
SetExtras(pRootNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID);
}
}
}
// Create both maps
if(!RebuildNameMaps())
nError = ERROR_NOT_ENOUGH_MEMORY;
return nError;
}
void CASC_FILE_TREE::Free()
{
// Free both arrays
NodeTable.Free();
NameTable.Free();
FileDataIds.Free();
// Free the name map
NameMap.Free();
// Zero the object
memset(this, 0, sizeof(CASC_FILE_TREE));
}
PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
{
CASC_COMPARE_CONTEXT CmpCtx;
PCASC_FILE_NODE pFileNode;
// Sanity checks
assert(szFileName != NULL && szFileName[0] != 0);
assert(pCKeyEntry != NULL);
// Calculate the file name hash
CmpCtx.FileNameHash = CalcFileNameHash(szFileName);
// CmpCtx.szFileName = szFileName;
// CmpCtx.pThis = this;
// Do nothing if the file name is there already.
// pFileNode = (PCASC_FILE_NODE)NameMap.FindObjectEx(CompareFileNode, &CmpCtx);
pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&CmpCtx.FileNameHash);
if(pFileNode == NULL)
{
// Insert new item
pFileNode = InsertNew(pCKeyEntry);
if(pFileNode != NULL)
{
// Supply the name hash
pFileNode->FileNameHash = CmpCtx.FileNameHash;
// Set the file data id and the extra values
SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags);
// Insert the file node to the hash map
InsertToHashTable(pFileNode);
// Also make sure that it's in the file data id table, if the table is initialized
InsertToIdTable(pFileNode);
// Set the file name of the new file node. This also increments the number of references
SetNodeFileName(pFileNode, szFileName);
// If we created a new node, we need to increment the reference count
assert(pCKeyEntry->RefCount != 0xFFFF);
pCKeyEntry->RefCount++;
}
}
return pFileNode;
}
PCASC_FILE_NODE CASC_FILE_TREE::InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
{
PCASC_FILE_NODE pFileNode;
// Sanity checks
assert(FileDataIds.IsInitialized());
assert(FileDataId != CASC_INVALID_ID);
assert(FileNameHash != 0);
assert(pCKeyEntry != NULL);
// Insert the node to the tree by file data id
pFileNode = InsertById(pCKeyEntry, FileDataId, LocaleFlags, ContentFlags);
if(pFileNode != NULL)
{
// Supply the name hash
pFileNode->FileNameHash = FileNameHash;
// Insert the file node to the hash map
InsertToHashTable(pFileNode);
}
return pFileNode;
}
PCASC_FILE_NODE CASC_FILE_TREE::InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
{
PCASC_FILE_NODE pFileNode;
// Sanity checks
assert(FileDataIds.IsInitialized());
assert(FileDataId != CASC_INVALID_ID);
assert(pCKeyEntry != NULL);
// Check whether the file data id exists in the array of file data ids
if((pFileNode = FindById(FileDataId)) == NULL)
{
// Insert the new file node
pFileNode = InsertNew(pCKeyEntry);
if(pFileNode != NULL)
{
// Set the file data id and the extra values
SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags);
// Insert the file node to the FileDataId array
InsertToIdTable(pFileNode);
// Increment the number of references
pCKeyEntry->RefCount++;
}
}
// Return the new or old node
return pFileNode;
}
PCASC_FILE_NODE CASC_FILE_TREE::ItemAt(size_t nItemIndex)
{
return (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex);
}
PCASC_FILE_NODE CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex)
{
PCASC_FILE_NODE pFileNode = NULL;
// If we have FileDataId, then we need to enumerate the files by FileDataId
if(FileDataIds.IsInitialized())
pFileNode = *(PCASC_FILE_NODE *)FileDataIds.ItemAt(nItemIndex);
else
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex);
// Construct the entire path
PathAt(szBuffer, cchBuffer, pFileNode);
return pFileNode;
}
size_t CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode)
{
PCASC_FILE_NODE pParentNode;
const char * szNamePtr;
char * szSaveBuffer = szBuffer;
char * szBufferEnd = szBuffer + cchBuffer - 1;
if(pFileNode != NULL && pFileNode->Parent != CASC_INVALID_INDEX)
{
// Copy all parents
pParentNode = (PCASC_FILE_NODE)NodeTable.ItemAt(pFileNode->Parent);
if(pParentNode != NULL)
{
// Query the parent and move the buffer
szBuffer = szBuffer + PathAt(szBuffer, cchBuffer, pParentNode);
}
// Retrieve the node name
szNamePtr = (const char *)NameTable.ItemAt(pFileNode->NameIndex);
// Check whether we have enough space
if((szBuffer + pFileNode->NameLength) < szBufferEnd)
{
// Copy the path part
memcpy(szBuffer, szNamePtr, pFileNode->NameLength);
szBuffer += pFileNode->NameLength;
// Append backslash
if((pFileNode->Flags & CFN_FLAG_FOLDER) && ((szBuffer + 1) < szBufferEnd))
{
*szBuffer++ = (pFileNode->Flags & CFN_FLAG_MOUNT_POINT) ? ':' : '\\';
}
}
}
// Terminate buffer with zero
szBuffer[0] = 0;
// Return length of the copied string
return (szBuffer - szSaveBuffer);
}
PCASC_FILE_NODE CASC_FILE_TREE::Find(const char * szFullPath, DWORD FileDataId, PCASC_FIND_DATA pFindData)
{
PCASC_FILE_NODE pFileNode = NULL;
ULONGLONG FileNameHash;
// Can we search by FileDataId?
if(FileDataIds.IsInitialized() && (FileDataId != CASC_INVALID_ID || IsFileDataIdName(szFullPath, FileDataId)))
{
pFileNode = FindById(FileDataId);
}
else
{
if(szFullPath != NULL && szFullPath[0] != 0)
{
FileNameHash = CalcFileNameHash(szFullPath);
pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash);
}
}
// Did we find anything?
if(pFileNode != NULL && pFindData != NULL)
{
GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags);
pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0);
pFindData->bCanOpenByDataId = (FileDataIdOffset != 0);
}
return pFileNode;
}
PCASC_FILE_NODE CASC_FILE_TREE::Find(PCASC_CKEY_ENTRY pCKeyEntry)
{
PCASC_FILE_NODE pFileNode;
for(size_t i = 0; i < NodeTable.ItemCount(); i++)
{
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i);
if((pFileNode->Flags & (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT)) == 0)
{
if(pFileNode->pCKeyEntry == pCKeyEntry)
return pFileNode;
}
}
return NULL;
}
PCASC_FILE_NODE CASC_FILE_TREE::Find(ULONGLONG FileNameHash)
{
return (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash);
}
PCASC_FILE_NODE CASC_FILE_TREE::FindById(DWORD FileDataId)
{
PCASC_FILE_NODE * RefElement;
PCASC_FILE_NODE pFileNode = NULL;
if(FileDataId != CASC_INVALID_ID && FileDataIds.IsInitialized())
{
// Insert the element to the array
RefElement = (PCASC_FILE_NODE *)FileDataIds.ItemAt(FileDataId);
if(RefElement != NULL)
{
pFileNode = RefElement[0];
}
}
return pFileNode;
}
bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName)
{
ULONGLONG FileNameHash = 0;
PCASC_FILE_NODE pFolderNode = NULL;
const char * szNodeBegin = szFileName;
char szPathBuffer[MAX_PATH+1];
size_t nFileNode = NodeTable.IndexOf(pFileNode);
size_t i;
DWORD Parent = 0;
// Sanity checks
assert(szFileName != NULL && szFileName[0] != 0);
assert(pFileNode->pCKeyEntry != NULL);
// Traverse the entire path. For each subfolder, we insert an appropriate fake entry
for(i = 0; szFileName[i] != 0; i++)
{
char chOneChar = szFileName[i];
// Is there a path separator?
// Note: Warcraft III paths may contain "mount points".
// Example: "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j"
if(chOneChar == '\\' || chOneChar == '/' || chOneChar == ':')
{
// Calculate hash of the file name up to the end of the node name
FileNameHash = CalcNormNameHash(szPathBuffer, i);
// If the entry is not there yet, create new one
if((pFolderNode = Find(FileNameHash)) == NULL)
{
// Insert new entry to the tree
pFolderNode = InsertNew();
if(pFolderNode == NULL)
return false;
// Populate the file entry
pFolderNode->FileNameHash = FileNameHash;
pFolderNode->Parent = Parent;
pFolderNode->Flags |= (chOneChar == ':') ? CFN_FLAG_MOUNT_POINT : 0;
pFolderNode->Flags |= CFN_FLAG_FOLDER;
// Set the node sub name to the node
SetNodePlainName(pFolderNode, szNodeBegin, szFileName + i);
// Insert the entry to the name map
InsertToHashTable(pFolderNode);
}
// Move the parent to the current node
Parent = (DWORD)NodeTable.IndexOf(pFolderNode);
// Move the begin of the node after the separator
szNodeBegin = szFileName + i + 1;
}
// Copy the next character, even if it was slash/backslash before
szPathBuffer[i] = AsciiToUpperTable_BkSlash[chOneChar];
}
// If anything left, this is gonna be our node name
if(szNodeBegin < szFileName + i)
{
// We need to reset the file node pointer, as the file node table might have changed
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nFileNode);
SetNodePlainName(pFileNode, szNodeBegin, szFileName + i);
pFileNode->Parent = Parent;
}
return true;
}
size_t CASC_FILE_TREE::GetMaxFileIndex()
{
if(FileDataIds.IsInitialized())
{
return FileDataIds.ItemCount();
}
else
{
return NodeTable.ItemCount();
}
}
size_t CASC_FILE_TREE::GetCount()
{
return NodeTable.ItemCount();
}
size_t CASC_FILE_TREE::IndexOf(PCASC_FILE_NODE pFileNode)
{
return NodeTable.IndexOf(pFileNode);
}
void CASC_FILE_TREE::GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags)
{
DWORD FileDataId = CASC_INVALID_ID;
DWORD LocaleFlags = CASC_INVALID_ID;
DWORD ContentFlags = CASC_INVALID_ID;
// Retrieve the data ID, if supported
if(PtrFileDataId != NULL)
{
if(FileDataIdOffset != 0)
FileDataId = GET_NODE_INT32(pFileNode, FileDataIdOffset);
PtrFileDataId[0] = FileDataId;
}
// Retrieve the locale ID, if supported
if(PtrLocaleFlags != NULL)
{
if(LocaleFlagsOffset != 0)
LocaleFlags = GET_NODE_INT32(pFileNode, LocaleFlagsOffset);
PtrLocaleFlags[0] = LocaleFlags;
}
if(PtrContentFlags != NULL)
{
if(ContentFlagsOffset != 0)
ContentFlags = GET_NODE_INT32(pFileNode, ContentFlagsOffset);
PtrContentFlags[0] = ContentFlags;
}
}
void CASC_FILE_TREE::SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
{
// Set the file data ID, if supported
if(FileDataIdOffset != 0)
{
SET_NODE_INT32(pFileNode, FileDataIdOffset, FileDataId);
}
// Set the locale ID, if supported
if(LocaleFlagsOffset != 0)
{
SET_NODE_INT32(pFileNode, LocaleFlagsOffset, LocaleFlags);
}
// Set the locale ID, if supported
if(ContentFlagsOffset != 0)
{
SET_NODE_INT32(pFileNode, ContentFlagsOffset, ContentFlags);
}
}
+116
View File
@@ -0,0 +1,116 @@
/*****************************************************************************/
/* FileTree.h Copyright (c) Ladislav Zezula 2018 */
/*---------------------------------------------------------------------------*/
/* Common implementation of a file tree object for various ROOt file formats */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.05.18 1.00 Lad The first version of FileTree.h */
/*****************************************************************************/
#ifndef __FILETREE_H__
#define __FILETREE_H__
//-----------------------------------------------------------------------------
// Structures
#define FTREE_FLAG_USE_DATA_ID 0x0001 // The FILE_NODE also contains file data ID
#define FTREE_FLAG_USE_LOCALE_FLAGS 0x0002 // The FILE_NODE also contains file locale flags
#define FTREE_FLAG_USE_CONTENT_FLAGS 0x0004 // The FILE_NODE also contains content flags
#define CFN_FLAG_FOLDER 0x0001 // This item is a folder
#define CFN_FLAG_MOUNT_POINT 0x0002 // This item is a mount point.
// Common structure for holding a single folder/file node
typedef struct _CASC_FILE_NODE
{
ULONGLONG FileNameHash; // Jenkins hash of the normalized file name (uppercase, backslashes)
PCASC_CKEY_ENTRY pCKeyEntry; // Pointer to the CKey entry.
DWORD Parent; // The index of a parent directory. If CASC_INVALID_INDEX, then this is the root item
DWORD NameIndex; // Index of the node name. If CASC_INVALID_INDEX, then this node has no name
USHORT NameLength; // Length of the node name (without the zero terminator)
USHORT Flags; // See CFE_FLAG_XXX
DWORD ExtraValues[4]; // FileDataId: Only if FTREE_FLAG_USE_DATA_ID specified at create
// LocaleFlags: Only if FTREE_FLAG_USE_LOCALE_FLAGS specified at create
// ContentFlags: Only if FTREE_FLAG_USE_CONTENT_FLAGS specified at create
} CASC_FILE_NODE, *PCASC_FILE_NODE;
// Common structure for comparing a file node
typedef struct _CASC_COMPARE_CONTEXT
{
ULONGLONG FileNameHash;
const char * szFileName;
void * pThis;
} CASC_COMPARE_CONTEXT, *PCASC_COMPARE_CONTEXT;
// Main structure for the file tree
class CASC_FILE_TREE
{
public:
// Initializes/destroys the entire tree
int Create(DWORD Flags = 0);
void Free();
// Inserts a new node to the tree; either with name or nameless
PCASC_FILE_NODE InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId = CASC_INVALID_ID, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID);
PCASC_FILE_NODE InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID);
PCASC_FILE_NODE InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID);
// Returns an item at the given index. The PathAt also builds the full path of the node
PCASC_FILE_NODE ItemAt(size_t nItemIndex);
PCASC_FILE_NODE PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex);
size_t PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode);
// Finds a file using its full path, FileDataId or CKey/EKey
PCASC_FILE_NODE Find(const char * szFullPath, DWORD FileDataId, struct _CASC_FIND_DATA * pFindData);
PCASC_FILE_NODE Find(PCASC_CKEY_ENTRY pCKeyEntry);
PCASC_FILE_NODE Find(ULONGLONG FileNameHash);
PCASC_FILE_NODE FindById(DWORD FileDataId);
// Assigns a file name to the node
bool SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName);
// Returns the number of items in the tree
size_t GetMaxFileIndex();
size_t GetCount();
// Returns the index of an item in the tree
size_t IndexOf(PCASC_FILE_NODE pFileNode);
// Retrieves the extra values from the node (if supported)
void GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags);
void SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags);
// Change the length of the key
bool SetKeyLength(DWORD KeyLength);
// Retrieve the maximum FileDataId ever inserted
DWORD GetNextFileDataId();
protected:
PCASC_FILE_NODE InsertNew(PCASC_CKEY_ENTRY pCKeyEntry);
PCASC_FILE_NODE InsertNew();
bool InsertToHashTable(PCASC_FILE_NODE pFileNode);
bool InsertToIdTable(PCASC_FILE_NODE pFileNode);
bool SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd);
bool RebuildNameMaps();
CASC_ARRAY NodeTable; // Dynamic array that holds all CASC_FILE_NODEs
CASC_ARRAY NameTable; // Dynamic array that holds all node names
CASC_ARRAY FileDataIds; // Dynamic array that maps FileDataId -> CASC_FILE_NODE
CASC_MAP NameMap; // Map of FileNameHash -> CASC_FILE_NODE
size_t FileDataIdOffset; // If nonzero, this is the offset of the "FileDataId" field in the CASC_FILE_NODE
size_t LocaleFlagsOffset; // If nonzero, this is the offset of the "LocaleFlags" field in the CASC_FILE_NODE
size_t ContentFlagsOffset; // If nonzero, this is the offset of the "ContentFlags" field in the CASC_FILE_NODE
DWORD KeyLength; // Actual length of the key supported by the root handler
};
typedef CASC_FILE_TREE * PCASC_FILE_TREE;
#endif // __FILETREE_H__
+303
View File
@@ -0,0 +1,303 @@
/*****************************************************************************/
/* ListFile.cpp Copyright (c) Ladislav Zezula 2004 */
/*---------------------------------------------------------------------------*/
/* Description: */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 12.06.04 1.00 Lad The first version of ListFile.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
// Listfile cache structure
#define LISTFILE_FLAG_USES_FILEDATAID 0x0001 // A new CSV format, containing FileDataId; FullFileName
typedef struct _LISTFILE_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
DWORD Flags;
// Followed by the cache (variable length)
} LISTFILE_CACHE, *PLISTFILE_CACHE;
//-----------------------------------------------------------------------------
// Creating the listfile cache for the given amount of data
static PLISTFILE_CACHE ListFile_CreateCache(DWORD dwFileSize)
{
PLISTFILE_CACHE pCache;
// Allocate cache for one file block
pCache = (PLISTFILE_CACHE)CASC_ALLOC(BYTE, sizeof(LISTFILE_CACHE) + dwFileSize);
if(pCache != NULL)
{
// Set the initial pointers
pCache->pBegin =
pCache->pPos = (char *)(pCache + 1);
pCache->pEnd = pCache->pBegin + dwFileSize;
pCache->Flags = 0;
}
// Return the cache
return pCache;
}
static char * ListFile_SkipSpaces(PLISTFILE_CACHE pCache)
{
// 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
return pCache->pPos;
}
static void ListFile_CheckFormat(PLISTFILE_CACHE pCache)
{
// Only if the listfile is greatger than 2 MB
if((pCache->pEnd - pCache->pBegin) > 0x100000)
{
char * szPtr = pCache->pBegin;
size_t nDigitCount = 0;
// Calculate the amount of digits
while(nDigitCount <= 20 && '0' <= szPtr[nDigitCount] && szPtr[nDigitCount] <= '9')
nDigitCount++;
// There must be a semicolon after
if(nDigitCount <= 10 && szPtr[nDigitCount] == ';')
{
pCache->Flags |= LISTFILE_FLAG_USES_FILEDATAID;
}
}
}
static int ListFile_GetFileDataId(PLISTFILE_CACHE pCache, PDWORD PtrFileDataId)
{
char * szLineBegin = ListFile_SkipSpaces(pCache);
char * szLineEnd;
DWORD dwNewInt32 = 0;
DWORD dwInt32 = 0;
// Set the limit for loading the number
szLineEnd = CASCLIB_MIN((szLineBegin + 20), pCache->pEnd);
// Extract decimal digits from the string
while(szLineBegin < szLineEnd && '0' <= szLineBegin[0] && szLineBegin[0] <= '9')
{
// Check integer overflow
dwNewInt32 = (dwInt32 * 10) + (szLineBegin[0] - '0');
if(dwNewInt32 < dwInt32)
return ERROR_BAD_FORMAT;
dwInt32 = dwNewInt32;
szLineBegin++;
}
// There must still be some space
if(szLineBegin < szLineEnd)
{
// There must be a semicolon after the decimal integer
// The decimal integer must be smaller than 10 MB (files)
if(szLineBegin[0] != ';' || dwInt32 >= 0xA00000)
return ERROR_BAD_FORMAT;
pCache->pPos = szLineBegin + 1;
PtrFileDataId[0] = dwInt32;
return ERROR_SUCCESS;
}
return ERROR_NO_MORE_FILES;
}
//-----------------------------------------------------------------------------
// Functions for parsing an external listfile
void * ListFile_OpenExternal(const TCHAR * szListFile)
{
PLISTFILE_CACHE pCache = NULL;
TFileStream * pStream;
ULONGLONG FileSize = 0;
// Open the external listfile
pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY);
if(pStream != NULL)
{
// Retrieve the size of the external listfile
FileStream_GetSize(pStream, &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 = ListFile_CreateCache((DWORD)FileSize);
if(pCache != NULL)
{
if(FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize))
{
ListFile_CheckFormat(pCache);
}
else
{
CASC_FREE(pCache);
}
}
}
// Close the file stream
FileStream_Close(pStream);
}
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 = ListFile_CreateCache(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 CascVerifyDataBlockHash(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
// Remember the begin of the line
szLineBegin = ListFile_SkipSpaces(pCache);
// Copy the remaining characters
while(pCache->pPos < pCache->pEnd)
{
// If we have found a newline, stop loading
// Note: the 0x85 char came from Overwatch build 24919
if(pCache->pPos[0] == 0x0A || pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x85)
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, char * szBuffer, size_t nMaxChars, PDWORD PtrFileDataId)
{
PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile;
size_t nLength = 0;
int nError = ERROR_SUCCESS;
// Check for parameters
for(;;)
{
DWORD FileDataId = CASC_INVALID_ID;
// If this is a CSV-format listfile, we need to extract the FileDataId
// Lines that contain bogus data, invalid numbers or too big values will be skipped
if(pCache->Flags & LISTFILE_FLAG_USES_FILEDATAID)
{
nError = ListFile_GetFileDataId(pCache, &FileDataId);
if(nError == ERROR_NO_MORE_FILES)
break;
if(nError != ERROR_SUCCESS || FileDataId == CASC_INVALID_ID)
continue;
}
// Read the (next) line
nLength = ListFile_GetNextLine(pvListFile, szBuffer, nMaxChars);
if(nLength == 0)
{
nError = ERROR_NO_MORE_FILES;
break;
}
// Give the file data id and return true
PtrFileDataId[0] = FileDataId;
return nLength;
}
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return nLength;
}
LPBYTE ListFile_GetData(void * pvListFile, PDWORD PtrDataSize)
{
PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile;
LPBYTE pbData = NULL;
DWORD cbData = 0;
// Get data from the list file cache
if (pvListFile != NULL)
{
pbData = (LPBYTE)pCache->pBegin;
cbData = (DWORD)(pCache->pEnd - pCache->pBegin);
}
// Give the data to the caller
if (PtrDataSize != NULL)
PtrDataSize[0] = cbData;
return pbData;
}
+25
View File
@@ -0,0 +1,25 @@
/*****************************************************************************/
/* ListFile.h Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Common functions for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 10.05.14 1.00 Lad The first version of ListFile.h */
/*****************************************************************************/
#ifndef __LISTFILE_H__
#define __LISTFILE_H__
//-----------------------------------------------------------------------------
// 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, char * szBuffer, size_t nMaxChars, PDWORD PtrFileDataId);
LPBYTE ListFile_GetData(void * pvListFile, PDWORD PtrDataSize);
#endif // __LISTFILE_H__
+362
View File
@@ -0,0 +1,362 @@
/*****************************************************************************/
/* Map.h Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Interface of hash-to-ptr map for CascLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 10.06.14 1.00 Lad The first version of Map.h */
/*****************************************************************************/
#ifndef __CASC_MAP_H__
#define __CASC_MAP_H__
//-----------------------------------------------------------------------------
// Structures
#define MIN_HASH_TABLE_SIZE 0x00000100 // The smallest size of the hash table.
#define MAX_HASH_TABLE_SIZE 0x00800000 // The largest size of the hash table. Should be enough for any game.
typedef int (*PFNCOMPAREFUNC)(const void * pvObjectKey, const void * pvKey, size_t nKeyLength);
typedef DWORD (*PFNHASHFUNC)(void * pvKey, size_t nKeyLength);
typedef enum _KEY_TYPE
{
KeyIsHash, // Use when the key is already a hash. If specified, the map will not hash the key
KeyIsArbitrary, // The key is an arbitrary array of bytes. The map will hash it to get a map index
KeyIsString // Use when the key is a string
} KEY_TYPE, *PKEY_TYPE;
#define KEY_LENGTH_STRING 0xFFFFFFFF
//-----------------------------------------------------------------------------
// Hashing functions
// Used when the key is a hash already - no need to hash it again
inline DWORD CalcHashValue_Hash(void * pvKey, size_t /* nKeyLength */)
{
// Get the hash directly as value
return ConvertBytesToInteger_4((LPBYTE)pvKey);
}
// Calculates hash value from a key
inline DWORD CalcHashValue_Key(void * pvKey, size_t nKeyLength)
{
LPBYTE pbKey = (LPBYTE)pvKey;
DWORD dwHash = 0x7EEE7EEE;
// Construct the hash from the key
for(DWORD i = 0; i < nKeyLength; i++)
dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[i];
// Return the hash limited by the table size
return dwHash;
}
// Calculates hash value from a string
inline DWORD CalcHashValue_String(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;
}
//-----------------------------------------------------------------------------
// Map implementation
class CASC_MAP
{
public:
CASC_MAP()
{
PfnCalcHashValue = NULL;
m_HashTable = NULL;
m_HashTableSize = 0;
m_ItemCount = 0;
m_KeyOffset = 0;
m_KeyLength = 0;
m_bKeyIsHash = false;
}
~CASC_MAP()
{
Free();
}
int Create(size_t MaxItems, size_t KeyLength, size_t KeyOffset, KEY_TYPE KeyType = KeyIsHash)
{
// Set the class variables
m_KeyLength = CASCLIB_MAX(KeyLength, 8);
m_KeyOffset = KeyOffset;
m_ItemCount = 0;
// Setup the hashing function
switch(KeyType)
{
case KeyIsHash:
PfnCalcHashValue = CalcHashValue_Hash;
break;
case KeyIsArbitrary:
PfnCalcHashValue = CalcHashValue_Key;
break;
case KeyIsString:
PfnCalcHashValue = NULL;
break;
default:
assert(false);
return ERROR_NOT_SUPPORTED;
}
// Calculate the hash table size. Take 133% of the item count and round it up to the next power of two
// This will make the hash table indexes somewhat more resilient against count changes and will make
// e.g. file order in the file tree more stable.
m_HashTableSize = GetNearestPowerOfTwo(MaxItems * 4 / 3);
if(m_HashTableSize == 0)
return ERROR_NOT_ENOUGH_MEMORY;
// Allocate new map for the objects
m_HashTable = (void **)CASC_ALLOC(void *, m_HashTableSize);
if(m_HashTable == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Initialize the map object
memset(m_HashTable, 0, m_HashTableSize * sizeof(void *));
return ERROR_SUCCESS;
}
void * FindObject(void * pvKey, PDWORD PtrIndex = NULL)
{
void * pvObject;
DWORD dwHashIndex;
// Verify pointer to the map
if(m_HashTable != NULL)
{
// Construct the hash index
dwHashIndex = HashToIndex(PfnCalcHashValue(pvKey, m_KeyLength));
// Search the hash table
while((pvObject = m_HashTable[dwHashIndex]) != NULL)
{
// Compare the hash
if(CompareObject_Key(pvObject, pvKey))
{
if(PtrIndex != NULL)
PtrIndex[0] = dwHashIndex;
return pvObject;
}
// Move to the next entry
dwHashIndex = HashToIndex(dwHashIndex + 1);
}
}
// Not found, sorry
return NULL;
}
bool InsertObject(void * pvNewObject, void * pvKey)
{
void * pvExistingObject;
DWORD dwHashIndex;
// Verify pointer to the map
if(m_HashTable != NULL)
{
// Limit check
if((m_ItemCount + 1) >= m_HashTableSize)
return false;
// Construct the hash index
dwHashIndex = HashToIndex(PfnCalcHashValue(pvKey, m_KeyLength));
// Search the hash table
while((pvExistingObject = m_HashTable[dwHashIndex]) != NULL)
{
// Check if hash being inserted conflicts with an existing hash
if(CompareObject_Key(pvExistingObject, pvKey))
return false;
// Move to the next entry
dwHashIndex = HashToIndex(dwHashIndex + 1);
}
// Insert at that position
m_HashTable[dwHashIndex] = pvNewObject;
m_ItemCount++;
return true;
}
// Failed
return false;
}
const char * FindString(const char * szString, const char * szStringEnd)
{
const char * szExistingString;
DWORD dwHashIndex;
// Verify pointer to the map
if(m_HashTable != NULL)
{
// Construct the main index
dwHashIndex = HashToIndex(CalcHashValue_String(szString, szStringEnd));
// Search the hash table
while((szExistingString = (const char *)m_HashTable[dwHashIndex]) != NULL)
{
// Compare the hash
if(CompareObject_String(szExistingString, szString, szStringEnd))
return szExistingString;
// Move to the next entry
dwHashIndex = HashToIndex(dwHashIndex + 1);
}
}
// Not found, sorry
return NULL;
}
bool InsertString(const char * szString, bool bCutExtension)
{
const char * szExistingString;
const char * szStringEnd = NULL;
DWORD dwHashIndex;
// Verify pointer to the map
if(m_HashTable != NULL)
{
// Limit check
if((m_ItemCount + 1) >= m_HashTableSize)
return false;
// Retrieve the length of the string without extension
if(bCutExtension)
szStringEnd = GetFileExtension(szString);
else
szStringEnd = szString + strlen(szString);
// Construct the hash index
dwHashIndex = HashToIndex(CalcHashValue_String(szString, szStringEnd));
// Search the hash table
while((szExistingString = (const char *)m_HashTable[dwHashIndex]) != NULL)
{
// Check if hash being inserted conflicts with an existing hash
if(CompareObject_String(szExistingString, szString, szStringEnd))
return false;
// Move to the next entry
dwHashIndex = HashToIndex(dwHashIndex + 1);
}
// Insert at that position
m_HashTable[dwHashIndex] = (void *)szString;
m_ItemCount++;
return true;
}
// Failed
return false;
}
void * ItemAt(size_t nIndex)
{
assert(nIndex < m_HashTableSize);
return m_HashTable[nIndex];
}
size_t HashTableSize()
{
return m_HashTableSize;
}
size_t ItemCount()
{
return m_ItemCount;
}
bool IsInitialized()
{
return (m_HashTable && m_HashTableSize);
}
void Free()
{
PfnCalcHashValue = NULL;
CASC_FREE(m_HashTable);
m_HashTableSize = 0;
}
protected:
DWORD HashToIndex(DWORD HashValue)
{
return HashValue & (m_HashTableSize - 1);
}
bool CompareObject_Key(void * pvObject, void * pvKey)
{
LPBYTE pbObjectKey = (LPBYTE)pvObject + m_KeyOffset;
return (memcmp(pbObjectKey, pvKey, m_KeyLength) == 0);
}
bool CompareObject_String(const char * szExistingString, const char * szString, const char * szStringEnd)
{
// Compare the whole part, case insensitive
while(szString < szStringEnd)
{
if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString])
return false;
szExistingString++;
szString++;
}
return true;
}
size_t GetNearestPowerOfTwo(size_t MaxItems)
{
size_t PowerOfTwo;
// Round the hash table size up to the nearest power of two
for(PowerOfTwo = MIN_HASH_TABLE_SIZE; PowerOfTwo < MAX_HASH_TABLE_SIZE; PowerOfTwo <<= 1)
{
if(PowerOfTwo > MaxItems)
{
return PowerOfTwo;
}
}
// If the hash table is too big, we cannot create the map
assert(false);
return 0;
}
PFNHASHFUNC PfnCalcHashValue;
void ** m_HashTable; // Hash table
size_t m_HashTableSize; // Size of the hash table, in entries. Always a power of two.
size_t m_ItemCount; // Number of objects in the map
size_t m_KeyOffset; // How far is the hash from the begin of the objects (in bytes)
size_t m_KeyLength; // Length of the hash key, in bytes
bool m_bKeyIsHash; // If set, then it means that the key is a hash of some sort.
// Will improve performance, as we will not hash a hash :-)
};
#endif // __CASC_MAP_H__
+117
View File
@@ -0,0 +1,117 @@
/*****************************************************************************/
/* 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"
//-----------------------------------------------------------------------------
// Constructor and destructor - TFileTreeRoot
TFileTreeRoot::TFileTreeRoot(DWORD FileTreeFlags) : TRootHandler()
{
// Initialize the file tree
FileTree.Create(FileTreeFlags);
}
TFileTreeRoot::~TFileTreeRoot()
{
// Free the file tree
FileTree.Free();
dwFeatures = 0;
}
//-----------------------------------------------------------------------------
// Virtual functions - TFileTreeRoot
int TFileTreeRoot::Insert(
const char * szFileName,
PCASC_CKEY_ENTRY pCKeyEntry)
{
PCASC_FILE_NODE pFileNode;
pFileNode = FileTree.InsertByName(pCKeyEntry, szFileName, FileTree.GetNextFileDataId());
return (pFileNode != NULL) ? ERROR_SUCCESS : ERROR_CAN_NOT_COMPLETE;
}
PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, const char * szFileName)
{
PCASC_FILE_NODE pFileNode;
ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
pFileNode = FileTree.Find(FileNameHash);
return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL;
}
PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, DWORD FileDataId)
{
PCASC_FILE_NODE pFileNode;
pFileNode = FileTree.FindById(FileDataId);
return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL;
}
PCASC_CKEY_ENTRY TFileTreeRoot::Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_FILE_NODE pFileNode;
size_t nMaxFileIndex = FileTree.GetMaxFileIndex();
// Are we still inside the root directory range?
while(pSearch->nFileIndex < nMaxFileIndex)
{
//BREAKIF(pSearch->nFileIndex >= 2823765);
// Retrieve the file item
pFileNode = FileTree.PathAt(pFindData->szFileName, MAX_PATH, pSearch->nFileIndex++);
if(pFileNode != NULL)
{
// Ignore folders and mount points
if(!(pFileNode->Flags & CFN_FLAG_FOLDER))
{
// Check the wildcard
if (CascCheckWildCard(pFindData->szFileName, pSearch->szMask))
{
// Retrieve the extra values (FileDataId, file size and locale flags)
FileTree.GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags);
// Supply the bCanOpenByDataId variable
pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0);
pFindData->bCanOpenByDataId = (pFindData->dwFileDataId != CASC_INVALID_ID);
// Return the found CKey entry
return pFileNode->pCKeyEntry;
}
}
}
}
// No more entries
return NULL;
}
bool TFileTreeRoot::GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, PCASC_FILE_FULL_INFO pFileInfo)
{
PCASC_FILE_NODE pFileNode;
// Can't do much if the root key is NULL
if(pCKeyEntry != NULL)
{
pFileNode = FileTree.Find(pCKeyEntry);
if(pFileNode != NULL)
{
FileTree.GetExtras(pFileNode, &pFileInfo->FileDataId, &pFileInfo->LocaleFlags, &pFileInfo->ContentFlags);
pFileInfo->FileNameHash = pFileNode->FileNameHash;
return true;
}
}
return false;
}
+111
View File
@@ -0,0 +1,111 @@
/*****************************************************************************/
/* 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_TVFS_ROOT_SIGNATURE 0x53465654 // 'TVFS'
#define CASC_DIABLO3_ROOT_SIGNATURE 0x8007D0C4
#define CASC_WOW82_ROOT_SIGNATURE 0x4D465354 // 'TSFM', WoW since 8.2
#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
//-----------------------------------------------------------------------------
// Class for generic root handler
struct TRootHandler
{
public:
TRootHandler()
{
dwFeatures = 0;
}
virtual ~TRootHandler()
{}
// Inserts new file name to the root handler
// szFileName - Pointer to the file name
// pCKeyEntry - Pointer to the CASC_CKEY_ENTRY for the file
virtual int Insert(const char * /* szFileName */, PCASC_CKEY_ENTRY /* pCKeyEntry */)
{
return ERROR_NOT_SUPPORTED;
}
// Searches the file by file name
// hs - Pointer to the storage structure
// szFileName - Pointer to the file name
virtual PCASC_CKEY_ENTRY GetFile(struct TCascStorage * /* hs */, const char * /* szFileName */)
{
return NULL;
}
// Searches the file by file data id
// hs - Pointer to the storage structure
// FileDataId - File data id
virtual PCASC_CKEY_ENTRY GetFile(struct TCascStorage * /* hs */, DWORD /* FileDataId */)
{
return NULL;
}
// Performs find-next-file operation
// pSearch - Pointer to the initialized search structure
// pFindData - Pointer to output structure that will contain the information
virtual PCASC_CKEY_ENTRY Search(struct TCascSearch * /* pSearch */, struct _CASC_FIND_DATA * /* pFindData */)
{
return NULL;
}
// Returns advanced info from the root file entry.
// pCKeyEntry - CKey/EKey, depending on which type the root handler provides
// pFileInfo - Pointer to CASC_FILE_FULL_INFO structure
virtual bool GetInfo(PCASC_CKEY_ENTRY /* pCKeyEntry */, struct _CASC_FILE_FULL_INFO * /* pFileInfo */)
{
return false;
}
DWORD GetFeatures()
{
return dwFeatures;
}
protected:
DWORD dwFeatures; // CASC features. See CASC_FEATURE_XXX
};
//-----------------------------------------------------------------------------
// Class for root handler that has basic mapping of FileName -> CASC_FILE_NODE
struct TFileTreeRoot : public TRootHandler
{
TFileTreeRoot(DWORD FileTreeFlags);
virtual ~TFileTreeRoot();
int Insert(const char * szFileName, PCASC_CKEY_ENTRY pCKeyEntry);
PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, const char * szFileName);
PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, DWORD FileDataId);
PCASC_CKEY_ENTRY Search(struct TCascSearch * pSearch, struct _CASC_FIND_DATA * pFindData);
bool GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, struct _CASC_FILE_FULL_INFO * pFileInfo);
protected:
CASC_FILE_TREE FileTree;
};
#endif // __ROOT_HANDLER_H__
+24
View File
@@ -0,0 +1,24 @@
#ifndef __LOOKUP3_H__
#define __LOOKUP3_H__
#ifdef WIN32
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
#else
#include <stdint.h> /* defines uint32_t etc */
#endif
#ifdef __cplusplus
extern "C"
{
#endif
uint32_t hashlittle(const void *key, size_t length, uint32_t initval);
void hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb);
#ifdef __cplusplus
}
#endif
#endif // __LOOKUP3_H__
File diff suppressed because it is too large Load Diff
+291
View File
@@ -0,0 +1,291 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* (This is a heavily cut-down "BSD license".)
*
* This differs from Colin Plumb's older public domain implementation in that
* no exactly 32-bit integer data type is required (any 32-bit or wider
* unsigned integer data type will do), there's no compile-time endianness
* configuration, and the function prototypes match OpenSSL's. No code from
* Colin Plumb's implementation has been reused; this comment merely compares
* the properties of the two independent implementations.
*
* The primary goals of this implementation are portability and ease of use.
* It is meant to be fast, but not as fast as possible. Some known
* optimizations are not included to reduce source code size and avoid
* compile-time configuration.
*/
#ifndef HAVE_OPENSSL
#include <string.h>
#include "md5.h"
/*
* The basic MD5 functions.
*
* F and G are optimized compared to their RFC 1321 definitions for
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
* implementation.
*/
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
#define H(x, y, z) (((x) ^ (y)) ^ (z))
#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
/*
* The MD5 transformation for all four rounds.
*/
#define STEP(f, a, b, c, d, x, t, s) \
(a) += f((b), (c), (d)) + (x) + (t); \
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
(a) += (b);
/*
* SET reads 4 input bytes in little-endian byte order and stores them in a
* properly aligned word in host byte order.
*
* The check for little-endian architectures that tolerate unaligned memory
* accesses is just an optimization. Nothing will break if it fails to detect
* a suitable architecture.
*
* Unfortunately, this optimization may be a C strict aliasing rules violation
* if the caller's data buffer has effective type that cannot be aliased by
* MD5_u32plus. In practice, this problem may occur if these MD5 routines are
* inlined into a calling function, or with future and dangerously advanced
* link-time optimizations. For the time being, keeping these MD5 routines in
* their own translation unit avoids the problem.
*/
#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
#define SET(n) \
(*(MD5_u32plus *)&ptr[(n) * 4])
#define GET(n) \
SET(n)
#else
#define SET(n) \
(ctx->block[(n)] = \
(MD5_u32plus)ptr[(n) * 4] | \
((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
#define GET(n) \
(ctx->block[(n)])
#endif
/*
* This processes one or more 64-byte data blocks, but does NOT update the bit
* counters. There are no alignment requirements.
*/
static const void *body(MD5_CTX *ctx, const void *data, unsigned long size)
{
const unsigned char *ptr;
MD5_u32plus a, b, c, d;
MD5_u32plus saved_a, saved_b, saved_c, saved_d;
ptr = (const unsigned char *)data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
/* Round 2 */
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
/* Round 3 */
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
/* Round 4 */
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
void MD5_Init(MD5_CTX *ctx)
{
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size)
{
MD5_u32plus saved_lo;
unsigned long used, available;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
ctx->hi++;
ctx->hi += size >> 29;
used = saved_lo & 0x3f;
if (used) {
available = 64 - used;
if (size < available) {
memcpy(&ctx->buffer[used], data, size);
return;
}
memcpy(&ctx->buffer[used], data, available);
data = (const unsigned char *)data + available;
size -= available;
body(ctx, ctx->buffer, 64);
}
if (size >= 64) {
data = body(ctx, data, size & ~(unsigned long)0x3f);
size &= 0x3f;
}
memcpy(ctx->buffer, data, size);
}
#define OUT(dst, src) \
(dst)[0] = (unsigned char)(src); \
(dst)[1] = (unsigned char)((src) >> 8); \
(dst)[2] = (unsigned char)((src) >> 16); \
(dst)[3] = (unsigned char)((src) >> 24);
void MD5_Final(unsigned char *result, MD5_CTX *ctx)
{
unsigned long used, available;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
available = 64 - used;
if (available < 8) {
memset(&ctx->buffer[used], 0, available);
body(ctx, ctx->buffer, 64);
used = 0;
available = 64;
}
memset(&ctx->buffer[used], 0, available - 8);
ctx->lo <<= 3;
OUT(&ctx->buffer[56], ctx->lo)
OUT(&ctx->buffer[60], ctx->hi)
body(ctx, ctx->buffer, 64);
OUT(&result[0], ctx->a)
OUT(&result[4], ctx->b)
OUT(&result[8], ctx->c)
OUT(&result[12], ctx->d)
memset(ctx, 0, sizeof(*ctx));
}
#endif
+45
View File
@@ -0,0 +1,45 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* See md5.c for more information.
*/
#ifdef HAVE_OPENSSL
#include <openssl/md5.h>
#elif !defined(_MD5_H)
#define _MD5_H
/* Any 32-bit or wider unsigned integer data type will do */
typedef unsigned int MD5_u32plus;
typedef struct {
MD5_u32plus lo, hi;
MD5_u32plus a, b, c, d;
unsigned char buffer[64];
MD5_u32plus block[16];
} MD5_CTX;
void MD5_Init(MD5_CTX *ctx);
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size);
void MD5_Final(unsigned char *result, MD5_CTX *ctx);
#endif