This commit is contained in:
Shauren
2023-09-05 00:10:35 +02:00
parent ba224f70ad
commit d57b58849b
36 changed files with 18102 additions and 645 deletions

View File

@@ -2,15 +2,12 @@ set(HEADER_FILES
src/CascCommon.h
src/CascLib.h
src/CascPort.h
src/CascStructs.h
src/common/Array.h
src/common/ArraySparse.h
src/common/Common.h
src/common/Csv.h
src/common/Directory.h
src/common/FileStream.h
src/common/FileTree.h
src/common/IndexMap.h
src/common/ListFile.h
src/common/Map.h
src/common/Mime.h
@@ -22,15 +19,20 @@ set(HEADER_FILES
set(SRC_FILES
src/common/Common.cpp
src/common/Csv.cpp
src/common/Directory.cpp
src/common/Csv.cpp
src/common/FileStream.cpp
src/common/FileTree.cpp
src/common/ListFile.cpp
src/common/Mime.cpp
src/common/RootHandler.cpp
src/common/Sockets.cpp
src/hashes/md5.cpp
src/hashes/sha1.cpp
src/jenkins/lookup3.c
src/overwatch/apm.cpp
src/overwatch/cmf.cpp
src/overwatch/aes.cpp
src/CascDecompress.cpp
src/CascDecrypt.cpp
src/CascDumpData.cpp
@@ -43,18 +45,13 @@ set(SRC_FILES
src/CascRootFile_Diablo3.cpp
src/CascRootFile_Install.cpp
src/CascRootFile_MNDX.cpp
src/CascRootFile_OW.cpp
src/CascRootFile_Text.cpp
src/CascRootFile_TVFS.cpp
src/CascRootFile_OW.cpp
src/CascRootFile_WoW.cpp
)
set(MD5_FILES
src/md5/md5.cpp
src/md5/md5.h
)
add_library(casc STATIC ${SRC_FILES} ${HEADER_FILES} ${MD5_FILES})
add_library(casc STATIC ${SRC_FILES} ${HEADER_FILES})
target_include_directories(casc
PUBLIC

View File

@@ -40,8 +40,9 @@
#include "common/RootHandler.h"
#include "common/Sockets.h"
// Headers from Alexander Peslyak's MD5 implementation
#include "md5/md5.h"
// Headers for hashes used in CascLib
#include "hashes/md5.h"
#include "hashes/sha1.h"
// For HashStringJenkins
#include "jenkins/lookup.h"

View File

@@ -523,7 +523,7 @@ static DWORD LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName *
// "build-name = 1.21.5.4037-retail"
while(szDataBegin < szDataEnd)
{
// There must be at least three digits (build 99 anyone?)
// If the character is a digit, we include it into the built number
if(IsCharDigit(szDataBegin[0]))
{
dwBuildNumber = (dwBuildNumber * 10) + (szDataBegin[0] - '0');
@@ -539,9 +539,12 @@ static DWORD LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName *
szDataBegin++;
}
// If not there, just take value from build file
if((hs->dwBuildNumber = dwMaxValue) == 0)
hs->dwBuildNumber = hs->CdnBuildKey.pbData[0] % 100;
// If we don't have a build number yet, take the max value, if any
if(hs->dwBuildNumber == 0 && dwMaxValue >= 100)
{
hs->dwBuildNumber = dwMaxValue;
return ERROR_SUCCESS;
}
return ERROR_BAD_FORMAT;
}

View File

@@ -596,7 +596,7 @@ static DWORD CaptureArchiveIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE p
// 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);
CascHash_MD5(checksum_data, sizeof(FILE_INDEX_FOOTER<0x08>) - MD5_HASH_SIZE, md5_hash);
if(!memcmp(md5_hash, InFooter.FooterHash, InFooter.FooterHashBytes))
return ERROR_SUCCESS;
}

View File

@@ -88,6 +88,8 @@ extern "C" {
#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_OPEN_CKEY_ONCE 0x00000040 // Only opens a file with given CKey once, regardless on how many file names does it have. Used by CascLib test program
// If the file was already open before, CascOpenFile returns false and ERROR_FILE_ALREADY_OPENED
#define CASC_LOCALE_ALL 0xFFFFFFFF
#define CASC_LOCALE_ALL_WOW 0x0001F3F6 // All except enCN and enTW
@@ -131,6 +133,11 @@ extern "C" {
#define MD5_STRING_SIZE 0x20
#endif
#ifndef SHA1_HASH_SIZE
#define SHA1_HASH_SIZE 0x14
#define SHA1_STRING_SIZE 0x28
#endif
// Invalid values of all kind
#define CASC_INVALID_INDEX 0xFFFFFFFF
#define CASC_INVALID_SIZE 0xFFFFFFFF

View File

@@ -409,6 +409,21 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocal
break;
}
// Check opening unique file
if(dwOpenFlags & CASC_OPEN_CKEY_ONCE)
{
// Was the file already open since CascOpenStorage?
if(pCKeyEntry->Flags & CASC_CE_OPEN_CKEY_ONCE)
{
SetCascError(ERROR_CKEY_ALREADY_OPENED);
return false;
}
else
{
pCKeyEntry->Flags |= CASC_CE_OPEN_CKEY_ONCE;
}
}
// Perform the open operation
return OpenFileByCKeyEntry(hs, pCKeyEntry, dwOpenFlags, PtrFileHandle);
}

View File

@@ -147,7 +147,6 @@ TCascStorage * TCascStorage::Release()
delete this;
return NULL;
}
return this;
}
@@ -233,7 +232,6 @@ static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, PFILE_CKEY_ENTRY pFil
pCKeyEntry->Flags = CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING;
pCKeyEntry->RefCount = 0;
pCKeyEntry->SpanCount = 1;
pCKeyEntry->Priority = 0;
// Copy the information from index files to the CKey entry
CopyEKeyEntry(hs, pCKeyEntry);
@@ -298,7 +296,7 @@ static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_DOWNLOAD_ENTRY &
pCKeyEntry->Flags = (pCKeyEntry->Flags & ~CASC_CE_HAS_EKEY_PARTIAL) | CASC_CE_IN_DOWNLOAD;
}
// Supply the rest
// Save the rest and return the entry
pCKeyEntry->Priority = DlEntry.Priority;
return pCKeyEntry;
}
@@ -406,7 +404,7 @@ int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, si
return ERROR_SUCCESS;
}
static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage)
static DWORD LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage)
{
PFILE_CKEY_ENTRY pFileEntry;
LPBYTE pbFileEntry = pbPageBegin;
@@ -437,7 +435,7 @@ static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHead
return ERROR_SUCCESS;
}
static int LoadEncodingManifest(TCascStorage * hs)
static DWORD LoadEncodingManifest(TCascStorage * hs)
{
CASC_CKEY_ENTRY & CKeyEntry = hs->EncodingCKey;
CASC_BLOB EncodingFile;
@@ -892,7 +890,7 @@ __LoadRootFile:
dwErrCode = RootHandler_CreateTVFS(hs, RootFile);
break;
case CASC_WOW82_ROOT_SIGNATURE:
case CASC_WOW_ROOT_SIGNATURE:
dwErrCode = RootHandler_CreateWoW(hs, RootFile, dwLocaleMask);
break;
@@ -942,7 +940,6 @@ __LoadRootFile:
hs->pRootHandler->Copy(pOldRootHandler);
delete pOldRootHandler;
}
return dwErrCode;
}
@@ -960,7 +957,7 @@ static DWORD GetStorageTotalFileCount(TCascStorage * hs)
{
// If there is zero or one file name reference, we count the item as one file.
// If there is more than 1 name reference, we count the file as many times as number of references
DWORD RefCount = (pCKeyEntry->RefCount > 0) ? pCKeyEntry->RefCount : 1;
DWORD RefCount = (pCKeyEntry->RefCount) ? pCKeyEntry->RefCount : 1;
// Add the number of references to the total file count
TotalFileCount += RefCount;
@@ -1178,6 +1175,13 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs, L
dwErrCode = LoadCdnBuildFile(hs);
}
// Make sure we have a build number. If we don't, we assign a build number
// that is derived from the first beta TVFS build number
if(hs->dwBuildNumber == 0)
{
hs->dwBuildNumber = 21742 + hs->InstallCKey.ContentSize;
}
// Create the array of CKey entries. Each entry represents a file in the storage
if(dwErrCode == ERROR_SUCCESS)
{
@@ -1213,9 +1217,11 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs, L
// Continue loading the manifest
dwErrCode = LoadBuildManifest(hs, dwLocaleMask);
if(dwErrCode != ERROR_SUCCESS)
// If we fail to load the ROOT file, we take the file names from the INSTALL manifest
// Beware on low memory condition - in that case, we cannot guarantee a consistent state of the root file
if(dwErrCode != ERROR_SUCCESS && dwErrCode != ERROR_NOT_ENOUGH_MEMORY)
{
// If we fail to load the ROOT file, we take the file names from the INSTALL manifest
dwErrCode = LoadInstallManifest(hs);
}
}

View File

@@ -232,50 +232,54 @@
// Platform-specific error codes for UNIX-based platforms
#if defined(CASCLIB_PLATFORM_MAC) || defined(CASCLIB_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
#define ERROR_FILE_TOO_LARGE 1006 // No such error code under Linux
#define ERROR_ARITHMETIC_OVERFLOW 1007 // The string value is too large to fit in the given type
#define ERROR_NETWORK_NOT_AVAILABLE 1008 // Cannot connect to the internet
#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
#define ERROR_FILE_TOO_LARGE 1006 // No such error code under Linux
#define ERROR_ARITHMETIC_OVERFLOW 1007 // The string value is too large to fit in the given type
#define ERROR_NETWORK_NOT_AVAILABLE 1008 // Cannot connect to the internet
#endif
#ifndef ERROR_FILE_INCOMPLETE
#define ERROR_FILE_INCOMPLETE 1006 // The required file part is missing
#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
#define ERROR_FILE_OFFLINE 1007 // The file is not available in the local storage
#endif
#ifndef ERROR_BUFFER_OVERFLOW
#define ERROR_BUFFER_OVERFLOW 1008
#define ERROR_BUFFER_OVERFLOW 1008
#endif
#ifndef ERROR_CANCELLED
#define ERROR_CANCELLED 1009
#define ERROR_CANCELLED 1009
#endif
#ifndef ERROR_INDEX_PARSING_DONE
#define ERROR_INDEX_PARSING_DONE 1010
#define ERROR_INDEX_PARSING_DONE 1010
#endif
#ifndef ERROR_REPARSE_ROOT
#define ERROR_REPARSE_ROOT 1011
#define ERROR_REPARSE_ROOT 1011
#endif
#ifndef ERROR_CKEY_ALREADY_OPENED
#define ERROR_CKEY_ALREADY_OPENED 1012 // The file with this CKey was already open since CascOpenStorage
#endif
#ifndef _countof

View File

