base: DestinyCore upstream @2d631f4e (fork SylvaniaCore ; sql/old retire)
This commit is contained in:
@@ -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")
|
||||
@@ -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.
|
||||
@@ -0,0 +1,4 @@
|
||||
CascLib
|
||||
=======
|
||||
|
||||
An open-source implementation of library for reading CASC storage from Blizzard games since 2014
|
||||
@@ -0,0 +1,6 @@
|
||||
CascLib history
|
||||
===============
|
||||
|
||||
Version 1.00
|
||||
|
||||
- Created
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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__
|
||||
@@ -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
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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__
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user