summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLadislav <Zezula>2013-12-22 12:47:26 +0100
committerLadislav <Zezula>2013-12-22 12:47:26 +0100
commit3dd8d1198c46c42a47cd6089e9dd99b9dfdaa798 (patch)
tree75f88998ddb4e09187c163a50680a212a4403643
parentebd502e0c220f7c2d3b9ce65bd83ed569d65f314 (diff)
+ MPQ Bitmap processign was moved to TFileStream
+ Preparing for implementation of master-mirror streaming (like Blizzard games do)
-rw-r--r--src/FileStream.cpp2163
-rw-r--r--src/FileStream.h130
-rw-r--r--src/SFileCompactArchive.cpp2
-rw-r--r--src/SFileGetFileInfo.cpp14
-rw-r--r--src/SFileOpenArchive.cpp7
-rw-r--r--src/StormLib.h30
-rw-r--r--test/Test.cpp192
7 files changed, 1243 insertions, 1295 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp
index 403d1d4..58543e3 100644
--- a/src/FileStream.cpp
+++ b/src/FileStream.cpp
@@ -48,100 +48,125 @@ void SetLastError(int nError)
#endif
//-----------------------------------------------------------------------------
-// Preparing file bitmap for a complete file of a given size
+// Dummy init function
-static bool FileBitmap_CheckRange(
- TFileBitmap * pBitmap,
- ULONGLONG ByteOffset,
- ULONGLONG EndOffset)
+static void BaseNone_Init(TFileStream *)
{
- LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1);
- DWORD BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize);
- DWORD ByteIndex = (BlockIndex / 0x08);
- BYTE BitMask = (BYTE)(0x01 << (BlockIndex & 0x07));
+ // Nothing here
+}
- // Mask the bye offset down to the begin of the block
- ByteOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1);
+//-----------------------------------------------------------------------------
+// Local functions - base file support
- // Check each block
- while(ByteOffset < EndOffset)
+static bool BaseFile_Create(TFileStream * pStream)
+{
+#ifdef PLATFORM_WINDOWS
{
- // Check availability of that block
- if((pbBitmap[ByteIndex] & BitMask) == 0)
+ DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
+
+ pStream->Base.File.hFile = CreateFile(pStream->szFileName,
+ GENERIC_READ | GENERIC_WRITE,
+ dwWriteShare | FILE_SHARE_READ,
+ NULL,
+ CREATE_ALWAYS,
+ 0,
+ NULL);
+ if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
return false;
+ }
+#endif
- // Move to the next block
- ByteOffset += pBitmap->BlockSize;
- ByteIndex += (BitMask >> 0x07);
- BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ intptr_t handle;
+
+ handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if(handle == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ pStream->Base.File.hFile = (HANDLE)handle;
}
+#endif
- // All blocks are present
+ // Reset the file size and position
+ pStream->Base.File.FileSize = 0;
+ pStream->Base.File.FilePos = 0;
return true;
}
-static void FileBitmap_SetRange(
- TFileBitmap * pBitmap,
- ULONGLONG ByteOffset,
- ULONGLONG EndOffset)
+static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags)
{
- LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1);
- DWORD BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize);
- DWORD ByteIndex = (BlockIndex / 0x08);
- BYTE BitMask = (BYTE)(0x01 << (BlockIndex & 0x07));
-
- // Mask the bye offset down to the begin of the block
- ByteOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1);
-
- // Check each block
- while(ByteOffset < EndOffset)
+#ifdef PLATFORM_WINDOWS
{
- // Set that bit
- pbBitmap[ByteIndex] |= BitMask;
+ ULARGE_INTEGER FileSize;
+ DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES;
+ DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
- // Move to the next block
- ByteOffset += pBitmap->BlockSize;
- ByteIndex += (BitMask >> 0x07);
- BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
- }
-}
+ // Open the file
+ pStream->Base.File.hFile = CreateFile(pStream->szFileName,
+ FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess,
+ FILE_SHARE_READ | dwWriteShare,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
+ return false;
-static DWORD FileBitmap_CheckFile(TFileBitmap * pBitmap)
-{
- LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1);
- DWORD WholeByteCount = (pBitmap->BlockCount / 8);
- DWORD ExtraBitsCount = (pBitmap->BlockCount & 7);
- BYTE ExpectedValue;
+ // Query the file size
+ FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart);
+ pStream->Base.File.FileSize = FileSize.QuadPart;
- // Verify the whole bytes - their value must be 0xFF
- for(DWORD i = 0; i < WholeByteCount; i++)
- {
- if(pbBitmap[i] != 0xFF)
- return 0;
+ // Query last write time
+ GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime);
}
+#endif
- // If there are extra bits, calculate the mask
- if(ExtraBitsCount != 0)
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
{
- ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1);
- if(pbBitmap[WholeByteCount] != ExpectedValue)
- return 0;
+ struct stat64 fileinfo;
+ int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR;
+ intptr_t handle;
+
+ // Open the file
+ handle = open(szFileName, oflag | O_LARGEFILE);
+ if(handle == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ // Get the file size
+ if(fstat64(handle, &fileinfo) == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ // time_t is number of seconds since 1.1.1970, UTC.
+ // 1 second = 10000000 (decimal) in FILETIME
+ // Set the start to 1.1.1970 00:00:00
+ pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime);
+ pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size;
+ pStream->Base.File.hFile = (HANDLE)handle;
}
+#endif
- // Yes, the file is complete
- return 1;
+ // Reset the file position
+ pStream->Base.File.FilePos = 0;
+ return true;
}
-//-----------------------------------------------------------------------------
-// Local functions - base file support
-
static bool BaseFile_Read(
TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
void * pvBuffer, // Pointer to data to be read
DWORD dwBytesToRead) // Number of bytes to read from the file
{
- ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos;
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
DWORD dwBytesRead = 0; // Must be set by platform-specific code
#ifdef PLATFORM_WINDOWS
@@ -152,7 +177,7 @@ static bool BaseFile_Read(
// one system call to SetFilePointer
// Update the byte offset
- pStream->FilePos = ByteOffset;
+ pStream->Base.File.FilePos = ByteOffset;
// Read the data
if(dwBytesToRead != 0)
@@ -197,7 +222,7 @@ static bool BaseFile_Read(
// Increment the current file position by number of bytes read
// If the number of bytes read doesn't match to required amount, return false
- pStream->FilePos = ByteOffset + dwBytesRead;
+ pStream->Base.File.FilePos = ByteOffset + dwBytesRead;
if(dwBytesRead != dwBytesToRead)
SetLastError(ERROR_HANDLE_EOF);
return (dwBytesRead == dwBytesToRead);
@@ -212,7 +237,7 @@ static bool BaseFile_Read(
static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
{
- ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos;
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
DWORD dwBytesWritten = 0; // Must be set by platform-specific code
#ifdef PLATFORM_WINDOWS
@@ -223,7 +248,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
// one system call to SetFilePointer
// Update the byte offset
- pStream->FilePos = ByteOffset;
+ pStream->Base.File.FilePos = ByteOffset;
// Read the data
if(dwBytesToWrite != 0)
@@ -264,11 +289,11 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
#endif
// Increment the current file position by number of bytes read
- pStream->FilePos = ByteOffset + dwBytesWritten;
+ pStream->Base.File.FilePos = ByteOffset + dwBytesWritten;
// Also modify the file size, if needed
- if(pStream->FilePos > pStream->FileSize)
- pStream->FileSize = pStream->FilePos;
+ if(pStream->Base.File.FilePos > pStream->Base.File.FileSize)
+ pStream->Base.File.FileSize = pStream->Base.File.FilePos;
if(dwBytesWritten != dwBytesToWrite)
SetLastError(ERROR_DISK_FULL);
@@ -279,7 +304,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
* \a pStream Pointer to an open stream
* \a NewFileSize New size of the file
*/
-static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
+static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize)
{
#ifdef PLATFORM_WINDOWS
{
@@ -297,8 +322,8 @@ static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
bResult = (bool)SetEndOfFile(pStream->Base.File.hFile);
// Restore the file position
- FileSizeHi = (LONG)(pStream->FilePos >> 32);
- FileSizeLo = (LONG)(pStream->FilePos);
+ FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32);
+ FileSizeLo = (LONG)(pStream->Base.File.FilePos);
SetFilePointer(pStream->Base.File.hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN);
return bResult;
}
@@ -317,8 +342,26 @@ static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
#endif
}
+// Gives the current file size
+static bool BaseFile_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
+{
+ // Note: Used by all thre base providers.
+ // Requires the TBaseData union to have the same layout for all three base providers
+ *pFileSize = pStream->Base.File.FileSize;
+ return true;
+}
+
+// Gives the current file position
+static bool BaseFile_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ // Note: Used by all thre base providers.
+ // Requires the TBaseData union to have the same layout for all three base providers
+ *pByteOffset = pStream->Base.File.FilePos;
+ return true;
+}
+
// Renames the file pointed by pStream so that it contains data from pNewStream
-static bool BaseFile_Switch(TFileStream * pStream, TFileStream * pNewStream)
+static bool BaseFile_Replace(TFileStream * pStream, TFileStream * pNewStream)
{
#ifdef PLATFORM_WINDOWS
// Delete the original stream file. Don't check the result value,
@@ -358,163 +401,23 @@ static void BaseFile_Close(TFileStream * pStream)
pStream->Base.File.hFile = INVALID_HANDLE_VALUE;
}
-static bool BaseFile_Create(TFileStream * pStream)
+// Initializes base functions for the disk file
+static void BaseFile_Init(TFileStream * pStream)
{
-#ifdef PLATFORM_WINDOWS
- {
- DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
-
- pStream->Base.File.hFile = CreateFile(pStream->szFileName,
- GENERIC_READ | GENERIC_WRITE,
- dwWriteShare | FILE_SHARE_READ,
- NULL,
- CREATE_ALWAYS,
- 0,
- NULL);
- if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
- return false;
- }
-#endif
-
-#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
- {
- intptr_t handle;
-
- handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if(handle == -1)
- {
- nLastError = errno;
- return false;
- }
-
- pStream->Base.File.hFile = (HANDLE)handle;
- }
-#endif
-
- // Fill-in the entry points
- pStream->BaseRead = BaseFile_Read;
- pStream->BaseWrite = BaseFile_Write;
- pStream->BaseSetSize = BaseFile_SetSize;
- pStream->BaseClose = BaseFile_Close;
-
- // Reset the file position
- pStream->FileSize = 0;
- pStream->FilePos = 0;
- return true;
-}
-
-static bool BaseFile_Open(TFileStream * pStream, DWORD dwStreamFlags)
-{
-#ifdef PLATFORM_WINDOWS
- {
- ULARGE_INTEGER FileSize;
- DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES;
- DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
-
- // Open the file
- pStream->Base.File.hFile = CreateFile(pStream->szFileName,
- FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess,
- FILE_SHARE_READ | dwWriteShare,
- NULL,
- OPEN_EXISTING,
- 0,
- NULL);
- if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
- return false;
-
- // Query the file size
- FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart);
- pStream->FileSize = FileSize.QuadPart;
-
- // Query last write time
- GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->FileTime);
- }
-#endif
-
-#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
- {
- struct stat64 fileinfo;
- int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR;
- intptr_t handle;
-
- // Open the file
- handle = open(szFileName, oflag | O_LARGEFILE);
- if(handle == -1)
- {
- nLastError = errno;
- return false;
- }
-
- // Get the file size
- if(fstat64(handle, &fileinfo) == -1)
- {
- nLastError = errno;
- return false;
- }
-
- // time_t is number of seconds since 1.1.1970, UTC.
- // 1 second = 10000000 (decimal) in FILETIME
- // Set the start to 1.1.1970 00:00:00
- pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime);
- pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size;
- pStream->Base.File.hFile = (HANDLE)handle;
- }
-#endif
-
- // Fill-in the entry points
+ pStream->BaseCreate = BaseFile_Create;
+ pStream->BaseOpen = BaseFile_Open;
pStream->BaseRead = BaseFile_Read;
pStream->BaseWrite = BaseFile_Write;
- pStream->BaseSetSize = BaseFile_SetSize;
+ pStream->BaseResize = BaseFile_Resize;
+ pStream->BaseGetSize = BaseFile_GetSize;
+ pStream->BaseGetPos = BaseFile_GetPos;
pStream->BaseClose = BaseFile_Close;
-
- // Reset the file position
- pStream->FilePos = 0;
- return true;
}
//-----------------------------------------------------------------------------
// Local functions - base memory-mapped file support
-static bool BaseMap_Read(
- TFileStream * pStream, // Pointer to an open stream
- ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
- void * pvBuffer, // Pointer to data to be read
- DWORD dwBytesToRead) // Number of bytes to read from the file
-{
- ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos;
-
- // Do we have to read anything at all?
- if(dwBytesToRead != 0)
- {
- // Don't allow reading past file size
- if((ByteOffset + dwBytesToRead) > pStream->FileSize)
- return false;
-
- // Copy the required data
- memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead);
- }
-
- // Move the current file position
- pStream->FilePos += dwBytesToRead;
- return true;
-}
-
-static void BaseMap_Close(TFileStream * pStream)
-{
-#ifdef PLATFORM_WINDOWS
- if(pStream->Base.Map.pbFile != NULL)
- UnmapViewOfFile(pStream->Base.Map.pbFile);
-#endif
-
-#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
- if(pStream->Base.Map.pbFile != NULL)
- munmap(pStream->Base.Map.pbFile, (size_t )pStream->FileSize);
-#endif
-
- pStream->Base.Map.pbFile = NULL;
-}
-
-static bool BaseMap_Open(TFileStream * pStream)
+static bool BaseMap_Open(TFileStream * pStream, DWORD dwStreamFlags)
{
#ifdef PLATFORM_WINDOWS
@@ -523,6 +426,9 @@ static bool BaseMap_Open(TFileStream * pStream)
HANDLE hMap;
bool bResult = false;
+ // Keep compiler happy
+ dwStreamFlags = dwStreamFlags;
+
// Open the file for read access
hFile = CreateFile(pStream->szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if(hFile != NULL)
@@ -531,9 +437,6 @@ static bool BaseMap_Open(TFileStream * pStream)
FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart);
if(FileSize.QuadPart != 0)
{
- // Retrieve file time
- GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->FileTime);
-
// Now create mapping object
hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if(hMap != NULL)
@@ -544,8 +447,12 @@ static bool BaseMap_Open(TFileStream * pStream)
pStream->Base.Map.pbFile = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if(pStream->Base.Map.pbFile != NULL)
{
- pStream->FileSize = FileSize.QuadPart;
- pStream->FilePos = 0;
+ // Retrieve file time
+ GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime);
+
+ // Retrieve file size and position
+ pStream->Base.Map.FileSize = FileSize.QuadPart;
+ pStream->Base.Map.FilePos = 0;
bResult = true;
}
@@ -599,10 +506,60 @@ static bool BaseMap_Open(TFileStream * pStream)
}
#endif
- // Fill-in entry points
+ return true;
+}
+
+static bool BaseMap_Read(
+ TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
+ void * pvBuffer, // Pointer to data to be read
+ DWORD dwBytesToRead) // Number of bytes to read from the file
+{
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos;
+
+ // Do we have to read anything at all?
+ if(dwBytesToRead != 0)
+ {
+ // Don't allow reading past file size
+ if((ByteOffset + dwBytesToRead) > pStream->Base.Map.FileSize)
+ return false;
+
+ // Copy the required data
+ memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead);
+ }
+
+ // Move the current file position
+ pStream->Base.Map.FilePos += dwBytesToRead;
+ return true;
+}
+
+static void BaseMap_Close(TFileStream * pStream)
+{
+#ifdef PLATFORM_WINDOWS
+ if(pStream->Base.Map.pbFile != NULL)
+ UnmapViewOfFile(pStream->Base.Map.pbFile);
+#endif
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ if(pStream->Base.Map.pbFile != NULL)
+ munmap(pStream->Base.Map.pbFile, (size_t )pStream->FileSize);
+#endif
+
+ pStream->Base.Map.pbFile = NULL;
+}
+
+// Initializes base functions for the mapped file
+static void BaseMap_Init(TFileStream * pStream)
+{
+ // Supply the file stream functions
+ pStream->BaseOpen = BaseMap_Open;
pStream->BaseRead = BaseMap_Read;
+ pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
+ pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
pStream->BaseClose = BaseMap_Close;
- return true;
+
+ // Mapped files are read-only
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
}
//-----------------------------------------------------------------------------
@@ -631,6 +588,110 @@ static const TCHAR * BaseHttp_ExtractServerName(const TCHAR * szFileName, TCHAR
return szFileName;
}
+static bool BaseHttp_Open(TFileStream * pStream, DWORD dwStreamFlags)
+{
+#ifdef PLATFORM_WINDOWS
+
+ const TCHAR * szFileName;
+ HINTERNET hRequest;
+ DWORD dwTemp = 0;
+ bool bFileAvailable = false;
+ int nError = ERROR_SUCCESS;
+
+ // Keep compiler happy
+ dwStreamFlags = dwStreamFlags;
+
+ // Don't connect to the internet
+ if(!InternetGetConnectedState(&dwTemp, 0))
+ nError = GetLastError();
+
+ // Initiate the connection to the internet
+ if(nError == ERROR_SUCCESS)
+ {
+ pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"),
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL,
+ NULL,
+ 0);
+ if(pStream->Base.Http.hInternet == NULL)
+ nError = GetLastError();
+ }
+
+ // Connect to the server
+ if(nError == ERROR_SUCCESS)
+ {
+ TCHAR szServerName[MAX_PATH];
+ DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE;
+
+ // Initiate connection with the server
+ szFileName = BaseHttp_ExtractServerName(pStream->szFileName, szServerName);
+ pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet,
+ szServerName,
+ INTERNET_DEFAULT_HTTP_PORT,
+ NULL,
+ NULL,
+ INTERNET_SERVICE_HTTP,
+ dwFlags,
+ 0);
+ if(pStream->Base.Http.hConnect == NULL)
+ nError = GetLastError();
+ }
+
+ // Now try to query the file size
+ if(nError == ERROR_SUCCESS)
+ {
+ // Open HTTP request to the file
+ hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
+ if(hRequest != NULL)
+ {
+ if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
+ {
+ ULONGLONG FileTime = 0;
+ DWORD dwFileSize = 0;
+ DWORD dwDataSize;
+ DWORD dwIndex = 0;
+
+ // Check if the MPQ has Last Modified field
+ dwDataSize = sizeof(ULONGLONG);
+ if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex))
+ pStream->Base.Http.FileTime = FileTime;
+
+ // Verify if the server supports random access
+ dwDataSize = sizeof(DWORD);
+ if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex))
+ {
+ if(dwFileSize != 0)
+ {
+ pStream->Base.Http.FileSize = dwFileSize;
+ pStream->Base.Http.FilePos = 0;
+ bFileAvailable = true;
+ }
+ }
+ }
+ InternetCloseHandle(hRequest);
+ }
+ }
+
+ // If the file is not there and is not available for random access,
+ // report error
+ if(bFileAvailable == false)
+ {
+ pStream->BaseClose(pStream);
+ return false;
+ }
+
+ return true;
+
+#else
+
+ // Not supported
+ SetLastError(ERROR_NOT_SUPPORTED);
+ pStream = pStream;
+ return false;
+
+#endif
+}
+
static bool BaseHttp_Read(
TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
@@ -638,7 +699,7 @@ static bool BaseHttp_Read(
DWORD dwBytesToRead) // Number of bytes to read from the file
{
#ifdef PLATFORM_WINDOWS
- ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos;
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos;
DWORD dwTotalBytesRead = 0;
// Do we have to read anything at all?
@@ -688,7 +749,7 @@ static bool BaseHttp_Read(
}
// Increment the current file position by number of bytes read
- pStream->FilePos = ByteOffset + dwTotalBytesRead;
+ pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead;
// If the number of bytes read doesn't match the required amount, return false
if(dwTotalBytesRead != dwBytesToRead)
@@ -723,242 +784,318 @@ static void BaseHttp_Close(TFileStream * pStream)
#endif
}
-static bool BaseHttp_Open(TFileStream * pStream)
+// Initializes base functions for the mapped file
+static void BaseHttp_Init(TFileStream * pStream)
{
-#ifdef PLATFORM_WINDOWS
+ // Supply the stream functions
+ pStream->BaseOpen = BaseHttp_Open;
+ pStream->BaseRead = BaseHttp_Read;
+ pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
+ pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
+ pStream->BaseClose = BaseHttp_Close;
- const TCHAR * szFileName;
- HINTERNET hRequest;
- DWORD dwTemp = 0;
- bool bFileAvailable = false;
- int nError = ERROR_SUCCESS;
+ // HTTP files are read-only
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+}
- // Don't connect to the internet
- if(!InternetGetConnectedState(&dwTemp, 0))
- nError = GetLastError();
+//-----------------------------------------------------------------------------
+// Local functions - base block-based support
- // Initiate the connection to the internet
- if(nError == ERROR_SUCCESS)
+// Generic function that loads blocks from the file
+// The function groups the block with the same availability,
+// so the called BlockRead can finish the request in a single system call
+static bool BlockStream_Read(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
+ void * pvBuffer, // Pointer to data to be read
+ DWORD dwBytesToRead) // Number of bytes to read from the file
+{
+ ULONGLONG BlockOffset0;
+ ULONGLONG BlockOffset;
+ ULONGLONG ByteOffset;
+ ULONGLONG EndOffset;
+ LPBYTE TransferBuffer;
+ LPBYTE BlockBuffer;
+ DWORD BlockBufferOffset; // Offset of the desired data in the block buffer
+ DWORD BytesNeeded; // Number of bytes that really need to be read
+ DWORD BlockSize = pStream->BlockSize;
+ DWORD BlockCount;
+ bool bPrevBlockAvailable;
+ bool bBlockAvailable;
+ bool bResult = true;
+
+ // The base block read function must be present
+ assert(pStream->BlockCheck != NULL);
+ assert(pStream->BlockRead != NULL);
+
+ // NOP reading of zero bytes
+ if(dwBytesToRead == 0)
+ return true;
+
+ // Get the current position in the stream
+ ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos;
+ EndOffset = ByteOffset + dwBytesToRead;
+ if(EndOffset > pStream->StreamSize)
{
- pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"),
- INTERNET_OPEN_TYPE_PRECONFIG,
- NULL,
- NULL,
- 0);
- if(pStream->Base.Http.hInternet == NULL)
- nError = GetLastError();
+ SetLastError(ERROR_CAN_NOT_COMPLETE);
+ return false;
}
- // Connect to the server
- if(nError == ERROR_SUCCESS)
- {
- TCHAR szServerName[MAX_PATH];
- DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE;
+ // Calculate the block parameters
+ BlockOffset0 = BlockOffset = ByteOffset & ~((ULONGLONG)BlockSize - 1);
+ BlockCount = (DWORD)(((EndOffset - BlockOffset) + (BlockSize - 1)) / BlockSize);
+ BytesNeeded = (DWORD)(EndOffset - BlockOffset);
- // Initiate connection with the server
- szFileName = BaseHttp_ExtractServerName(pStream->szFileName, szServerName);
- pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet,
- szServerName,
- INTERNET_DEFAULT_HTTP_PORT,
- NULL,
- NULL,
- INTERNET_SERVICE_HTTP,
- dwFlags,
- 0);
- if(pStream->Base.Http.hConnect == NULL)
- nError = GetLastError();
+ // Remember where we have our data
+ assert((BlockSize & (BlockSize - 1)) == 0);
+ BlockBufferOffset = (DWORD)(ByteOffset & (BlockSize - 1));
+
+ // Allocate buffer for reading blocks
+ TransferBuffer = BlockBuffer = STORM_ALLOC(BYTE, (BlockCount * BlockSize));
+ if(TransferBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return false;
}
- // Now try to query the file size
- if(nError == ERROR_SUCCESS)
+ // If all blocks are available, just read all blocks at once
+ if(pStream->IsComplete == 0)
{
- // Open HTTP request to the file
- hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
- if(hRequest != NULL)
+ // Now parse the blocks and send the block read request
+ // to all blocks with the same availability
+ bPrevBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
+
+ // Loop as long as we have something to read
+ while(BlockOffset < EndOffset)
{
- if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
+ // Determine availability of the next block
+ bBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
+
+ // If the availability has changed, read all blocks up to this one
+ if(bBlockAvailable != bPrevBlockAvailable)
{
- ULONGLONG FileTime = 0;
- DWORD dwFileSize = 0;
- DWORD dwDataSize;
- DWORD dwIndex = 0;
+ // Load the continuous blocks with the same availability
+ assert(BlockOffset > BlockOffset0);
+ bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
+ if(!bResult)
+ break;
+
+ // Move the block offset
+ BlockBuffer += (DWORD)(BlockOffset - BlockOffset0);
+ BytesNeeded -= (DWORD)(BlockOffset - BlockOffset0);
+ bPrevBlockAvailable = bBlockAvailable;
+ BlockOffset0 = BlockOffset;
+ }
- // Check if the MPQ has Last Modified field
- dwDataSize = sizeof(ULONGLONG);
- if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex))
- pStream->FileTime = FileTime;
+ // Move to the block offset in the stream
+ BlockOffset += BlockSize;
+ }
- // Verify if the server supports random access
- dwDataSize = sizeof(DWORD);
- if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex))
- {
- if(dwFileSize != 0)
- {
- pStream->FileSize = dwFileSize;
- pStream->FilePos = 0;
- bFileAvailable = true;
- }
- }
- }
- InternetCloseHandle(hRequest);
+ // If there is a block(s) remaining to be read, do it
+ if(BlockOffset > BlockOffset0)
+ {
+ // Read the complete blocks from the file
+ if(BlockOffset > pStream->StreamSize)
+ BlockOffset = pStream->StreamSize;
+ bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
}
}
+ else
+ {
+ // Read the complete blocks from the file
+ if(EndOffset > pStream->StreamSize)
+ EndOffset = pStream->StreamSize;
+ bResult = pStream->BlockRead(pStream, BlockOffset, EndOffset, BlockBuffer, BytesNeeded, true);
+ }
- // If the file is not there and is not available for random access,
- // report error
- if(bFileAvailable == false)
+ // Now copy the data to the user buffer
+ if(bResult)
{
- BaseHttp_Close(pStream);
- return false;
+ memcpy(pvBuffer, TransferBuffer + BlockBufferOffset, dwBytesToRead);
+ pStream->StreamPos = ByteOffset + dwBytesToRead;
}
- // Fill-in entry points
- pStream->BaseRead = BaseHttp_Read;
- pStream->BaseClose = BaseHttp_Close;
+ // Free the block buffer and return
+ STORM_FREE(TransferBuffer);
+ return bResult;
+}
+
+static bool BlockStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
+{
+ *pFileSize = pStream->StreamSize;
return true;
+}
-#else
+static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ *pByteOffset = pStream->StreamPos;
+ return true;
+}
- // Not supported
- SetLastError(ERROR_NOT_SUPPORTED);
- pStream = pStream;
- return false;
+static void BlockStream_Close(TBlockStream * pStream)
+{
+ // Free the data map, if any
+ if(pStream->FileBitmap != NULL)
+ STORM_FREE(pStream->FileBitmap);
+ pStream->FileBitmap = NULL;
-#endif
+ // Call the base class for closing the stream
+ pStream->BaseClose(pStream);
}
//-----------------------------------------------------------------------------
-// Local functions - bitmap for a complete file
-
-#define DEFAULT_BLOCK_SIZE 0x4000
-
-static bool CompleteFile_GetBmp(
- TFileStream * pStream,
- void * pvBitmap,
- DWORD Length,
- LPDWORD LengthNeeded)
-{
- TFileBitmap * pBitmap = (TFileBitmap *)pvBitmap;
- ULONGLONG FileSize = pStream->FileSize;
- DWORD TotalLength;
- DWORD BlockCount = (DWORD)(((FileSize - 1) / DEFAULT_BLOCK_SIZE) + 1);
- DWORD BitmapSize = (DWORD)(((BlockCount - 1) / 8) + 1);
- DWORD LastByte;
-
- // Calculate and give the total length
- TotalLength = sizeof(TFileBitmap) + BitmapSize;
- if(LengthNeeded != NULL)
- *LengthNeeded = TotalLength;
-
- // Has the caller given enough space for storing the structure?
- if(Length >= sizeof(TFileBitmap))
- {
- pBitmap->StartOffset = 0;
- pBitmap->EndOffset = FileSize;
- pBitmap->BitmapSize = BitmapSize;
- pBitmap->BlockSize = DEFAULT_BLOCK_SIZE;
- pBitmap->BlockCount = BlockCount;
- pBitmap->IsComplete = 1;
-
- // Do we have enough space to fill the bitmap as well?
- if(Length >= TotalLength)
- {
- LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1);
+// File stream allocation function
- // Fill the full blocks
- memset(pbBitmap, 0xFF, (BlockCount / 8));
- pbBitmap += (BlockCount / 8);
+static STREAM_INIT StreamBaseInit[4] =
+{
+ BaseFile_Init,
+ BaseMap_Init,
+ BaseHttp_Init,
+ BaseNone_Init
+};
- // Supply the last block
- if(BlockCount & 7)
- {
- LastByte = (1 << (BlockCount & 7)) - 1;
- pbBitmap[0] = (BYTE)LastByte;
- }
- }
+// This function allocates an empty structure for the file stream
+// The stream structure is created as variable length, linear block of data
+// The file name is placed after the end of the stream structure data
+static TFileStream * AllocateFileStream(
+ const TCHAR * szFileName,
+ size_t StreamSize,
+ DWORD dwStreamFlags)
+{
+ TFileStream * pMaster = NULL;
+ TFileStream * pStream;
+ const TCHAR * szNextFile = szFileName;
+ size_t FileNameSize;
- return true;
+ // Sanity check
+ assert(StreamSize != 0);
+
+ // The caller can specify chain of files in the following form:
+ // C:\archive.MPQ*http://www.server.com/MPQs/archive-server.MPQ
+ // In that case, we use the part after "*" as master file name
+ while(szNextFile[0] != 0 && szNextFile[0] != _T('*'))
+ szNextFile++;
+ FileNameSize = (size_t)((szNextFile - szFileName) * sizeof(TCHAR));
+
+ // If we have a next file, we need to open it as master stream
+ // Note that we don't care if the master stream exists or not,
+ // If it doesn't, later attempts to read missing file block will fail
+ if(szNextFile[0] == _T('*'))
+ {
+ // Don't allow another master file in the string
+ if(_tcschr(szNextFile + 1, _T('*')) != NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return NULL;
+ }
+
+ // Open the master file
+ pMaster = FileStream_OpenFile(szNextFile + 1, STREAM_FLAG_READ_ONLY);
}
- else
+
+ // Allocate the stream structure for the given stream type
+ pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize + sizeof(TCHAR));
+ if(pStream != NULL)
{
- SetLastError(ERROR_INSUFFICIENT_BUFFER);
- return false;
+ // Zero the entire structure
+ memset(pStream, 0, StreamSize);
+ pStream->pMaster = pMaster;
+ pStream->dwFlags = dwStreamFlags;
+
+ // Initialize the file name
+ pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize);
+ memcpy(pStream->szFileName, szFileName, FileNameSize);
+ pStream->szFileName[FileNameSize / sizeof(TCHAR)] = 0;
+
+ // Initialize the stream functions
+ StreamBaseInit[dwStreamFlags & 0x03](pStream);
}
+
+ return pStream;
}
//-----------------------------------------------------------------------------
// Local functions - linear stream support
-typedef struct _DATA_BLOCK_INFO
+static DWORD LinearStream_CheckFile(TBlockStream * pStream)
{
- ULONGLONG BlockOffset0; // Offset of the first block in the continuous array
- ULONGLONG BlockOffset; // Offset of the current block
- ULONGLONG ByteOffset; // Offset of the loaded data
- ULONGLONG EndOffset; // Offset of the end of the block
- LPBYTE ReadBuffer; // Pointer to the buffer where to read the data
- DWORD BlockSize; // Length of one block, in bytes
- DWORD ByteIndex0; // Byte index of the first block in the continuous array
- DWORD ByteIndex; // Index of the byte in the file bitmap
- BYTE MirrorUpdated; // If set to nonzero, the mirror stream has been updated
- BYTE BitMask0; // Bit mask of the first block in the continuous array
- BYTE BitMask; // Bit mask of the current bit in file bitmap
+ LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
+ DWORD WholeByteCount = (pStream->BlockCount / 8);
+ DWORD ExtraBitsCount = (pStream->BlockCount & 7);
+ BYTE ExpectedValue;
+
+ // Verify the whole bytes - their value must be 0xFF
+ for(DWORD i = 0; i < WholeByteCount; i++)
+ {
+ if(FileBitmap[i] != 0xFF)
+ return 0;
+ }
-} DATA_BLOCK_INFO, *PDATA_BLOCK_INFO;
+ // If there are extra bits, calculate the mask
+ if(ExtraBitsCount != 0)
+ {
+ ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1);
+ if(FileBitmap[WholeByteCount] != ExpectedValue)
+ return 0;
+ }
-static bool LinearStream_LoadBitmap(
- TLinearStream * pStream) // Pointer to an open stream
+ // Yes, the file is complete
+ return 1;
+}
+
+static bool LinearStream_LoadBitmap(TBlockStream * pStream)
{
FILE_BITMAP_FOOTER Footer;
- TFileBitmap * pBitmap;
ULONGLONG ByteOffset;
+ LPBYTE FileBitmap;
DWORD BlockCount;
DWORD BitmapSize;
- // Only if the size is greater than sizeof bitmap footer
- if(pStream->FileSize > sizeof(FILE_BITMAP_FOOTER))
+ // Do not load the bitmap if we should not have to
+ if(!(pStream->dwFlags & STREAM_FLAG_USE_BITMAP))
+ return false;
+
+ // Only if the size is greater than size of bitmap footer
+ if(pStream->Base.File.FileSize > sizeof(FILE_BITMAP_FOOTER))
{
// Load the bitmap footer
- ByteOffset = pStream->FileSize - sizeof(FILE_BITMAP_FOOTER);
+ ByteOffset = pStream->Base.File.FileSize - sizeof(FILE_BITMAP_FOOTER);
if(pStream->StreamRead(pStream, &ByteOffset, &Footer, sizeof(FILE_BITMAP_FOOTER)))
{
// Make sure that the array is properly BSWAP-ed
BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&Footer), sizeof(FILE_BITMAP_FOOTER));
// Verify if there is actually a footer
- if(Footer.dwSignature == ID_FILE_BITMAP_FOOTER && Footer.dwAlways3 == 0x03)
+ if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03)
{
- // Get offset of the bitmap, size of the bitmap and check for match
- ByteOffset = MAKE_OFFSET64(Footer.dwMapOffsetHi, Footer.dwMapOffsetLo);
- BlockCount = (DWORD)(((ByteOffset - 1) / Footer.dwBlockSize) + 1);
+ // Get the offset of the bitmap, number of blocks and size of the bitmap
+ ByteOffset = MAKE_OFFSET64(Footer.MapOffsetHi, Footer.MapOffsetLo);
+ BlockCount = (DWORD)(((ByteOffset - 1) / Footer.BlockSize) + 1);
BitmapSize = ((BlockCount + 7) / 8);
// Check if the sizes match
- if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->FileSize)
+ if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize)
{
// Allocate space for the linear bitmap
- pBitmap = (TFileBitmap *)STORM_ALLOC(BYTE, sizeof(TFileBitmap) + BitmapSize);
- if(pBitmap != NULL)
+ FileBitmap = STORM_ALLOC(BYTE, BitmapSize);
+ if(FileBitmap != NULL)
{
- // Fill the bitmap header
- pBitmap->StartOffset = 0;
- pBitmap->EndOffset = ByteOffset;
- pBitmap->BitmapSize = BitmapSize;
- pBitmap->BlockSize = Footer.dwBlockSize;
- pBitmap->BlockCount = BlockCount;
-
// Load the bitmap bits
- if(!pStream->BaseRead(pStream, &ByteOffset, (pBitmap + 1), BitmapSize))
+ if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize))
{
- STORM_FREE(pBitmap);
+ STORM_FREE(FileBitmap);
return false;
}
- // Verify if the file is complete or not
- pBitmap->IsComplete = FileBitmap_CheckFile(pBitmap);
-
- // Set the file bitmap into the file stream
- pStream->FileSize = ByteOffset;
- pStream->pBitmap = pBitmap;
+ // Update the stream size
+ pStream->StreamSize = ByteOffset;
+
+ // Fill the bitmap information
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = BitmapSize;
+ pStream->BlockSize = Footer.BlockSize;
+ pStream->BlockCount = BlockCount;
+ pStream->IsComplete = LinearStream_CheckFile(pStream);
return true;
}
}
@@ -969,370 +1106,171 @@ static bool LinearStream_LoadBitmap(
return false;
}
-static bool LinearStream_LoadMissingBlocks(
- TLinearStream * pStream,
- ULONGLONG ByteOffset,
- ULONGLONG EndOffset,
- void * pvBuffer)
-{
- TFileBitmap * pBitmap = pStream->pBitmap;
- ULONGLONG BlockSizeMask = pStream->pBitmap->BlockSize;
- ULONGLONG BlockOffset = ByteOffset & ~(BlockSizeMask - 1);
- ULONGLONG BlockEnd = (EndOffset + (pStream->pBitmap->BlockSize - 1)) & ~(BlockSizeMask - 1);
- LPBYTE pbDataBlock;
- DWORD cbBytesToCopy = (DWORD)(EndOffset - ByteOffset);
- DWORD cbBlockSize = (DWORD)(BlockEnd - BlockOffset);
- bool bResult = false;
-
- // Sanity check
- assert(pStream->pBitmap != NULL);
-
- // Cannot load missing blocks if no master file
- if(pStream->pMaster == NULL)
- return false;
-
- // Allocate space for the file block
- pbDataBlock = STORM_ALLOC(BYTE, cbBlockSize);
- if(pbDataBlock != NULL)
- {
- // Load the entire missing block from the master MPQ
- if(FileStream_Read(pStream->pMaster, &BlockOffset, pbDataBlock, cbBlockSize))
- {
- // We can satisfy the read from the loaded data
- assert(cbBytesToCopy <= cbBlockSize);
- memcpy(pvBuffer, pbDataBlock + (DWORD)(ByteOffset - BlockOffset), cbBytesToCopy);
- bResult = true;
-
- // Write the file block to the cached archive
- if(pStream->BaseWrite(pStream, &BlockOffset, pbDataBlock, cbBlockSize))
- {
- // Update the file bitmap
- FileBitmap_SetRange(pStream->pBitmap, BlockOffset, BlockEnd);
-
- // If this fails, the data blocks will be re-downloaded next time,
- // but the file is not corrupt
- ByteOffset = pBitmap->EndOffset;
- if(!pStream->BaseWrite(pStream, &ByteOffset, pBitmap + 1, pBitmap->BitmapSize))
- bResult = false;
- }
- }
-
- // Free the file block
- STORM_FREE(pbDataBlock);
- }
-
- return bResult;
-}
-
-static bool LinearStream_ReadBlocks(
- TLinearStream * pStream, // Pointer to an open stream
- DATA_BLOCK_INFO & bi,
- bool bBlocksAreAvailable)
-{
- ULONGLONG EndOffset;
- LPBYTE pbBlockBuffer;
- DWORD BlockToRead = 0;
- DWORD BytesToRead = 0;
- DWORD ReadOffset;
- bool bResult = true;
-
- // Only do something if there is at least one block to be read
- if(bi.BlockOffset > bi.BlockOffset0)
- {
- // Get the read range
- EndOffset = STORMLIB_MIN(bi.BlockOffset, bi.EndOffset);
- BytesToRead = (DWORD)(EndOffset - bi.ByteOffset);
-
- // If the block is not available, we need to load them from the master and store to the mirror
- if(bBlocksAreAvailable == false)
- {
- // If we have no master, we cannot satisfy read request
- if(pStream->pMaster == NULL)
- return false;
-
- // Allocate buffer and read the complete blocks
- BlockToRead = (DWORD)(bi.BlockOffset - bi.BlockOffset0);
- pbBlockBuffer = STORM_ALLOC(BYTE, BlockToRead);
- if(pbBlockBuffer == NULL)
- return false;
-
- // Load the block buffer from the master stream
- if(FileStream_Read(pStream->pMaster, &bi.BlockOffset0, pbBlockBuffer, BlockToRead))
- {
- // We can now satisfy the request
- ReadOffset = (DWORD)(bi.ByteOffset - bi.BlockOffset0);
- memcpy(bi.ReadBuffer, pbBlockBuffer + ReadOffset, BytesToRead);
-
- // Store the loaded blocks to the mirror file
- if(pStream->BaseWrite(pStream, &bi.BlockOffset0, pbBlockBuffer, BlockToRead))
- bi.MirrorUpdated = 1;
- }
-
- // Free the transfer buffer
- STORM_FREE(pbBlockBuffer);
- }
-
- // If the blocks are available, we just read them from the mirror
- else
- {
- // Perform the file read
- bResult = pStream->BaseRead(pStream, &bi.ByteOffset, bi.ReadBuffer, BytesToRead);
- }
-
- // Move the offsets and bit masks
- bi.BlockOffset0 = bi.BlockOffset;
- bi.ByteOffset += BytesToRead;
- bi.ReadBuffer += BytesToRead;
- }
- return bResult;
-}
-
-static bool LinearStream_Read(
- TLinearStream * pStream, // Pointer to an open stream
- ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
- void * pvBuffer, // Pointer to data to be read
- DWORD dwBytesToRead) // Number of bytes to read from the file
+static void LinearStream_UpdateBitmap(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset)
{
- DATA_BLOCK_INFO bi;
- TFileBitmap * pBitmap = pStream->pBitmap;
- ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos;
- LPBYTE FileBitmap;
+ LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
DWORD BlockIndex;
- bool bPrevBlockAvailable;
- bool bBlockAvailable;
-
- // NOP reading zero bytes
- if(dwBytesToRead == 0)
- return true;
-
- // Cannot read past the end of the file
- if((ByteOffset + dwBytesToRead) > pStream->FileSize)
- {
- SetLastError(ERROR_HANDLE_EOF);
- return false;
- }
+ DWORD BlockSize = pStream->BlockSize;
+ DWORD ByteIndex;
+ BYTE BitMask;
- // If we have no data bitmap, we assume that the file is complete.
- if(pBitmap == NULL)
- return pStream->BaseRead(pStream, pByteOffset, pvBuffer, dwBytesToRead);
+ // Sanity checks
+ assert((StartOffset & (BlockSize - 1)) == 0);
+ assert(FileBitmap != NULL);
// Calculate the index of the block
- FileBitmap = (LPBYTE)(pBitmap + 1);
- BlockIndex = (DWORD)(ByteOffset / pBitmap->BlockSize);
-
- // Fill the data block info
- bi.BlockOffset0 =
- bi.BlockOffset = ByteOffset & ~((ULONGLONG)pBitmap->BlockSize - 1);
- bi.ByteOffset = ByteOffset;
- bi.EndOffset = ByteOffset + dwBytesToRead;
- bi.ReadBuffer = (LPBYTE)pvBuffer;
- bi.BlockSize = pBitmap->BlockSize;
- bi.ByteIndex0 =
- bi.ByteIndex = (BlockIndex / 0x08);
- bi.BitMask0 =
- bi.BitMask = (BYTE)(0x01 << (BlockIndex & 0x07));
-
- // Check if the current block is available
- bPrevBlockAvailable = (FileBitmap[bi.ByteIndex] & bi.BitMask) ? true : false;
-
- // Loop as long as we have something to read
- while(bi.BlockOffset < bi.EndOffset)
- {
- // Determine if that block is available in the mirror file
- bBlockAvailable = (FileBitmap[bi.ByteIndex] & bi.BitMask) ? true : false;
-
- // If the availability has changed,
- // reload all the previous blocks with the same availability
- if(bBlockAvailable != bPrevBlockAvailable)
- {
- if(!LinearStream_ReadBlocks(pStream, bi, bPrevBlockAvailable))
- {
- SetLastError(ERROR_CAN_NOT_COMPLETE);
- return false;
- }
-
- bPrevBlockAvailable = bBlockAvailable;
- }
+ BlockIndex = (DWORD)(StartOffset / BlockSize);
+ ByteIndex = (BlockIndex / 0x08);
+ BitMask = (BYTE)(1 << (BlockIndex & 0x07));
- // Move to the next block in the stream
- bi.BlockOffset += bi.BlockSize;
- bi.ByteIndex += (bi.BitMask >> 0x07);
- bi.BitMask = (bi.BitMask >> 0x07) | (bi.BitMask << 0x01);
- }
-
- // We now need to read the last blocks that weren't loaded in the loop
- if(!LinearStream_ReadBlocks(pStream, bi, bPrevBlockAvailable))
+ // Set all bits for the specified range
+ while(StartOffset < EndOffset)
{
- SetLastError(ERROR_CAN_NOT_COMPLETE);
- return false;
- }
+ // Set the bit
+ FileBitmap[ByteIndex] |= BitMask;
- // We also need to update the file bitmap in the file
- if(bi.MirrorUpdated)
- {
- // Update all bits in the bitmap
- while(bi.ByteIndex0 != bi.ByteIndex || bi.BitMask0 != bi.BitMask)
- {
- FileBitmap[bi.ByteIndex0] |= bi.BitMask0;
- bi.ByteIndex0 += (bi.BitMask0 >> 0x07);
- bi.BitMask0 = (bi.BitMask0 >> 0x07) | (bi.BitMask0 << 0x01);
- }
-
- // Write the updated bitmap to the mirror file
- pStream->BaseWrite(pStream, &pBitmap->EndOffset, FileBitmap, pBitmap->BitmapSize);
+ // Move all
+ StartOffset += BlockSize;
+ ByteIndex += (BitMask >> 0x07);
+ BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
}
-
- // Increment the position
- pStream->FilePos = ByteOffset + dwBytesToRead;
- return true;
}
-static bool LinearStream_Write(TLinearStream *, ULONGLONG *, const void *, DWORD)
+static bool LinearStream_BlockCheck(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG BlockOffset)
{
- // Writing to linear stream with bitmap is not allowed
- SetLastError(ERROR_ACCESS_DENIED);
- return false;
-}
+ LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
+ DWORD BlockIndex;
+ BYTE BitMask;
-static bool LinearStream_GetSize(TLinearStream * pStream, ULONGLONG * pFileSize)
-{
- *pFileSize = pStream->FileSize;
- return true;
-}
+ // Sanity checks
+ assert((BlockOffset & (pStream->BlockSize - 1)) == 0);
+ assert(FileBitmap != NULL);
+
+ // Calculate the index of the block
+ BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize);
+ BitMask = (BYTE)(1 << (BlockIndex & 0x07));
-static bool LinearStream_GetTime(TLinearStream * pStream, ULONGLONG * pFileTime)
-{
- *pFileTime = pStream->FileTime;
- return true;
+ // Check if the bit is present
+ return (FileBitmap[BlockIndex / 0x08] & BitMask) ? true : false;
}
-static bool LinearStream_GetPos(TLinearStream * pStream, ULONGLONG * pByteOffset)
+static bool LinearStream_BlockRead(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
{
- *pByteOffset = pStream->FilePos;
- return true;
-}
+ DWORD BytesToRead = (DWORD)(EndOffset - StartOffset);
-static bool LinearStream_GetBmp(
- TLinearStream * pStream,
- void * pvBitmap,
- DWORD Length,
- LPDWORD LengthNeeded)
-{
- DWORD TotalLength;
- DWORD CopyLength = sizeof(TFileBitmap);
+ // The starting offset must be aligned to size of the block
+ assert(pStream->FileBitmap != NULL && pStream->IsComplete == 0);
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+
+ // If the blocks are not available, we need to load them from the master
+ // and then save to the mirror
+ if(bAvailable == false)
+ {
+ // If we have no master, we cannot satisfy read request
+ if(pStream->pMaster == NULL)
+ return false;
- // Assumed that we have bitmap now
- assert(pStream->pBitmap != NULL);
+ // Load the blocks from the master stream
+ // Note that we always have to read complete blocks
+ // so they get properly stored to the mirror stream
+ if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead))
+ return false;
- // Give the bitmap length
- TotalLength = sizeof(TFileBitmap) + pStream->pBitmap->BitmapSize;
- if(LengthNeeded != NULL)
- *LengthNeeded = TotalLength;
+ // Store the loaded blocks to the mirror file.
+ // Note that this operation is not required to succeed
+ if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead) && bAvailable == false)
+ LinearStream_UpdateBitmap(pStream, StartOffset, EndOffset);
- // Do we have enough space to fill at least the bitmap structure?
- if(Length >= sizeof(TFileBitmap))
- {
- // Enough space for the complete bitmap?
- if(Length >= TotalLength)
- CopyLength = TotalLength;
- memcpy(pvBitmap, pStream->pBitmap, CopyLength);
return true;
}
else
{
- SetLastError(ERROR_INSUFFICIENT_BUFFER);
- return false;
+ if(BytesToRead > BytesNeeded)
+ BytesToRead = BytesNeeded;
+ return pStream->BaseRead(pStream, &StartOffset, BlockBuffer, BytesToRead);
}
}
-static bool LinearStream_Switch(TLinearStream * pStream, TLinearStream * pNewStream)
+static bool LinearStream_CreateMirror(TBlockStream * pStream)
{
- // Sanity checks
- assert((pNewStream->dwFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_LINEAR);
- assert((pNewStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_FILE);
- assert((pStream->dwFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_LINEAR);
- assert((pStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_FILE);
-
- // Close the new stream
- pNewStream->BaseClose(pNewStream);
-
- // Close the source stream
- pStream->BaseClose(pStream);
-
- // Rename the new data source file to the existing file
- if(!BaseFile_Switch(pStream, pNewStream))
- return false;
-
- // Now we have to open the "pStream" again
- if(!BaseFile_Open(pStream, pStream->dwFlags))
+ // Do we have master function and base creation function?
+ if(pStream->pMaster == NULL || pStream->BaseCreate == NULL)
return false;
- // We need to cleanup the new data stream
- FileStream_Close(pNewStream);
- return true;
+ return false;
}
-static void LinearStream_Close(TLinearStream * pStream)
+static TFileStream * LinearStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
{
- // Free the souce stream, if any
- if(pStream->pMaster != NULL)
- FileStream_Close(pStream->pMaster);
- pStream->pMaster = NULL;
+ TBlockStream * pStream;
- // Free the data map, if any
- if(pStream->pBitmap != NULL)
- STORM_FREE(pStream->pBitmap);
- pStream->pBitmap = NULL;
+ // Create new empty stream
+ pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
- // Call the base class for closing the stream
- pStream->BaseClose(pStream);
-}
+ // Attempt to open the base stream. If this fails
+ // and we have a master stream, we can create new mirror
+ assert(pStream->BaseOpen != NULL);
+ if(!pStream->BaseOpen(pStream, dwStreamFlags))
+ {
+ // Do we have base create function and master stream?
+// if(!LinearStream_CreateMirror(pStream))
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return NULL;
+ }
+ }
-static bool LinearStream_Open(TLinearStream * pStream)
-{
- // Set the entry points
+ // Set the stream function as if this was linear file without bitmap
pStream->StreamRead = pStream->BaseRead;
pStream->StreamWrite = pStream->BaseWrite;
- pStream->StreamSetSize = pStream->BaseSetSize;
- pStream->StreamGetSize = (STREAM_GETSIZE)LinearStream_GetSize;
- pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime;
- pStream->StreamGetPos = (STREAM_GETPOS)LinearStream_GetPos;
- pStream->StreamGetBmp = (STREAM_GETBMP)CompleteFile_GetBmp;
- pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch;
- pStream->StreamClose = (STREAM_CLOSE)LinearStream_Close;
-
- // If the caller wanted us to load stream bitmap, do it
- if(pStream->dwFlags & STREAM_FLAG_USE_BITMAP)
+ pStream->StreamResize = pStream->BaseResize;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamClose = pStream->BaseClose;
+
+ // Setup the stream size
+ pStream->StreamSize = pStream->Base.File.FileSize;
+
+ // If we load the bitmap and find out that it's an incomplete file,
+ // We set the reading functions which check presence of each file block
+ if(LinearStream_LoadBitmap(pStream))
{
- // Attempt to load the file bitmap
- LinearStream_LoadBitmap(pStream);
+ // Set the stream position to zero. Stream size is already set
+ assert(pStream->StreamSize != 0);
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- // Reset the position to zero after manipulating with file pointer
- pStream->FilePos = 0;
-
- // If there is a file bitmap and the file is not complete,
- // we need to set the reading function so that it verifies each block
- if(pStream->pBitmap != NULL)
+ // Set the reading function
+ if(pStream->IsComplete == 0)
{
- // Set different function for retrieving file bitmap
- pStream->StreamGetBmp = (STREAM_GETBMP)LinearStream_GetBmp;
-
- // If the file is not complete, we also need to set different function for file read+write
- if(pStream->pBitmap->IsComplete == 0)
- {
- // If we also have source file, open that one
- if(pStream->szSourceName != NULL)
- pStream->pMaster = FileStream_OpenFile(pStream->szSourceName, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP);
-
- // Change functions for read+write
- pStream->StreamWrite = (STREAM_WRITE)LinearStream_Write;
- pStream->StreamRead = (STREAM_READ)LinearStream_Read;
- pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- }
+ // Supply the reading function
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamClose = (STREAM_CLOSE)BlockStream_Close;
+
+ // Supply the block functions
+ pStream->BlockCheck = (BLOCK_CHECK)LinearStream_BlockCheck;
+ pStream->BlockRead = (BLOCK_READ)LinearStream_BlockRead;
}
}
- return true;
+ return pStream;
}
//-----------------------------------------------------------------------------
@@ -1355,260 +1293,188 @@ static bool IsPartHeader(PPART_FILE_HEADER pPartHdr)
return false;
}
-static bool PartialStream_Read(
- TPartialStream * pStream,
- ULONGLONG * pByteOffset,
- void * pvBuffer,
- DWORD dwBytesToRead)
+static bool PartialStream_LoadBitmap(TBlockStream * pStream)
{
- ULONGLONG RawByteOffset;
- LPBYTE pbBuffer = (LPBYTE)pvBuffer;
- DWORD dwBytesRemaining = dwBytesToRead;
- DWORD dwPartOffset;
- DWORD dwPartIndex;
- DWORD dwBytesRead = 0;
- DWORD dwBlockSize = pStream->BlockSize;
- int nFailReason = ERROR_HANDLE_EOF; // Why it failed if not enough bytes was read
-
- // If the byte offset is not entered, use the current position
- if(pByteOffset == NULL)
- pByteOffset = &pStream->VirtualPos;
-
- // Check if the file position is not at or beyond end of the file
- if(*pByteOffset >= pStream->VirtualSize)
- {
- SetLastError(ERROR_HANDLE_EOF);
- return false;
- }
-
- // Get the part index where the read offset is
- // Note that the part index should now be within the range,
- // as read requests beyond-EOF are handled by the previous test
- dwPartIndex = (DWORD)(*pByteOffset / pStream->BlockSize);
- assert(dwPartIndex < pStream->BlockCount);
-
- // If the number of bytes remaining goes past
- // the end of the file, cut them
- if((*pByteOffset + dwBytesRemaining) > pStream->VirtualSize)
- dwBytesRemaining = (DWORD)(pStream->VirtualSize - *pByteOffset);
-
- // Calculate the offset in the current part
- dwPartOffset = (DWORD)(*pByteOffset) & (pStream->BlockSize - 1);
+ PPART_FILE_MAP_ENTRY FileBitmap;
+ PART_FILE_HEADER PartHdr;
+ ULONGLONG ByteOffset = 0;
+ ULONGLONG StreamSize = 0;
+ DWORD BlockCount;
+ DWORD BitmapSize;
- // Read all data, one part at a time
- while(dwBytesRemaining != 0)
+ // Only if the size is greater than size of the bitmap header
+ if(pStream->Base.File.FileSize > sizeof(PART_FILE_HEADER))
{
- PPART_FILE_MAP_ENTRY PartMap = pStream->PartMap + dwPartIndex;
- DWORD dwBytesInPart;
-
- // If the part is not present in the file, we fail the read
- if((PartMap->Flags & 3) == 0)
+ // Attempt to read PART file header
+ if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER)))
{
- nFailReason = ERROR_FILE_CORRUPT;
- break;
- }
-
- // If we are in the last part, we have to cut the number of bytes in the last part
- if(dwPartIndex == pStream->BlockCount - 1)
- dwBlockSize = (DWORD)pStream->VirtualSize & (pStream->BlockSize - 1);
+ // We need to swap PART file header on big-endian platforms
+ BSWAP_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER));
- // Get the number of bytes reamining in the current part
- dwBytesInPart = dwBlockSize - dwPartOffset;
+ // Verify the PART file header
+ if(IsPartHeader(&PartHdr))
+ {
+ // Get the number of blocks and size of one block
+ StreamSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo);
+ ByteOffset = sizeof(PART_FILE_HEADER);
+ BlockCount = (DWORD)((StreamSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize);
+ BitmapSize = BlockCount * sizeof(PART_FILE_MAP_ENTRY);
+
+ // Check if sizes match
+ if((ByteOffset + BitmapSize) < pStream->Base.File.FileSize)
+ {
+ // Allocate space for the array of PART_FILE_MAP_ENTRY
+ FileBitmap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount);
+ if(FileBitmap != NULL)
+ {
+ // Load the block map
+ if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize))
+ {
+ STORM_FREE(FileBitmap);
+ return false;
+ }
- // Compute the raw file offset of the file part
- RawByteOffset = MAKE_OFFSET64(PartMap->BlockOffsHi, PartMap->BlockOffsLo);
- if(RawByteOffset == 0)
- {
- nFailReason = ERROR_FILE_CORRUPT;
- break;
- }
+ // Make sure that the byte order is correct
+ BSWAP_ARRAY32_UNSIGNED(FileBitmap, BitmapSize);
- // If the number of bytes in part is too big, cut it
- if(dwBytesInPart > dwBytesRemaining)
- dwBytesInPart = dwBytesRemaining;
+ // Update the stream size
+ pStream->StreamSize = StreamSize;
- // Append the offset within the part
- RawByteOffset += dwPartOffset;
- if(!pStream->BaseRead(pStream, &RawByteOffset, pbBuffer, dwBytesInPart))
- {
- nFailReason = ERROR_FILE_CORRUPT;
- break;
+ // Fill the bitmap information
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = BitmapSize;
+ pStream->BlockSize = PartHdr.BlockSize;
+ pStream->BlockCount = BlockCount;
+ pStream->IsComplete = 0;
+ return true;
+ }
+ }
+ }
}
-
- // Increment the file position
- dwBytesRemaining -= dwBytesInPart;
- dwBytesRead += dwBytesInPart;
- pbBuffer += dwBytesInPart;
-
- // Move to the next file part
- dwPartOffset = 0;
- dwPartIndex++;
}
- // Move the file position by the number of bytes read
- pStream->VirtualPos = *pByteOffset + dwBytesRead;
- if(dwBytesRead != dwBytesToRead)
- SetLastError(nFailReason);
- return (dwBytesRead == dwBytesToRead);
+ return false;
}
-static bool PartialStream_GetPos(
- TPartialStream * pStream,
- ULONGLONG & ByteOffset)
+static bool PartialStream_BlockCheck(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG BlockOffset)
{
- ByteOffset = pStream->VirtualPos;
- return true;
-}
+ PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap;
+ DWORD BlockIndex;
-static bool PartialStream_GetSize(
- TPartialStream * pStream, // Pointer to an open stream
- ULONGLONG & FileSize) // Pointer where to store file size
-{
- FileSize = pStream->VirtualSize;
- return true;
+ // Sanity checks
+ assert((BlockOffset & (pStream->BlockSize - 1)) == 0);
+ assert(FileBitmap != NULL);
+
+ // Calculate the index of the block
+ BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize);
+
+ // Check if the flags are present
+ return (FileBitmap[BlockIndex].Flags & 0x03) ? true : false;
}
-static bool PartialStream_GetBitmap(
- TPartialStream * pStream,
- TFileBitmap * pBitmap,
- DWORD Length,
- LPDWORD LengthNeeded)
+static bool PartialStream_BlockRead(
+ TBlockStream * pStream,
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
{
- LPBYTE pbBitmap;
- DWORD TotalLength;
- DWORD BitmapSize = 0;
- DWORD ByteOffset;
- DWORD BitMask;
- bool bResult = false;
+ PPART_FILE_MAP_ENTRY FileBitmap;
+ ULONGLONG ByteOffset;
+ DWORD BytesToRead;
+ DWORD BlockIndex = (DWORD)(StartOffset / pStream->BlockSize);
- // Do we have stream bitmap?
- BitmapSize = ((pStream->BlockCount - 1) / 8) + 1;
+ // The starting offset must be aligned to size of the block
+ assert(pStream->FileBitmap != NULL);
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
- // Give the bitmap length
- TotalLength = sizeof(TFileBitmap) + BitmapSize;
- if(LengthNeeded != NULL)
- *LengthNeeded = TotalLength;
+ // Get the file map entry
+ FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex;
- // Do we have enough to fill at least the header?
- if(Length >= sizeof(TFileBitmap))
+ // If the blocks are not available, we need to load them from the master
+ // and then save to the mirror
+ if(bAvailable == false)
{
- // Fill the bitmap header
- pBitmap->StartOffset = 0;
- pBitmap->EndOffset = pStream->VirtualSize;
- pBitmap->BitmapSize = BitmapSize;
- pBitmap->BlockSize = pStream->BlockSize;
- pBitmap->BlockCount = pStream->BlockCount;
- pBitmap->IsComplete = 1;
-
- // Is there at least one incomplete block?
- for(DWORD i = 0; i < pStream->BlockCount; i++)
- {
- if(pStream->PartMap[i].Flags != 3)
- {
- pBitmap->IsComplete = 0;
- break;
- }
- }
-
- bResult = true;
+ // TODO: Support for downloading blocks
+ return false;
}
-
- // Do we have enough space for supplying the bitmap?
- if(Length >= TotalLength)
+ else
{
- // Fill the file bitmap
- pbBitmap = (LPBYTE)(pBitmap + 1);
- for(DWORD i = 0; i < pStream->BlockCount; i++)
+ while(StartOffset < EndOffset)
{
- // Is the block there?
- if(pStream->PartMap[i].Flags == 3)
- {
- ByteOffset = i / 8;
- BitMask = 1 << (i & 7);
- pbBitmap[ByteOffset] |= BitMask;
- }
+ // Get the number of bytes to be read
+ BytesToRead = (DWORD)(EndOffset - StartOffset);
+ if(BytesToRead > pStream->BlockSize)
+ BytesToRead = pStream->BlockSize;
+ if(BytesToRead > BytesNeeded)
+ BytesToRead = BytesNeeded;
+
+ // Read the block
+ ByteOffset = MAKE_OFFSET64(FileBitmap->BlockOffsHi, FileBitmap->BlockOffsLo);
+ if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead))
+ return false;
+
+ // Move the pointers
+ StartOffset += pStream->BlockSize;
+ BlockBuffer += pStream->BlockSize;
+ BytesNeeded -= pStream->BlockSize;
}
- bResult = true;
}
- return bResult;
+ return true;
}
-static void PartialStream_Close(TPartialStream * pStream)
+static TFileStream * PartialStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
{
- // Free the part map
- if(pStream->PartMap != NULL)
- STORM_FREE(pStream->PartMap);
- pStream->PartMap = NULL;
-
- // Clear variables
- pStream->VirtualSize = 0;
- pStream->VirtualPos = 0;
+ TBlockStream * pStream;
- // Close the base stream
- assert(pStream->BaseClose != NULL);
- pStream->BaseClose(pStream);
-}
-
-static bool PartialStream_Open(TPartialStream * pStream)
-{
- PART_FILE_HEADER PartHdr;
- ULONGLONG VirtualSize; // Size of the file stored in part file
- ULONGLONG ByteOffset = {0};
- DWORD BlockCount;
+ // Create new empty stream
+ pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
- // Sanity check
- assert(pStream->BaseRead != NULL);
+ // Attempt to open the base stream. If this fails
+ // and we have a master stream, we can create new mirror
+ assert(pStream->BaseOpen != NULL);
+ if(!pStream->BaseOpen(pStream, dwStreamFlags))
+ {
+ // Do we have base create function and master stream?
+// if(!PartialStream_CreateMirror(pStream))
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return NULL;
+ }
+ }
- // Attempt to read PART file header
- if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER)))
+ // Load the stream bitmap
+ if(PartialStream_LoadBitmap(pStream))
{
- // We need to swap PART file header on big-endian platforms
- BSWAP_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER));
+ // Set the stream position to zero. Stream size is already set
+ assert(pStream->StreamSize != 0);
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- // Verify the PART file header
- if(IsPartHeader(&PartHdr))
- {
- // Calculate the number of parts in the file
- VirtualSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo);
- assert(VirtualSize != 0);
- BlockCount = (DWORD)((VirtualSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize);
-
- // Allocate the map entry array
- pStream->PartMap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount);
- if(pStream->PartMap != NULL)
- {
- // Load the block map
- if(pStream->BaseRead(pStream, NULL, pStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY)))
- {
- // Swap the array of file map entries
- BSWAP_ARRAY32_UNSIGNED(pStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY));
-
- // Fill the members of PART file stream
- pStream->VirtualSize = ((ULONGLONG)PartHdr.FileSizeHi) + PartHdr.FileSizeLo;
- pStream->VirtualPos = 0;
- pStream->BlockCount = BlockCount;
- pStream->BlockSize = PartHdr.BlockSize;
-
- // Set new function pointers
- pStream->StreamRead = (STREAM_READ)PartialStream_Read;
- pStream->StreamGetPos = (STREAM_GETPOS)PartialStream_GetPos;
- pStream->StreamGetSize = (STREAM_GETSIZE)PartialStream_GetSize;
- pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime;
- pStream->StreamGetBmp = (STREAM_GETBMP)PartialStream_GetBitmap;
- pStream->StreamClose = (STREAM_CLOSE)PartialStream_Close;
- return true;
- }
+ // Set new function pointers
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamClose = (STREAM_CLOSE)BlockStream_Close;
- // Free the part map
- STORM_FREE(pStream->PartMap);
- pStream->PartMap = NULL;
- }
- }
+ // Supply the block functions
+ pStream->BlockCheck = (BLOCK_CHECK)PartialStream_BlockCheck;
+ pStream->BlockRead = (BLOCK_READ)PartialStream_BlockRead;
+ return pStream;
}
+ // Cleanup the stream and return
+ FileStream_Close(pStream);
SetLastError(ERROR_BAD_FORMAT);
- return false;
+ return NULL;
}
//-----------------------------------------------------------------------------
@@ -1806,32 +1672,79 @@ static void DecryptFileChunk(
}
}
-static bool DetectFileKey(LPBYTE pbKeyBuffer, LPBYTE pbEncryptedHeader)
+static bool EncryptedStream_DetectFileKey(TEncryptedStream * pStream)
{
ULONGLONG ByteOffset = 0;
+ BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
BYTE FileHeader[MPQE_CHUNK_SIZE];
- // We just try all known keys one by one
- for(int i = 0; AuthCodeArray[i] != NULL; i++)
+ // Read the first file chunk
+ if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader)))
{
- // Prepare they decryption key from game serial number
- CreateKeyFromAuthCode(pbKeyBuffer, AuthCodeArray[i]);
-
- // Try to decrypt with the given key
- memcpy(FileHeader, pbEncryptedHeader, MPQE_CHUNK_SIZE);
- DecryptFileChunk((LPDWORD)FileHeader, pbKeyBuffer, ByteOffset, MPQE_CHUNK_SIZE);
-
- // We check the decrypted data
- // All known encrypted MPQs have header at the begin of the file,
- // so we check for MPQ signature there.
- if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q')
- return true;
+ // We just try all known keys one by one
+ for(int i = 0; AuthCodeArray[i] != NULL; i++)
+ {
+ // Prepare they decryption key from game serial number
+ CreateKeyFromAuthCode(pStream->Key, AuthCodeArray[i]);
+
+ // Try to decrypt with the given key
+ memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE);
+ DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE);
+
+ // We check the decrypted data
+ // All known encrypted MPQs have header at the begin of the file,
+ // so we check for MPQ signature there.
+ if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q')
+ {
+ // Update the stream size
+ pStream->StreamSize = pStream->Base.File.FileSize;
+
+ // Fill the block information
+ pStream->BlockSize = MPQE_CHUNK_SIZE;
+ pStream->BlockCount = (DWORD)(pStream->Base.File.FileSize + MPQE_CHUNK_SIZE - 1) / MPQE_CHUNK_SIZE;
+ pStream->IsComplete = 1;
+ return true;
+ }
+ }
}
// Key not found, sorry
return false;
}
+static bool EncryptedStream_BlockCheck(TEncryptedStream *, ULONGLONG)
+{
+ return true;
+}
+
+static bool EncryptedStream_BlockRead(
+ TEncryptedStream * pStream,
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
+{
+ DWORD dwBytesToRead;
+
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+ assert(bAvailable != false);
+ BytesNeeded = BytesNeeded;
+
+ // Read the file from the stream as-is
+ // Limit the reading to number of blocks really needed
+ dwBytesToRead = (DWORD)(EndOffset - StartOffset);
+ if(!pStream->BaseRead(pStream, &StartOffset, BlockBuffer, dwBytesToRead))
+ return false;
+
+ // Decrypt the data
+ dwBytesToRead = (dwBytesToRead + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1);
+ DecryptFileChunk((LPDWORD)BlockBuffer, pStream->Key, StartOffset, dwBytesToRead);
+ return true;
+}
+
+/*
static bool EncryptedStream_Read(
TEncryptedStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
@@ -1848,7 +1761,7 @@ static bool EncryptedStream_Read(
bool bResult = false;
// Get the byte offset
- ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->FilePos;
+ ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->StreamPos;
// Cut it down to MPQE chunk size
StartOffset = ByteOffset;
@@ -1857,7 +1770,7 @@ static bool EncryptedStream_Read(
// Calculate number of bytes to decrypt
dwBytesToDecrypt = (DWORD)(EndOffset - StartOffset);
- dwBytesToAllocate = (dwBytesToDecrypt + (MPQE_CHUNK_SIZE - 1)) & ~(MPQE_CHUNK_SIZE - 1);
+ dwBytesToAllocate = (dwBytesToDecrypt + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1);
// Allocate buffers for encrypted and decrypted data
pbMpqData = STORM_ALLOC(BYTE, dwBytesToAllocate);
@@ -1885,136 +1798,50 @@ static bool EncryptedStream_Read(
STORM_FREE(pbMpqData);
}
- // Free buffers and exit
+ // Update stream position
+ pStream->StreamPos = ByteOffset + dwBytesToRead;
return bResult;
}
+*/
-static bool EncryptedStream_Open(TEncryptedStream * pStream)
-{
- ULONGLONG ByteOffset = 0;
- BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
-
- // Sanity check
- assert(pStream->BaseRead != NULL);
-
- // Load one MPQE chunk and try to detect the file key
- if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader)))
- {
- // Attempt to decrypt the MPQ header with all known keys
- if(DetectFileKey(pStream->Key, EncryptedHeader))
- {
- // Assign functions
- pStream->StreamRead = (STREAM_READ)EncryptedStream_Read;
- pStream->StreamGetSize = (STREAM_GETSIZE)LinearStream_GetSize;
- pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime;
- pStream->StreamGetPos = (STREAM_GETPOS)LinearStream_GetPos;
- pStream->StreamGetBmp = (STREAM_GETBMP)CompleteFile_GetBmp;
- pStream->StreamClose = pStream->BaseClose;
-
- // We need to reset the position back to the begin of the file
- pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, 0);
- return true;
- }
-
- // An unknown key
- SetLastError(ERROR_UNKNOWN_FILE_KEY);
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// File stream allocation function
-
-/**
- * This function allocates an empty structure for the file stream
- * The stream structure is created as variable length, linear block of data
- * The file name is placed after the end of the stream structure data
- *
- * \a szFileName Name of the file
- * \a dwStreamFlags Stream flags telling what kind of stream structure to create
- */
-
-static TFileStream * AllocateFileStream(
- const TCHAR * szFileName,
- DWORD dwStreamFlags)
+static TFileStream * EncryptedStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
{
- TFileStream * pStream;
- TCHAR * szSourceName;
- size_t FileNameSize = (_tcslen(szFileName) + 1) * sizeof(TCHAR);
- size_t StreamSize = 0;
- DWORD dwStreamProvider = dwStreamFlags & STREAM_PROVIDER_MASK;
- DWORD dwBaseProvider = dwStreamFlags & BASE_PROVIDER_MASK;
+ TEncryptedStream * pStream;
- // The "file:" prefix forces the BASE_PROVIDER_FILE
- if(!_tcsicmp(szFileName, _T("file:")))
- {
- dwBaseProvider = BASE_PROVIDER_FILE;
- szFileName += 5;
- }
-
- // The "map:" prefix forces the BASE_PROVIDER_MAP
- if(!_tcsicmp(szFileName, _T("map:")))
- {
- dwBaseProvider = BASE_PROVIDER_MAP;
- szFileName += 4;
- }
-
- // The "http:" prefix forces the BASE_PROVIDER_HTTP
- if(!_tcsicmp(szFileName, _T("http:")))
- {
- dwBaseProvider = BASE_PROVIDER_HTTP;
- szFileName += 5;
- }
+ // Create new empty stream
+ pStream = (TEncryptedStream *)AllocateFileStream(szFileName, sizeof(TEncryptedStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
- // Re-create the stream flags
- dwStreamFlags = (dwStreamFlags & STREAM_FLAG_MASK) | dwStreamProvider | dwBaseProvider;
+ // Attempt to open the base stream
+ assert(pStream->BaseOpen != NULL);
+ if(!pStream->BaseOpen(pStream, dwStreamFlags))
+ return NULL;
- // Allocate file stream for each stream provider
- switch(dwStreamFlags & STREAM_PROVIDER_MASK)
+ // Determine the encryption key for the MPQ
+ if(EncryptedStream_DetectFileKey(pStream))
{
- case STREAM_PROVIDER_LINEAR: // Allocate structure for linear stream
- StreamSize = sizeof(TLinearStream);
- break;
-
- case STREAM_PROVIDER_PARTIAL:
- dwStreamFlags |= STREAM_FLAG_READ_ONLY;
- StreamSize = sizeof(TPartialStream);
- break;
+ // Set the stream position and size
+ assert(pStream->StreamSize != 0);
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- case STREAM_PROVIDER_ENCRYPTED:
- dwStreamFlags |= STREAM_FLAG_READ_ONLY;
- StreamSize = sizeof(TEncryptedStream);
- break;
+ // Set new function pointers
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamClose = pStream->BaseClose;
- default:
- return NULL;
+ // Supply the block functions
+ pStream->BlockCheck = (BLOCK_CHECK)EncryptedStream_BlockCheck;
+ pStream->BlockRead = (BLOCK_READ)EncryptedStream_BlockRead;
+ return pStream;
}
- // Allocate the stream structure for the given stream type
- pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize);
- if(pStream == NULL)
- {
- SetLastError(ERROR_NOT_ENOUGH_MEMORY);
- return NULL;
- }
-
- // Fill the stream structure with zeros
- memset(pStream, 0, StreamSize);
-
- // Remember the file name
- pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize);
- pStream->dwFlags = dwStreamFlags;
- memcpy(pStream->szFileName, szFileName, FileNameSize);
-
- // If we have source file name, setup it as well
- szSourceName = _tcschr(pStream->szFileName, _T('*'));
- if(szSourceName != NULL)
- {
- // Remember the source name and cut these two file names
- pStream->szSourceName = szSourceName + 1;
- szSourceName[0] = 0;
- }
- return pStream;
+ // Cleanup the stream and return
+ FileStream_Close(pStream);
+ SetLastError(ERROR_UNKNOWN_FILE_KEY);
+ return NULL;
}
//-----------------------------------------------------------------------------
@@ -2044,14 +1871,14 @@ TFileStream * FileStream_CreateFile(
TFileStream * pStream;
// We only support creation of linear, local file
- if((dwStreamFlags & (STREAM_PROVIDER_MASK | BASE_PROVIDER_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
+ if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
{
SetLastError(ERROR_NOT_SUPPORTED);
return NULL;
}
// Allocate file stream structure for linear stream
- pStream = AllocateFileStream(szFileName, dwStreamFlags);
+ pStream = AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
if(pStream != NULL)
{
// Attempt to create the disk file
@@ -2060,12 +1887,9 @@ TFileStream * FileStream_CreateFile(
// Fill the stream provider functions
pStream->StreamRead = pStream->BaseRead;
pStream->StreamWrite = pStream->BaseWrite;
- pStream->StreamSetSize = pStream->BaseSetSize;
- pStream->StreamGetSize = (STREAM_GETSIZE)LinearStream_GetSize;
- pStream->StreamGetTime = (STREAM_GETTIME)LinearStream_GetTime;
- pStream->StreamGetPos = (STREAM_GETPOS)LinearStream_GetPos;
- pStream->StreamGetBmp = (STREAM_GETBMP)CompleteFile_GetBmp;
- pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch;
+ pStream->StreamResize = pStream->BaseResize;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamGetPos = pStream->BaseGetPos;
pStream->StreamClose = pStream->BaseClose;
return pStream;
}
@@ -2087,8 +1911,6 @@ TFileStream * FileStream_CreateFile(
* - If the file does not exist, the function must return NULL
* - If the file exists but cannot be open, then function must return NULL
* - The parameters of the function must be validate by the caller
- * - The function must check if the file is a PART file,
- * and create TPartialStream object if so.
* - The function must initialize all stream function pointers in TFileStream
* - If the function fails from any reason, it must close all handles
* and free all memory that has been allocated in the process of stream creation,
@@ -2102,83 +1924,46 @@ TFileStream * FileStream_OpenFile(
const TCHAR * szFileName,
DWORD dwStreamFlags)
{
- TFileStream * pStream = NULL;
- DWORD dwClearMask = STREAM_OPTIONS_MASK;
- bool bStreamResult = false;
- bool bBaseResult = false;
-
- // Allocate the stream of the given type
- pStream = AllocateFileStream(szFileName, dwStreamFlags);
- if(pStream == NULL)
- return NULL;
+ DWORD dwBaseProvider = dwStreamFlags & BASE_PROVIDER_MASK;
- // Few special checks when we want the stream to be cached from another source,
- if(pStream->szSourceName != NULL)
+ // The "file:" prefix forces the BASE_PROVIDER_FILE
+ if(!_tcsicmp(szFileName, _T("file:")))
{
- // We don't allow other base types than BASE_PROVIDER_FILE
- if((pStream->dwFlags & BASE_PROVIDER_MASK) != BASE_PROVIDER_FILE)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- STORM_FREE(pStream);
- return NULL;
- }
-
- // Clear the STREAM_FLAG_READ_ONLY flag for the base file when being open
- // as local cache of a remote file
- dwClearMask &= ~STREAM_FLAG_READ_ONLY;
+ dwBaseProvider = BASE_PROVIDER_FILE;
+ szFileName += 5;
}
-
- // Now initialize the respective base provider
- switch(pStream->dwFlags & BASE_PROVIDER_MASK)
+
+ // The "map:" prefix forces the BASE_PROVIDER_MAP
+ if(!_tcsicmp(szFileName, _T("map:")))
{
- case BASE_PROVIDER_FILE:
- bBaseResult = BaseFile_Open(pStream, pStream->dwFlags & dwClearMask);
- break;
-
- case BASE_PROVIDER_MAP:
- pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- bBaseResult = BaseMap_Open(pStream);
- break;
-
- case BASE_PROVIDER_HTTP:
- pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
- bBaseResult = BaseHttp_Open(pStream);
- break;
+ dwBaseProvider = BASE_PROVIDER_MAP;
+ szFileName += 4;
}
-
- // If we failed to open the base storage, fail the operation
- if(bBaseResult == false)
+
+ // The "http:" prefix forces the BASE_PROVIDER_HTTP
+ if(!_tcsicmp(szFileName, _T("http:")))
{
- STORM_FREE(pStream);
- return NULL;
+ dwBaseProvider = BASE_PROVIDER_HTTP;
+ szFileName += 5;
}
- // Now initialize the stream provider
- switch(pStream->dwFlags & STREAM_PROVIDER_MASK)
+ // Re-assemble the stream flags
+ dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | (dwStreamFlags & STREAM_PROVIDER_MASK) | dwBaseProvider;
+ switch(dwStreamFlags & STREAM_PROVIDER_MASK)
{
case STREAM_PROVIDER_LINEAR:
- bStreamResult = LinearStream_Open((TLinearStream *)pStream);
- break;
+ return LinearStream_Open(szFileName, dwStreamFlags);
case STREAM_PROVIDER_PARTIAL:
- bStreamResult = PartialStream_Open((TPartialStream *)pStream);
- break;
+ return PartialStream_Open(szFileName, dwStreamFlags);
case STREAM_PROVIDER_ENCRYPTED:
- bStreamResult = EncryptedStream_Open((TEncryptedStream *)pStream);
- break;
- }
+ return EncryptedStream_Open(szFileName, dwStreamFlags);
- // If the operation failed, free the stream and set it to NULL
- if(bStreamResult == false)
- {
- // Only close the base stream
- pStream->BaseClose(pStream);
- STORM_FREE(pStream);
- pStream = NULL;
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return NULL;
}
-
- return pStream;
}
/**
@@ -2221,24 +2006,16 @@ bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBu
bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
{
if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
return false;
+ }
assert(pStream->StreamWrite != NULL);
return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite);
}
/**
- * This function returns the current file position
- * \a pStream
- * \a ByteOffset
- */
-bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
-{
- assert(pStream->StreamGetPos != NULL);
- return pStream->StreamGetPos(pStream, pByteOffset);
-}
-
-/**
* Returns the size of a file
*
* \a pStream Pointer to an open stream
@@ -2259,10 +2036,24 @@ bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
{
if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
return false;
+ }
+
+ assert(pStream->StreamResize != NULL);
+ return pStream->StreamResize(pStream, NewFileSize);
+}
- assert(pStream->StreamSetSize != NULL);
- return pStream->StreamSetSize(pStream, NewFileSize);
+/**
+ * This function returns the current file position
+ * \a pStream
+ * \a pByteOffset
+ */
+bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ assert(pStream->StreamGetPos != NULL);
+ return pStream->StreamGetPos(pStream, pByteOffset);
}
/**
@@ -2273,8 +2064,9 @@ bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
*/
bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFileTime)
{
- assert(pStream->StreamGetTime != NULL);
- return pStream->StreamGetTime(pStream, pFileTime);
+ // Just use the saved filetime value
+ *pFileTime = pStream->Base.File.FileTime;
+ return true;
}
/**
@@ -2298,15 +2090,39 @@ bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags)
* 3) Opens the MPQ stores the handle and stream position to the new stream structure
*
* \a pStream Pointer to an open stream
- * \a pTempStream Temporary ("working") stream (created during archive compacting)
+ * \a pNewStream Temporary ("working") stream (created during archive compacting)
*/
-bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream)
+bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream)
{
+ // Only supported on linear files
+ if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+ }
+
+ // Not supported on read-only streams
if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // Close both stream's base providers
+ pNewStream->BaseClose(pNewStream);
+ pStream->BaseClose(pStream);
+
+ // Now we have to delete the (now closed) old file and rename the new file
+ if(!BaseFile_Replace(pStream, pNewStream))
+ return false;
+
+ // Now open the base file again
+ if(BaseFile_Open(pStream, pStream->dwFlags))
return false;
- assert(pStream->StreamSwitch != NULL);
- return pStream->StreamSwitch(pStream, pNewStream);
+ // Cleanup the new stream
+ FileStream_Close(pNewStream);
+ return true;
}
/**
@@ -2331,23 +2147,6 @@ bool FileStream_IsReadOnly(TFileStream * pStream)
}
/**
- * This function retrieves the file bitmap. A file bitmap is an array
- * of bits, each bit representing one file block. A value of 1 means
- * that the block is present in the file, a value of 0 means that the
- * block is not present.
- *
- * \a pStream Pointer to an open stream
- * \a pBitmap Pointer to buffer where to store the file bitmap
- * \a Length Size of buffer pointed by pBitmap, in bytes
- * \a LengthNeeded If non-NULL, the function supplies the necessary byte size of the buffer
- */
-bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD Length, LPDWORD LengthNeeded)
-{
- assert(pStream->StreamGetBmp != NULL);
- return pStream->StreamGetBmp(pStream, pvBitmap, Length, LengthNeeded);
-}
-
-/**
* This function closes an archive file and frees any data buffers
* that have been allocated for stream management. The function must also
* support partially allocated structure, i.e. one or more buffers
@@ -2360,10 +2159,18 @@ void FileStream_Close(TFileStream * pStream)
// Check if the stream structure is allocated at all
if(pStream != NULL)
{
+ // Free the master stream, if any
+ if(pStream->pMaster != NULL)
+ FileStream_Close(pStream->pMaster);
+ pStream->pMaster = NULL;
+
// Close the stream provider.
- // This will also close the base stream
- assert(pStream->StreamClose != NULL);
- pStream->StreamClose(pStream);
+ if(pStream->StreamClose != NULL)
+ pStream->StreamClose(pStream);
+
+ // Also close base stream, if any
+ else if(pStream->BaseClose != NULL)
+ pStream->BaseClose(pStream);
// Free the stream itself
STORM_FREE(pStream);
diff --git a/src/FileStream.h b/src/FileStream.h
index a44b48e..f660a87 100644
--- a/src/FileStream.h
+++ b/src/FileStream.h
@@ -14,6 +14,19 @@
//-----------------------------------------------------------------------------
// Function prototypes
+typedef void (*STREAM_INIT)(
+ struct TFileStream * pStream // Pointer to an unopened stream
+);
+
+typedef bool (*STREAM_CREATE)(
+ struct TFileStream * pStream // Pointer to an unopened stream
+ );
+
+typedef bool (*STREAM_OPEN)(
+ struct TFileStream * pStream, // Pointer to an unopened stream
+ DWORD dwStreamFlags // Stream flags
+ );
+
typedef bool (*STREAM_READ)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
@@ -28,7 +41,7 @@ typedef bool (*STREAM_WRITE)(
DWORD dwBytesToWrite // Number of bytes to read from the file
);
-typedef bool (*STREAM_SETSIZE)(
+typedef bool (*STREAM_RESIZE)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG FileSize // New size for the file, in bytes
);
@@ -38,36 +51,34 @@ typedef bool (*STREAM_GETSIZE)(
ULONGLONG * pFileSize // Receives the file size, in bytes
);
-typedef bool (*STREAM_GETTIME)(
- struct TFileStream * pStream,
- ULONGLONG * pFT
- );
-
typedef bool (*STREAM_GETPOS)(
struct TFileStream * pStream, // Pointer to an open stream
ULONGLONG * pByteOffset // Pointer to store current file position
);
-typedef bool (*STREAM_GETBMP)(
- TFileStream * pStream,
- void * pvBitmap,
- DWORD Length,
- LPDWORD LengthNeeded
+typedef void (*STREAM_CLOSE)(
+ struct TFileStream * pStream
);
-typedef bool (*STREAM_SWITCH)(
- struct TFileStream * pStream,
- struct TFileStream * pNewStream
+typedef bool (*BLOCK_READ)(
+ struct TFileStream * pStream, // Pointer to an opened block stream
+ ULONGLONG StartOffset, // Byte offset of start of the block array
+ ULONGLONG EndOffset, // End offset (either end of the block or end of the file)
+ LPBYTE BlockBuffer, // Pointer to block-aligned buffer
+ DWORD BytesNeeded, // Number of bytes that are really needed
+ bool bAvailable // true if the block is available
);
-typedef void (*STREAM_CLOSE)(
- struct TFileStream * pStream
+typedef DWORD (*BLOCK_CHECK)(
+ struct TFileStream * pStream,
+ ULONGLONG BlockOffset
);
//-----------------------------------------------------------------------------
// Local structures - partial file structure and bitmap footer
#define ID_FILE_BITMAP_FOOTER 0x33767470 // Signature of the file bitmap footer ('ptv3')
+#define DEFAULT_BLOCK_SIZE 0x00004000 // Default size of the stream block
typedef struct _PART_FILE_HEADER
{
@@ -93,91 +104,94 @@ typedef struct _PART_FILE_MAP_ENTRY
typedef struct _FILE_BITMAP_FOOTER
{
- DWORD dwSignature; // 'ptv3' (MPQ_DATA_BITMAP_SIGNATURE)
- DWORD dwAlways3; // Unknown, seems to always have value of 3
- DWORD dwBuildNumber; // Game build number for that MPQ
- DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map
- DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map
- DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes)
+ DWORD Signature; // 'ptv3' (ID_FILE_BITMAP_FOOTER)
+ DWORD Version; // Unknown, seems to always have value of 3 (version?)
+ DWORD BuildNumber; // Game build number for that MPQ
+ DWORD MapOffsetLo; // Low 32-bits of the offset of the bit map
+ DWORD MapOffsetHi; // High 32-bits of the offset of the bit map
+ DWORD BlockSize; // Size of one block (usually 0x4000 bytes)
} FILE_BITMAP_FOOTER, *PFILE_BITMAP_FOOTER;
//-----------------------------------------------------------------------------
-// Local structures
+// Structure for file stream
-union TBaseData
+union TBaseProviderData
{
struct
{
+ ULONGLONG FileSize; // Size of the file
+ ULONGLONG FilePos; // Current file position
+ ULONGLONG FileTime; // Last write time
HANDLE hFile; // File handle
} File;
struct
{
+ ULONGLONG FileSize; // Size of the file
+ ULONGLONG FilePos; // Current file position
+ ULONGLONG FileTime; // Last write time
LPBYTE pbFile; // Pointer to mapped view
} Map;
struct
{
+ ULONGLONG FileSize; // Size of the file
+ ULONGLONG FilePos; // Current file position
+ ULONGLONG FileTime; // Last write time
HANDLE hInternet; // Internet handle
HANDLE hConnect; // Connection to the internet server
} Http;
};
-//-----------------------------------------------------------------------------
-// Structure for linear stream
-
struct TFileStream
{
// Stream provider functions
STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly.
STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly.
- STREAM_SETSIZE StreamSetSize; // Pointer to function changing file size
+ STREAM_RESIZE StreamResize; // Pointer to function changing file size
STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size
- STREAM_GETTIME StreamGetTime; // Pointer to function retrieving the file time
STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position
- STREAM_GETBMP StreamGetBmp; // Pointer to function that retrieves the file bitmap
- STREAM_SWITCH StreamSwitch; // Pointer to function changing the stream to another file
STREAM_CLOSE StreamClose; // Pointer to function closing the stream
+ // Block-oriented functions
+ BLOCK_READ BlockRead; // Pointer to function reading one or more blocks
+ BLOCK_CHECK BlockCheck; // Pointer to function checking whether the block is present
+
// Base provider functions
- STREAM_READ BaseRead;
- STREAM_WRITE BaseWrite;
- STREAM_SETSIZE BaseSetSize; // Pointer to function changing file size
+ STREAM_CREATE BaseCreate; // Pointer to base create function
+ STREAM_OPEN BaseOpen; // Pointer to base open function
+ STREAM_READ BaseRead; // Read from the stream
+ STREAM_WRITE BaseWrite; // Write to the stream
+ STREAM_RESIZE BaseResize; // Pointer to function changing file size
+ STREAM_GETSIZE BaseGetSize; // Pointer to function returning file size
+ STREAM_GETPOS BaseGetPos; // Pointer to function that returns current file position
STREAM_CLOSE BaseClose; // Pointer to function closing the stream
- ULONGLONG FileSize; // Size of the file
- ULONGLONG FilePos; // Current file position
- ULONGLONG FileTime; // Date/time of last modification of the file
- TCHAR * szSourceName; // Name of the source file (might be HTTP file server or local file)
+ // Base provider data (file size, file position)
+ TBaseProviderData Base;
+
+ // Stream provider data
+ TFileStream * pMaster; // Master stream (e.g. MPQ on a web server)
TCHAR * szFileName; // File name (self-relative pointer)
- DWORD dwFlags; // Stream flags
- TBaseData Base; // Base provider data
+ ULONGLONG StreamSize; // Stream size (can be less than file size)
+ ULONGLONG StreamPos; // Stream position
+ DWORD dwFlags; // Stream flags
// Followed by stream provider data, with variable length
};
//-----------------------------------------------------------------------------
-// Structures for linear stream
+// Structures for block-oriented stream
-struct TLinearStream : public TFileStream
+struct TBlockStream : public TFileStream
{
- TFileStream * pMaster; // Master file for loading missing data blocks
- TFileBitmap * pBitmap; // Pointer to the linear bitmap
-};
-
-//-----------------------------------------------------------------------------
-// Structure for partial stream
-
-struct TPartialStream : public TFileStream
-{
- ULONGLONG VirtualSize; // Virtual size of the file
- ULONGLONG VirtualPos; // Virtual position in the file
- DWORD BlockCount; // Number of file blocks. Used by partial file stream
- DWORD BlockSize; // Size of one block. Used by partial file stream
-
- PPART_FILE_MAP_ENTRY PartMap; // File map, variable length
+ void * FileBitmap; // Array of bits for file blocks
+ DWORD BitmapSize; // Size of the file bitmap (in bytes)
+ DWORD BlockSize; // Size of one block, in bytes
+ DWORD BlockCount; // Number of data blocks in the file
+ DWORD IsComplete; // If nonzero, no blocks are missing
};
//-----------------------------------------------------------------------------
@@ -185,7 +199,7 @@ struct TPartialStream : public TFileStream
#define MPQE_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted
-struct TEncryptedStream : public TFileStream
+struct TEncryptedStream : public TBlockStream
{
BYTE Key[MPQE_CHUNK_SIZE]; // File key
};
diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp
index 5ca4065..b5482c8 100644
--- a/src/SFileCompactArchive.cpp
+++ b/src/SFileCompactArchive.cpp
@@ -575,7 +575,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR
// If succeeded, switch the streams
if(nError == ERROR_SUCCESS)
{
- if(FileStream_Switch(ha->pStream, pTempStream))
+ if(FileStream_Replace(ha->pStream, pTempStream))
pTempStream = NULL;
else
nError = ERROR_CAN_NOT_COMPLETE;
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp
index 31ee594..e56e2f6 100644
--- a/src/SFileGetFileInfo.cpp
+++ b/src/SFileGetFileInfo.cpp
@@ -163,10 +163,20 @@ bool WINAPI SFileGetFileInfo(
}
break;
- case SFileMpqFileBitmap:
+ case SFileMpqStreamBlockSize:
ha = IsValidMpqHandle(hMpqOrFile);
if(ha != NULL)
- return FileStream_GetBitmap(ha->pStream, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+ {
+ // TODO
+ }
+ break;
+
+ case SFileMpqStreamBlockAvailable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ // TODO
+ }
break;
case SFileMpqUserDataOffset:
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index e5dcf03..9970791 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -161,8 +161,13 @@ bool WINAPI SFileOpenArchive(
// Open the MPQ archive file
if(nError == ERROR_SUCCESS)
{
+ DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK);
+
+ // If not forcing MPQ v 1.0, also use file bitmap
+ dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP;
+
// Initialize the stream
- pStream = FileStream_OpenFile(szMpqName, (dwFlags & STREAM_OPTIONS_MASK) | STREAM_FLAG_USE_BITMAP);
+ pStream = FileStream_OpenFile(szMpqName, dwStreamFlags);
if(pStream == NULL)
nError = GetLastError();
}
diff --git a/src/StormLib.h b/src/StormLib.h
index fcc1925..e528283 100644
--- a/src/StormLib.h
+++ b/src/StormLib.h
@@ -272,8 +272,10 @@ extern "C" {
#define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only
#define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write
#define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it
-#define STREAM_FLAG_MASK 0x0000FF00 // Mask for stream flags
-#define STREAM_OPTIONS_MASK 0x0000FFFF // Mask for all stream options
+#define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options
+
+#define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers
+#define STREAM_FLAGS_MASK 0x0000FFFF // Mask for all stream flags (providers+options)
#define MPQ_OPEN_NO_LISTFILE 0x00010000 // Don't load the internal listfile
#define MPQ_OPEN_NO_ATTRIBUTES 0x00020000 // Don't open the attributes
@@ -361,7 +363,8 @@ typedef enum _SFileInfoClass
{
// Info classes for archives
SFileMpqFileName, // Name of the archive file (TCHAR [])
- SFileMpqFileBitmap, // Bitmap of the archive (TFileBitmap + BYTE[])
+ SFileMpqStreamBlockSize, // Size of one stream block in bytes (DWORD)
+ SFileMpqStreamBlockAvailable, // Nonzero if the stream block at the given offset is available (input: ByteOffset, ULONGLONG)
SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG)
SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData)
SFileMpqUserData, // MPQ USer data, without the header (BYTE [])
@@ -695,19 +698,6 @@ typedef struct _TPatchHeader
#define SIZE_OF_XFRM_HEADER 0x0C
-// Structure for file bitmap. Used by SFileGetFileInfo(SFileMpqFileBitmap)
-typedef struct _TFileBitmap
-{
- ULONGLONG StartOffset; // Starting offset of the file, covered by bitmap
- ULONGLONG EndOffset; // Ending offset of the file, covered by bitmap
- DWORD BitmapSize; // Size of the file bitmap (in bytes)
- DWORD BlockSize; // Size of one block, in bytes
- DWORD BlockCount; // Number of data blocks in the file
- DWORD IsComplete; // If nonzero, no blocks are missing
-
- // Followed by file bitmap (variable length), array of BYTEs)
-} TFileBitmap;
-
// This is the combined file entry for maintaining file list in the MPQ.
// This structure is combined from block table, hi-block table,
// (attributes) file and from (listfile).
@@ -949,14 +939,12 @@ const TCHAR * FileStream_GetFileName(TFileStream * pStream);
bool FileStream_IsReadOnly(TFileStream * pStream);
bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead);
bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite);
-bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset);
-bool FileStream_SetPos(TFileStream * pStream, ULONGLONG ByteOffset);
-bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize);
bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize);
+bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize);
+bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset);
bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFT);
bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags);
-bool FileStream_Switch(TFileStream * pStream, TFileStream * pTempStream);
-bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD Length, LPDWORD LengthNeeded);
+bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream);
void FileStream_Close(TFileStream * pStream);
//-----------------------------------------------------------------------------
diff --git a/test/Test.cpp b/test/Test.cpp
index e3e78f0..30b2fd4 100644
--- a/test/Test.cpp
+++ b/test/Test.cpp
@@ -770,6 +770,53 @@ static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONG
return ERROR_SUCCESS;
}
+static int VerifyFilePosition(
+ TLogHelper * pLogger,
+ TFileStream * pStream,
+ ULONGLONG ExpectedPosition)
+{
+ ULONGLONG ByteOffset = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Retrieve the file position
+ if(FileStream_GetPos(pStream, &ByteOffset))
+ {
+ if(ByteOffset != ExpectedPosition)
+ {
+ pLogger->PrintMessage("The file position is different than expected (expected: " I64u_a ", current: " I64u_a, ExpectedPosition, ByteOffset);
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ nError = pLogger->PrintError("Failed to retrieve the file offset");
+ }
+
+ return nError;
+}
+
+static int VerifyFileMpqHeader(TLogHelper * pLogger, TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ TMPQHeader Header;
+ int nError = ERROR_SUCCESS;
+
+ memset(&Header, 0xFE, sizeof(TMPQHeader));
+ if(FileStream_Read(pStream, pByteOffset, &Header, sizeof(TMPQHeader)))
+ {
+ if(Header.dwID != ID_MPQ)
+ {
+ pLogger->PrintMessage("Read error - the data is not a MPQ header");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ nError = pLogger->PrintError("Failed to read the MPQ header");
+ }
+
+ return nError;
+}
+
static int WriteMpqUserDataHeader(
TLogHelper * pLogger,
TFileStream * pStream,
@@ -1792,59 +1839,120 @@ static int TestReadFile_MasterMirror(const char * szMirrorName, const char * szM
return nError;
}
-//
-static int TestReadFile_Partial(const char * szPlainName)
+// Test of the TFileStream object
+static int TestFileStreamOperations(const char * szPlainName, DWORD dwStreamFlags)
{
- TLogHelper Logger("PartFileRead", szPlainName);
- TMPQHeader Header;
+ TFileStream * pStream;
+ TLogHelper Logger("FileStreamTest", szPlainName);
ULONGLONG ByteOffset;
ULONGLONG FileSize = 0;
- TFileStream * pStream;
- char szFileName[MAX_PATH];
- BYTE Buffer[0x100];
+ DWORD dwRequiredFlags = 0;
+ char szFullPath[MAX_PATH];
+ BYTE Buffer[0x10];
int nError = ERROR_SUCCESS;
- // Open the partial file
- CreateFullPathName(szFileName, szMpqSubDir, szPlainName);
- pStream = OpenLocalFile(szFileName, STREAM_PROVIDER_PARTIAL | BASE_PROVIDER_FILE | STREAM_FLAG_READ_ONLY);
+ // Copy the file so we won't screw up
+ CreateFileCopy(&Logger, szPlainName, szPlainName, szFullPath);
+
+ // Open the file stream
+ pStream = OpenLocalFile(szFullPath, dwStreamFlags);
if(pStream == NULL)
- nError = Logger.PrintError("Failed to open %s", szFileName);
+ nError = Logger.PrintError("Failed to open %s", szFullPath);
- // Get the size of the stream
+ // Get the size of the file stream
if(nError == ERROR_SUCCESS)
{
+ if(!FileStream_GetFlags(pStream, &dwStreamFlags))
+ nError = Logger.PrintError("Failed to retrieve the stream flags");
+
if(!FileStream_GetSize(pStream, &FileSize))
- nError = Logger.PrintError("Failed to retrieve virtual file size");
+ nError = Logger.PrintError("Failed to retrieve the file size");
+
+ // Any other stream except STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE should be read-only
+ if((dwStreamFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
+ dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
+// if(pStream->BlockPresent)
+// dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
+
+ // Check the flags there
+ if((dwStreamFlags & dwRequiredFlags) != dwRequiredFlags)
+ {
+ Logger.PrintMessage("The stream should be read-only but it isn't");
+ nError = ERROR_FILE_CORRUPT;
+ }
}
- // Read the MPQ header
+ // After successful open, the stream position must be zero
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, 0);
+
+ // Read the MPQ header from the current file offset.
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFileMpqHeader(&Logger, pStream, NULL);
+
+ // After successful open, the stream position must sizeof(TMPQHeader)
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, sizeof(TMPQHeader));
+
+ // Now try to read the MPQ header from the offset 0
if(nError == ERROR_SUCCESS)
{
ByteOffset = 0;
- if(!FileStream_Read(pStream, &ByteOffset, &Header, MPQ_HEADER_SIZE_V2))
- nError = Logger.PrintError("Failed to read the MPQ header");
- if(Header.dwID != ID_MPQ || Header.dwHeaderSize != MPQ_HEADER_SIZE_V2)
- nError = Logger.PrintError("MPQ Header error");
+ nError = VerifyFileMpqHeader(&Logger, pStream, &ByteOffset);
}
- // Read the last 0x100 bytes
+ // After successful open, the stream position must sizeof(TMPQHeader)
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, sizeof(TMPQHeader));
+
+ // Try a write operation
if(nError == ERROR_SUCCESS)
{
- ByteOffset = FileSize - sizeof(Buffer);
- if(!FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer)))
- nError = Logger.PrintError("Failed to read from the file");
+ bool bExpectedResult = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? false : true;
+ bool bResult;
+
+ // Attempt to write to the file
+ ByteOffset = 0;
+ bResult = FileStream_Write(pStream, &ByteOffset, Buffer, sizeof(Buffer));
+
+ // If the result is not expected
+ if(bResult != bExpectedResult)
+ {
+ Logger.PrintMessage("FileStream_Write result is different than expected");
+ nError = ERROR_FILE_CORRUPT;
+ }
}
- // Read 0x100 bytes from position (FileSize - 0xFF)
- // This test must fail
+ // Move the position 9 bytes from the end and try to read 10 bytes.
+ // This must fail, because stream reading functions are "all or nothing"
if(nError == ERROR_SUCCESS)
{
- ByteOffset = FileSize - sizeof(Buffer) + 1;
- if(FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer)))
- nError = Logger.PrintError("Test Failed: Reading 0x100 bytes from (FileSize - 0xFF)");
+ ByteOffset = FileSize - 9;
+ if(FileStream_Read(pStream, &ByteOffset, Buffer, 10))
+ {
+ Logger.PrintMessage("FileStream_Read succeeded, but it shouldn't");
+ nError = ERROR_FILE_CORRUPT;
+ }
}
- FileStream_Close(pStream);
+ // Try again with 9 bytes. This must succeed, unless the file block is not available
+ if(nError == ERROR_SUCCESS)
+ {
+ ByteOffset = FileSize - 9;
+ if(!FileStream_Read(pStream, &ByteOffset, Buffer, 9))
+ {
+ Logger.PrintMessage("FileStream_Read from the end of the file failed");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Verify file position - it must be at the end of the file
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, FileSize);
+
+ // Close the stream
+ if(pStream != NULL)
+ FileStream_Close(pStream);
return nError;
}
@@ -2157,7 +2265,7 @@ static int TestOpenArchive_MasterMirror(const char * szMirrorName, const char *
}
// Verify the file
- if(nError == ERROR_SUCCESS && szFileToExtract)
+ if(nError == ERROR_SUCCESS && szFileToExtract != NULL)
{
dwVerifyResult = SFileVerifyFile(hMpq, szFileToExtract, SFILE_VERIFY_ALL);
if(dwVerifyResult & VERIFY_FILE_ERROR_MASK)
@@ -3105,20 +3213,36 @@ int main(int argc, char * argv[])
// nError = FindFilePairs(ForEachFile_CreateArchiveLink, "2004 - WoW\\06080", "2004 - WoW\\06299");
// Search all testing archives and verify their SHA1 hash
+// if(nError == ERROR_SUCCESS)
+// nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqDirectory);
+
+ // Test reading linear file without bitmap
if(nError == ERROR_SUCCESS)
- nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqDirectory);
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-original.MPQ", 0);
- // Search in listfile
+ // Test reading linear file without bitmap (read only)
if(nError == ERROR_SUCCESS)
- nError = TestSearchListFile("ListFile_Blizzard.txt");
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-original.MPQ", STREAM_FLAG_READ_ONLY);
+
+ // Test reading linear file with bitmap
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-downloaded.MPQ", STREAM_FLAG_USE_BITMAP);
+
+ // Test reading partial file
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("MPQ_2009_v2_WoW_patch.MPQ.part", STREAM_PROVIDER_PARTIAL);
+
+ // Test reading encrypted file
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("MPQ_2011_v2_EncryptedMpq.MPQE", STREAM_PROVIDER_ENCRYPTED);
// Search in listfile
if(nError == ERROR_SUCCESS)
nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "MPQ_2013_v4_alternate-original.MPQ");
- // Test reading partial file
+ // Search in listfile
if(nError == ERROR_SUCCESS)
- nError = TestReadFile_Partial("MPQ_2009_v2_WoW_patch.MPQ.part");
+ nError = TestSearchListFile("ListFile_Blizzard.txt");
// Test opening local file with SFileOpenFileEx
if(nError == ERROR_SUCCESS)