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
+66
View File
@@ -0,0 +1,66 @@
set(HEADER_FILES
src/CascCommon.h
src/CascLib.h
src/CascPort.h
src/common/Array.h
src/common/Common.h
src/common/Csv.h
src/common/FileStream.h
src/common/FileTree.h
src/common/ListFile.h
src/common/Map.h
src/jenkins/lookup.h
)
set(SRC_FILES
src/common/Common.cpp
src/common/Directory.cpp
src/common/Csv.cpp
src/common/FileStream.cpp
src/common/FileTree.cpp
src/common/ListFile.cpp
src/common/RootHandler.cpp
src/jenkins/lookup3.c
src/CascCommon.cpp
src/CascDecompress.cpp
src/CascDecrypt.cpp
src/CascDumpData.cpp
src/CascFiles.cpp
src/CascFindFile.cpp
src/CascIndexFiles.cpp
src/CascOpenFile.cpp
src/CascOpenStorage.cpp
src/CascReadFile.cpp
src/CascRootFile_Diablo3.cpp
src/CascRootFile_Install.cpp
src/CascRootFile_MNDX.cpp
src/CascRootFile_Text.cpp
src/CascRootFile_TVFS.cpp
src/CascRootFile_OW.cpp
src/CascRootFile_WoW.cpp
)
set(MD5_FILES
src/md5/md5.cpp
)
add_library(casc STATIC ${SRC_FILES} ${HEADER_FILES} ${MD5_FILES})
target_include_directories(casc
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
PRIVATE
${CMAKE_SOURCE_DIR}/dep)
target_compile_definitions(casc PUBLIC -D__SYS_ZLIB -DCASCLIB_NO_AUTO_LINK_LIBRARY)
target_link_libraries(casc
PRIVATE
trinity-dependency-interface
PUBLIC
zlib)
set_target_properties(casc
PROPERTIES
FOLDER
"dep")
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Ladislav Zezula
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+4
View File
@@ -0,0 +1,4 @@
CascLib
=======
An open-source implementation of library for reading CASC storage from Blizzard games since 2014
+6
View File
@@ -0,0 +1,6 @@
CascLib history
===============
Version 1.00
- Created
+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