mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Dep/CascLib: Update to ladislav-zezula/CascLib@5c60050770
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -588,7 +588,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot
|
||||
{
|
||||
PCASC_CKEY_ENTRY pSpanEntries;
|
||||
PCASC_FILE_NODE pFileNode;
|
||||
USHORT RefCount;
|
||||
DWORD RefCount;
|
||||
bool bFilePresent = true;
|
||||
|
||||
//
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
214
dep/CascLib/src/hashes/sha1.cpp
Normal file
214
dep/CascLib/src/hashes/sha1.cpp
Normal 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 ===== */
|
||||
34
dep/CascLib/src/hashes/sha1.h
Normal file
34
dep/CascLib/src/hashes/sha1.h
Normal 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 */
|
||||
2112
dep/CascLib/src/overwatch/aes.cpp
Normal file
2112
dep/CascLib/src/overwatch/aes.cpp
Normal file
File diff suppressed because it is too large
Load Diff
53
dep/CascLib/src/overwatch/aes.h
Normal file
53
dep/CascLib/src/overwatch/aes.h
Normal 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
|
||||
42
dep/CascLib/src/overwatch/aes_local.h
Normal file
42
dep/CascLib/src/overwatch/aes_local.h
Normal 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 */
|
||||
154
dep/CascLib/src/overwatch/apm.cpp
Normal file
154
dep/CascLib/src/overwatch/apm.cpp
Normal 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;
|
||||
}
|
||||
13907
dep/CascLib/src/overwatch/cmf-key.cpp
Normal file
13907
dep/CascLib/src/overwatch/cmf-key.cpp
Normal file
File diff suppressed because it is too large
Load Diff
482
dep/CascLib/src/overwatch/cmf-update.py
Normal file
482
dep/CascLib/src/overwatch/cmf-update.py
Normal 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")
|
||||
|
||||
221
dep/CascLib/src/overwatch/cmf.cpp
Normal file
221
dep/CascLib/src/overwatch/cmf.cpp
Normal 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;
|
||||
}
|
||||
279
dep/CascLib/src/overwatch/overwatch.h
Normal file
279
dep/CascLib/src/overwatch/overwatch.h
Normal 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__
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user