@@ -161,9 +161,8 @@ static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderO
}
#endif
static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize)
static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, 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;
@@ -176,29 +175,29 @@ static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEn
// On local files, there is just BLTE_HEADER
if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE)
{
PBLTE_ENCODED_HEADER pEncodedHeader;
// There must be at least some bytes
if(cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F))
return ERROR_BAD_FORMAT;
pEncodedHeader = (PBLTE_ENCODED_HEADER)pbEncodedBuffer;
// Note that some newer WoW builds have the entire encoded part zeroed
// Tested on WoW retail 50401, file DBFilesClient\\LoreTextPublic.db2
if(pEncodedHeader->EncodedSize != 0 && pEncodedHeader->EncodedSize != pCKeyEntry->EncodedSize)
// Since Jul-2023, users report that the the encoded part of the BLTE header
// may contain zeros or even complete garbage. Do NOT test anything else than the signature
// Tested on WoW Classic 49821, file "Sound\\Music\\GlueScreenMusic\\wow_main_theme.mp3"
// Data File: data.004, file offset 00000000-18BDD2AA (encoded header zeroed)
if(ConvertBytesToInteger_4_LE(pEncodedHeader->Signature) != BLTE_HEADER_SIGNATURE)
return ERROR_BAD_FORMAT;
pBlteHeader = (PBLTE_HEADER)(pEncodedHeader->Signature);
ExHeaderSize = FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature);
#ifdef CASCLIB_DEBUG
// Not really needed, it's here just for explanation of what the values mean
//assert(memcmp(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);
@@ -230,7 +229,7 @@ static LPBYTE ReadMissingHeaderData(PCASC_FILE_SPAN pFileSpan, ULONGLONG DataFil
{
LPBYTE pbNewBuffer;
// Reallocate the buffer
// Reallocate the buffer. Note that if this fails, the original buffer is still valid
pbNewBuffer = CASC_REALLOC(pbEncodedBuffer, cbTotalHeaderSize);
if(pbNewBuffer != NULL)
{
@@ -408,7 +407,7 @@ static DWORD LoadEncodedHeaderAndSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKE
if(FileStream_Read(pFileSpan->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
{
// Parse the BLTE header
dwErrCode = ParseBlteHeader(pFileSpan, pCKeyEntry, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
dwErrCode = ParseBlteHeader(pFileSpan, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
if(dwErrCode == ERROR_SUCCESS)
{
// If the headers are larger than the initial read size, we read the missing data

View File

@@ -264,7 +264,7 @@ struct TDiabloRoot : public TFileTreeRoot
return ERROR_BAD_FORMAT;
// Capture the array of DIABLO3_ASSET_ENTRY
pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetEntries, DIABLO3_ASSET_ENTRY, DirHeader.dwAssetEntries);
pbDirectory = CaptureArrayAsByte<DIABLO3_ASSET_ENTRY>(pbDirectory, pbDataEnd, &DirHeader.pbAssetEntries, DirHeader.dwAssetEntries);
if(pbDirectory == NULL)
return ERROR_BAD_FORMAT;
@@ -274,7 +274,7 @@ struct TDiabloRoot : public TFileTreeRoot
return ERROR_BAD_FORMAT;
// Capture the array of DIABLO3_ASSETIDX_ENTRY
pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetIdxEntries, DIABLO3_ASSETIDX_ENTRY, DirHeader.dwAssetIdxEntries);
pbDirectory = CaptureArrayAsByte<DIABLO3_ASSETIDX_ENTRY>(pbDirectory, pbDataEnd, &DirHeader.pbAssetIdxEntries, DirHeader.dwAssetIdxEntries);
if(pbDirectory == NULL)
return ERROR_BAD_FORMAT;
}

View File

@@ -12,107 +12,15 @@
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Structure definitions for CMF files
// Implemented in "overwatch/apm.cpp"
DWORD LoadApplicationPackageManifestFile(TCascStorage * hs, CASC_FILE_TREE & FileTree, PCASC_CKEY_ENTRY pCKeyEntry, const char * szApmFileName);
#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;
// Implemented in "overwatch/cmf.cpp"
DWORD LoadContentManifestFile(TCascStorage * hs, CASC_FILE_TREE & FileTree, PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName);
//-----------------------------------------------------------------------------
// 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
@@ -126,7 +34,7 @@ typedef struct _APM_HEADER_V3
// 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;
} APM_HEADER_V3, * PAPM_HEADER_V3;
typedef struct _APM_HEADER_V2
{
@@ -140,7 +48,7 @@ typedef struct _APM_HEADER_V2
// 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;
} APM_HEADER_V2, * PAPM_HEADER_V2;
typedef struct _APM_HEADER_V1
{
@@ -153,224 +61,218 @@ typedef struct _APM_HEADER_V1
// 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;
} APM_HEADER_V1, * PAPM_HEADER_V1;
// 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;
// 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
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;
// 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;
//-----------------------------------------------------------------------------
// Handler classes
// Local functions (non-class)
/*
struct TCmfFile
static bool IsManifestFolderName(const char * szFileName, const char * szManifestFolder, size_t nLength)
{
TCmfFile()
if(!_strnicmp(szFileName, szManifestFolder, nLength))
{
memset(this, 0, sizeof(TCmfFile));
return (szFileName[nLength] == '\\' || szFileName[nLength] == '/');
}
return false;
}
LPBYTE CaptureHeader(LPBYTE pbCmfData, LPBYTE pbCmfEnd)
{
DWORD BuildNumber = *(PDWORD)pbCmfData;
//-----------------------------------------------------------------------------
// Public functions (non-class)
// 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
static void BinaryReverse64(LPBYTE GuidReversed, LPBYTE pbGuid)
{
TApmFile()
{
memset(this, 0, sizeof(TApmFile));
}
GuidReversed[0] = pbGuid[7];
GuidReversed[1] = pbGuid[6];
GuidReversed[2] = pbGuid[5];
GuidReversed[3] = pbGuid[4];
GuidReversed[4] = pbGuid[3];
GuidReversed[5] = pbGuid[2];
GuidReversed[6] = pbGuid[1];
GuidReversed[7] = pbGuid[0];
}
~TApmFile()
{
CASC_FREE(pApmPackages);
CASC_FREE(pApmEntries);
}
static const char * ExtractAssetSubString(char * szBuffer, size_t ccBuffer, const char * szPlainName)
{
char * szBufferEnd = szBuffer + ccBuffer - 1;
LPBYTE CaptureHeader(LPBYTE pbApmData, LPBYTE pbApmEnd)
while(szBuffer < szBufferEnd && szPlainName[0] != 0 && szPlainName[0] != '.' && szPlainName[0] != '_')
*szBuffer++ = *szPlainName++;
if(szBuffer <= szBufferEnd)
szBuffer[0] = 0;
return szPlainName;
}
static const char * AppendAssetSubString(char * szBuffer, size_t ccBuffer, const char * szPlainName)
{
char * szBufferPtr = szBuffer + strlen(szBuffer);
char * szBufferEnd = szBuffer + ccBuffer - 1;
if(szBufferPtr < szBufferEnd)
*szBufferPtr++ = '-';
while(szBufferPtr < szBufferEnd && szPlainName[0] != '_')
*szBufferPtr++ = *szPlainName++;
szBufferPtr[0] = 0;
return szPlainName;
}
size_t BuildAssetFileNameTemplate(
char * szNameTemplate,
size_t ccNameTemplate,
const char * szPrefix,
const char * szAssetName)
{
const char * szFileName = "0000000000000000"; // Base name for 64-bit GUID
const char * szFileExt = NULL;
char * szBufferEnd = szNameTemplate + ccNameTemplate;
char * szBufferPtr = szNameTemplate;
char * szPlainName;
char szPlatform[64] = {0};
char szLocale[64] = {0};
char szAsset[64] = {0};
// Parse the plain name
while(szAssetName[0] != '.')
{
// Check the data size for the largest possible header size
if((pbApmData + sizeof(APM_HEADER_V3)) < pbApmEnd)
// Watch start of the new field
if(szAssetName[0] == '_')
{
// 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)
// Extract platform from "_SP"
if(szAssetName[1] == 'S' && szAssetName[2] == 'P' && !_strnicmp(szAssetName, "_SPWin_", 7))
{
BuildNumber = pApmFile3->BuildNumber;
PackageCount = pApmFile3->PackageCount;
EntryCount = pApmFile3->EntryCount;
Checksum = pApmFile3->Checksum;
return pbApmData + 0x24;
CascStrCopy(szPlatform, _countof(szPlatform), "Windows");
szAssetName += 6;
continue;
}
// Try the version 2
PAPM_HEADER_V2 pApmFile2 = (PAPM_HEADER_V2)(pbApmData);
if(pApmFile2->ZeroValue1 == 0 && pApmFile2->PackageCount && pApmFile2->EntryCount && pApmFile2->Checksum)
// Extract "RDEV" or "RCN"
if(szAssetName[1] == 'R')
{
BuildNumber = pApmFile2->BuildNumber;
PackageCount = pApmFile2->PackageCount;
EntryCount = pApmFile2->EntryCount;
Checksum = pApmFile2->Checksum;
return pbApmData + 0x20;
szAssetName = AppendAssetSubString(szPlatform, _countof(szPlatform), szAssetName + 1);
continue;
}
// Try the version 1 (build 24919)
PAPM_HEADER_V1 pApmHeader1 = (PAPM_HEADER_V1)(pbApmData);
if(pApmHeader1->BuildVersion != 0 && pApmHeader1->PackageCount && pApmHeader1->EntryCount && pApmHeader1->Checksum)
// Extract locale
if(szAssetName[1] == 'L')
{
BuildNumber = pApmHeader1->BuildNumber;
PackageCount = pApmHeader1->PackageCount;
EntryCount = pApmHeader1->EntryCount;
Checksum = pApmHeader1->Checksum;
return pbApmData + 0x18;
szAssetName = ExtractAssetSubString(szLocale, _countof(szLocale), szAssetName + 2);
continue;
}
// Ignore "_EExt"
if(szAssetName[1] == 'E' && szAssetName[2] == 'E')
{
szAssetName += 5;
continue;
}
// Extract the asset name
szAssetName = ExtractAssetSubString(szAsset, _countof(szAsset), szAssetName + 1);
// Extract a possible extension
//if(!_stricmp(szAsset, "speech"))
// szFileExt = ".wav";
//if(!_stricmp(szAsset, "text"))
// szFileExt = ".text";
continue;
}
return NULL;
szAssetName++;
}
LPBYTE CaptureArrayOfEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd)
// Combine the path like "%PREFIX%\\%PLATFORM%-%DEV%\\%LOCALE%\\%ASSET%\\%PLAIN_NAME%.%EXTENSSION%"
if(szPrefix && szPrefix[0])
szBufferPtr += CascStrPrintf(szBufferPtr, (szBufferEnd - szBufferPtr), "%s\\", szPrefix);
if(szPlatform[0])
szBufferPtr += CascStrPrintf(szBufferPtr, (szBufferEnd - szBufferPtr), "%s\\", szPlatform);
if(szLocale[0])
szBufferPtr += CascStrPrintf(szBufferPtr, (szBufferEnd - szBufferPtr), "%s\\", szLocale);
if(szAsset[0])
szBufferPtr += CascStrPrintf(szBufferPtr, (szBufferEnd - szBufferPtr), "%s\\", szAsset);
szPlainName = szBufferPtr;
// Append file name and extension
if(szFileName && szFileName[0])
szBufferPtr += CascStrPrintf(szBufferPtr, (szBufferEnd - szBufferPtr), "%s", szFileName);
if(szFileExt && szFileExt[0])
CascStrPrintf(szBufferPtr, (szBufferEnd - szBufferPtr), "%s", szFileExt);
// Return the length of the path
return (szPlainName - szNameTemplate);
}
DWORD InsertAssetFile(
TCascStorage * hs,
CASC_FILE_TREE & FileTree,
char * szFileName,
size_t nPlainName, // Offset of the plain name in the name template
LPBYTE pbCKey,
LPBYTE pbGuid)
{
PCASC_CKEY_ENTRY pCKeyEntry;
DWORD dwErrCode = ERROR_SUCCESS;
BYTE GuidReversed[8];
// Try to find the CKey
if((pCKeyEntry = FindCKeyEntry_CKey(hs, pbCKey)) != NULL)
{
// 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);
// Save the character at the end of the name (dot or EOS)
char chSaveChar = szFileName[nPlainName + 16];
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);
}
// Imprint the GUID as binary value
BinaryReverse64(GuidReversed, pbGuid);
StringFromBinary(GuidReversed, sizeof(GuidReversed), szFileName + nPlainName);
szFileName[nPlainName + 16] = chSaveChar;
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;
// Insert the asset to the file tree
if(FileTree.InsertByName(pCKeyEntry, szFileName) == NULL)
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
}
LPBYTE CapturePackageEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd)
{
// Allocate array of entries
pApmPackages = CASC_ALLOC_ZERO<APM_PACKAGE_ENTRY>(PackageCount);
if(pApmPackages != NULL)
{
// 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")
};
*/
return dwErrCode;
}
//-----------------------------------------------------------------------------
// Handler definition for OVERWATCH root file
//
// -------------------------------------
// Overwatch ROOT file (build 24919):
@@ -397,97 +299,12 @@ struct TRootHandler_OW : public TFileTreeRoot
// 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;
}
DWORD LoadApmFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName)
{
TApmFile ApmFile;
LPBYTE pbApmData;
DWORD cbApmData = 0;
DWORD dwErrCode = 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 dwErrCode;
}
static DWORD LoadCmfFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName)
{
TCmfFile CmfFile;
LPBYTE pbCmfData;
DWORD cbCmfData = 0;
DWORD dwErrCode = 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 dwErrCode;
}
*/
int Load(TCascStorage * hs, CASC_CSV & Csv, size_t nFileNameIndex, size_t nCKeyIndex)
DWORD Load(TCascStorage * hs, CASC_CSV & Csv, size_t nFileNameIndex, size_t nCKeyIndex)
{
PCASC_CKEY_ENTRY pCKeyEntry;
// size_t ApmFiles[0x80];
// size_t nApmFiles = 0;
size_t nFileCount;
DWORD dwErrCode = ERROR_SUCCESS;
BYTE CKey[MD5_HASH_SIZE];
CASCLIB_UNUSED(hs);
@@ -509,49 +326,43 @@ struct TRootHandler_OW : public TFileTreeRoot
{
// 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(dwErrCode == ERROR_SUCCESS)
// Get the total file count that we loaded so far
nFileCount = FileTree.GetCount();
// Parse Content Manifest Files (.cmf)
for(size_t i = 0; i < nFileCount && dwErrCode == ERROR_SUCCESS; i++)
{
for(size_t i = 0; i < nApmFiles; i++)
PCASC_FILE_NODE pFileNode;
const char * szExtension;
char szFileName[MAX_PATH];
// Get the n-th file
pFileNode = (PCASC_FILE_NODE)FileTree.PathAt(szFileName, _countof(szFileName), i);
if(pFileNode != NULL)
{
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
dwErrCode = LoadCmfFile(hs, pFileNode2->CKey, szCmfFile);
if(dwErrCode != ERROR_SUCCESS)
break;
if(IsManifestFolderName(szFileName, "Manifest", 8) || IsManifestFolderName(szFileName, "TactManifest", 12))
{
// Retrieve the file extension
szExtension = GetFileExtension(szFileName);
// Check for content manifest files
if(!_stricmp(szExtension, ".cmf"))
{
dwErrCode = LoadContentManifestFile(hs, FileTree, pFileNode->pCKeyEntry, szFileName);
}
else if(!_stricmp(szExtension, ".apm"))
{
dwErrCode = LoadApplicationPackageManifestFile(hs, FileTree, pFileNode->pCKeyEntry, szFileName);
}
}
}
}
*/
return ERROR_SUCCESS;
return dwErrCode;
}
};

View File

@@ -588,7 +588,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot
{
PCASC_CKEY_ENTRY pSpanEntries;
PCASC_FILE_NODE pFileNode;
USHORT RefCount;
DWORD RefCount;
bool bFilePresent = true;
//

View File

@@ -31,17 +31,27 @@
typedef enum _ROOT_FORMAT
{
RootFormatWoW6x, // WoW 6.x - 8.1.x
RootFormatWoW82 // WoW 8.2 or newer
RootFormatWoW_v1, // Since build 18125 (WoW 6.0.1)
RootFormatWoW_v2, // Since build 30080 (WoW 8.2.0)
} ROOT_FORMAT, *PROOT_FORMAT;
// ROOT file header, since 8.2
typedef struct _FILE_ROOT_HEADER_82
// ROOT file header since build 50893 (10.1.7)
typedef struct _FILE_ROOT_HEADER_50893
{
DWORD Signature; // Must be CASC_WOW82_ROOT_SIGNATURE
DWORD Signature; // Must be CASC_WOW_ROOT_SIGNATURE
DWORD SizeOfHeader;
DWORD Version; // Must be 1
DWORD TotalFiles;
DWORD FilesWithNameHash;
} FILE_ROOT_HEADER_82, *PFILE_ROOT_HEADER_82;
} FILE_ROOT_HEADER_50893, * PFILE_ROOT_HEADER_50893;
// ROOT file header since build 30080 (8.2.0)
typedef struct _FILE_ROOT_HEADER_30080
{
DWORD Signature; // Must be CASC_WOW_ROOT_SIGNATURE
DWORD TotalFiles;
DWORD FilesWithNameHash;
} FILE_ROOT_HEADER_30080, *PFILE_ROOT_HEADER_30080;
// On-disk version of root group. A root group contains a group of file
// with the same locale and file flags
@@ -58,7 +68,7 @@ typedef struct _FILE_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
// In WoW build 30080 (8.2.0)+, CKey and FileNameHash are split into separate arrays
// and FileNameHash is optional
typedef struct _FILE_ROOT_ENTRY
{
@@ -72,9 +82,9 @@ 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)
PFILE_ROOT_ENTRY pRootEntries; // Valid for WoW since 18125
PCONTENT_KEY pCKeyEntries; // Valid for WoW since 30080
PULONGLONG pHashes; // Valid for WoW since 30080 (optional)
} FILE_ROOT_GROUP, *PFILE_ROOT_GROUP;
@@ -87,6 +97,8 @@ struct TRootHandler_WoW : public TFileTreeRoot
{
public:
typedef LPBYTE (*CAPTURE_ROOT_HEADER)(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless);
TRootHandler_WoW(ROOT_FORMAT RFormat, DWORD HashlessFileCount) : TFileTreeRoot(FTREE_FLAGS_WOW)
{
// Turn off the "we know file names" bit
@@ -97,30 +109,102 @@ struct TRootHandler_WoW : public TFileTreeRoot
// Update the flags based on format
switch(RootFormat)
{
case RootFormatWoW6x:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FNAME_HASHES;
case RootFormatWoW_v2:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_FNAME_HASHES_OPTIONAL;
break;
case RootFormatWoW82:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_FNAME_HASHES_OPTIONAL;
case RootFormatWoW_v1:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FNAME_HASHES;
break;
}
}
static LPBYTE CaptureRootHeader(FILE_ROOT_HEADER_82 & RootHeader, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
// Check for the new format (World of Warcraft 10.1.7, build 50893)
static LPBYTE CaptureRootHeader_50893(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
{
FILE_ROOT_HEADER_50893 RootHeader;
// Validate the root file header
if((pbRootPtr + sizeof(FILE_ROOT_HEADER_82)) >= pbRootEnd)
if((pbRootPtr + sizeof(FILE_ROOT_HEADER_50893)) >= pbRootEnd)
return NULL;
memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_82));
memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_50893));
// Verify the root file header
if(RootHeader.Signature != CASC_WOW82_ROOT_SIGNATURE)
if(RootHeader.Signature != CASC_WOW_ROOT_SIGNATURE)
return NULL;
if(RootHeader.Version != 1)
return NULL;
if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
return NULL;
// wow client doesn't seem to think this is a fatal error, we will do the same for now
if(RootHeader.SizeOfHeader < 4)
RootHeader.SizeOfHeader = 4;
*RootFormat = RootFormatWoW_v2;
*FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
return pbRootPtr + RootHeader.SizeOfHeader;
}
// Check for the root format for build 30080+ (WoW 8.2.0)
static LPBYTE CaptureRootHeader_30080(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
{
FILE_ROOT_HEADER_30080 RootHeader;
// Validate the root file header
if((pbRootPtr + sizeof(FILE_ROOT_HEADER_30080)) >= pbRootEnd)
return NULL;
memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_30080));
// Verify the root file header
if(RootHeader.Signature != CASC_WOW_ROOT_SIGNATURE)
return NULL;
if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
return NULL;
return pbRootPtr + sizeof(FILE_ROOT_HEADER_82);
*RootFormat = RootFormatWoW_v2;
*FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
return pbRootPtr + sizeof(FILE_ROOT_HEADER_30080);
}
// Check for the root format for build 18125+ (WoW 6.0.1)
static LPBYTE CaptureRootHeader_18125(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
{
size_t DataLength;
// There is no header. Right at the begin, there's FILE_ROOT_GROUP_HEADER structure,
// followed by the array of DWORDs and FILE_ROOT_ENTRYs
if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER)) >= pbRootEnd)
return NULL;
DataLength = ((PFILE_ROOT_GROUP_HEADER)(pbRootPtr))->NumberOfFiles * (sizeof(DWORD) + sizeof(FILE_ROOT_ENTRY));
// Validate the array of data
if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER) + DataLength) >= pbRootEnd)
return NULL;
*RootFormat = RootFormatWoW_v1;
*FileCounterHashless = 0;
return pbRootPtr;
}
static LPBYTE CaptureRootHeader(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
{
CAPTURE_ROOT_HEADER PfnCaptureRootHeader[] =
{
&CaptureRootHeader_50893,
&CaptureRootHeader_30080,
&CaptureRootHeader_18125,
};
for(size_t i = 0; i < _countof(PfnCaptureRootHeader); i++)
{
LPBYTE pbCapturedPtr;
if((pbCapturedPtr = PfnCaptureRootHeader[i](pbRootPtr, pbRootEnd, RootFormat, FileCounterHashless)) != NULL)
{
return pbCapturedPtr;
}
}
return NULL;
}
LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
@@ -146,15 +230,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
// 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:
case RootFormatWoW_v2:
// Verify the position of array of CONTENT_KEY
if((pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
@@ -173,49 +249,21 @@ struct TRootHandler_WoW : public TFileTreeRoot
return pbRootPtr;
case RootFormatWoW_v1:
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);
default:
return NULL;
}
}
DWORD 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;
}
DWORD ParseWowRootFile_AddFiles_82(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
// Since WoW build 30080 (8.2.0)
DWORD ParseWowRootFile_AddFiles_v2(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
{
PCASC_CKEY_ENTRY pCKeyEntry;
PCONTENT_KEY pCKey = RootGroup.pCKeyEntries;
@@ -253,6 +301,44 @@ struct TRootHandler_WoW : public TFileTreeRoot
return ERROR_SUCCESS;
}
// Since WoW build 18125 (6.0.1)
DWORD ParseWowRootFile_AddFiles_v1(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;
}
DWORD ParseWowRootFile_Level2(
TCascStorage * hs,
LPBYTE pbRootPtr,
@@ -297,12 +383,12 @@ struct TRootHandler_WoW : public TFileTreeRoot
// Now call the custom function
switch(RootFormat)
{
case RootFormatWoW82:
ParseWowRootFile_AddFiles_82(hs, RootBlock);
case RootFormatWoW_v2:
ParseWowRootFile_AddFiles_v2(hs, RootBlock);
break;
case RootFormatWoW6x:
ParseWowRootFile_AddFiles_6x(hs, RootBlock);
case RootFormatWoW_v1:
ParseWowRootFile_AddFiles_v1(hs, RootBlock);
break;
default:
@@ -407,7 +493,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
DWORD FileDataId = CASC_INVALID_ID;
char szFileName[MAX_PATH];
if(RootFormat == RootFormatWoW82)
if(RootFormat == RootFormatWoW_v2)
{
// Keep going through the listfile
for(;;)
@@ -421,11 +507,25 @@ struct TRootHandler_WoW : public TFileTreeRoot
break;
}
// Try to find the file node by file data id
pFileNode = FileTree.FindById(FileDataId);
if(pFileNode != NULL && pFileNode->NameLength == 0)
//
// Several files were renamed around WoW build 50893 (10.1.7). Example:
//
// * 2965132; interface/icons/inv_helm_armor_explorer_d_01.blp file name hash = 0x770b8d2dc4d940aa
// * 2965132; interface/icons/inv_armor_explorer_d_01_helm.blp file name hash = 0xf47ec17f4a1e49a2
//
// For that reason, we also need to check whether the file name hash matches
//
// BREAKIF(FileDataId == 2965132);
if((pFileNode = FileTree.FindById(FileDataId)) != NULL)
{
FileTree.SetNodeFileName(pFileNode, szFileName);
if(pFileNode->NameLength == 0)
{
if(pFileNode->FileNameHash && pFileNode->FileNameHash != CalcFileNameHash(szFileName))
continue;
FileTree.SetNodeFileName(pFileNode, szFileName);
}
}
}
}
@@ -462,7 +562,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
}
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 FileCounterHashless; // Number of files for which we don't have hash. Meaningless for WoW before 8.2.0
DWORD FileCounter; // Counter of loaded files. Only used during loading of ROOT file
};
@@ -472,22 +572,16 @@ struct TRootHandler_WoW : public TFileTreeRoot
DWORD RootHandler_CreateWoW(TCascStorage * hs, CASC_BLOB & RootFile, DWORD dwLocaleMask)
{
TRootHandler_WoW * pRootHandler = NULL;
FILE_ROOT_HEADER_82 RootHeader;
ROOT_FORMAT RootFormat = RootFormatWoW6x;
ROOT_FORMAT RootFormat = RootFormatWoW_v1;
LPBYTE pbRootFile = RootFile.pbData;
LPBYTE pbRootEnd = RootFile.End();
LPBYTE pbRootPtr;
DWORD FileCounterHashless = 0;
DWORD dwErrCode = 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;
}
// Verify the root header
if((pbRootPtr = TRootHandler_WoW::CaptureRootHeader(pbRootFile, pbRootEnd, &RootFormat, &FileCounterHashless)) == NULL)
return ERROR_BAD_FORMAT;
// Create the WOW handler
pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless);
@@ -496,7 +590,7 @@ DWORD RootHandler_CreateWoW(TCascStorage * hs, CASC_BLOB & RootFile, DWORD dwLoc
//fp = fopen("E:\\file-data-ids2.txt", "wt");
// Load the root directory. If load failed, we free the object
dwErrCode = pRootHandler->Load(hs, pbRootFile, pbRootEnd, dwLocaleMask);
dwErrCode = pRootHandler->Load(hs, pbRootPtr, pbRootEnd, dwLocaleMask);
if(dwErrCode != ERROR_SUCCESS)
{
delete pRootHandler;

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,50,0,205
PRODUCTVERSION 1,50,0,205
FILEVERSION 1,50,0,206
PRODUCTVERSION 1,50,0,206
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,12 +43,12 @@ BEGIN
BEGIN
VALUE "Comments", "http://www.zezula.net/casc.html"
VALUE "FileDescription", "CascLib library for reading Blizzard CASC storages"
VALUE "FileVersion", "1, 50, 0, 205"
VALUE "FileVersion", "1, 50, 0, 206\0"
VALUE "InternalName", "CascLib"
VALUE "LegalCopyright", "Copyright (c) 2014 - 2021 Ladislav Zezula"
VALUE "OriginalFilename", "CascLib.dll"
VALUE "ProductName", "CascLib"
VALUE "ProductVersion", "1, 50, 0, 205"
VALUE "ProductVersion", "1, 50, 0, 206\0"
END
END
BLOCK "VarFileInfo"

View File

@@ -211,7 +211,7 @@ class CASC_ARRAY
while (ItemCountMax < NewItemCount)
ItemCountMax = ItemCountMax << 1;
// Allocate new table
// Allocate new table. If this fails, the 'm_pItemArray' remains valid
NewItemArray = CASC_REALLOC(m_pItemArray, (ItemCountMax * m_ItemSize));
if(NewItemArray == NULL)
return false;

View File

@@ -207,21 +207,6 @@ LPBYTE CaptureEncodedKey(LPBYTE pbEKey, LPBYTE pbData, BYTE EKeyLength)
return pbData + EKeyLength;
}
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
@@ -598,7 +583,7 @@ bool CascIsValidMD5(LPBYTE pbMd5)
return (Int32Array[0] | Int32Array[1] | Int32Array[2] | Int32Array[3]) ? true : false;
}
bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
bool CascVerifyDataBlockHash(void * pvDataBlock, size_t cbDataBlock, LPBYTE expected_md5)
{
MD5_CTX md5_ctx;
BYTE md5_digest[MD5_HASH_SIZE];
@@ -609,18 +594,27 @@ bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expec
// Calculate the MD5 of the data block
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock);
MD5_Update(&md5_ctx, pvDataBlock, (unsigned long)(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)
void CascHash_MD5(const void * pvDataBlock, size_t cbDataBlock, LPBYTE md5_hash)
{
MD5_CTX md5_ctx;
MD5_Init(&md5_ctx);
MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock);
MD5_Update(&md5_ctx, pvDataBlock, (unsigned long)(cbDataBlock));
MD5_Final(md5_hash, &md5_ctx);
}
void CascHash_SHA1(const void * pvDataBlock, size_t cbDataBlock, LPBYTE sha1_hash)
{
SHA1_CTX sha1_ctx;
SHA1_Init(&sha1_ctx);
SHA1_Update(&sha1_ctx, pvDataBlock, (u32)(cbDataBlock));
SHA1_Final(&sha1_ctx, sha1_hash);
}

