mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
1318 lines
46 KiB
C++
1318 lines
46 KiB
C++
/****************************************************************************/
|
|
/* CascOpenFile.cpp Copyright (c) Ladislav Zezula 2014 */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* System-dependent directory functions for CascLib */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Date Ver Who Comment */
|
|
/* -------- ---- --- ------- */
|
|
/* 01.05.14 1.00 Lad The first version of CascOpenFile.cpp */
|
|
/*****************************************************************************/
|
|
|
|
#define __CASCLIB_SELF__
|
|
#include "CascLib.h"
|
|
#include "CascCommon.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local functions
|
|
|
|
static DWORD GetStreamEncodedSize(TFileStream * pStream)
|
|
{
|
|
ULONGLONG FileSize = 0;
|
|
|
|
FileStream_GetSize(pStream, &FileSize);
|
|
assert((FileSize >> 32) == 0);
|
|
|
|
return (DWORD)(FileSize);
|
|
}
|
|
|
|
static DWORD OpenDataStream(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, bool bDownloadFileIf)
|
|
{
|
|
TCascStorage * hs = hf->hs;
|
|
TFileStream * pStream = NULL;
|
|
TCHAR szPlainName[0x80];
|
|
DWORD dwErrCode;
|
|
|
|
// If the file is available locally, we rely on data files.
|
|
// If not, we download the file and open the stream
|
|
if(pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL)
|
|
{
|
|
DWORD dwArchiveIndex = pFileSpan->ArchiveIndex;
|
|
|
|
// Lock the storage to make the operation thread-safe
|
|
CascLock(hs->StorageLock);
|
|
|
|
// If the data archive is not open yet, open it now.
|
|
if(hs->DataFiles[dwArchiveIndex] == NULL)
|
|
{
|
|
// Prepare the name of the data file
|
|
CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), dwArchiveIndex);
|
|
|
|
// Create the full path of the data file
|
|
CASC_PATH<TCHAR> DataFile(hs->szIndexPath, szPlainName, NULL);
|
|
|
|
// Open the data stream with read+write sharing to prevent Battle.net agent
|
|
// detecting a corruption and redownloading the entire package
|
|
pStream = FileStream_OpenFile(DataFile, STREAM_FLAG_READ_ONLY | STREAM_FLAG_WRITE_SHARE | STREAM_PROVIDER_FLAT | STREAM_FLAG_FILL_MISSING | BASE_PROVIDER_FILE);
|
|
hs->DataFiles[dwArchiveIndex] = pStream;
|
|
}
|
|
|
|
// Unlock the storage
|
|
CascUnlock(hs->StorageLock);
|
|
|
|
// Return error or success
|
|
pFileSpan->pStream = hs->DataFiles[dwArchiveIndex];
|
|
return (pFileSpan->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
if(bDownloadFileIf)
|
|
{
|
|
CASC_ARCHIVE_INFO ArchiveInfo = {0};
|
|
CASC_PATH<TCHAR> LocalPath;
|
|
CPATH_TYPE PathType = (pCKeyEntry->Flags & CASC_CE_FILE_PATCH) ? PathTypePatch : PathTypeData;
|
|
|
|
// Fetch the file
|
|
dwErrCode = FetchCascFile(hs, PathType, pCKeyEntry->EKey, NULL, LocalPath, &ArchiveInfo);
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
pStream = FileStream_OpenFile(LocalPath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
|
|
if(pStream != NULL)
|
|
{
|
|
// Initialize information about the position and size of the file in archive
|
|
// On loose files, their position is zero and encoded size is length of the file
|
|
if(CascIsValidMD5(ArchiveInfo.ArchiveKey))
|
|
{
|
|
// Archive position
|
|
pFileSpan->ArchiveIndex = ArchiveInfo.ArchiveIndex;
|
|
pFileSpan->ArchiveOffs = ArchiveInfo.ArchiveOffs;
|
|
|
|
// Encoded size
|
|
if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE)
|
|
pCKeyEntry->EncodedSize = ArchiveInfo.EncodedSize;
|
|
assert(pCKeyEntry->EncodedSize == ArchiveInfo.EncodedSize);
|
|
}
|
|
else
|
|
{
|
|
// Archive position
|
|
pFileSpan->ArchiveIndex = 0;
|
|
pFileSpan->ArchiveOffs = 0;
|
|
|
|
// Encoded size
|
|
if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE)
|
|
pCKeyEntry->EncodedSize = GetStreamEncodedSize(pStream);
|
|
assert(pCKeyEntry->EncodedSize == GetStreamEncodedSize(pStream));
|
|
}
|
|
|
|
// We need to close the file stream after we're done
|
|
pFileSpan->pStream = pStream;
|
|
hf->bCloseFileStream = true;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
return dwErrCode;
|
|
}
|
|
|
|
return ERROR_FILE_OFFLINE;
|
|
}
|
|
}
|
|
|
|
#ifdef CASCLIB_DEBUG
|
|
static unsigned int table_16C57A8[0x10] =
|
|
{
|
|
0x049396B8, 0x72A82A9B, 0xEE626CCA, 0x9917754F,
|
|
0x15DE40B1, 0xF5A8A9B6, 0x421EAC7E, 0xA9D55C9A,
|
|
0x317FD40C, 0x04FAF80D, 0x3D6BE971, 0x52933CFD,
|
|
0x27F64B7D, 0xC6F5C11B, 0xD5757E3A, 0x6C388745
|
|
};
|
|
|
|
// Obtained from Agent.exe v 2.15.0.6296 (d14ec9d9a1b396a42964b05f40ea55f37eae5478d550c07ebb6cb09e50968d62)
|
|
// Note the "Checksum" value probably won't match with older game versions.
|
|
static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderOffset)
|
|
{
|
|
LPBYTE pbBlteHeader = (LPBYTE)pBlteHeader;
|
|
DWORD dwInt32;
|
|
BYTE EncodedOffset[4] = { 0 };
|
|
BYTE HashedHeader[4] = { 0 };
|
|
BYTE JenkinsHash[4];
|
|
BYTE Checksum[4];
|
|
size_t i, j;
|
|
|
|
// Seems to be hardcoded to zero
|
|
assert(pBlteHeader->field_15 == 0);
|
|
|
|
// Calculate the Jenkins hash and write it to the header
|
|
dwInt32 = hashlittle(pbBlteHeader, FIELD_OFFSET(BLTE_ENCODED_HEADER, JenkinsHash), 0x3D6BE971);
|
|
ConvertIntegerToBytes_4_LE(dwInt32, JenkinsHash);
|
|
// assert(memcmp(pBlteHeader->JenkinsHash, JenkinsHash, sizeof(JenkinsHash)) == 0);
|
|
|
|
// Encode the lower 32-bits of the offset
|
|
dwInt32 = (DWORD)(HeaderOffset + FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature));
|
|
dwInt32 = table_16C57A8[dwInt32 & 0x0F] ^ dwInt32;
|
|
ConvertIntegerToBytes_4_LE(dwInt32, EncodedOffset);
|
|
|
|
// Calculate checksum of the so-far filled structure
|
|
for(i = 0; i < FIELD_OFFSET(BLTE_ENCODED_HEADER, Checksum); i++)
|
|
HashedHeader[i & 3] ^= pbBlteHeader[i];
|
|
|
|
// XOR the two values together to get the final checksum.
|
|
for(j = 0; j < 4; j++, i++)
|
|
Checksum[j] = HashedHeader[i & 3] ^ EncodedOffset[i & 3];
|
|
// assert(memcmp(pBlteHeader->Checksum, Checksum, sizeof(Checksum)) == 0);
|
|
}
|
|
#endif
|
|
|
|
static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize)
|
|
{
|
|
PBLTE_HEADER pBlteHeader = (PBLTE_HEADER)pbEncodedBuffer;
|
|
DWORD ExpectedHeaderSize;
|
|
DWORD ExHeaderSize = 0;
|
|
DWORD HeaderSize;
|
|
DWORD FrameCount = 0;
|
|
|
|
CASCLIB_UNUSED(HeaderOffset);
|
|
|
|
// On files within storage segments ("data.###"), there is BLTE_ENCODED_HEADER
|
|
// On local files, there is just 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;
|
|
|
|
// 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 header size. If this is non-zero, then array
|
|
// of chunk headers follow. Otherwise, the file is just one chunk
|
|
HeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSize);
|
|
if(HeaderSize != 0)
|
|
{
|
|
if(pBlteHeader->MustBe0F != 0x0F)
|
|
return ERROR_BAD_FORMAT;
|
|
|
|
// Verify the header size
|
|
FrameCount = ConvertBytesToInteger_3(pBlteHeader->FrameCount);
|
|
ExpectedHeaderSize = 0x0C + FrameCount * sizeof(BLTE_FRAME);
|
|
if(ExpectedHeaderSize != HeaderSize)
|
|
return ERROR_BAD_FORMAT;
|
|
|
|
// Give the values
|
|
pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F) + sizeof(DWORD);
|
|
}
|
|
else
|
|
{
|
|
pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F);
|
|
}
|
|
|
|
// Give the frame count
|
|
pFileSpan->FrameCount = FrameCount;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static LPBYTE ReadMissingHeaderData(PCASC_FILE_SPAN pFileSpan, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize)
|
|
{
|
|
LPBYTE pbNewBuffer;
|
|
|
|
// Reallocate the buffer. Note that if this fails, the original buffer is still valid
|
|
pbNewBuffer = CASC_REALLOC(pbEncodedBuffer, cbTotalHeaderSize);
|
|
if(pbNewBuffer != NULL)
|
|
{
|
|
// Load the missing data
|
|
DataFileOffset += cbEncodedBuffer;
|
|
if(FileStream_Read(pFileSpan->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
|
|
{
|
|
return pbNewBuffer;
|
|
}
|
|
}
|
|
|
|
// If anything failed, we free the original buffer and return NULL;
|
|
CASC_FREE(pbEncodedBuffer);
|
|
return NULL;
|
|
}
|
|
|
|
static LPBYTE CaptureBlteFileFrame(CASC_FILE_FRAME & Frame, LPBYTE pbFramePtr, LPBYTE pbFrameEnd)
|
|
{
|
|
PBLTE_FRAME pFileFrame = (PBLTE_FRAME)pbFramePtr;
|
|
|
|
// Check whether we have enough data ready
|
|
if((pbFramePtr + sizeof(BLTE_FRAME)) > pbFrameEnd)
|
|
return NULL;
|
|
|
|
Frame.FrameHash = pFileFrame->FrameHash;
|
|
Frame.ContentSize = ConvertBytesToInteger_4(pFileFrame->ContentSize);
|
|
Frame.EncodedSize = ConvertBytesToInteger_4(pFileFrame->EncodedSize);
|
|
return pbFramePtr + sizeof(BLTE_FRAME);
|
|
}
|
|
|
|
static DWORD LoadSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize)
|
|
{
|
|
PCASC_FILE_FRAME pFrames = NULL;
|
|
DWORD ContentSize = 0;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
assert(pFileSpan != NULL);
|
|
assert(pFileSpan->pStream != NULL);
|
|
assert(pFileSpan->pFrames == NULL);
|
|
|
|
if(pFileSpan->FrameCount != 0)
|
|
{
|
|
// Move the raw archive offset
|
|
DataFileOffset += ((ULONGLONG)pFileSpan->FrameCount * sizeof(BLTE_FRAME));
|
|
|
|
// Allocate array of file frames
|
|
pFrames = CASC_ALLOC<CASC_FILE_FRAME>(pFileSpan->FrameCount);
|
|
if(pFrames != NULL)
|
|
{
|
|
// Copy the frames to the file structure
|
|
for(DWORD i = 0; i < pFileSpan->FrameCount; i++)
|
|
{
|
|
CASC_FILE_FRAME & Frame = pFrames[i];
|
|
|
|
// Capture the single BLTE frame
|
|
pbFramePtr = CaptureBlteFileFrame(Frame, pbFramePtr, pbFrameEnd);
|
|
if(pbFramePtr == NULL)
|
|
{
|
|
dwErrCode = ERROR_BAD_FORMAT;
|
|
break;
|
|
}
|
|
|
|
// Fill-in the file range of the frame
|
|
Frame.StartOffset = pFileSpan->StartOffset + ContentSize;
|
|
Frame.EndOffset = Frame.StartOffset + Frame.ContentSize;
|
|
ContentSize += Frame.ContentSize;
|
|
|
|
// Fill-in the archive range of the frame
|
|
assert((DataFileOffset + Frame.EncodedSize) > DataFileOffset);
|
|
Frame.DataFileOffset = DataFileOffset;
|
|
DataFileOffset += Frame.EncodedSize;
|
|
}
|
|
|
|
// Save the content size of the file
|
|
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
|
|
{
|
|
pCKeyEntry->ContentSize = ContentSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Allocate single "dummy" frame
|
|
pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1);
|
|
if(pFrames != NULL)
|
|
{
|
|
// Fill the single frame
|
|
memset(&pFrames->FrameHash, 0, sizeof(CONTENT_KEY));
|
|
pFrames->StartOffset = pFileSpan->StartOffset;
|
|
pFrames->EndOffset = pFileSpan->EndOffset;
|
|
pFrames->DataFileOffset = DataFileOffset;
|
|
pFrames->EncodedSize = (DWORD)(pCKeyEntry->EncodedSize - cbHeaderSize);
|
|
pFrames->ContentSize = pCKeyEntry->ContentSize;
|
|
|
|
// Save the number of file frames
|
|
pFileSpan->FrameCount = 1;
|
|
}
|
|
else
|
|
{
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
// Free the frame array on error
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
pFileSpan->FrameCount = 0;
|
|
CASC_FREE(pFrames);
|
|
}
|
|
|
|
pFileSpan->pFrames = pFrames;
|
|
return dwErrCode;
|
|
}
|
|
|
|
static DWORD LoadSpanFramesForPlainFile(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry)
|
|
{
|
|
PCASC_FILE_FRAME pFrames;
|
|
|
|
// Allocate single "dummy" frame
|
|
pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1);
|
|
if(pFrames != NULL)
|
|
{
|
|
// Setup the size
|
|
pFileSpan->EndOffset = pFileSpan->StartOffset + pCKeyEntry->ContentSize;
|
|
pCKeyEntry->Flags |= CASC_CE_PLAIN_DATA;
|
|
|
|
// Fill the single frame
|
|
memset(&pFrames->FrameHash, 0, sizeof(CONTENT_KEY));
|
|
pFrames->StartOffset = pFileSpan->StartOffset;
|
|
pFrames->EndOffset = pFrames->StartOffset + pCKeyEntry->ContentSize;
|
|
pFrames->DataFileOffset = 0;
|
|
pFrames->EncodedSize = pCKeyEntry->EncodedSize;
|
|
pFrames->ContentSize = pCKeyEntry->ContentSize;
|
|
|
|
// Save the number of file frames
|
|
pFileSpan->FrameCount = 1;
|
|
pFileSpan->pFrames = pFrames;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
static DWORD LoadEncodedHeaderAndSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry)
|
|
{
|
|
LPBYTE pbEncodedBuffer;
|
|
size_t cbEncodedBuffer = MAX_ENCODED_HEADER;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Should only be called when the file frames are NOT loaded
|
|
assert(pFileSpan->pFrames == NULL);
|
|
assert(pFileSpan->FrameCount == 0);
|
|
|
|
// Allocate the initial buffer for the encoded headers
|
|
pbEncodedBuffer = CASC_ALLOC<BYTE>(MAX_ENCODED_HEADER);
|
|
if(pbEncodedBuffer != NULL)
|
|
{
|
|
ULONGLONG ReadOffset = pFileSpan->ArchiveOffs;
|
|
size_t cbTotalHeaderSize;
|
|
size_t cbHeaderSize = 0;
|
|
|
|
// At this point, we expect encoded size to be known
|
|
assert(pCKeyEntry->EncodedSize != CASC_INVALID_SIZE);
|
|
|
|
// Do not read more than encoded size
|
|
cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, pCKeyEntry->EncodedSize);
|
|
|
|
// Load the entire (eventual) header area. This is faster than doing
|
|
// two read operations in a row. Read as much as possible. If the file is cut,
|
|
// the FileStream will pad it with zeros
|
|
if(FileStream_Read(pFileSpan->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
|
|
{
|
|
// Parse the BLTE header
|
|
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
|
|
pFileSpan->HeaderSize = (DWORD)(cbTotalHeaderSize = cbHeaderSize + (pFileSpan->FrameCount * sizeof(BLTE_FRAME)));
|
|
if(cbTotalHeaderSize > cbEncodedBuffer)
|
|
{
|
|
pbEncodedBuffer = ReadMissingHeaderData(pFileSpan, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize);
|
|
if(pbEncodedBuffer == NULL)
|
|
dwErrCode = GetCascError();
|
|
cbEncodedBuffer = cbTotalHeaderSize;
|
|
}
|
|
|
|
// Load the array of frame headers
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
assert((ReadOffset + cbHeaderSize) > ReadOffset);
|
|
dwErrCode = LoadSpanFrames(pFileSpan, pCKeyEntry, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Special treatment for plain files ("PATCH"): If the content size and encoded size
|
|
// are equal, we will create a single fake frame
|
|
if(pCKeyEntry->EncodedSize == pCKeyEntry->ContentSize)
|
|
{
|
|
dwErrCode = LoadSpanFramesForPlainFile(pFileSpan, pCKeyEntry);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwErrCode = ERROR_FILE_CORRUPT;
|
|
}
|
|
|
|
// Free the frame buffer
|
|
CASC_FREE(pbEncodedBuffer);
|
|
}
|
|
else
|
|
{
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
static DWORD LoadSpanFrames(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry)
|
|
{
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Sanity check
|
|
assert(pFileSpan->pFrames == NULL);
|
|
|
|
// Make sure that the data stream is open for that span
|
|
if(pFileSpan->pStream == NULL)
|
|
{
|
|
dwErrCode = OpenDataStream(hf, pFileSpan, pCKeyEntry, hf->bDownloadFileIf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
}
|
|
|
|
// Make sure we have header area loaded
|
|
return LoadEncodedHeaderAndSpanFrames(pFileSpan, pCKeyEntry);
|
|
}
|
|
|
|
// Loads all file spans to memory
|
|
static DWORD LoadFileSpanFrames(TCascFile * hf)
|
|
{
|
|
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
|
|
PCASC_FILE_SPAN pFileSpan = hf->pFileSpan;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// If the ContentSize/EncodedSize is still unknown, we need to get it from the file frames
|
|
if(hf->ContentSize == CASC_INVALID_SIZE64 || hf->EncodedSize == CASC_INVALID_SIZE64)
|
|
{
|
|
// Set initially to zero
|
|
hf->ContentSize = 0;
|
|
hf->EncodedSize = 0;
|
|
|
|
// Load file frames for all spans
|
|
for(DWORD i = 0; i < hf->SpanCount; i++, pCKeyEntry++, pFileSpan++)
|
|
{
|
|
// Init the range of the file span
|
|
pFileSpan->StartOffset = hf->ContentSize;
|
|
pFileSpan->EndOffset = hf->ContentSize;
|
|
|
|
// Load the frames of the file span
|
|
dwErrCode = LoadSpanFrames(hf, pFileSpan, pCKeyEntry);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
|
|
hf->ContentSize += pCKeyEntry->ContentSize;
|
|
hf->EncodedSize += pCKeyEntry->EncodedSize;
|
|
pFileSpan->EndOffset = hf->ContentSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Load file frames for all spans
|
|
for(DWORD i = 0; i < hf->SpanCount; i++, pCKeyEntry++, pFileSpan++)
|
|
{
|
|
// Load the frames of the file span
|
|
dwErrCode = LoadSpanFrames(hf, pFileSpan, pCKeyEntry);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
static DWORD EnsureFileSpanFramesLoaded(TCascFile * hf)
|
|
{
|
|
DWORD dwErrCode;
|
|
|
|
if(hf->ContentSize == CASC_INVALID_SIZE64 || hf->pFileSpan->pFrames == NULL)
|
|
{
|
|
// Load all frames of all file spans
|
|
dwErrCode = LoadFileSpanFrames(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// Now the content size must be known
|
|
if(hf->ContentSize == CASC_INVALID_SIZE64)
|
|
return ERROR_CAN_NOT_COMPLETE;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static DWORD DecodeFileFrame(
|
|
TCascFile * hf,
|
|
PCASC_CKEY_ENTRY pCKeyEntry,
|
|
PCASC_FILE_FRAME pFrame,
|
|
LPBYTE pbEncoded,
|
|
LPBYTE pbDecoded,
|
|
DWORD FrameIndex)
|
|
{
|
|
TCascStorage * hs = hf->hs;
|
|
LPBYTE pbWorkBuffer = NULL;
|
|
DWORD cbDecodedExpected = 0;
|
|
DWORD cbWorkBuffer = 0;
|
|
DWORD dwStepCount = 0;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
DWORD cbEncoded = pFrame->EncodedSize;
|
|
DWORD cbDecoded = pFrame->ContentSize;
|
|
bool bWorkComplete = false;
|
|
|
|
//if(pFrame->EncodedSize == 0xda001)
|
|
//{
|
|
// FILE * fp = fopen("E:\\frame-da001-002.dat", "wb");
|
|
// fwrite(pbEncoded, 1, pFrame->EncodedSize, fp);
|
|
// fclose(fp);
|
|
//}
|
|
|
|
// If this is a file span with plain data, just copy the data
|
|
if(pCKeyEntry->Flags & CASC_CE_PLAIN_DATA)
|
|
{
|
|
assert(pCKeyEntry->ContentSize == pCKeyEntry->EncodedSize);
|
|
assert(pCKeyEntry->ContentSize == pFrame->ContentSize);
|
|
assert(pFrame->ContentSize == pFrame->EncodedSize);
|
|
memcpy(pbDecoded, pbEncoded, pCKeyEntry->ContentSize);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
// Shall we verify the frame integrity?
|
|
if(hf->bVerifyIntegrity)
|
|
{
|
|
if(!CascVerifyDataBlockHash(pbEncoded, pFrame->EncodedSize, pFrame->FrameHash.Value))
|
|
return ERROR_FILE_CORRUPT;
|
|
}
|
|
|
|
// Perform the loop
|
|
while(bWorkComplete == false)
|
|
{
|
|
// There should never be a 3rd step
|
|
assert(dwStepCount < 2);
|
|
|
|
// Perform the operation specific by the first byte
|
|
switch(pbEncoded[0])
|
|
{
|
|
case 'E': // Encrypted files
|
|
|
|
// The work buffer should not have been allocated by any step
|
|
assert(pbWorkBuffer == NULL && cbWorkBuffer == 0);
|
|
|
|
// Allocate temporary buffer to decrypt into
|
|
// Example storage: "2016 - WoW/23420", File: "4ee6bc9c6564227f1748abd0b088e950"
|
|
pbWorkBuffer = CASC_ALLOC<BYTE>(cbEncoded - 1);
|
|
cbWorkBuffer = cbEncoded - 1;
|
|
if(pbWorkBuffer == NULL)
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// Decrypt the stream to the work buffer
|
|
dwErrCode = CascDecrypt(hs, pbWorkBuffer, &cbWorkBuffer, pbEncoded + 1, cbEncoded - 1, FrameIndex);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
bWorkComplete = true;
|
|
break;
|
|
}
|
|
|
|
// When encrypted, there is always one more step after this.
|
|
// Setup the work buffer as input buffer for the next operation
|
|
pbEncoded = pbWorkBuffer;
|
|
cbEncoded = cbWorkBuffer;
|
|
break;
|
|
|
|
case 'Z': // ZLIB compressed files
|
|
|
|
// If we decompressed less than expected, we simply fill the rest with zeros
|
|
// Example: INSTALL file from the TACT CASC storage
|
|
cbDecodedExpected = cbDecoded;
|
|
dwErrCode = CascDecompress(pbDecoded, &cbDecoded, pbEncoded + 1, cbEncoded - 1);
|
|
|
|
// We exactly know what the output buffer size will be.
|
|
// If the uncompressed data is smaller, fill the rest with zeros
|
|
if(cbDecoded < cbDecodedExpected)
|
|
memset(pbDecoded + cbDecoded, 0, (cbDecodedExpected - cbDecoded));
|
|
bWorkComplete = true;
|
|
break;
|
|
|
|
case 'N': // Normal stored files
|
|
dwErrCode = CascDirectCopy(pbDecoded, &cbDecoded, pbEncoded + 1, cbEncoded - 1);
|
|
bWorkComplete = true;
|
|
break;
|
|
|
|
case 'F': // Recursive frames (not supported)
|
|
default: // Unrecognized. Could be a plain file data
|
|
dwErrCode = ERROR_NOT_SUPPORTED;
|
|
bWorkComplete = true;
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
// Increment the step count
|
|
dwStepCount++;
|
|
}
|
|
|
|
// Some people find it handy to extract data from partially encrypted file,
|
|
// even at the cost of producing corrupt files.
|
|
// We overcome missing decryption key by zeroing the encrypted portions
|
|
if(dwErrCode == ERROR_FILE_ENCRYPTED && hf->bOvercomeEncrypted)
|
|
{
|
|
memset(pbDecoded, 0, cbDecoded);
|
|
dwErrCode = ERROR_SUCCESS;
|
|
}
|
|
|
|
// Free the temporary buffer
|
|
CASC_FREE(pbWorkBuffer);
|
|
return dwErrCode;
|
|
}
|
|
|
|
static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
|
|
{
|
|
PCASC_FILE_FULL_INFO pFileInfo;
|
|
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
|
|
TCascStorage * hs = hf->hs;
|
|
DWORD dwErrCode;
|
|
|
|
// Make sure that the file spans are loaded
|
|
dwErrCode = EnsureFileSpanFramesLoaded(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
SetCascError(dwErrCode);
|
|
return false;
|
|
}
|
|
|
|
// Verify whether we have enough space in the buffer
|
|
pFileInfo = (PCASC_FILE_FULL_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_FULL_INFO), pcbLengthNeeded);
|
|
if(pFileInfo != NULL)
|
|
{
|
|
// Reset the entire structure
|
|
CopyMemory16(pFileInfo->CKey, pCKeyEntry->CKey);
|
|
CopyMemory16(pFileInfo->EKey, pCKeyEntry->EKey);
|
|
pFileInfo->FileDataId = CASC_INVALID_ID;
|
|
pFileInfo->LocaleFlags = CASC_INVALID_ID;
|
|
pFileInfo->ContentFlags = CASC_INVALID_ID;
|
|
|
|
// Supply information not depending on root
|
|
CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->pFileSpan->ArchiveIndex);
|
|
pFileInfo->StorageOffset = pCKeyEntry->StorageOffset;
|
|
pFileInfo->SegmentOffset = hf->pFileSpan->ArchiveOffs;
|
|
pFileInfo->FileNameHash = 0;
|
|
pFileInfo->TagBitMask = pCKeyEntry->TagBitMask;
|
|
pFileInfo->ContentSize = hf->ContentSize;
|
|
pFileInfo->EncodedSize = hf->EncodedSize;
|
|
pFileInfo->SegmentIndex = hf->pFileSpan->ArchiveIndex;
|
|
pFileInfo->SpanCount = hf->SpanCount;
|
|
|
|
// Supply the root-specific information
|
|
hs->pRootHandler->GetInfo(pCKeyEntry, pFileInfo);
|
|
}
|
|
|
|
return (pFileInfo != NULL);
|
|
}
|
|
|
|
static bool GetFileSpanInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
|
|
{
|
|
PCASC_FILE_SPAN_INFO pFileInfo;
|
|
PCASC_FILE_SPAN pFileSpan = hf->pFileSpan;
|
|
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Make sure that the file spans are loaded
|
|
dwErrCode = EnsureFileSpanFramesLoaded(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
SetCascError(dwErrCode);
|
|
return false;
|
|
}
|
|
|
|
// Verify whether we have enough space in the buffer
|
|
pFileInfo = (PCASC_FILE_SPAN_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_SPAN_INFO) * hf->SpanCount, pcbLengthNeeded);
|
|
if(pFileInfo != NULL)
|
|
{
|
|
// Copy all file spans
|
|
for(DWORD i = 0; i < hf->SpanCount; i++, pFileInfo++, pFileSpan++, pCKeyEntry++)
|
|
{
|
|
CopyMemory16(pFileInfo->CKey, pCKeyEntry->CKey);
|
|
CopyMemory16(pFileInfo->EKey, pCKeyEntry->EKey);
|
|
pFileInfo->StartOffset = pFileSpan->StartOffset;
|
|
pFileInfo->EndOffset = pFileSpan->EndOffset;
|
|
pFileInfo->ArchiveIndex = pFileSpan->ArchiveIndex;
|
|
pFileInfo->ArchiveOffs = pFileSpan->ArchiveOffs;
|
|
pFileInfo->HeaderSize = pFileSpan->HeaderSize;
|
|
pFileInfo->FrameCount = pFileSpan->FrameCount;
|
|
}
|
|
}
|
|
|
|
return (pFileInfo != NULL);
|
|
}
|
|
|
|
|
|
// Reads the file data from cache. Returns the number of bytes read
|
|
static DWORD ReadFile_Cache(TCascFile * hf, LPBYTE pbBuffer, ULONGLONG StartOffset, ULONGLONG EndOffset)
|
|
{
|
|
// Is there a file cache at all?
|
|
if(hf->pbFileCache != NULL && hf->FileCacheStart <= StartOffset && StartOffset < hf->FileCacheEnd)
|
|
{
|
|
LPBYTE pbStartBlock = hf->pbFileCache + (size_t)(StartOffset - hf->FileCacheStart);
|
|
|
|
// Can we handle the entire request from the cache?
|
|
if(EndOffset <= hf->FileCacheEnd)
|
|
{
|
|
DWORD dwBytesToCopy = (DWORD)(EndOffset - StartOffset);
|
|
|
|
memcpy(pbBuffer, pbStartBlock, dwBytesToCopy);
|
|
return dwBytesToCopy;
|
|
}
|
|
|
|
// We copy as much bytes as available. The rest is handled by normal read
|
|
else
|
|
{
|
|
DWORD dwBytesToCopy = (DWORD)(hf->FileCacheEnd - StartOffset);
|
|
|
|
memcpy(pbBuffer, pbStartBlock, dwBytesToCopy);
|
|
return dwBytesToCopy;
|
|
}
|
|
}
|
|
|
|
// Can't handle the request from the cache
|
|
return 0;
|
|
}
|
|
|
|
// No cache at all. The entire file will be read directly to the user buffer
|
|
static DWORD ReadFile_WholeFile(TCascFile * hf, LPBYTE pbBuffer)
|
|
{
|
|
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
|
|
PCASC_FILE_SPAN pFileSpan = hf->pFileSpan;
|
|
LPBYTE pbSaveBuffer = pbBuffer;
|
|
LPBYTE pbEncoded;
|
|
LPBYTE pbEncodedPtr;
|
|
DWORD dwErrCode;
|
|
|
|
for(DWORD SpanIndex = 0; SpanIndex < hf->SpanCount; SpanIndex++, pCKeyEntry++, pFileSpan++)
|
|
{
|
|
ULONGLONG ByteOffset = pFileSpan->ArchiveOffs + pFileSpan->HeaderSize;
|
|
DWORD EncodedSize = pCKeyEntry->EncodedSize - pFileSpan->HeaderSize;
|
|
|
|
// Allocate the buffer for the entire encoded span
|
|
pbEncodedPtr = pbEncoded = CASC_ALLOC<BYTE>(EncodedSize);
|
|
if(pbEncoded == NULL)
|
|
{
|
|
SetCascError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return 0;
|
|
}
|
|
|
|
// Load the encoded buffer
|
|
if(FileStream_Read(pFileSpan->pStream, &ByteOffset, pbEncoded, EncodedSize))
|
|
{
|
|
PCASC_FILE_FRAME pFileFrame = pFileSpan->pFrames;
|
|
|
|
for(DWORD FrameIndex = 0; FrameIndex < pFileSpan->FrameCount; FrameIndex++, pFileFrame++)
|
|
{
|
|
// Decode the file frame
|
|
dwErrCode = DecodeFileFrame(hf, pCKeyEntry, pFileFrame, pbEncodedPtr, pbBuffer, FrameIndex);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
|
|
// Move pointers
|
|
pbEncodedPtr += pFileFrame->EncodedSize;
|
|
pbBuffer += pFileFrame->ContentSize;
|
|
}
|
|
}
|
|
|
|
CASC_FREE(pbEncoded);
|
|
}
|
|
|
|
// Give the amount of bytes read
|
|
return (DWORD)(pbBuffer - pbSaveBuffer);
|
|
}
|
|
|
|
static DWORD ReadFile_FrameCached(TCascFile * hf, LPBYTE pbBuffer, ULONGLONG StartOffset, ULONGLONG EndOffset)
|
|
{
|
|
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
|
|
PCASC_FILE_SPAN pFileSpan = hf->pFileSpan;
|
|
PCASC_FILE_FRAME pFileFrame = NULL;
|
|
LPBYTE pbSaveBuffer = pbBuffer;
|
|
LPBYTE pbEncoded = NULL;
|
|
LPBYTE pbDecoded = NULL;
|
|
DWORD dwBytesRead = 0;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
bool bNeedFreeDecoded = true;
|
|
|
|
// Parse all file spans
|
|
for(DWORD SpanIndex = 0; SpanIndex < hf->SpanCount; SpanIndex++, pCKeyEntry++, pFileSpan++)
|
|
{
|
|
if(pFileSpan->StartOffset <= StartOffset && StartOffset < pFileSpan->EndOffset)
|
|
{
|
|
for(DWORD FrameIndex = 0; FrameIndex < pFileSpan->FrameCount; FrameIndex++)
|
|
{
|
|
// Get the current file frame
|
|
pFileFrame = pFileSpan->pFrames + FrameIndex;
|
|
|
|
// Check the frame byte range
|
|
if(pFileFrame->StartOffset <= StartOffset && StartOffset < pFileFrame->EndOffset)
|
|
{
|
|
// Check bytes read overflow
|
|
if((dwBytesRead + pFileFrame->ContentSize) < dwBytesRead)
|
|
{
|
|
SetCascError(ERROR_BUFFER_OVERFLOW);
|
|
return 0;
|
|
}
|
|
|
|
// Pick the buffer for decoded data. If we are going to read the entire frame,
|
|
// there is a little chance that the caller will read the same file range again
|
|
// So we can as well just unpack the entire frame into the output buffer
|
|
if(pFileFrame->StartOffset < StartOffset || EndOffset < pFileFrame->EndOffset)
|
|
{
|
|
if((pbDecoded = CASC_ALLOC<BYTE>(pFileFrame->ContentSize)) == NULL)
|
|
{
|
|
SetCascError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return 0;
|
|
}
|
|
bNeedFreeDecoded = true;
|
|
}
|
|
else
|
|
{
|
|
bNeedFreeDecoded = false;
|
|
pbDecoded = pbBuffer;
|
|
}
|
|
|
|
// Allocate the encoded frame
|
|
if((pbEncoded = CASC_ALLOC<BYTE>(pFileFrame->EncodedSize)) == NULL)
|
|
{
|
|
CASC_FREE(pbDecoded);
|
|
SetCascError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return 0;
|
|
}
|
|
|
|
// Load the frame to the encoded buffer
|
|
if(FileStream_Read(pFileSpan->pStream, &pFileFrame->DataFileOffset, pbEncoded, pFileFrame->EncodedSize))
|
|
{
|
|
ULONGLONG EndOfCopy = CASCLIB_MIN(pFileFrame->EndOffset, EndOffset);
|
|
DWORD dwBytesToCopy = (DWORD)(EndOfCopy - StartOffset);
|
|
|
|
// Decode the frame
|
|
dwErrCode = DecodeFileFrame(hf, pCKeyEntry, pFileFrame, pbEncoded, pbDecoded, FrameIndex);
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Copy the data
|
|
if(pbDecoded != pbBuffer)
|
|
memcpy(pbBuffer, pbDecoded + (DWORD)(StartOffset - pFileFrame->StartOffset), dwBytesToCopy);
|
|
StartOffset += dwBytesToCopy;
|
|
pbBuffer += dwBytesToCopy;
|
|
}
|
|
}
|
|
|
|
// Free the encoded buffer
|
|
CASC_FREE(pbEncoded);
|
|
|
|
// If we are at the end of the read area, break all loops
|
|
if(dwErrCode != ERROR_SUCCESS || StartOffset >= EndOffset)
|
|
goto __WorkComplete;
|
|
if(bNeedFreeDecoded)
|
|
CASC_FREE(pbDecoded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
__WorkComplete:
|
|
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// If there is some data left in the frame, we set it as cache
|
|
if(pFileFrame != NULL && pbDecoded != NULL && EndOffset < pFileFrame->EndOffset)
|
|
{
|
|
CASC_FREE(hf->pbFileCache);
|
|
|
|
hf->FileCacheStart = pFileFrame->StartOffset;
|
|
hf->FileCacheEnd = pFileFrame->EndOffset;
|
|
hf->pbFileCache = pbDecoded;
|
|
pbDecoded = NULL;
|
|
}
|
|
}
|
|
|
|
// Final free of the decoded buffer, if needeed
|
|
if(bNeedFreeDecoded)
|
|
CASC_FREE(pbDecoded);
|
|
pbDecoded = NULL;
|
|
|
|
// Return the number of bytes read. Always set LastError.
|
|
SetCascError(dwErrCode);
|
|
return (DWORD)(pbBuffer - pbSaveBuffer);
|
|
}
|
|
|
|
// No cache at all. The entire file will be read directly to the user buffer
|
|
static DWORD ReadFile_NonCached(TCascFile * hf, LPBYTE pbBuffer, ULONGLONG StartOffset, ULONGLONG EndOffset)
|
|
{
|
|
// Reading the whole file?
|
|
if(StartOffset == 0 && EndOffset == hf->ContentSize)
|
|
{
|
|
return ReadFile_WholeFile(hf, pbBuffer);
|
|
}
|
|
|
|
// Reading just a part of the file?
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Public functions
|
|
|
|
bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
|
|
{
|
|
TCascFile * hf;
|
|
LPBYTE pbOutputValue = NULL;
|
|
LPBYTE pbInfoValue = NULL;
|
|
size_t cbInfoValue = 0;
|
|
|
|
// Validate the file handle
|
|
if((hf = TCascFile::IsValid(hFile)) == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
// Differentiate between info classes
|
|
switch(InfoClass)
|
|
{
|
|
case CascFileContentKey:
|
|
|
|
// Do we have content key at all?
|
|
if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_CKEY) == 0)
|
|
{
|
|
SetCascError(ERROR_NOT_SUPPORTED);
|
|
return false;
|
|
}
|
|
|
|
// Give the content key
|
|
pbInfoValue = hf->pCKeyEntry->CKey;
|
|
cbInfoValue = CASC_CKEY_SIZE;
|
|
break;
|
|
|
|
case CascFileEncodedKey:
|
|
|
|
// Do we have content key at all?
|
|
if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_EKEY) == 0)
|
|
{
|
|
SetCascError(ERROR_NOT_SUPPORTED);
|
|
return false;
|
|
}
|
|
|
|
// Give the content key
|
|
pbInfoValue = hf->pCKeyEntry->EKey;
|
|
cbInfoValue = CASC_CKEY_SIZE;
|
|
break;
|
|
|
|
case CascFileFullInfo:
|
|
return GetFileFullInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
|
|
|
|
case CascFileSpanInfo:
|
|
return GetFileSpanInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
|
|
|
|
default:
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Sanity check
|
|
assert(pbInfoValue != NULL);
|
|
assert(cbInfoValue != 0);
|
|
|
|
// Give the result
|
|
pbOutputValue = (LPBYTE)ProbeOutputBuffer(pvFileInfo, cbFileInfo, cbInfoValue, pcbLengthNeeded);
|
|
if(pbOutputValue != NULL)
|
|
memcpy(pbOutputValue, pbInfoValue, cbInfoValue);
|
|
return (pbOutputValue != NULL);
|
|
}
|
|
|
|
bool WINAPI CascSetFileFlags(HANDLE hFile, DWORD dwOpenFlags)
|
|
{
|
|
TCascFile * hf;
|
|
|
|
// Validate the file handle
|
|
if((hf = TCascFile::IsValid(hFile)) == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
// Currently, only CASC_OVERCOME_ENCRYPTED can be changed
|
|
if(dwOpenFlags & ~CASC_OVERCOME_ENCRYPTED)
|
|
{
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Set "overcome encrypted" flag. Will apply on next CascReadFile
|
|
hf->bOvercomeEncrypted = (dwOpenFlags & CASC_OVERCOME_ENCRYPTED) ? true : false;
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// THE FILE SIZE PROBLEM
|
|
//
|
|
// There are members called "FileSize" in many CASC-related structure
|
|
// For various files, these variables have different meaning.
|
|
//
|
|
// Storage FileName FileSize FrameSum HdrArea CKeyEntry EKeyEntry RootEntry
|
|
// ----------- -------- ---------- -------- -------- ---------- ---------- ----------
|
|
// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 n/a 0x0024BA45 n/a
|
|
// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x00193340 0x0010db65 n/a
|
|
// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x00001080 0x000008eb 0x00001080
|
|
//
|
|
// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b n/a 0x030d487b n/a
|
|
// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x016a9800 0x0131313d n/a
|
|
// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x000007d0 0x00000397 n/a
|
|
//
|
|
|
|
bool WINAPI CascGetFileSize64(HANDLE hFile, PULONGLONG PtrFileSize)
|
|
{
|
|
TCascFile * hf;
|
|
DWORD dwErrCode;
|
|
|
|
// Validate the file pointer
|
|
if(PtrFileSize == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Validate the file handle
|
|
if((hf = TCascFile::IsValid(hFile)) == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
// If the content key is zeros, we treat the file as a file with size of 0
|
|
if(hf->ContentSize == 0)
|
|
{
|
|
PtrFileSize[0] = 0;
|
|
return true;
|
|
}
|
|
|
|
// ENCODING on older storages: Content size is not present in the BUILD file
|
|
// For that reason, we need to query the content size from the file frames
|
|
dwErrCode = EnsureFileSpanFramesLoaded(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
SetCascError(dwErrCode);
|
|
return false;
|
|
}
|
|
|
|
// Give the file size to the caller
|
|
PtrFileSize[0] = hf->ContentSize;
|
|
return true;
|
|
}
|
|
|
|
DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD PtrFileSizeHigh)
|
|
{
|
|
ULONGLONG FileSize = 0;
|
|
|
|
// Retrieve the 64-bit file size
|
|
if(!CascGetFileSize64(hFile, &FileSize))
|
|
return CASC_INVALID_SIZE;
|
|
|
|
// Give the file size to the caller
|
|
if(PtrFileSizeHigh != NULL)
|
|
PtrFileSizeHigh[0] = (DWORD)(FileSize >> 32);
|
|
return (DWORD)(FileSize);
|
|
}
|
|
|
|
bool WINAPI CascSetFilePointer64(HANDLE hFile, LONGLONG DistanceToMove, PULONGLONG PtrNewPos, DWORD dwMoveMethod)
|
|
{
|
|
ULONGLONG FilePosition;
|
|
TCascFile * hf;
|
|
|
|
// If the hFile is not a valid file handle, return an error.
|
|
hf = TCascFile::IsValid(hFile);
|
|
if(hf == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
// Get the relative point where to move from
|
|
switch(dwMoveMethod)
|
|
{
|
|
case FILE_BEGIN:
|
|
FilePosition = 0;
|
|
break;
|
|
|
|
case FILE_CURRENT:
|
|
FilePosition = hf->FilePointer;
|
|
break;
|
|
|
|
case FILE_END:
|
|
FilePosition = hf->ContentSize;
|
|
break;
|
|
|
|
default:
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Now calculate the new file pointer
|
|
if(DistanceToMove >= 0)
|
|
{
|
|
// Do not allow the file pointer to overflow 64-bit range
|
|
if((FilePosition + DistanceToMove) < FilePosition)
|
|
{
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Do not allow the file pointer to overflow the file size
|
|
if((FilePosition = FilePosition + DistanceToMove) > hf->ContentSize)
|
|
FilePosition = hf->ContentSize;
|
|
hf->FilePointer = FilePosition;
|
|
}
|
|
else
|
|
{
|
|
// Do not allow the file pointer to underflow 64-bit range
|
|
if((FilePosition + DistanceToMove) > FilePosition)
|
|
{
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Do not allow the file pointer to move to negative values
|
|
if((LONGLONG)(FilePosition = FilePosition + DistanceToMove) < 0)
|
|
FilePosition = 0;
|
|
hf->FilePointer = FilePosition;
|
|
}
|
|
|
|
// Give the result size to the caller
|
|
if(PtrNewPos != NULL)
|
|
PtrNewPos[0] = hf->FilePointer;
|
|
return true;
|
|
}
|
|
|
|
DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * PtrFilePosHigh, DWORD dwMoveMethod)
|
|
{
|
|
ULONGLONG NewPos = 0;
|
|
LONGLONG DistanceToMove;
|
|
|
|
// Assemble the 64-bit distance to move
|
|
DistanceToMove = (PtrFilePosHigh != NULL) ? MAKE_OFFSET64(PtrFilePosHigh[0], lFilePos) : (LONGLONG)(LONG)lFilePos;
|
|
|
|
// Set the file offset
|
|
if(!CascSetFilePointer64(hFile, DistanceToMove, &NewPos, dwMoveMethod))
|
|
return CASC_INVALID_POS;
|
|
|
|
// Give the result to the caller
|
|
if(PtrFilePosHigh != NULL)
|
|
PtrFilePosHigh[0] = (LONG)(NewPos >> 32);
|
|
return (DWORD)(NewPos);
|
|
}
|
|
|
|
bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD PtrBytesRead)
|
|
{
|
|
ULONGLONG SaveFilePointer;
|
|
ULONGLONG StartOffset;
|
|
ULONGLONG EndOffset;
|
|
TCascFile * hf;
|
|
LPBYTE pbBuffer = (LPBYTE)pvBuffer;
|
|
DWORD dwBytesRead1 = 0; // From cache
|
|
DWORD dwBytesRead2 = 0; // From file
|
|
DWORD dwErrCode;
|
|
|
|
// The buffer must be valid
|
|
if(pvBuffer == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Validate the file handle
|
|
if((hf = TCascFile::IsValid(hFile)) == NULL)
|
|
{
|
|
SetCascError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
// Check files with zero size
|
|
if(hf->ContentSize == 0)
|
|
{
|
|
PtrBytesRead[0] = 0;
|
|
return true;
|
|
}
|
|
|
|
// If we don't have file frames loaded, we need to do it now.
|
|
// Need to do it before file range check, as the file size may be unknown at this point
|
|
dwErrCode = EnsureFileSpanFramesLoaded(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
SetCascError(dwErrCode);
|
|
return false;
|
|
}
|
|
|
|
// If the file position is at or beyond end of file, do nothing
|
|
SaveFilePointer = StartOffset = hf->FilePointer;
|
|
if(StartOffset >= hf->ContentSize)
|
|
{
|
|
PtrBytesRead[0] = 0;
|
|
return true;
|
|
}
|
|
|
|
// If the read area goes beyond end of the file, cut the number of bytes to read
|
|
EndOffset = StartOffset + dwBytesToRead;
|
|
if(EndOffset > hf->ContentSize)
|
|
{
|
|
EndOffset = hf->ContentSize;
|
|
}
|
|
|
|
// Can we handle the request (at least partially) from the cache?
|
|
if((dwBytesRead1 = ReadFile_Cache(hf, pbBuffer, StartOffset, EndOffset)) != 0)
|
|
{
|
|
// Move pointers
|
|
StartOffset = StartOffset + dwBytesRead1;
|
|
pbBuffer += dwBytesRead1;
|
|
|
|
// Has the read request been fully satisfied?
|
|
if(StartOffset == EndOffset)
|
|
{
|
|
if(PtrBytesRead != NULL)
|
|
PtrBytesRead[0] = dwBytesRead1;
|
|
hf->FilePointer = EndOffset;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Perform the cache-strategy-specific read
|
|
switch(hf->CacheStrategy)
|
|
{
|
|
// No caching at all. The entire file will be read directly to the user buffer
|
|
// Used for loading internal files, where we need to read the whole file
|
|
case CascCacheNothing:
|
|
dwBytesRead2 = ReadFile_NonCached(hf, pbBuffer, StartOffset, EndOffset);
|
|
break;
|
|
|
|
// Read as many frames as we can. The last loaded frame, if not read entirely,
|
|
// will stay in the cache - We expect the next read to continue from that offset.
|
|
case CascCacheLastFrame:
|
|
dwBytesRead2 = ReadFile_FrameCached(hf, pbBuffer, StartOffset, EndOffset);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// If the second-stage-read failed, we invalidate the entire operation and return 0 bytes read
|
|
if(dwBytesRead2 != 0)
|
|
{
|
|
// Give the result to the caller
|
|
if(PtrBytesRead != NULL)
|
|
PtrBytesRead[0] = (dwBytesRead1 + dwBytesRead2);
|
|
hf->FilePointer = StartOffset + dwBytesRead2;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Give the result to the caller
|
|
if(PtrBytesRead != NULL)
|
|
PtrBytesRead[0] = 0;
|
|
hf->FilePointer = SaveFilePointer;
|
|
|
|
// If 0 bytes were requested, it's actually a success
|
|
return (dwBytesToRead == 0);
|
|
}
|
|
}
|