aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/CascReadFile.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2019-08-10 19:01:24 +0200
committerShauren <shauren.trinity@gmail.com>2019-08-10 19:01:24 +0200
commitcd720efbfa60f434f420ab66e220eca742c48e45 (patch)
tree3c960ac5249d0711b71fbfdc62f6c0b665ed85ee /dep/CascLib/src/CascReadFile.cpp
parent0d6320dfd3932865edb69c8528327b767bd476ef (diff)
Dep/CascLib: Update to ladislav-zezula/CascLib@b91f87c770c78340dcd96df970e55b5c0469e884
Diffstat (limited to 'dep/CascLib/src/CascReadFile.cpp')
-rw-r--r--dep/CascLib/src/CascReadFile.cpp1045
1 files changed, 737 insertions, 308 deletions
diff --git a/dep/CascLib/src/CascReadFile.cpp b/dep/CascLib/src/CascReadFile.cpp
index 2f444d0d80f..aa2a718198c 100644
--- a/dep/CascLib/src/CascReadFile.cpp
+++ b/dep/CascLib/src/CascReadFile.cpp
@@ -15,62 +15,97 @@
//-----------------------------------------------------------------------------
// Local functions
-static int EnsureDataStreamIsOpen(TCascFile * hf)
+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;
- ULONGLONG EncodedSize = 0;
- TCHAR * szDataFile;
TCHAR szCachePath[MAX_PATH];
+ TCHAR szDataFile[MAX_PATH];
TCHAR szPlainName[0x80];
- int nError;
+ DWORD dwErrCode;
// 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(pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL)
{
+ DWORD dwArchiveIndex = pFileSpan->ArchiveIndex;
+
// If the file is not open yet, do it
- if(hs->DataFiles[hf->ArchiveIndex] == NULL)
+ if(hs->DataFiles[dwArchiveIndex] == NULL)
{
// Prepare the name of the data file
- CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), hf->ArchiveIndex);
- szDataFile = CombinePath(hs->szIndexPath, szPlainName);
+ CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), dwArchiveIndex);
+ CombinePath(szDataFile, _countof(szDataFile), PATH_SEP_CHAR, hs->szIndexPath, szPlainName, NULL);
- // 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);
- }
+ // 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[dwArchiveIndex] = pStream;
}
// Return error or success
- hf->pStream = hs->DataFiles[hf->ArchiveIndex];
- return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
+ pFileSpan->pStream = hs->DataFiles[dwArchiveIndex];
+ return (pFileSpan->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
}
else
{
- if(hf->bDownloadFileIf)
+ if(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)
+ CASC_CDN_DOWNLOAD CdnsInfo = {0};
+ LPCTSTR szPathType = (pCKeyEntry->Flags & CASC_CE_FILE_PATCH) ? _T("patch") : _T("data");
+
+ // Prepare the download structure for "%CDNS_HOST%/%CDNS_PATH%/##/##/EKey" file
+ CdnsInfo.szCdnsPath = hs->szCdnPath;
+ CdnsInfo.szPathType = szPathType;
+ CdnsInfo.pbEKey = pCKeyEntry->EKey;
+ CdnsInfo.szLocalPath = szCachePath;
+ CdnsInfo.ccLocalPath = _countof(szCachePath);
+
+ // Download the file from CDN
+ dwErrCode = DownloadFileFromCDN(hs, CdnsInfo);
+ if(dwErrCode == ERROR_SUCCESS)
{
- hf->pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
- if(hf->pStream != NULL)
+ pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
+ if(pStream != NULL)
{
- // Supply the file size, if unknown yet
- if(hf->EncodedSize == CASC_INVALID_SIZE)
+ // 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(CdnsInfo.pbArchiveKey != NULL)
{
- FileStream_GetSize(hf->pStream, &EncodedSize);
- hf->pCKeyEntry->EncodedSize = (DWORD)EncodedSize;
- hf->EncodedSize = (DWORD)EncodedSize;
+ // Archive position
+ pFileSpan->ArchiveIndex = CdnsInfo.ArchiveIndex;
+ pFileSpan->ArchiveOffs = (DWORD)CdnsInfo.ArchiveOffs;
+
+ // Encoded size
+ if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE)
+ pCKeyEntry->EncodedSize = CdnsInfo.EncodedSize;
+ assert(pCKeyEntry->EncodedSize == CdnsInfo.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));
}
- hf->bLocalFileStream = true;
+ // We need to close the file stream after we're done
+ pFileSpan->pStream = pStream;
+ hf->bCloseFileStream = true;
return ERROR_SUCCESS;
}
}
@@ -125,7 +160,7 @@ static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderO
}
#endif
-static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize)
+static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize)
{
PBLTE_ENCODED_HEADER pEncodedHeader = (PBLTE_ENCODED_HEADER)pbEncodedBuffer;
PBLTE_HEADER pBlteHeader = (PBLTE_HEADER)pbEncodedBuffer;
@@ -143,12 +178,12 @@ static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEnco
// There must be at least some bytes
if (cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F))
return ERROR_BAD_FORMAT;
- if (pEncodedHeader->EncodedSize != hf->EncodedSize)
+ if (pEncodedHeader->EncodedSize != pCKeyEntry->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);
+ //assert(memcmp(pCKeyEntry->EKey, pEncodedHeader->EKey.Value, MD5_HASH_SIZE) == 0);
VerifyHeaderSpan(pEncodedHeader, HeaderOffset);
#endif
// Capture the EKey
@@ -183,11 +218,11 @@ static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEnco
}
// Give the frame count
- hf->FrameCount = FrameCount;
+ pFileSpan->FrameCount = FrameCount;
return ERROR_SUCCESS;
}
-static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize)
+static LPBYTE ReadMissingHeaderData(PCASC_FILE_SPAN pFileSpan, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize)
{
LPBYTE pbNewBuffer;
@@ -197,7 +232,7 @@ static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LP
{
// Load the missing data
DataFileOffset += cbEncodedBuffer;
- if (FileStream_Read(hf->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
+ if (FileStream_Read(pFileSpan->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
{
return pbNewBuffer;
}
@@ -208,138 +243,200 @@ static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LP
return NULL;
}
-static int LoadFileFrames(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize)
+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, DWORD DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize)
{
- PBLTE_FRAME pFileFrame;
+ PCASC_FILE_FRAME pFrames = NULL;
DWORD ContentSize = 0;
- DWORD FileOffset = 0;
- int nError = ERROR_SUCCESS;
+ DWORD dwErrCode = ERROR_SUCCESS;
- assert(hf != NULL);
- assert(hf->pStream != NULL);
- assert(hf->pFrames == NULL);
+ assert(pFileSpan != NULL);
+ assert(pFileSpan->pStream != NULL);
+ assert(pFileSpan->pFrames == NULL);
- if (hf->FrameCount != 0)
+ if (pFileSpan->FrameCount != 0)
{
// Move the raw archive offset
- DataFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME));
+ DataFileOffset += (pFileSpan->FrameCount * sizeof(BLTE_FRAME));
// Allocate array of file frames
- hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount);
- if (hf->pFrames != NULL)
+ pFrames = CASC_ALLOC<CASC_FILE_FRAME>(pFileSpan->FrameCount);
+ if (pFrames != NULL)
{
// Copy the frames to the file structure
- for (DWORD i = 0; i < hf->FrameCount; i++, pbFramePtr += sizeof(BLTE_FRAME))
+ for (DWORD i = 0; i < pFileSpan->FrameCount; i++)
{
- // 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;
+ 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(hf->pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
+ if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
{
- hf->pCKeyEntry->ContentSize = ContentSize;
- hf->ContentSize = ContentSize;
+ pCKeyEntry->ContentSize = ContentSize;
}
}
+ else
+ {
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+ }
}
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)
+ // Allocate single "dummy" frame
+ pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1);
+ if (pFrames != NULL)
{
- assert(false);
- return ERROR_CAN_NOT_COMPLETE;
+ // 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;
}
-
- // 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)
+ else
{
- 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;
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
}
}
- if (hf->pFrames == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
- return nError;
+ // Free the frame array on error
+ if(dwErrCode != ERROR_SUCCESS)
+ {
+ pFileSpan->FrameCount = 0;
+ CASC_FREE(pFrames);
+ }
+
+ pFileSpan->pFrames = pFrames;
+ return dwErrCode;
}
-static int LoadEncodedHeaderAndFileFrames(TCascFile * hf)
+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;
- int nError = ERROR_SUCCESS;
+ DWORD dwErrCode = ERROR_SUCCESS;
// Should only be called when the file frames are NOT loaded
- assert(hf->pFrames == NULL);
- assert(hf->FrameCount == 0);
+ assert(pFileSpan->pFrames == NULL);
+ assert(pFileSpan->FrameCount == 0);
// Allocate the initial buffer for the encoded headers
- pbEncodedBuffer = CASC_ALLOC(BYTE, MAX_ENCODED_HEADER);
+ pbEncodedBuffer = CASC_ALLOC<BYTE>(MAX_ENCODED_HEADER);
if (pbEncodedBuffer != NULL)
{
- ULONGLONG ReadOffset = hf->ArchiveOffset;
+ ULONGLONG ReadOffset = pFileSpan->ArchiveOffs;
size_t cbTotalHeaderSize;
size_t cbHeaderSize = 0;
// At this point, we expect encoded size to be known
- assert(hf->EncodedSize != CASC_INVALID_SIZE);
+ assert(pCKeyEntry->EncodedSize != CASC_INVALID_SIZE);
// Do not read more than encoded size
- cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, hf->EncodedSize);
+ 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(hf->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
+ if (FileStream_Read(pFileSpan->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
{
// Parse the BLTE header
- nError = ParseBlteHeader(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
- if (nError == ERROR_SUCCESS)
+ dwErrCode = ParseBlteHeader(pFileSpan, pCKeyEntry, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
+ if (dwErrCode == 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 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(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize);
+ pbEncodedBuffer = ReadMissingHeaderData(pFileSpan, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize);
if (pbEncodedBuffer == NULL)
- nError = GetLastError();
+ dwErrCode = GetLastError();
cbEncodedBuffer = cbTotalHeaderSize;
}
// Load the array of frame headers
- if (nError == ERROR_SUCCESS)
+ if (dwErrCode == ERROR_SUCCESS)
+ {
+ assert((DWORD)(ReadOffset + cbHeaderSize) > (DWORD)ReadOffset);
+ dwErrCode = LoadSpanFrames(pFileSpan, pCKeyEntry, (DWORD)(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)
{
- nError = LoadFileFrames(hf, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize);
+ dwErrCode = LoadSpanFramesForPlainFile(pFileSpan, pCKeyEntry);
}
}
}
else
{
- nError = ERROR_FILE_CORRUPT;
+ dwErrCode = ERROR_FILE_CORRUPT;
}
// Free the frame buffer
@@ -347,67 +444,137 @@ static int LoadEncodedHeaderAndFileFrames(TCascFile * hf)
}
else
{
- nError = ERROR_NOT_ENOUGH_MEMORY;
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
}
- return nError;
+ return dwErrCode;
}
-static int EnsureFileFramesLoaded(TCascFile * hf)
+static DWORD LoadSpanFrames(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry)
{
- int nError = ERROR_SUCCESS;
+ DWORD dwErrCode = 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;
+ // Sanity check
+ assert(pFileSpan->pFrames == NULL);
- // Make sure we have header area loaded
- nError = LoadEncodedHeaderAndFileFrames(hf);
+ // 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;
}
- return nError;
+ // Make sure we have header area loaded
+ return LoadEncodedHeaderAndSpanFrames(pFileSpan, pCKeyEntry);
}
-static int LoadEncodedFrame(TFileStream * pStream, PCASC_FILE_FRAME pFrame, LPBYTE pbEncodedFrame, bool bVerifyIntegrity)
+// Loads all file spans to memory
+static DWORD LoadFileSpanFrames(TCascFile * hf)
{
- ULONGLONG FileOffset = pFrame->DataFileOffset;
- int nError = ERROR_SUCCESS;
+ PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry;
+ PCASC_FILE_SPAN pFileSpan = hf->pFileSpan;
+ DWORD dwErrCode = ERROR_SUCCESS;
- // Load the encoded frame to memory
- if(FileStream_Read(pStream, &FileOffset, pbEncodedFrame, pFrame->EncodedSize))
+ // 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)
{
- if (bVerifyIntegrity)
+ // 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++)
{
- if (!CascVerifyDataBlockHash(pbEncodedFrame, pFrame->EncodedSize, pFrame->FrameHash.Value))
- nError = ERROR_FILE_CORRUPT;
+ // 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
{
- nError = GetLastError();
+ // 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 nError;
+ return dwErrCode;
}
-static int ProcessFileFrame(
- TCascStorage * hs,
- LPBYTE pbOutBuffer,
- DWORD cbOutBuffer,
- LPBYTE pbInBuffer,
- DWORD cbInBuffer,
- DWORD dwFrameIndex)
+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 cbOutBufferExpected = 0;
+ DWORD cbDecodedExpected = 0;
DWORD cbWorkBuffer = 0;
DWORD dwStepCount = 0;
+ DWORD dwErrCode = ERROR_SUCCESS;
+ DWORD cbEncoded = pFrame->EncodedSize;
+ DWORD cbDecoded = pFrame->ContentSize;
bool bWorkComplete = false;
- int nError = ERROR_SUCCESS;
+
+ //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)
@@ -416,7 +583,7 @@ static int ProcessFileFrame(
assert(dwStepCount < 2);
// Perform the operation specific by the first byte
- switch(pbInBuffer[0])
+ switch(pbEncoded[0])
{
case 'E': // Encrypted files
@@ -425,14 +592,14 @@ static int ProcessFileFrame(
// Allocate temporary buffer to decrypt into
// Example storage: "2016 - WoW/23420", File: "4ee6bc9c6564227f1748abd0b088e950"
- pbWorkBuffer = CASC_ALLOC(BYTE, cbInBuffer - 1);
- cbWorkBuffer = cbInBuffer - 1;
+ pbWorkBuffer = CASC_ALLOC<BYTE>(cbEncoded - 1);
+ cbWorkBuffer = cbEncoded - 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)
+ dwErrCode = CascDecrypt(hs, pbWorkBuffer, &cbWorkBuffer, pbEncoded + 1, cbEncoded - 1, FrameIndex);
+ if(dwErrCode != ERROR_SUCCESS)
{
bWorkComplete = true;
break;
@@ -440,32 +607,32 @@ static int ProcessFileFrame(
// 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;
+ 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
- cbOutBufferExpected = cbOutBuffer;
- nError = CascDecompress(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1);
+ 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(cbOutBuffer < cbOutBufferExpected)
- memset(pbOutBuffer + cbOutBuffer, 0, (cbOutBufferExpected - cbOutBuffer));
+ if(cbDecoded < cbDecodedExpected)
+ memset(pbDecoded + cbDecoded, 0, (cbDecodedExpected - cbDecoded));
bWorkComplete = true;
break;
case 'N': // Normal stored files
- nError = CascDirectCopy(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1);
+ dwErrCode = CascDirectCopy(pbDecoded, &cbDecoded, pbEncoded + 1, cbEncoded - 1);
bWorkComplete = true;
break;
- case 'F': // Recursive frames - not supported
- default: // Unrecognized - if we unpacked something, we consider it done
- nError = ERROR_NOT_SUPPORTED;
+ case 'F': // Recursive frames (not supported)
+ default: // Unrecognized. Could be a plain file data
+ dwErrCode = ERROR_NOT_SUPPORTED;
bWorkComplete = true;
assert(false);
break;
@@ -475,9 +642,18 @@ static int ProcessFileFrame(
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 nError;
+ return dwErrCode;
}
static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded)
@@ -485,6 +661,15 @@ static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo
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)
+ {
+ SetLastError(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);
@@ -498,14 +683,15 @@ static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo
pFileInfo->ContentFlags = CASC_INVALID_ID;
// Supply information not depending on root
- CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->ArchiveIndex);
+ CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->pFileSpan->ArchiveIndex);
pFileInfo->StorageOffset = pCKeyEntry->StorageOffset;
- pFileInfo->SegmentOffset = hf->ArchiveOffset;
+ pFileInfo->SegmentOffset = hf->pFileSpan->ArchiveOffs;
pFileInfo->FileNameHash = 0;
pFileInfo->TagBitMask = pCKeyEntry->TagBitMask;
- pFileInfo->SegmentIndex = hf->ArchiveIndex;
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);
@@ -514,6 +700,257 @@ static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo
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)
+ {
+ SetLastError(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)
+ {
+ SetLastError(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;
+ ULONGLONG ByteOffset;
+ 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)
+ {
+ SetLastError(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)
+ {
+ SetLastError(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);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return 0;
+ }
+
+ // Load the frame to the encoded buffer
+ ByteOffset = pFileFrame->DataFileOffset;
+ if(FileStream_Read(pFileSpan->pStream, &ByteOffset, 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.
+ SetLastError(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
@@ -565,6 +1002,9 @@ bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void *
case CascFileFullInfo:
return GetFileFullInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+ case CascFileSpanInfo:
+ return GetFileSpanInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+
default:
SetLastError(ERROR_INVALID_PARAMETER);
return false;
@@ -598,55 +1038,64 @@ bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void *
// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x000007d0 0x00000397 n/a
//
-DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
+bool WINAPI CascGetFileSize64(HANDLE hFile, PULONGLONG PtrFileSize)
{
TCascFile * hf;
- int nError;
-
- CASCLIB_UNUSED(pdwFileSizeHigh);
+ DWORD dwErrCode;
// Validate the file handle
if((hf = TCascFile::IsValid(hFile)) == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
- return CASC_INVALID_SIZE;
+ return false;
}
- // 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)
+ // Validate the file pointer
+ if(PtrFileSize == NULL)
{
- // Make sure that the file header area is loaded
- nError = EnsureFileFramesLoaded(hf);
- if(nError != ERROR_SUCCESS)
- {
- SetLastError(nError);
- return CASC_INVALID_SIZE;
- }
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
- // The content size should be loaded from the frames
- assert(hf->ContentSize != CASC_INVALID_SIZE);
+ // 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)
+ {
+ SetLastError(dwErrCode);
+ return false;
}
// Give the file size to the caller
- if(pdwFileSizeHigh != NULL)
- *pdwFileSizeHigh = 0;
- return hf->ContentSize;
+ PtrFileSize[0] = hf->ContentSize;
+ return true;
}
-DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod)
+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)
{
- TCascFile * hf;
ULONGLONG FilePosition;
- ULONGLONG MoveOffset;
- DWORD dwFilePosHi;
+ TCascFile * hf;
// 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;
+ return false;
}
// Get the relative point where to move from
@@ -666,42 +1115,73 @@ DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHig
default:
SetLastError(ERROR_INVALID_PARAMETER);
- return CASC_INVALID_POS;
+ return false;
}
- // 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;
+ if(DistanceToMove >= 0)
+ {
+ // Do not allow the file pointer to overflow 64-bit range
+ if((FilePosition + DistanceToMove) < FilePosition)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
- // CASC files can't be bigger than 4 GB.
- // We don't allow to go past 4 GB
- if(FilePosition >> 32)
+ // Do not allow the file pointer to overflow the file size
+ if((FilePosition = FilePosition + DistanceToMove) > hf->ContentSize)
+ FilePosition = hf->ContentSize;
+ hf->FilePointer = FilePosition;
+ }
+ else
{
- SetLastError(ERROR_INVALID_PARAMETER);
- return CASC_INVALID_POS;
+ // Do not allow the file pointer to underflow 64-bit range
+ if((FilePosition + DistanceToMove) > FilePosition)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // Do not allow the file pointer to move to negative values
+ if((FilePosition = FilePosition + DistanceToMove) < 0)
+ FilePosition = 0;
+ hf->FilePointer = FilePosition;
}
- // Change the file position
- hf->FilePointer = (DWORD)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;
- // Return the new file position
- if(plFilePosHigh != NULL)
- *plFilePosHigh = 0;
- return hf->FilePointer;
+ // 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 pdwBytesRead)
+bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD PtrBytesRead)
{
+ ULONGLONG SaveFilePointer;
+ ULONGLONG StartOffset;
+ ULONGLONG EndOffset;
TCascFile * hf;
- int nError = ERROR_SUCCESS;
+ LPBYTE pbBuffer = (LPBYTE)pvBuffer;
+ DWORD dwBytesRead1 = 0; // From cache
+ DWORD dwBytesRead2 = 0; // From file
+ DWORD dwErrCode;
// The buffer must be valid
if(pvBuffer == NULL)
@@ -717,129 +1197,78 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
return false;
}
- // If the file frames are not loaded yet, do it now
- if(nError == ERROR_SUCCESS)
+ // 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)
{
- nError = EnsureFileFramesLoaded(hf);
+ SetLastError(dwErrCode);
+ return false;
}
// If the file position is at or beyond end of file, do nothing
- if(nError == ERROR_SUCCESS)
+ SaveFilePointer = StartOffset = hf->FilePointer;
+ if(StartOffset >= hf->ContentSize)
{
- // 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;
- }
+ PtrBytesRead[0] = 0;
+ return true;
}
- // 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 the read area goes beyond end of the file, cut the number of bytes to read
+ EndOffset = StartOffset + dwBytesToRead;
+ if(EndOffset > hf->ContentSize)
{
- 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;
- }
+ EndOffset = hf->ContentSize;
}
- // Load all frames that are not loaded yet
- if(nError == ERROR_SUCCESS)
+ // Can we handle the request (at least partially) from the cache?
+ if((dwBytesRead1 = ReadFile_Cache(hf, pbBuffer, StartOffset, EndOffset)) != 0)
{
- PCASC_FILE_FRAME pFrame = hf->pFrames;
- DWORD StartFrameOffset = 0;
- DWORD StartReadOffset = hf->FilePointer;
- DWORD EndReadOffset = hf->FilePointer + dwBytesToRead;
+ // Move pointers
+ StartOffset = StartOffset + dwBytesRead1;
+ pbBuffer += dwBytesRead1;
- for(DWORD i = 0; (i < hf->FrameCount) && (nError == ERROR_SUCCESS); i++, pFrame++)
+ // Has the read request been fully satisfied?
+ if(StartOffset == EndOffset)
{
- 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;
+ if(PtrBytesRead != NULL)
+ PtrBytesRead[0] = dwBytesRead1;
+ hf->FilePointer = EndOffset;
+ return true;
}
}
- // Now all frames have been loaded into the cache; copy the entire block to the output buffer
- if(nError == ERROR_SUCCESS)
+ // Perform the cache-strategy-specific read
+ switch(hf->CacheStrategy)
{
- // Copy the entire data
- memcpy(pvBuffer, hf->pbFileCache + hf->FilePointer, dwBytesToRead);
- hf->FilePointer += dwBytesToRead;
+ // 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;
- // Give the number of bytes read
- if(pdwBytesRead != NULL)
- *pdwBytesRead = dwBytesToRead;
+ // 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;
+ }
+
+ // 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
{
- SetLastError(nError);
+ // Give the result to the caller
+ if(PtrBytesRead != NULL)
+ PtrBytesRead[0] = 0;
+ hf->FilePointer = SaveFilePointer;
return false;
}
}