View File

@@ -59,18 +59,19 @@ typedef struct _CASC_EKEY_ENTRY
// 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_IN_BUILD 0x00000040 // Present in the BUILD (text) manifest
#define CASC_CE_IN_ARCHIVE 0x00000080 // File is stored in an archive (for online storages)
#define CASC_CE_FOLDER_ENTRY 0x00000100 // This CKey entry is a folder
#define CASC_CE_FILE_SPAN 0x00000200 // This CKey entry is a follow-up file span
#define CASC_CE_FILE_PATCH 0x00000400 // The file is in PATCH subfolder in remote storage
#define CASC_CE_PLAIN_DATA 0x00000800 // The file data is not BLTE encoded, but in plain format
#define CASC_CE_FILE_IS_LOCAL 0x0001 // The file is available locally. Keep this flag to have value of 1
#define CASC_CE_HAS_CKEY 0x0002 // The CKey is present in the entry
#define CASC_CE_HAS_EKEY 0x0004 // The EKey is present, at least partial one
#define CASC_CE_HAS_EKEY_PARTIAL 0x0008 // The EKey is only partial, padded by zeros. Always used with CASC_CE_HAS_EKEY
#define CASC_CE_IN_ENCODING 0x0010 // Present in the ENCODING manifest
#define CASC_CE_IN_DOWNLOAD 0x0020 // Present in the DOWNLOAD manifest
#define CASC_CE_IN_BUILD 0x0040 // Present in the BUILD (text) manifest
#define CASC_CE_IN_ARCHIVE 0x0080 // File is stored in an archive (for online storages)
#define CASC_CE_FOLDER_ENTRY 0x0100 // This CKey entry is a folder
#define CASC_CE_FILE_SPAN 0x0200 // This CKey entry is a follow-up file span
#define CASC_CE_FILE_PATCH 0x0400 // The file is in PATCH subfolder in remote storage
#define CASC_CE_PLAIN_DATA 0x0800 // The file data is not BLTE encoded, but in plain format
#define CASC_CE_OPEN_CKEY_ONCE 0x1000 // Used by CascLib test program - only opens a file with given CKey once, regardless on how many file names does it have
// In-memory representation of a single entry.
struct CASC_CKEY_ENTRY
@@ -112,10 +113,10 @@ struct CASC_CKEY_ENTRY
ULONGLONG TagBitMask; // Bitmap for the tags. 0 ig tags are not supported
DWORD ContentSize; // Content size of the file
DWORD EncodedSize; // Encoded size of the file
DWORD Flags; // See CASC_CE_XXX
USHORT RefCount; // This is the number of file names referencing this entry
DWORD RefCount; // This is the number of file names referencing this entry
USHORT Flags; // See CASC_CE_XXX
BYTE SpanCount; // Number of spans for the file
BYTE Priority; // Download priority
BYTE Priority; // Number of spans for the file
};
typedef CASC_CKEY_ENTRY *PCASC_CKEY_ENTRY;
@@ -143,12 +144,9 @@ extern unsigned char IntToHexChar[];
template <typename T>
T * CASC_REALLOC(T * old_ptr, size_t count)
{
T * new_ptr = (T *)realloc(old_ptr, count * sizeof(T));
// If realloc fails, then the old buffer remains unfreed
if(new_ptr == NULL)
free(old_ptr);
return new_ptr;
// Note: If realloc fails, then the old buffer remains unfreed!
// The caller needs to handle this
return (T *)realloc(old_ptr, count * sizeof(T));
}
template <typename T>
@@ -310,9 +308,43 @@ 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 CaptureEncodedKey(LPBYTE pbEKey, LPBYTE pbData, BYTE EKeyLength);
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)
template <typename STRUCTURE>
LPBYTE CaptureStructure(LPBYTE pbDataPtr, LPBYTE pbDataEnd, STRUCTURE ** lpStructure)
{
if((pbDataPtr + sizeof(STRUCTURE)) <= pbDataEnd)
{
lpStructure[0] = (STRUCTURE *)(pbDataPtr);
return pbDataPtr + sizeof(STRUCTURE);
}
return NULL;
}
template <typename STRUCTURE>
LPBYTE CaptureArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, STRUCTURE ** PtrArray, size_t nCount)
{
size_t nTotalSize = nCount * sizeof(STRUCTURE);
if((pbDataPtr + nTotalSize) <= pbDataEnd)
{
PtrArray[0] = (STRUCTURE *)(pbDataPtr);
return pbDataPtr + nTotalSize;
}
return NULL;
}
template <typename STRUCTURE>
LPBYTE CaptureArrayAsByte(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t nCount)
{
size_t nTotalSize = nCount * sizeof(STRUCTURE);
if((pbDataPtr + nTotalSize) <= pbDataEnd)
{
PtrArray[0] = (LPBYTE)(pbDataPtr);
return pbDataPtr + nTotalSize;
}
return NULL;
}
//-----------------------------------------------------------------------------
// String copying and conversion
@@ -571,8 +603,9 @@ bool CascCheckWildCard(const char * szString, const char * szWildCard);
// Hashing functions
bool CascIsValidMD5(LPBYTE pbMd5);
void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash);
bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5);
void CascHash_MD5(const void * pvDataBlock, size_t cbDataBlock, LPBYTE md5_hash);
void CascHash_SHA1(const void * pvDataBlock, size_t cbDataBlock, LPBYTE sha1_hash);
bool CascVerifyDataBlockHash(void * pvDataBlock, size_t cbDataBlock, LPBYTE expected_md5);
//-----------------------------------------------------------------------------
// Argument structure versioning

