Files
TrinityCore/dep/CascLib/src/CascReadFile.cpp

846 lines
30 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 int EnsureDataStreamIsOpen(TCascFile * hf)
{
TCascStorage * hs = hf->hs;
TFileStream * pStream = NULL;
ULONGLONG EncodedSize = 0;
TCHAR * szDataFile;
TCHAR szCachePath[MAX_PATH];
TCHAR szPlainName[0x80];
int nError;
// If the file is available locally, we rely on data files.
// If not, we download the file and open the stream
if(hf->pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL)
{
// If the file is not open yet, do it
if(hs->DataFiles[hf->ArchiveIndex] == NULL)
{
// Prepare the name of the data file
CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), hf->ArchiveIndex);
szDataFile = CombinePath(hs->szIndexPath, szPlainName);
// Open the data file
if(szDataFile != NULL)
{
// Open the data stream with read+write sharing to prevent Battle.net agent
// detecting a corruption and redownloading the entire package
pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_FLAG_WRITE_SHARE | STREAM_PROVIDER_FLAT | STREAM_FLAG_FILL_MISSING | BASE_PROVIDER_FILE);
hs->DataFiles[hf->ArchiveIndex] = pStream;
CASC_FREE(szDataFile);
}
}
// Return error or success
hf->pStream = hs->DataFiles[hf->ArchiveIndex];
return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
}
else
{
if(hf->bDownloadFileIf)
{
// Create the local folder path and download the file from CDN
nError = DownloadFileFromCDN(hf->hs, _T("data"), hf->pCKeyEntry->EKey, NULL, szCachePath, _countof(szCachePath));
if(nError == ERROR_SUCCESS)
{
hf->pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
if(hf->pStream != NULL)
{
// Supply the file size, if unknown yet
if(hf->EncodedSize == CASC_INVALID_SIZE)
{
FileStream_GetSize(hf->pStream, &EncodedSize);
hf->pCKeyEntry->EncodedSize = (DWORD)EncodedSize;
hf->EncodedSize = (DWORD)EncodedSize;
}
hf->bLocalFileStream = true;
return ERROR_SUCCESS;
}
}
}
return ERROR_FILE_OFFLINE;
}
}
#ifdef _DEBUG
static unsigned int table_16C57A8[0x10] =
{
0x049396B8, 0x72A82A9B, 0xEE626CCA, 0x9917754F,
0x15DE40B1, 0xF5A8A9B6, 0x421EAC7E, 0xA9D55C9A,
0x317FD40C, 0x04FAF80D, 0x3D6BE971, 0x52933CFD,
0x27F64B7D, 0xC6F5C11B, 0xD5757E3A, 0x6C388745
};
// Obtained from Agent.exe v 2.15.0.6296 (d14ec9d9a1b396a42964b05f40ea55f37eae5478d550c07ebb6cb09e50968d62)
// Note the "Checksum" value probably won't match with older game versions.
static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderOffset)
{
LPBYTE pbBlteHeader = (LPBYTE)pBlteHeader;
DWORD dwInt32;
BYTE EncodedOffset[4] = { 0 };
BYTE HashedHeader[4] = { 0 };
BYTE JenkinsHash[4];
BYTE Checksum[4];
size_t i, j;
// Seems to be hardcoded to zero
assert(pBlteHeader->field_15 == 0);
// Calculate the Jenkins hash and write it to the header
dwInt32 = hashlittle(pbBlteHeader, FIELD_OFFSET(BLTE_ENCODED_HEADER, JenkinsHash), 0x3D6BE971);
ConvertIntegerToBytes_4_LE(dwInt32, JenkinsHash);
// assert(memcmp(pBlteHeader->JenkinsHash, JenkinsHash, sizeof(JenkinsHash)) == 0);
// Encode the lower 32-bits of the offset
dwInt32 = (DWORD)(HeaderOffset + FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature));
dwInt32 = table_16C57A8[dwInt32 & 0x0F] ^ dwInt32;
ConvertIntegerToBytes_4_LE(dwInt32, EncodedOffset);
// Calculate checksum of the so-far filled structure
for (i = 0; i < FIELD_OFFSET(BLTE_ENCODED_HEADER, Checksum); i++)
HashedHeader[i & 3] ^= pbBlteHeader[i];
// XOR the two values together to get the final checksum.
for (j = 0; j < 4; j++, i++)
Checksum[j] = HashedHeader[i & 3] ^ EncodedOffset[i & 3];
// assert(memcmp(pBlteHeader->Checksum, Checksum, sizeof(Checksum)) == 0);
}
#endif
static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize)
{
PBLTE_ENCODED_HEADER pEncodedHeader = (PBLTE_ENCODED_HEADER)pbEncodedBuffer;
PBLTE_HEADER pBlteHeader = (PBLTE_HEADER)pbEncodedBuffer;
DWORD ExpectedHeaderSize;
DWORD ExHeaderSize = 0;
DWORD HeaderSize;
DWORD FrameCount = 0;
CASCLIB_UNUSED(HeaderOffset);
// On files within storage segments ("data.###"), there is BLTE_ENCODED_HEADER
// On local files, there is just PBLTE_HEADER
if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE)
{
// There must be at least some bytes
if (cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F))
return ERROR_BAD_FORMAT;
if (pEncodedHeader->EncodedSize != hf->EncodedSize)
return ERROR_BAD_FORMAT;
#ifdef _DEBUG
// Not really needed, it's here just for explanation of what the values mean
//assert(memcmp(hf->pCKeyEntry->EKey, pEncodedHeader->EKey.Value, MD5_HASH_SIZE) == 0);
VerifyHeaderSpan(pEncodedHeader, HeaderOffset);
#endif
// Capture the EKey
ExHeaderSize = FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature);
pBlteHeader = (PBLTE_HEADER)(pbEncodedBuffer + ExHeaderSize);
}
// Verify the signature
if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE)
return ERROR_BAD_FORMAT;
// Capture the header size. If this is non-zero, then array
// of chunk headers follow. Otherwise, the file is just one chunk
HeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSize);
if (HeaderSize != 0)
{
if (pBlteHeader->MustBe0F != 0x0F)
return ERROR_BAD_FORMAT;
// Verify the header size
FrameCount = ConvertBytesToInteger_3(pBlteHeader->FrameCount);
ExpectedHeaderSize = 0x0C + FrameCount * sizeof(BLTE_FRAME);
if (ExpectedHeaderSize != HeaderSize)
return ERROR_BAD_FORMAT;
// Give the values
pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F) + sizeof(DWORD);
}
else
{
pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F);
}
// Give the frame count
hf->FrameCount = FrameCount;
return ERROR_SUCCESS;
}
static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize)
{
LPBYTE pbNewBuffer;
// Reallocate the buffer
pbNewBuffer = CASC_REALLOC(BYTE, pbEncodedBuffer, cbTotalHeaderSize);
if (pbNewBuffer != NULL)
{
// Load the missing data
DataFileOffset += cbEncodedBuffer;
if (FileStream_Read(hf->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
{
return pbNewBuffer;
}
}
// If anything failed, we free the original buffer and return NULL;
CASC_FREE(pbEncodedBuffer);
return NULL;
}
static int LoadFileFrames(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize)
{
PBLTE_FRAME pFileFrame;
DWORD ContentSize = 0;
DWORD FileOffset = 0;
int nError = ERROR_SUCCESS;
assert(hf != NULL);
assert(hf->pStream != NULL);
assert(hf->pFrames == NULL);
if (hf->FrameCount != 0)
{
// Move the raw archive offset
DataFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME));
// Allocate array of file frames
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount);
if (hf->pFrames != NULL)
{
// Copy the frames to the file structure
for (DWORD i = 0; i < hf->FrameCount; i++, pbFramePtr += sizeof(BLTE_FRAME))
{
// Capture the file frame
if ((pbFramePtr + sizeof(BLTE_FRAME)) > pbFrameEnd)
return ERROR_BAD_FORMAT;
pFileFrame = (PBLTE_FRAME)pbFramePtr;
// Convert the file frame to the native format
hf->pFrames[i].DataFileOffset = (DWORD)DataFileOffset;
hf->pFrames[i].FileOffset = CASC_INVALID_POS;
hf->pFrames[i].EncodedSize = ConvertBytesToInteger_4(pFileFrame->EncodedSize);
hf->pFrames[i].ContentSize = ConvertBytesToInteger_4(pFileFrame->ContentSize);
hf->pFrames[i].FrameHash = pFileFrame->FrameHash;
DataFileOffset += hf->pFrames[i].EncodedSize;
ContentSize += hf->pFrames[i].ContentSize;
FileOffset += hf->pFrames[i].ContentSize;
}
// Save the content size of the file
if(hf->pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
{
hf->pCKeyEntry->ContentSize = ContentSize;
hf->ContentSize = ContentSize;
}
}
}
else
{
// The content size in the file structure must be valid at this point,
// otherwise we don't know the frame content size
if (hf->ContentSize == CASC_INVALID_SIZE)
{
assert(false);
return ERROR_CAN_NOT_COMPLETE;
}
// Save the number of file frames
hf->FrameCount = 1;
// Allocate single "dummy" frame
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, 1);
if (hf->pFrames != NULL)
{
memset(&hf->pFrames->FrameHash, 0, sizeof(CONTENT_KEY));
hf->pFrames->DataFileOffset = (DWORD)DataFileOffset;
hf->pFrames->FileOffset = CASC_INVALID_POS;
hf->pFrames->EncodedSize = (DWORD)(hf->EncodedSize - cbHeaderSize);
hf->pFrames->ContentSize = hf->ContentSize;
}
}
if (hf->pFrames == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
return nError;
}
static int LoadEncodedHeaderAndFileFrames(TCascFile * hf)
{
LPBYTE pbEncodedBuffer;
size_t cbEncodedBuffer = MAX_ENCODED_HEADER;
int nError = ERROR_SUCCESS;
// Should only be called when the file frames are NOT loaded
assert(hf->pFrames == NULL);
assert(hf->FrameCount == 0);
// Allocate the initial buffer for the encoded headers
pbEncodedBuffer = CASC_ALLOC(BYTE, MAX_ENCODED_HEADER);
if (pbEncodedBuffer != NULL)
{
ULONGLONG ReadOffset = hf->ArchiveOffset;
size_t cbTotalHeaderSize;
size_t cbHeaderSize = 0;
// At this point, we expect encoded size to be known
assert(hf->EncodedSize != CASC_INVALID_SIZE);
// Do not read more than encoded size
cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, hf->EncodedSize);
// Load the entire (eventual) header area. This is faster than doing
// two read operations in a row. Read as much as possible. If the file is cut,
// the FileStream will pad it with zeros
if (FileStream_Read(hf->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
{
// Parse the BLTE header
nError = ParseBlteHeader(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
if (nError == ERROR_SUCCESS)
{
// If the headers are larger than the initial read size,
// We read the missing data
cbTotalHeaderSize = cbHeaderSize + (hf->FrameCount * sizeof(BLTE_FRAME));
if (cbTotalHeaderSize > cbEncodedBuffer)
{
pbEncodedBuffer = ReadMissingHeaderData(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize);
if (pbEncodedBuffer == NULL)
nError = GetLastError();
cbEncodedBuffer = cbTotalHeaderSize;
}
// Load the array of frame headers
if (nError == ERROR_SUCCESS)
{
nError = LoadFileFrames(hf, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize);
}
}
}
else
{
nError = ERROR_FILE_CORRUPT;
}
// Free the frame buffer
CASC_FREE(pbEncodedBuffer);
}
else
{
nError = ERROR_NOT_ENOUGH_MEMORY;
}
return nError;
}
static int EnsureFileFramesLoaded(TCascFile * hf)
{
int nError = ERROR_SUCCESS;
// If the encoded frames are not loaded, do it now
if(hf->pFrames == NULL)
{
// We need the data file to be open
nError = EnsureDataStreamIsOpen(hf);
if(nError != ERROR_SUCCESS)
return nError;
// Make sure we have header area loaded
nError = LoadEncodedHeaderAndFileFrames(hf);
}
return nError;
}
static int LoadEncodedFrame(TFileStream * pStream, PCASC_FILE_FRAME pFrame, LPBYTE pbEncodedFrame, bool bVerifyIntegrity)
{
ULONGLONG FileOffset = pFrame->DataFileOffset;
int nError = ERROR_SUCCESS;
// Load the encoded frame to memory
if(FileStream_Read(pStream, &FileOffset, pbEncodedFrame, pFrame->EncodedSize))
{
if (bVerifyIntegrity)
{
if (!CascVerifyDataBlockHash(pbEncodedFrame, pFrame->EncodedSize, pFrame->FrameHash.Value))
nError = ERROR_FILE_CORRUPT;
}
}
else
{
nError = GetLastError();
}
return nError;
}
static int ProcessFileFrame(
TCascStorage * hs,
LPBYTE pbOutBuffer,
DWORD cbOutBuffer,
LPBYTE pbInBuffer,
DWORD cbInBuffer,
DWORD dwFrameIndex)
{
LPBYTE pbWorkBuffer = NULL;
DWORD cbOutBufferExpected = 0;
DWORD cbWorkBuffer = 0;
DWORD dwStepCount = 0;
bool bWorkComplete = false;
int nError = ERROR_SUCCESS;
// Perform the loop
while(bWorkComplete == false)
{
// There should never be a 3rd step
assert(dwStepCount < 2);
// Perform the operation specific by the first byte
switch(pbInBuffer[0])
{
case 'E': // Encrypted files
// The work buffer should not have been allocated by any step
assert(pbWorkBuffer == NULL && cbWorkBuffer == 0);
// Allocate temporary buffer to decrypt into
// Example storage: "2016 - WoW/23420", File: "4ee6bc9c6564227f1748abd0b088e950"
pbWorkBuffer = CASC_ALLOC(BYTE, cbInBuffer - 1);
cbWorkBuffer = cbInBuffer - 1;
if(pbWorkBuffer == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Decrypt the stream to the work buffer
nError = CascDecrypt(hs, pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex);
if(nError != ERROR_SUCCESS)
{
bWorkComplete = true;
break;
}
// When encrypted, there is always one more step after this.
// Setup the work buffer as input buffer for the next operation
pbInBuffer = pbWorkBuffer;
cbInBuffer = cbWorkBuffer;
break;
case 'Z': // ZLIB compressed files
// If we decompressed less than expected, we simply fill the rest with zeros
// Example: INSTALL file from the TACT CASC storage
cbOutBufferExpected = cbOutBuffer;
nError = CascDecompress(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1);
// We exactly know what the output buffer size will be.
// If the uncompressed data is smaller, fill the rest with zeros
if(cbOutBuffer < cbOutBufferExpected)
memset(pbOutBuffer + cbOutBuffer, 0, (cbOutBufferExpected - cbOutBuffer));
bWorkComplete = true;
break;
case 'N': // Normal stored files
nError = CascDirectCopy(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1);
bWorkComplete = true;
break;
case 'F': // Recursive frames - not supported
default: // Unrecognized - if we unpacked something, we consider it done
nError = ERROR_NOT_SUPPORTED;
bWorkComplete = true;
assert(false);
break;
}
// Increment the step count
dwStepCount++;
}
// Free the temporary buffer
CASC_FREE(pbWorkBuffer);
return nError;
}
static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
{
PCASC_FILE_FULL_INFO pFileInfo;
PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
TCascStorage * hs = hf->hs;
// Verify whether we have enough space in the buffer
pFileInfo = (PCASC_FILE_FULL_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_FULL_INFO), pcbLengthNeeded);
if(pFileInfo != NULL)
{
// Reset the entire structure
CopyMemory16(pFileInfo->CKey, pCKeyEntry->CKey);
CopyMemory16(pFileInfo->EKey, pCKeyEntry->EKey);
pFileInfo->FileDataId = CASC_INVALID_ID;
pFileInfo->LocaleFlags = CASC_INVALID_ID;
pFileInfo->ContentFlags = CASC_INVALID_ID;
// Supply information not depending on root
CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->ArchiveIndex);
pFileInfo->StorageOffset = pCKeyEntry->StorageOffset;
pFileInfo->SegmentOffset = hf->ArchiveOffset;
pFileInfo->FileNameHash = 0;
pFileInfo->TagBitMask = pCKeyEntry->TagBitMask;
pFileInfo->SegmentIndex = hf->ArchiveIndex;
pFileInfo->ContentSize = hf->ContentSize;
pFileInfo->EncodedSize = hf->EncodedSize;
// Supply the root-specific information
hs->pRootHandler->GetInfo(pCKeyEntry, pFileInfo);
}
return (pFileInfo != NULL);
}
//-----------------------------------------------------------------------------
// Public functions
bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
{
TCascFile * hf;
LPBYTE pbOutputValue = NULL;
LPBYTE pbInfoValue = NULL;
size_t cbInfoValue = 0;
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// Differentiate between info classes
switch(InfoClass)
{
case CascFileContentKey:
// Do we have content key at all?
if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_CKEY) == 0)
{
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
// Give the content key
pbInfoValue = hf->pCKeyEntry->CKey;
cbInfoValue = CASC_CKEY_SIZE;
break;
case CascFileEncodedKey:
// Do we have content key at all?
if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_EKEY) == 0)
{
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
// Give the content key
pbInfoValue = hf->pCKeyEntry->EKey;
cbInfoValue = CASC_CKEY_SIZE;
break;
case CascFileFullInfo:
return GetFileFullInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
default:
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Sanity check
assert(pbInfoValue != NULL);
assert(cbInfoValue != 0);
// Give the result
pbOutputValue = (LPBYTE)ProbeOutputBuffer(pvFileInfo, cbFileInfo, cbInfoValue, pcbLengthNeeded);
if(pbOutputValue != NULL)
memcpy(pbOutputValue, pbInfoValue, cbInfoValue);
return (pbOutputValue != NULL);
}
//
// THE FILE SIZE PROBLEM
//
// There are members called "FileSize" in many CASC-related structure
// For various files, these variables have different meaning.
//
// Storage FileName FileSize FrameSum HdrArea CKeyEntry EKeyEntry RootEntry
// ----------- -------- ---------- -------- -------- ---------- ---------- ----------
// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 n/a 0x0024BA45 n/a
// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x00193340 0x0010db65 n/a
// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x00001080 0x000008eb 0x00001080
//
// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b n/a 0x030d487b n/a
// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x016a9800 0x0131313d n/a
// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x000007d0 0x00000397 n/a
//
DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
{
TCascFile * hf;
int nError;
CASCLIB_UNUSED(pdwFileSizeHigh);
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return CASC_INVALID_SIZE;
}
// Someone may have provided file content size.
// If yes, do not load the frames, as it's not necessary.
if(hf->ContentSize == CASC_INVALID_SIZE)
{
// Make sure that the file header area is loaded
nError = EnsureFileFramesLoaded(hf);
if(nError != ERROR_SUCCESS)
{
SetLastError(nError);
return CASC_INVALID_SIZE;
}
// The content size should be loaded from the frames
assert(hf->ContentSize != CASC_INVALID_SIZE);
}
// Give the file size to the caller
if(pdwFileSizeHigh != NULL)
*pdwFileSizeHigh = 0;
return hf->ContentSize;
}
DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod)
{
TCascFile * hf;
ULONGLONG FilePosition;
ULONGLONG MoveOffset;
DWORD dwFilePosHi;
// If the hFile is not a valid file handle, return an error.
hf = TCascFile::IsValid(hFile);
if(hf == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return CASC_INVALID_POS;
}
// Get the relative point where to move from
switch(dwMoveMethod)
{
case FILE_BEGIN:
FilePosition = 0;
break;
case FILE_CURRENT:
FilePosition = hf->FilePointer;
break;
case FILE_END:
FilePosition = hf->ContentSize;
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return CASC_INVALID_POS;
}
// Now get the move offset. Note that both values form
// a signed 64-bit value (a file pointer can be moved backwards)
if(plFilePosHigh != NULL)
dwFilePosHi = *plFilePosHigh;
else
dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0;
MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos);
// Now calculate the new file pointer
// Do not allow the file pointer to overflow
FilePosition = ((FilePosition + MoveOffset) >= FilePosition) ? (FilePosition + MoveOffset) : 0;
// CASC files can't be bigger than 4 GB.
// We don't allow to go past 4 GB
if(FilePosition >> 32)
{
SetLastError(ERROR_INVALID_PARAMETER);
return CASC_INVALID_POS;
}
// Change the file position
hf->FilePointer = (DWORD)FilePosition;
// Return the new file position
if(plFilePosHigh != NULL)
*plFilePosHigh = 0;
return hf->FilePointer;
}
bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead)
{
TCascFile * hf;
int nError = ERROR_SUCCESS;
// The buffer must be valid
if(pvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// If the file frames are not loaded yet, do it now
if(nError == ERROR_SUCCESS)
{
nError = EnsureFileFramesLoaded(hf);
}
// If the file position is at or beyond end of file, do nothing
if(nError == ERROR_SUCCESS)
{
// Check the starting position
if(hf->FilePointer >= hf->ContentSize)
{
*pdwBytesRead = 0;
return true;
}
// Check the ending position
if((hf->FilePointer + dwBytesToRead) > hf->ContentSize)
{
dwBytesToRead = hf->ContentSize - hf->FilePointer;
}
}
// Allocate cache buffer for the entire file. This is the fastest approach
// (without reallocations). However, this may consume quite a lot of memory
// (Storage: "2016 - Starcraft II/45364", file: "3d815f40c0413701aa2bd214070d0062"
// needs 0x239a09b3 bytes of memory (~600 MB)
if(nError == ERROR_SUCCESS)
{
if(hf->pbFileCache == NULL)
{
// Allocate buffer
hf->pbFileCache = CASC_ALLOC(BYTE, hf->ContentSize);
hf->cbFileCache = hf->ContentSize;
if(hf->pbFileCache == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
// Load all frames that are not loaded yet
if(nError == ERROR_SUCCESS)
{
PCASC_FILE_FRAME pFrame = hf->pFrames;
DWORD StartFrameOffset = 0;
DWORD StartReadOffset = hf->FilePointer;
DWORD EndReadOffset = hf->FilePointer + dwBytesToRead;
for(DWORD i = 0; (i < hf->FrameCount) && (nError == ERROR_SUCCESS); i++, pFrame++)
{
LPBYTE pbDecodedFrame = hf->pbFileCache + StartFrameOffset;
LPBYTE pbEncodedFrame;
DWORD EndFrameOffset = StartFrameOffset + pFrame->ContentSize;
// Does that frame belong to the range?
if(StartReadOffset < EndFrameOffset && EndReadOffset > StartFrameOffset)
{
// Is the frame already loaded?
if (pFrame->FileOffset == CASC_INVALID_POS)
{
// Allocate space for the encoded frame
pbEncodedFrame = CASC_ALLOC(BYTE, pFrame->EncodedSize);
if (pbEncodedFrame != NULL)
{
// Load the encoded frame data
nError = LoadEncodedFrame(hf->pStream, pFrame, pbEncodedFrame, hf->bVerifyIntegrity);
if (nError == ERROR_SUCCESS)
{
// Decode the frame
nError = ProcessFileFrame(hf->hs,
pbDecodedFrame,
pFrame->ContentSize,
pbEncodedFrame,
pFrame->EncodedSize,
(DWORD)(pFrame - hf->pFrames));
// Some people find it handy to extract data from partially encrypted file,
// even at the cost producing files that are corrupt.
// We overcome missing decryption key by zeroing the encrypted portions
if(nError == ERROR_FILE_ENCRYPTED && hf->bOvercomeEncrypted)
{
memset(pbDecodedFrame, 0, pFrame->ContentSize);
nError = ERROR_SUCCESS;
}
if (nError == ERROR_SUCCESS)
{
// Mark the frame as loaded
pFrame->FileOffset = StartFrameOffset;
}
}
// Free the frame buffer
CASC_FREE(pbEncodedFrame);
}
else
{
nError = ERROR_NOT_ENOUGH_MEMORY;
}
}
}
// If the frame start is past the read offset, stop the loop
if ((StartFrameOffset + pFrame->ContentSize) >= EndReadOffset)
break;
StartFrameOffset += pFrame->ContentSize;
}
}
// Now all frames have been loaded into the cache; copy the entire block to the output buffer
if(nError == ERROR_SUCCESS)
{
// Copy the entire data
memcpy(pvBuffer, hf->pbFileCache + hf->FilePointer, dwBytesToRead);
hf->FilePointer += dwBytesToRead;
// Give the number of bytes read
if(pdwBytesRead != NULL)
*pdwBytesRead = dwBytesToRead;
return true;
}
else
{
SetLastError(nError);
return false;
}
}