View File

@@ -87,7 +87,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertNew(PCASC_CKEY_ENTRY pCKeyEntry)
// Don't insert the node into any of the arrays here.
// That is the caller's responsibility
}
return pFileNode;
}
@@ -123,7 +122,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertNew()
}
}
}
return pFileNode;
}
@@ -162,7 +160,6 @@ bool CASC_FILE_TREE::InsertToIdTable(PCASC_FILE_NODE pFileNode)
}
}
}
return false;
}
@@ -183,7 +180,6 @@ bool CASC_FILE_TREE::SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * sz
pFileNode->NameLength = (USHORT)nLength;
return true;
}
return false;
}
@@ -233,7 +229,6 @@ bool CASC_FILE_TREE::RebuildNameMaps()
InsertToIdTable(pFileNode);
}
}
return true;
}
@@ -344,6 +339,7 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const
{
// Supply the name hash
pFileNode->FileNameHash = FileNameHash;
//bNewNodeInserted = true;
// Set the file data id and the extra values
SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags);
@@ -358,8 +354,7 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const
SetNodeFileName(pFileNode, szFileName);
// If we created a new node, we need to increment the reference count
assert(pCKeyEntry->RefCount != 0xFFFF);
//bNewNodeInserted = true;
assert(pCKeyEntry->RefCount < 0xFFFFFFFF);
pCKeyEntry->RefCount++;
FileNodes++;
}
@@ -392,7 +387,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGL
// Insert the file node to the hash map
InsertToNameMap(pFileNode);
}
return pFileNode;
}
@@ -422,8 +416,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD Fi
pCKeyEntry->RefCount++;
}
}
// Return the new or old node
return pFileNode;
}
@@ -443,7 +435,7 @@ PCASC_FILE_NODE CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, size_t
RefFileNode = (PCASC_FILE_NODE *)FileDataIds.ItemAt(nItemIndex);
if(RefFileNode != NULL)
{
pFileNode = *(PCASC_FILE_NODE *)FileDataIds.ItemAt(nItemIndex);
pFileNode = RefFileNode[0];
}
}
else
@@ -451,7 +443,7 @@ PCASC_FILE_NODE CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, size_t
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex);
}
// Construct the entire path
// Construct the full path
PathAt(szBuffer, cchBuffer, pFileNode);
return pFileNode;
}
@@ -522,7 +514,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::Find(const char * szFullPath, DWORD FileDataId,
{
GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags);
}
return pFileNode;
}
@@ -539,7 +530,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::Find(PCASC_CKEY_ENTRY pCKeyEntry)
return pFileNode;
}
}
return NULL;
}
@@ -562,7 +552,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::FindById(DWORD FileDataId)
pFileNode = RefElement[0];
}
}
return pFileNode;
}

View File

@@ -19,7 +19,7 @@
#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.
#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

View File

@@ -15,7 +15,6 @@
// 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);
@@ -36,7 +35,7 @@ typedef enum _KEY_TYPE
inline DWORD CalcHashValue_Hash(void * pvKey, size_t /* nKeyLength */)
{
// Get the hash directly as value
return ConvertBytesToInteger_4((LPBYTE)pvKey);
return ConvertBytesToInteger_4_LE((LPBYTE)pvKey);
}
// Calculates hash value from a key
@@ -328,20 +327,22 @@ class CASC_MAP
size_t GetNearestPowerOfTwo(size_t MaxItems)
{
size_t PowerOfTwo;
size_t PowerOfTwo = MIN_HASH_TABLE_SIZE;
// 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)
while(PowerOfTwo < MaxItems)
{
if(PowerOfTwo > MaxItems)
// Overflow check
if((PowerOfTwo << 1) < PowerOfTwo)
{
return PowerOfTwo;
assert(false);
return 0;
}
}
// If the hash table is too big, we cannot create the map
assert(false);
return 0;
// Shift the value
PowerOfTwo <<= 1;
}
return PowerOfTwo;
}
PFNHASHFUNC PfnCalcHashValue;

View File

@@ -80,7 +80,7 @@ PCASC_CKEY_ENTRY TFileTreeRoot::Search(TCascSearch * pSearch, PCASC_FIND_DATA pF
//BREAKIF(pSearch->nFileIndex >= 2823765);
// Retrieve the file item
pFileNode = FileTree.PathAt(pFindData->szFileName, MAX_PATH, pSearch->nFileIndex++);
pFileNode = FileTree.PathAt(pFindData->szFileName, _countof(pFindData->szFileName), pSearch->nFileIndex++);
if(pFileNode != NULL)
{
// Ignore folders, but report mount points. These can and should be able to open and read

View File

@@ -17,7 +17,7 @@
#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 CASC_WOW_ROOT_SIGNATURE 0x4D465354 // 'TSFM', since WoW build 30080 (8.2.0)
#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file
#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file

View File

@@ -47,6 +47,7 @@ static HANDLE inline SocketToHandle(SOCKET sock)
// Guarantees that there is zero terminator after the response
char * CASC_SOCKET::ReadResponse(const char * request, size_t request_length, CASC_MIME_RESPONSE & MimeResponse)
{
char * new_server_response = NULL;
char * server_response = NULL;
size_t total_received = 0;
size_t buffer_length = BUFFER_INITIAL_SIZE;
@@ -83,12 +84,16 @@ char * CASC_SOCKET::ReadResponse(const char * request, size_t request_length, CA
// Reallocate the buffer size, if needed
if(total_received == buffer_length)
{
// Reallocate the buffer
if((server_response = CASC_REALLOC(server_response, buffer_length + buffer_delta + 1)) == NULL)
// Reallocate the buffer. Note that if this fails, the old buffer remains valid
if((new_server_response = CASC_REALLOC(server_response, buffer_length + buffer_delta + 1)) == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
CASC_FREE(server_response);
break;
}
// Setup the new buffer
server_response = new_server_response;
buffer_length += buffer_delta;
buffer_delta = BUFFER_INITIAL_SIZE;
}

View File

@@ -0,0 +1,214 @@
/*
* SHA1 hash implementation and interface functions
* Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Alternatively, this software may be distributed under the terms of BSD
* license.
*
* See README and COPYING for more details.
*/
#include "sha1.h"
/* ===== start - public domain SHA1 implementation ===== */
/*
SHA-1 in C
By Steve Reid <sreid@sea-to-sky.net>
100% Public Domain
-----------------
Modified 7/98
By James H. Brown <jbrown@burgoyne.com>
Still 100% Public Domain
Corrected a problem which generated improper hash values on 16 bit machines
Routine SHA1Update changed from
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
len)
to
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
long len)
The 'len' parameter was declared an int which works fine on 32 bit machines.
However, on 16 bit machines an int is too small for the shifts being done
against
it. This caused the hash function to generate incorrect values if len was
greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
Since the file IO in main() reads 16K at a time, any file 8K or larger would
be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
"a"s).
I also changed the declaration of variables i & j in SHA1Update to
unsigned long from unsigned int for the same reason.
These changes should make no difference to any 32 bit implementations since
an
int and a long are the same size in those environments.
--
I also corrected a few compiler warnings generated by Borland C.
1. Added #include <process.h> for exit() prototype
2. Removed unused variable 'j' in SHA1Final
3. Changed exit(0) to return(0) at end of main.
ALL changes I made can be located by searching for comments containing 'JHB'
-----------------
Modified 8/98
By Steve Reid <sreid@sea-to-sky.net>
Still 100% public domain
1- Removed #include <process.h> and used return() instead of exit()
2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
-----------------
Modified 4/01
By Saul Kravitz <Saul.Kravitz@celera.com>
Still 100% PD
Modified to run on Compaq Alpha hardware.
-----------------
Modified 4/01
By Jouni Malinen <j@w1.fi>
Minor changes to match the coding style used in Dynamics.
Modified September 24, 2004
By Jouni Malinen <j@w1.fi>
Fixed alignment issue in SHA1Transform when SHA1HANDSOFF is defined.
*/
/*
Test Vectors (from FIPS PUB 180-1)
"abc"
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#ifndef WORDS_BIGENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \
(rol(block->l[i], 8) & 0x00FF00FF))
#else
#define blk0(i) block->l[i]
#endif
#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \
block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v,w,x,y,z,i) \
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R1(v,w,x,y,z,i) \
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R2(v,w,x,y,z,i) \
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); w = rol(w, 30);
#define R3(v,w,x,y,z,i) \
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
w = rol(w, 30);
#define R4(v,w,x,y,z,i) \
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
w=rol(w, 30);
/* Hash a single 512-bit block. This is the core of the algorithm. */
static void SHA1Transform(u32 state[5], const unsigned char buffer[64])
{
u32 a, b, c, d, e;
typedef union {
unsigned char c[64];
u32 l[16];
} CHAR64LONG16;
CHAR64LONG16 * block = (CHAR64LONG16 *) buffer;
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
/* Wipe variables */
a = b = c = d = e = 0;
}
/* SHA1Init - Initialize new context */
void SHA1_Init(SHA1_CTX * context)
{
/* SHA1 initialization constants */
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
/* Run your data through this. */
void SHA1_Update(SHA1_CTX * context, const void *_data, u32 len)
{
u32 i, j;
const unsigned char * data = (const unsigned char *)(_data);
j = (context->count[0] >> 3) & 63;
if ((context->count[0] += len << 3) < (len << 3))
context->count[1]++;
context->count[1] += (len >> 29);
if ((j + len) > 63) {
memcpy(&context->buffer[j], data, (i = 64-j));
SHA1Transform(context->state, context->buffer);
for ( ; i + 63 < len; i += 64) {
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
}
/* Add padding and return the message digest. */
void SHA1_Final(SHA1_CTX * context, unsigned char digest[20])
{
u32 i;
unsigned char finalcount[8];
for (i = 0; i < 8; i++) {
finalcount[i] = (unsigned char)
((context->count[(i >= 4 ? 0 : 1)] >>
((3-(i & 3)) * 8) ) & 255); /* Endian independent */
}
SHA1_Update(context, (unsigned char *) "\200", 1);
while ((context->count[0] & 504) != 448) {
SHA1_Update(context, (unsigned char *) "\0", 1);
}
SHA1_Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
for (i = 0; i < 20; i++)
{
digest[i] = (unsigned char)
((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) &
255);
}
}
/* ===== end - public domain SHA1 implementation ===== */

View File

@@ -0,0 +1,34 @@
/*
* SHA1 hash implementation and interface functions
* Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Alternatively, this software may be distributed under the terms of BSD
* license.
*
* See README and COPYING for more details.
*/
#ifndef _SHA1_H
#define _SHA1_H
#include <string.h>
/* Any 32-bit or wider unsigned integer data type will do */
typedef unsigned int u32;
typedef struct _SHA1_CTX
{
u32 state[5];
u32 count[2];
unsigned char buffer[64];
} SHA1_CTX;
void SHA1_Init (SHA1_CTX * context);
void SHA1_Update(SHA1_CTX * context, const void * data, u32 len);
void SHA1_Final (SHA1_CTX * context, unsigned char digest[20]);
#endif /* _SHA1_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2016 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the OpenSSL license (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef HEADER_AES_H
# define HEADER_AES_H
# include <stddef.h>
# ifdef __cplusplus
extern "C" {
# endif
# define AES_ENCRYPT 1
# define AES_DECRYPT 0
/*
* Because array size can't be a const in C, the following two are macros.
* Both sizes are in bytes.
*/
# define AES_MAXNR 14
# define AES_BLOCK_SIZE 16
typedef unsigned char u8;
typedef unsigned int u32;
/* This should be a hidden type, but EVP requires that the size be known */
struct aes_key_st {
# ifdef AES_LONG
unsigned long rd_key[4 * (AES_MAXNR + 1)];
# else
unsigned int rd_key[4 * (AES_MAXNR + 1)];
# endif
int rounds;
};
typedef struct aes_key_st AES_KEY;
int AES_set_encrypt_key(const unsigned char * userKey, const int bits, AES_KEY * key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
void AES_cbc_decrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec);
# ifdef __cplusplus
}
# endif
#endif

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the OpenSSL license (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef OSSL_CRYPTO_AES_LOCAL_H
# define OSSL_CRYPTO_AES_LOCAL_H
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64))
# define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00)
# define GETU32(p) SWAP(*((u32 *)(p)))
# define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); }
# else
# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3]))
# define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); }
# endif
typedef unsigned long long u64;
# ifdef AES_LONG
typedef unsigned long u32;
# else
typedef unsigned int u32;
# endif
typedef unsigned short u16;
typedef unsigned char u8;
# define MAXKC (256/32)
# define MAXKB (256/8)
# define MAXNR 14
/* This controls loop-unrolling in aes_core.c */
# undef FULL_UNROLL
#endif /* !OSSL_CRYPTO_AES_LOCAL_H */

View File

@@ -0,0 +1,154 @@
/*****************************************************************************/
/* apm.cpp Copyright (c) Ladislav Zezula 2023 */
/*---------------------------------------------------------------------------*/
/* Support for Application Package Manifest (.apm) */
/* Know-how from https://github.com/overtools/TACTLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 02.08.23 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
#include "overwatch.h"
//-----------------------------------------------------------------------------
// Local functions
template <typename APM_ENTRY>
static bool IsContinuousArray(APM_ENTRY * pEntry, size_t nCount)
{
// Must be at last 5 entries
if(nCount-- >= 5)
{
for(size_t i = 0; i < nCount; i++, pEntry++)
{
// n+1-th index must be greater than n-th index
if(pEntry[1].Index > pEntry[0].Index + 10)
{
return false;
}
}
return true;
}
return false;
}
static LPBYTE CaptureApmHeader(TCascStorage * hs, LPBYTE pbDataPtr, LPBYTE pbDataEnd, CASC_APM_HEADER & ApmHeader)
{
// Build 47161+
PCASC_APM_HEADER_V2 pHeader_V3 = NULL;
if(CaptureStructure<CASC_APM_HEADER_V2>(pbDataPtr, pbDataEnd, &pHeader_V3) != NULL)
{
if(pHeader_V3->BuildNumber == hs->dwBuildNumber &&
pHeader_V3->ZeroValue1 == 0 &&
pHeader_V3->ZeroValue2 == 0 &&
pHeader_V3->ZeroValue3 == 0)
{
ApmHeader.BuildNumber = pHeader_V3->BuildNumber;
ApmHeader.PackageCount = pHeader_V3->PackageCount;
ApmHeader.PackageCount = pHeader_V3->PackageCount;
ApmHeader.EntryCount = pHeader_V3->EntryCount;
ApmHeader.HeaderMagic = pHeader_V3->HeaderMagic;
return pbDataPtr + sizeof(CASC_APM_HEADER_V2);
}
}
// Build 24919
PCASC_APM_HEADER_V1 pHeader_V1 = NULL;
if(CaptureStructure<CASC_APM_HEADER_V1>(pbDataPtr, pbDataEnd, &pHeader_V1) != NULL)
{
if((pHeader_V1->HeaderMagic & 0x00FFFFFF) == CASC_APM_HEADER_MAGIC)
{
ApmHeader.BuildNumber = pHeader_V1->BuildNumber;
ApmHeader.PackageCount = pHeader_V1->PackageCount;
ApmHeader.PackageCount = pHeader_V1->PackageCount;
ApmHeader.EntryCount = pHeader_V1->EntryCount;
ApmHeader.HeaderMagic = pHeader_V1->HeaderMagic;
return pbDataPtr + sizeof(CASC_APM_HEADER_V1);
}
}
return NULL;
}
static LPBYTE SkipApmEntries(TCascStorage * hs, LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nCount)
{
PCASC_APM_ENTRY_V2 pApmEntries_V2 = NULL;
PCASC_APM_ENTRY_V1 pApmEntries_V1 = NULL;
LPBYTE pbSavePtr = pbDataPtr;
// Keep compiler happy
CASCLIB_UNUSED(hs);
// Try APM entries v2 (example: Overwatch build 47161)
if((pbDataPtr = CaptureArray(pbSavePtr, pbDataEnd, &pApmEntries_V2, nCount)) != NULL)
{
if(IsContinuousArray(pApmEntries_V2, nCount))
{
return pbDataPtr;
}
}
// Try APM entries v1 (example: Overwatch build 24919)
if((pbDataPtr = CaptureArray(pbSavePtr, pbDataEnd, &pApmEntries_V1, nCount)) != NULL)
{
if(IsContinuousArray(pApmEntries_V1, nCount))
{
return pbDataPtr;
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Public functions
DWORD LoadApplicationPackageManifestFile(TCascStorage * hs, CASC_FILE_TREE & FileTree, PCASC_CKEY_ENTRY pCKeyEntry, const char * szApmFileName)
{
CASC_BLOB ApmFile;
const char * szApmPlainName = GetPlainFileName(szApmFileName);
DWORD dwErrCode;
// Load the entire internal file to memory
if((dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, ApmFile)) == ERROR_SUCCESS)
{
PCASC_APM_PACKAGE_ENTRY_V1 pEntries;
CASC_APM_HEADER ApmHeader = {0};
LPBYTE pbDataEnd = ApmFile.pbData + ApmFile.cbData;
LPBYTE pbDataPtr = ApmFile.pbData;
size_t nPlainName;
char szFileName[MAX_PATH];
// Capture the header
if((pbDataPtr = CaptureApmHeader(hs, pbDataPtr, pbDataEnd, ApmHeader)) == NULL)
return ERROR_BAD_FORMAT;
// Skip the array of APM_ENTRYs. We use heuristics to determine which APM entry is there
if((pbDataPtr = SkipApmEntries(hs, pbDataPtr, pbDataEnd, ApmHeader.EntryCount)) == NULL)
return ERROR_BAD_FORMAT;
// Get the array of APM package entries. Only take those with CKey
if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pEntries, ApmHeader.PackageCount)) != NULL)
{
// The array must fill the whole file without any leftover
if(pbDataPtr == pbDataEnd && ApmHeader.PackageCount > 1)
{
// Check the first two entries - if their CKey is OK, then we consider them valid
if(FindCKeyEntry_CKey(hs, pEntries[0].CKey) != NULL && FindCKeyEntry_CKey(hs, pEntries[1].CKey) != NULL)
{
// Create the name template of the assets
nPlainName = BuildAssetFileNameTemplate(szFileName,
_countof(szFileName),
"AppPackageManifests",
szApmPlainName);
dwErrCode = InsertAssetFiles(hs, FileTree, szFileName, nPlainName, pEntries, ApmHeader.PackageCount);
}
}
}
}
return dwErrCode;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,482 @@
#!/usr/bin/env python3
import re, os, shutil, stat, subprocess
TACTLIB_GITHUB_REPOSITORY = "https://github.com/overtools/TACTLib"
TACTLIB_ROOT_SUBDIRECTORY = "./TACTLib"
TACTLIB_CMF_SUBDIRECTORY = os.path.join(TACTLIB_ROOT_SUBDIRECTORY, "TACTLib/Core/Product/Tank/CMF")
CPP_BLOCK_INDENT = " "
def create_file_backup(file_name):
try:
file_name_backup = file_name + ".bak"
backup_index = 1
while os.path.isfile(file_name_backup):
file_name_backup = file_name + ".b%02u" % backup_index
backup_index += 1
shutil.copyfile(file_name, file_name_backup)
except Exception as e:
return False
return True
def delete_directory(folder_name):
try:
for fs_item in os.listdir(folder_name):
full_path = os.path.join(folder_name, fs_item)
if os.path.isdir(full_path):
delete_directory(full_path)
else:
os.chmod(full_path, stat.S_IWRITE)
os.remove(full_path)
os.rmdir(folder_name)
except Exception as e:
pass
def get_file_build_number(plain_name):
try:
string_index1 = plain_name.find("_")
if string_index1 == -1:
return 0
string_index1 += 1
string_index2 = plain_name.find(".")
if string_index2 < string_index1:
return 0
return int(plain_name[string_index1:string_index2])
except:
pass
return 0
def load_build_number_list(folder_name):
# Create the list of available build numbers
build_number_list = []
# Enumerate files in that folder and scan all files with names like "ProCMF_######.cs"
for plain_name in os.listdir(folder_name):
# Retrieve the build number from that file name
build_number = get_file_build_number(plain_name)
if not build_number:
continue
# Append the build number to the list
build_number_list.append(build_number)
# Make it sorted
build_number_list.sort()
return build_number_list
def append_token(line_buffer, token):
if len(line_buffer):
line_buffer = line_buffer + " "
return line_buffer + token
def flush_line(cmf_cpp, line_buffer, closing_token, nest_level, nest_increment):
# Setup the indent line
indent_line = CPP_BLOCK_INDENT * nest_level
# If there is some remaining items in the single line, flush it to the stream
if len(line_buffer) != 0:
cmf_cpp.append(indent_line + line_buffer)
line_buffer = ""
# Opening a new block?
if nest_increment == +1:
cmf_cpp.append(indent_line + closing_token)
return "", nest_level + 1
# Closing a block?
if nest_increment == -1:
indent_line = CPP_BLOCK_INDENT * (nest_level - 1)
cmf_cpp.append(indent_line + closing_token)
return "", nest_level - 1
# Move the nest level
return "", nest_level
def append_hex_array(cmf_cpp, header_line, key_table_tokens, nest_level):
# Initialization
inside_block = False
indent_line = CPP_BLOCK_INDENT * nest_level
line_buffer = ""
hexa_values = 0
# Append the heading line
cmf_cpp.append(indent_line + header_line)
# Parse the tokens
for token in key_table_tokens:
# Skip spaces
if token == "":
continue
# Block opening
if token == "{" and inside_block == False:
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, "{", nest_level, +1)
inside_block = True
hexa_values = 0
continue
# Block closing
if token == "};" and inside_block == True:
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, "};", nest_level, -1)
inside_block = False
break
# A hexa value
if token.startswith("0x") or token.startswith("0X"):
if hexa_values >= 16:
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, None, nest_level, 0)
hexa_values = 0
line_buffer = append_token(line_buffer, token)
hexa_values += 1
continue
# An unexpected token
print("[x] Unexpected token: " + token)
# The line should be empty here
assert len(line_buffer) == 0, f"Unexpected remained in the single line: {line_buffer}"
assert nest_level == 1, f"Unexpected nest level: {nest_level}"
return
def append_cpp_function(cmf_cpp, header_line, key_table_tokens, nest_level):
# Initialization
indent_line = CPP_BLOCK_INDENT * nest_level
skipping_buffer_allocation = True
save_nest_level = nest_level
skipping_definition = True
inside_for_header = False
inside_case_label = False
line_buffer = ""
# Append the heading line
cmf_cpp.append(indent_line + header_line)
# Parse the tokens
for token in key_table_tokens:
# Skip spaces
if token == "":
continue
# Skip the function definition
if skipping_definition:
if token.endswith(")"):
skipping_definition = False
continue
# Opening brace
if token == "{":
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, "{", nest_level, +1)
continue
# Closing brace
if token == "}":
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, "}", nest_level, -1)
if nest_level == save_nest_level:
break
continue
# Skipping the buffer declaration
if skipping_buffer_allocation:
# Append the token to the line
if len(line_buffer) == 0:
line_buffer = append_token(line_buffer, "//")
line_buffer = append_token(line_buffer, token)
if token.endswith(";"):
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, None, nest_level, 0)
skipping_buffer_allocation = False
continue
# Anything else - append to the line buffer
line_buffer = append_token(line_buffer, token)
# Handle the begin and end of the "case" label
if token == "case":
inside_case_label = True
if inside_case_label and token.endswith(":"):
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, "", nest_level, +1)
continue
if inside_case_label and token == "break;":
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, "", nest_level, -1)
continue
# Handle the begin and end of the "for" header
if token == "for" or token.startswith("for("):
inside_for_header = True
cmf_cpp.append("")
if inside_for_header and token.endswith(")"):
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, None, nest_level, 0)
inside_for_header = False
continue
# End of line
if token.endswith(";"):
if inside_for_header == False:
line_buffer, nest_level = flush_line(cmf_cpp, line_buffer, None, nest_level, 0)
continue
return
def build_cmf_cpp(file_content_cs, key_table, key_function, iv_function, build_number):
cmf_cpp = []
# Build the header
cmf_cpp.append("//")
cmf_cpp.append("// Key+IV provider for build %u. Created automatically, DO NOT EDIT." % build_number)
cmf_cpp.append("// Source: .\TACTLib\TACTLib\Core\Product\Tank\CMF\ProCMF_%s.cs" % build_number)
cmf_cpp.append("//\n")
# Append the begin of the namespace
cmf_cpp.append("namespace KeyCMF_%06u" % build_number)
cmf_cpp.append("{")
# Append the key table
key_table_tokens = re.split(r"\s+", file_content_cs[key_table.end():])
append_hex_array(cmf_cpp, "static const BYTE Keytable[] =", key_table_tokens, 1)
cmf_cpp.append("")
# Append the key generation function
key_table_tokens = re.split(r"\s+", file_content_cs[key_function.end():])
append_cpp_function(cmf_cpp, "LPBYTE Key(const CASC_CMF_HEADER & header, LPBYTE buffer, int length)", key_table_tokens, 1)
cmf_cpp.append("")
# Append the IV generation function
iv_table_tokens = re.split(r"\s+", file_content_cs[iv_function.end():])
append_cpp_function(cmf_cpp, "LPBYTE IV(const CASC_CMF_HEADER & header, LPBYTE digest, LPBYTE buffer, int length)", iv_table_tokens, 1)
cmf_cpp.append("}")
return cmf_cpp
def convert_cs_to_cpp_cmf(source_file, target_file, build_number):
# Load the content of the file to memory
try:
file_content = None
with open(source_file, "rt") as f:
file_content_cs = f.read()
except Exception as e:
return False
# Locate the KeyTable, Key() and IV() procedures
try:
search_regexp = r"private\s+static\s+readonly\s+byte\s*\[\] +Keytable +="
key_table = re.search(search_regexp, file_content_cs, re.I)
if key_table is None:
print("\n[x] Failed to find the key table")
return 0
except Exception as e:
return False
# Locate the function for the key generation
try:
search_regexp = r"public\s+byte\s*\[\]\s*Key\s*"
key_function = re.search(search_regexp, file_content_cs, re.I)
if key_function is None:
print("\n[x] Failed to find the Key() function")
return 0
except Exception as e:
return False
# Locate the function for the IV generation
try:
search_regexp = r"public\s+byte\s*\[\]\s*IV\s*"
iv_function = re.search(search_regexp, file_content_cs, re.I)
if iv_function is None:
print("\n[x] Failed to find the IV() function")
return 0
except Exception as e:
return False
# Generate the content of the C++ file
try:
file_content_cpp = build_cmf_cpp(file_content_cs, key_table, key_function, iv_function, build_number)
if file_content_cpp is None:
print("\n[x] Failed to build the CPP file")
return 0
except Exception as e:
return False
# Write the content of the file
try:
target_file.writelines(single_line + "\n" for single_line in file_content_cpp)
target_file.write("\n")
except Exception as e:
return 0
return 1
def create_cmf_key_cpp(file_name):
# Create backup of the file
if not create_file_backup(file_name):
return False
# Create the file
try:
file = open(file_name, "wt")
except Exception as e:
return False
# Write the initial comment
file.write("//\n")
file.write("// This file was converted from the sources of TACTLib. DO NOT EDIT.\n")
file.write("// Source: https://github.com/overtools/TACTLib\n")
file.write("//\n\n")
return file
def download_TACTLib_repository():
try:
# Show what we're doing
print("[*] Downloading TACTLib ...")
# Run git clone
process = subprocess.Popen(["git", "clone", TACTLIB_GITHUB_REPOSITORY + ".git"], stderr=subprocess.PIPE, stdout=None)
process_output = process.communicate()[1].decode("ascii")
# Check for success
if process_output.startswith("Cloning into "):
return True
# Check for existing directory
if process_output.endswith("already exists and is not an empty directory.\n"):
return True
except subprocess.CalledProcessError as e:
pass
return False
def update_CascLib_repository():
try:
# Show what we're doing
print("[*] Updating git repository ...")
# Run git clone
process = subprocess.Popen(["git", "add", ".\cmf"], stderr=subprocess.PIPE, stdout=None)
process_output = process.communicate()[1].decode("ascii")
return True
except subprocess.CalledProcessError as e:
pass
return False
def check_TACTLib_repository(folder_name):
try:
print("[*] Checking the downloaded folder ...")
source_file_list = os.listdir(folder_name)
if len(source_file_list) != 0:
return True
except Exception as e:
pass
return False
def process_TACTLib_repository():
# Initialization
print("[*] Gathering build numbers ...")
folder_name = os.path.abspath(TACTLIB_CMF_SUBDIRECTORY)
build_number_list = load_build_number_list(folder_name)
# Create the new cmf-key.cpp
print("[*] Writing the source of providers ...")
target_file = create_cmf_key_cpp("cmf-key.cpp")
if target_file is None:
return False
# Write warning supression
target_file.write("// Supress warnings that may be raised by the converted C# code\n")
target_file.write("#ifdef _MSC_VER\n")
target_file.write("#pragma warning(push)\n")
target_file.write("#pragma warning(disable: 4100) // warning C4100: 'header': unreferenced formal parameter\n")
target_file.write("#pragma warning(disable: 4389) // warning C4389: '!=': signed/unsigned mismatch\n")
target_file.write("#endif // _MSC_VER\n\n")
# Convert every file in the directory
for build_number in build_number_list:
# Show the processed file name
plain_name = "ProCMF_%u.cs" % build_number
source_file = os.path.join(folder_name, plain_name)
print("[*] %s ... " % source_file, end="")
# Convert the content of the file into the cmf-key.cpp
if convert_cs_to_cpp_cmf(source_file, target_file, build_number) == 0:
break
print("(OK)")
# Write the table that contains the list of providers
print("[*] Writing the table of providers ...")
build_number_list.sort()
target_file.write("// Sorted list of Key+IV providers. DO NOT EDIT.\n")
target_file.write("static const CASC_CMF_KEY_PROVIDER CmfKeyProviders[] =\n")
target_file.write("{\n")
# Write the entries
for build_number in build_number_list:
target_file.write(" {%6u, KeyCMF_%06u::Key, KeyCMF_%06u::IV},\n" % (build_number, build_number, build_number))
target_file.write("};\n\n")
# Write the end of warnings
target_file.write("#ifdef _MSC_VER\n")
target_file.write("#pragma warning(pop)\n")
target_file.write("#endif // _MSC_VER\n")
# Write the tail
target_file.close()
return True
def perform_TACTLib_update():
# Download the content of the "TACTLib package"
if not download_TACTLib_repository():
print("[x] Failed to download the TACTLib library")
return
# Did we download something?
if not check_TACTLib_repository(TACTLIB_CMF_SUBDIRECTORY):
print("[x] It seems that the download failed")
return
# Create list of the supported game builds
if not process_TACTLib_repository():
print("[x] Failed to update the key providers")
return
if not update_CascLib_repository():
print("[x] Failed to update the git repository")
return
def perform_TACTLib_cleanup():
print("[*] Cleaning up")
folder_name = os.path.abspath(TACTLIB_ROOT_SUBDIRECTORY)
delete_directory(folder_name)
if __name__ == '__main__':
perform_TACTLib_update()
perform_TACTLib_cleanup()
print("[*] Complete")

View File

@@ -0,0 +1,221 @@
/*****************************************************************************/
/* cmf.cpp Copyright (c) Ladislav Zezula 2023 */
/*---------------------------------------------------------------------------*/
/* Support for Content Manifest Files (.cmf) */
/* Know-how from https://github.com/overtools/TACTLib */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 29.07.23 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
#include "aes.h"
#include "overwatch.h"
//-----------------------------------------------------------------------------
// Encryption key providers for CMF files. These are taken from TACTLib
// with the kind permission of the TACTLib authors
// (https://github.com/overtools/TACTLib)
// Key and IV provider functions
typedef LPBYTE(*GET_KEY)(const CASC_CMF_HEADER & Header, LPBYTE pbKey, int nLength);
typedef LPBYTE(*GET_IV)(const CASC_CMF_HEADER & Header, LPBYTE nameSha1, LPBYTE pbKey, int nLength);
// Structure for the single provider
typedef struct _CASC_CMF_KEY_PROVIDER
{
DWORD dwBuildNumber;
GET_KEY PfnGetKey;
GET_IV PfnGetIV;
} CASC_CMF_KEY_PROVIDER;
typedef const CASC_CMF_KEY_PROVIDER *PCASC_CMF_KEY_PROVIDER;
// Needed by various providers in the cmf-key.cpp file
struct TMath
{
template <typename TYPE>
TYPE Max(TYPE value1, TYPE value2)
{
return (value1 > value2) ? value1 : value2;
}
DWORD dwDummy;
} Math;
// Needed by various providers in the cmf-key.cpp file
static uint Constrain(LONGLONG value)
{
return (uint)(value % 0xFFFFFFFFULL);
}
// Needed by various providers in the cmf-key.cpp file
static int SignedMod(LONGLONG p1, LONGLONG p2)
{
int a = (int)p1;
int b = (int)p2;
return (a % b) < 0 ? (a % b + b) : (a % b);
}
// Include the CMF key provider functions and the table of providers
// This file is created by the "cmf-update.py" script, DO NOT EDIT.
#include "cmf-key.cpp"
//-----------------------------------------------------------------------------
// Local functions
static PCASC_CMF_KEY_PROVIDER FindCmfKeyProvider(DWORD dwBuildNumber)
{
PCASC_CMF_KEY_PROVIDER pStartEntry = CmfKeyProviders;
PCASC_CMF_KEY_PROVIDER pMidleEntry = NULL;
PCASC_CMF_KEY_PROVIDER pFinalEntry = &CmfKeyProviders[_countof(CmfKeyProviders)];
// Perform binary search on the table
while(pStartEntry < pFinalEntry)
{
// Calculate the middle of the interval
pMidleEntry = pStartEntry + ((pFinalEntry - pStartEntry) / 2);
// Did we find it?
if(dwBuildNumber == pMidleEntry->dwBuildNumber)
return pMidleEntry;
// Move the interval to the left or right
if(dwBuildNumber > pMidleEntry->dwBuildNumber)
pStartEntry = pMidleEntry + 1;
else
pFinalEntry = pMidleEntry;
}
/*
for(size_t i = 0; i < _countof(CmfKeyProviders); i++)
{
if(CmfKeyProviders[i].dwBuildNumber == dwBuildNumber)
{
return &CmfKeyProviders[i];
}
}
*/
return NULL;
}
static DWORD DecryptCmfStream(const CASC_CMF_HEADER & Header, const char * szPlainName, LPBYTE pbDataPtr, LPBYTE pbDataEnd)
{
PCASC_CMF_KEY_PROVIDER pKeyProvider;
AES_KEY AesKey;
BYTE RawKey[CASC_AES_KEY_LENGTH];
BYTE RawIV[CASC_AES_IV_LENGTH];
BYTE nameDigest[SHA1_HASH_SIZE];
// Find the provider for that Overwatch build
if((pKeyProvider = FindCmfKeyProvider(Header.m_buildVersion)) == NULL)
return ERROR_FILE_ENCRYPTED;
// Create SHA1 from the file name
CascHash_SHA1(szPlainName, strlen(szPlainName), nameDigest);
// Retrieve key and IV
pKeyProvider->PfnGetKey(Header, RawKey, sizeof(RawKey));
pKeyProvider->PfnGetIV(Header, nameDigest, RawIV, sizeof(RawIV));
// Decrypt the stream using AES
AES_set_decrypt_key(RawKey, 256, &AesKey);
AES_cbc_decrypt(pbDataPtr, pbDataPtr, (pbDataEnd - pbDataPtr), &AesKey, RawIV);
return ERROR_SUCCESS;
}
//-----------------------------------------------------------------------------
// Public functions
DWORD LoadContentManifestFile(TCascStorage * hs, CASC_FILE_TREE & FileTree, PCASC_CKEY_ENTRY pCKeyEntry, const char * szCmfFileName)
{
CASC_BLOB CmfFile;
const char * szCmfPlainName = GetPlainFileName(szCmfFileName);
DWORD dwErrCode;
// Load the entire internal file to memory
if((dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, CmfFile)) == ERROR_SUCCESS)
{
PCASC_APM_ENTRY_V2 pApmEntries = NULL;
CASC_CMF_HEADER CmfHeader = {0};
LPBYTE pbDataEnd = CmfFile.pbData + CmfFile.cbData;
LPBYTE pbDataPtr = CmfFile.pbData;
size_t nPlainName;
DWORD dwBuildVersion;
char szFileName[MAX_PATH];
// Get the build version
if((pbDataPtr = CaptureInteger32(pbDataPtr, pbDataEnd, &dwBuildVersion)) == NULL)
return ERROR_BAD_FORMAT;
pbDataPtr = CmfFile.pbData;
// Parse headers of various versions
if(dwBuildVersion > CASC_OVERWATCH_VERSION_148_PTR)
{
CASC_CMF_HEADER_148 * pHeader148;
if((pbDataPtr = CaptureStructure(pbDataPtr, pbDataEnd, &pHeader148)) == NULL)
return ERROR_BAD_FORMAT;
CmfHeader = *pHeader148;
}
else if(dwBuildVersion > CASC_OVERWATCH_VERSION_122_PTR)
{
CASC_CMF_HEADER_122 * pHeader122;
if((pbDataPtr = CaptureStructure(pbDataPtr, pbDataEnd, &pHeader122)) == NULL)
return ERROR_BAD_FORMAT;
CmfHeader = *pHeader122;
}
else
{
CASC_CMF_HEADER_100 * pHeader100;
if((pbDataPtr = CaptureStructure(pbDataPtr, pbDataEnd, &pHeader100)) == NULL)
return ERROR_BAD_FORMAT;
CmfHeader = *pHeader100;
}
// Decrypt the stream, if needed
if(CmfHeader.IsEncrypted())
{
if((dwErrCode = DecryptCmfStream(CmfHeader, szCmfPlainName, pbDataPtr, pbDataEnd)) != ERROR_SUCCESS)
{
return dwErrCode;
}
}
// Skip APM entries. We don't need them for anything, really
if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pApmEntries, CmfHeader.m_entryCount)) == NULL)
{
return ERROR_BAD_FORMAT;
}
// Create the name template of the assets
nPlainName = BuildAssetFileNameTemplate(szFileName,
_countof(szFileName),
"ContentManifestFiles",
szCmfPlainName);
// Load the hash list This is the list of Asset ID -> CKey
if(CmfHeader.m_buildVersion >= 57230)
{
PCASC_CMF_HASH_ENTRY_135 pHashList;
if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pHashList, CmfHeader.m_dataCount)) == NULL)
return ERROR_BAD_FORMAT;
dwErrCode = InsertAssetFiles(hs, FileTree, szFileName, nPlainName, pHashList, CmfHeader.m_dataCount);
}
else
{
PCASC_CMF_HASH_ENTRY_100 pHashList;
if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pHashList, CmfHeader.m_dataCount)) == NULL)
return ERROR_BAD_FORMAT;
dwErrCode = InsertAssetFiles(hs, FileTree, szFileName, nPlainName, pHashList, CmfHeader.m_dataCount);
}
}
return dwErrCode;
}

View File

@@ -0,0 +1,279 @@
/*****************************************************************************/
/* overwatch.h Copyright (c) Ladislav Zezula 2023 */
/*---------------------------------------------------------------------------*/
/* Definitions for overwatch on-disk structures */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 28.04.19 1.00 Lad The first version of CascStructs.h */
/*****************************************************************************/
#ifndef __CASC_OVERWATCH_H__
#define __CASC_OVERWATCH_H__
//-----------------------------------------------------------------------------
// Definitions
#define CASC_OVERWATCH_VERSION_122_PTR 47161
#define CASC_OVERWATCH_VERSION_148_PTR 68309
#define CASC_OVERWATCH_VERSION_152_PTR 72317
#define CASC_CMF_ENCRYPTED_MAGIC 0x636D66
#define CASC_AES_KEY_LENGTH 0x20
#define CASC_AES_IV_LENGTH 0x10
#define SHA1_DIGESTSIZE SHA1_HASH_SIZE // Used in cmf-key.cpp
//-----------------------------------------------------------------------------
// Data types used in cmf-key.cpp
typedef unsigned char byte;
typedef unsigned short ushort;
typedef unsigned int uint;
//-----------------------------------------------------------------------------
// Data structures related to Content Manifest Files (.cmf)
// 1.00+
struct CASC_CMF_HEADER_100
{
unsigned dwBuildVersion;
unsigned dwField04;
unsigned dwField08;
unsigned dwField10;
unsigned dwField14;
int nDataCount;
unsigned dwField1C;
int nEntryCount;
unsigned dwHeaderMagic;
};
// 1.22+
struct CASC_CMF_HEADER_122
{
unsigned dwBuildVersion;
unsigned dwField04;
unsigned dwField08;
unsigned dwField0C;
unsigned dwField10;
unsigned dwField14;
int nDataCount;
unsigned dwField1C;
int nEntryCount;
unsigned dwHeaderMagic;
};
// 1.48+
struct CASC_CMF_HEADER_148
{
unsigned dwBuildVersion;
unsigned dwField04;
unsigned dwField08;
unsigned dwField0C;
unsigned dwField10;
unsigned dwField14;
unsigned dwField18;
int nDataPatchRecordCount;
int nDataCount;
int nEntryPatchRecordCount;
int nEntryCount;
unsigned dwHeaderMagic;
};
// This structure has the members with the same names like CMFHeader in TACTLib
// This is because we reuse the C# code from that library as C++
struct CASC_CMF_HEADER
{
bool IsEncrypted() const
{
return ((m_magic >> 0x08) == CASC_CMF_ENCRYPTED_MAGIC);
}
byte GetVersion() const
{
return IsEncrypted() ? (byte)(m_magic & 0x000000FF) : (byte)((m_magic & 0xFF000000) >> 24);
}
uint GetNonEncryptedMagic() const
{
return (uint)(0x00666D63u | (GetVersion() << 24));
}
void operator = (const CASC_CMF_HEADER_100 & src)
{
memset(this, 0, sizeof(CASC_CMF_HEADER_148));
m_buildVersion = src.dwBuildVersion;
m_dataCount = src.nDataCount;
m_entryCount = src.nEntryCount;
m_magic = src.dwHeaderMagic;
}
void operator = (const CASC_CMF_HEADER_122 & src)
{
memset(this, 0, sizeof(CASC_CMF_HEADER_148));
m_buildVersion = src.dwBuildVersion;
m_dataCount = src.nDataCount;
m_entryCount = src.nEntryCount;
m_magic = src.dwHeaderMagic;
}
void operator = (const CASC_CMF_HEADER_148 & src)
{
// Copy 1:1
memcpy(this, &src, sizeof(CASC_CMF_HEADER_148));
}
uint m_buildVersion;
uint m_unk04;
uint m_unk08;
uint m_unk0C;
uint m_unk10;
uint m_unk14;
uint m_unk18;
int m_dataPatchRecordCount;
int m_dataCount;
int m_entryPatchRecordCount;
int m_entryCount;
uint m_magic;
};
typedef struct _CASC_CMF_HASH_ENTRY_100
{
BYTE GUID[8];
BYTE Size[4];
BYTE CKey[CASC_CKEY_SIZE];
} CASC_CMF_HASH_ENTRY_100, *PCASC_CMF_HASH_ENTRY_100;
typedef struct _CASC_CMF_HASH_ENTRY_135
{
BYTE GUID[8];
BYTE Size[4];
BYTE field_C;
BYTE CKey[CASC_CKEY_SIZE];
} CASC_CMF_HASH_ENTRY_135, *PCASC_CMF_HASH_ENTRY_135;
//-----------------------------------------------------------------------------
// Data structures related to Application Package Manifests (.apm)
#define CASC_APM_HEADER_MAGIC 0x00636D66
typedef struct _CASC_APM_HEADER_V1
{
ULONGLONG BuildVersion;
DWORD BuildNumber; // Build number of the game
DWORD PackageCount;
DWORD EntryCount;
DWORD HeaderMagic;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} CASC_APM_HEADER_V1, *PCASC_APM_HEADER_V1;
#pragma pack(push, 4)
typedef struct _CASC_APM_HEADER_V2
{
ULONGLONG BuildNumber; // Build number of the game
ULONGLONG ZeroValue1;
DWORD ZeroValue2;
DWORD PackageCount;
DWORD ZeroValue3;
DWORD EntryCount;
DWORD HeaderMagic;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} CASC_APM_HEADER_V2, *PCASC_APM_HEADER_V2;
#pragma pack(pop)
typedef struct _CASC_APM_HEADER
{
ULONGLONG BuildNumber;
DWORD PackageCount;
DWORD EntryCount;
DWORD HeaderMagic;
} CASC_APM_HEADER, *PCASC_APM_HEADER;
// On-disk format, size = 0x0C
typedef struct _CASC_APM_ENTRY_V1
{
DWORD Index;
BYTE HashValue[8];
} CASC_APM_ENTRY_V1, *PCASC_APM_ENTRY_V1;
// On-disk format, size = 0x14
typedef struct _CASC_APM_ENTRY_V2
{
DWORD Index;
BYTE HashValueA[8];
BYTE HashValueB[8];
} CASC_APM_ENTRY_V2, *PCASC_APM_ENTRY_V2;
// On-disk format
typedef struct _CASC_APM_PACKAGE_ENTRY_V1
{
ULONGLONG EntryPointGUID;
BYTE GUID[8];
ULONGLONG SecondaryGUID;
ULONGLONG Key;
ULONGLONG PackageGUID;
DWORD Unknown1;
DWORD ContentSize;
ULONGLONG Unknown3;
BYTE CKey[CASC_CKEY_SIZE]; // Content key
} CASC_APM_PACKAGE_ENTRY_V1, *PCASC_APM_PACKAGE_ENTRY_V1;
// On-disk format
typedef struct _CASC_APM_PACKAGE_ENTRY_V2
{
ULONGLONG GUID; // 077 file
ULONGLONG Unknown1;
ULONGLONG Unknown2;
ULONGLONG Unknown3;
} CASC_APM_PACKAGE_ENTRY_V2, *PCASC_APM_PACKAGE_ENTRY_V2;
//-----------------------------------------------------------------------------
// Functions common to both. Implemented in CascRootFile_OW.cpp
// Prepares the template of the asset file name. The file name is in format like:
// ContentManifestFiles\Windows-RCN\enUS\speech\05d0000000000086
size_t BuildAssetFileNameTemplate(
char * szNameTemplate, // File name template
size_t ccNameTemplate, // Offset of the plain name, relative to szNameTemplate
const char * szPrefix, // Top level folder name for asset files
const char * szAssetName // Plain name of the asset file ("Win_SPWin_RCN_LesES_EExt.apm")
);
// Inserts the asset file into the file tree.
DWORD InsertAssetFile(
TCascStorage * hs,
CASC_FILE_TREE & FileTree, // Reference to the file tree
char * szFileName, // Pointer to mutable asset file name template
size_t nPlainName, // Offset of the plain name in the name template
LPBYTE pbCKey, // Pointer to CKey (unaligned)
LPBYTE pbGuid // Pointer to file GUID (unaligned)
);
template <typename GUID_ENTRY>
DWORD InsertAssetFiles(
TCascStorage * hs,
CASC_FILE_TREE & FileTree, // Reference to the file tree
char * szFileName, // Pointer to mutable asset file name template
size_t nPlainName, // Offset of the plain name in the name template
GUID_ENTRY * pEntries, // Array of entries
size_t nEntries) // Number of entries
{
DWORD dwErrCode = ERROR_SUCCESS;
for(size_t i = 0; i < nEntries; i++)
{
dwErrCode = InsertAssetFile(hs, FileTree, szFileName, nPlainName, pEntries[i].CKey, pEntries[i].GUID);
if(dwErrCode != ERROR_SUCCESS)
break;
}
return dwErrCode;
}
#endif // __CASC_OVERWATCH_H__

View File

@@ -63,7 +63,7 @@ catch2
CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014)
https://github.com/ladislav-zezula/CascLib
Version: a27d2b3b9ccb326768e57b4cc7c49d15a0da9e0c
Version: 5c60050770767f2e606f6fec0c35beb8b9b00c60
rapidjson (A fast JSON parser/generator for C++ with both SAX/DOM style API http://rapidjson.org/)
https://github.com/Tencent/rapidjson