aboutsummaryrefslogtreecommitdiff
path: root/dep/StormLib/src/FileStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/StormLib/src/FileStream.cpp')
-rw-r--r--dep/StormLib/src/FileStream.cpp2525
1 files changed, 1479 insertions, 1046 deletions
diff --git a/dep/StormLib/src/FileStream.cpp b/dep/StormLib/src/FileStream.cpp
index b8de102cd2e..413f2acb3a2 100644
--- a/dep/StormLib/src/FileStream.cpp
+++ b/dep/StormLib/src/FileStream.cpp
@@ -16,6 +16,11 @@
#define __STORMLIB_SELF__
#include "StormLib.h"
#include "StormCommon.h"
+#include "FileStream.h"
+
+#ifdef _MSC_VER
+#pragma comment(lib, "wininet.lib")
+#endif
//-----------------------------------------------------------------------------
// Local defines
@@ -25,77 +30,11 @@
#endif
#ifdef _MSC_VER
-#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)
+#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)
#endif
//-----------------------------------------------------------------------------
-// Local structures
-
-// Structure describing the PART file header
-typedef struct _PART_FILE_HEADER
-{
- DWORD PartialVersion; // Always set to 2
- char GameBuildNumber[8]; // Minimum build number of the game that can use this MPQ
- DWORD Unknown0C;
- DWORD Unknown10;
- DWORD Unknown14; // Often contains 0x1C (size of the rest of the header ?)
- DWORD Unknown18;
- DWORD ZeroValue1C; // Seems to always be zero
- DWORD ZeroValue20; // Seems to always be zero
- DWORD ZeroValue24; // Seems to always be zero
- DWORD FileSizeLo; // Low 32 bits of the file size
- DWORD FileSizeHi; // High 32 bits of the file size
- DWORD BlockSize; // Size of one file block, in bytes
-
-} PART_FILE_HEADER, *PPART_FILE_HEADER;
-
-// Structure describing the block-to-file map entry
-typedef struct _PART_FILE_MAP_ENTRY
-{
- DWORD Flags; // 3 = the block is present in the file
- DWORD BlockOffsLo; // Low 32 bits of the block position in the file
- DWORD BlockOffsHi; // High 32 bits of the block position in the file
- DWORD Unknown0C;
- DWORD Unknown10;
-
-} PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY;
-
-struct TPartFileStream : 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
-
- PART_FILE_MAP_ENTRY PartMap[1]; // File map, variable length
-};
-
-#define MPQE_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted
-
-struct TEncryptedStream : public TFileStream
-{
- BYTE Key[MPQE_CHUNK_SIZE]; // File key
-};
-
-static bool IsPartHeader(PPART_FILE_HEADER pPartHdr)
-{
- // Version number must be 2
- if(pPartHdr->PartialVersion == 2)
- {
- // GameBuildNumber must be anm ASCII number
- if(isdigit(pPartHdr->GameBuildNumber[0]) && isdigit(pPartHdr->GameBuildNumber[1]) && isdigit(pPartHdr->GameBuildNumber[2]))
- {
- // Block size must be power of 2
- if((pPartHdr->BlockSize & (pPartHdr->BlockSize - 1)) == 0)
- return true;
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Non-Windows support for LastError
+// Local functions - platform-specific functions
#ifndef PLATFORM_WINDOWS
static int nLastError = ERROR_SUCCESS;
@@ -111,639 +50,1087 @@ void SetLastError(int nError)
}
#endif
-//-----------------------------------------------------------------------------
-// Local functions - platform-specific functions
-
#ifndef PLATFORM_LITTLE_ENDIAN
void ConvertPartHeader(void * partHeader)
{
PPART_FILE_HEADER theHeader = (PPART_FILE_HEADER)partHeader;
theHeader->PartialVersion = SwapUInt32(theHeader->PartialVersion);
- theHeader->Unknown0C = SwapUInt32(theHeader->Unknown0C);
- theHeader->Unknown10 = SwapUInt32(theHeader->Unknown10);
- theHeader->Unknown14 = SwapUInt32(theHeader->Unknown14);
- theHeader->Unknown18 = SwapUInt32(theHeader->Unknown18);
- theHeader->Unknown1C = SwapUInt32(theHeader->Unknown1C);
- theHeader->Unknown20 = SwapUInt32(theHeader->Unknown20);
- theHeader->ZeroValue = SwapUInt32(theHeader->ZeroValue);
+ theHeader->Flags = SwapUInt32(theHeader->Flags);
theHeader->FileSizeLo = SwapUInt32(theHeader->FileSizeLo);
theHeader->FileSizeHi = SwapUInt32(theHeader->FileSizeHi);
theHeader->BlockSize = SwapUInt32(theHeader->BlockSize);
}
#endif
-#ifdef PLATFORM_MAC
-static void ConvertUTCDateTimeToFileTime(const UTCDateTimePtr inTime, ULONGLONG * pFT)
-{
- UInt64 intTime = ((UInt64)inTime->highSeconds << 32) + inTime->lowSeconds;
- intTime *= 10000000;
- intTime += 0x0153b281e0fb4000ull;
+//-----------------------------------------------------------------------------
+// Preparing file bitmap for a complete file of a given size
- *pFT = intTime;
-}
+#define DEFAULT_BLOCK_SIZE 0x4000
-static OSErr FSOpenDFCompat(FSRef *ref, char permission, short *refNum)
+static bool Dummy_GetBitmap(
+ TFileStream * pStream,
+ TFileBitmap * pBitmap,
+ DWORD Length,
+ LPDWORD LengthNeeded)
{
- HFSUniStr255 forkName;
- OSErr theErr;
- Boolean isFolder, wasChanged;
-
- theErr = FSResolveAliasFile(ref, true, &isFolder, &wasChanged);
- if (theErr != noErr)
+ ULONGLONG FileSize = 0;
+ DWORD TotalLength;
+ DWORD BlockCount;
+ DWORD BitmapSize;
+ DWORD LastByte;
+ bool bResult = false;
+
+ // Get file size and calculate bitmap length
+ FileStream_GetSize(pStream, FileSize);
+ BlockCount = (DWORD)(((FileSize - 1) / DEFAULT_BLOCK_SIZE) + 1);
+ BitmapSize = (DWORD)(((BlockCount - 1) / 8) + 1);
+
+ // 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))
{
- return theErr;
+ memset(pBitmap, 0, sizeof(TFileBitmap));
+ pBitmap->EndOffset = FileSize;
+ pBitmap->IsComplete = 1;
+ pBitmap->BitmapSize = BitmapSize;
+ pBitmap->BlockSize = DEFAULT_BLOCK_SIZE;
+ bResult = true;
}
-
- FSGetDataForkName(&forkName);
-#ifdef PLATFORM_64BIT
- theErr = FSOpenFork(ref, forkName.length, forkName.unicode, permission, (FSIORefNum *)refNum);
-#else
- theErr = FSOpenFork(ref, forkName.length, forkName.unicode, permission, refNum);
-#endif
- return theErr;
-}
-#endif
-#ifdef PLATFORM_LINUX
-// time_t is number of seconds since 1.1.1970, UTC.
-// 1 second = 10000000 (decimal) in FILETIME
-static void ConvertTimeTToFileTime(ULONGLONG * pFileTime, time_t crt_time)
-{
- // Set the start to 1.1.1970 00:00:00
- *pFileTime = 0x019DB1DED53E8000ULL + (10000000 * crt_time);
+ // Do we have enough space to fill the bitmap as well?
+ if(Length >= TotalLength)
+ {
+ LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1);
+
+ // Fill the full blocks
+ memset(pbBitmap, 0xFF, (BlockCount / 8));
+ pbBitmap += (BlockCount / 8);
+ bResult = true;
+
+ // Supply the last block
+ if(BlockCount & 7)
+ {
+ LastByte = (1 << (BlockCount & 7)) - 1;
+ pbBitmap[0] = (BYTE)LastByte;
+ }
+ }
+
+ return bResult;
}
-#endif
-static HANDLE CreateNewFile(
- const TCHAR * szFileName) // Name of the file to open
+//-----------------------------------------------------------------------------
+// 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
{
- HANDLE hFile = INVALID_HANDLE_VALUE; // Pre-set the file handle to INVALID_HANDLE_VALUE
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
+ DWORD dwBytesRead = 0; // Must be set by platform-specific code
#ifdef PLATFORM_WINDOWS
{
- DWORD dwShareMode = FILE_SHARE_READ;
-
- if(dwGlobalFlags & SFILE_FLAG_ALLOW_WRITE_SHARE)
- dwShareMode |= FILE_SHARE_WRITE;
-
- hFile = CreateFile(szFileName,
- GENERIC_READ | GENERIC_WRITE,
- dwShareMode,
- NULL,
- CREATE_ALWAYS,
- 0,
- NULL);
- }
-#endif
+ // Note: StormLib no longer supports Windows 9x.
+ // Thus, we can use the OVERLAPPED structure to specify
+ // file offset to read from file. This allows us to skip
+ // one system call to SetFilePointer
-#ifdef PLATFORM_MAC
- {
- FSRef theParentRef;
- FSRef theFileRef;
- OSErr theErr;
- short fileRef;
-
- theErr = FSPathMakeRef((const UInt8 *)szFileName, &theFileRef, NULL);
-
- if (theErr == noErr)
- FSDeleteObject(&theFileRef);
-
- // Create the FSRef for the parent directory.
- UInt8 folderName[MAX_PATH];
- memset(&theFileRef, 0, sizeof(FSRef));
- CFStringRef filePathCFString = CFStringCreateWithCString(NULL, szFileName, kCFStringEncodingUTF8);
- CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, filePathCFString, kCFURLPOSIXPathStyle, false);
- CFURLRef folderURL = CFURLCreateCopyDeletingLastPathComponent(NULL, fileURL);
- CFURLGetFileSystemRepresentation(folderURL, true, folderName, MAX_PATH);
- theErr = FSPathMakeRef(folderName, &theParentRef, NULL);
- CFRelease(fileURL);
- CFRelease(folderURL);
-
- if (theErr != noErr)
+ // Update the byte offset
+ pStream->Base.File.FilePos = ByteOffset;
+
+ // Read the data
+ if(dwBytesToRead != 0)
{
- nLastError = theErr;
- return INVALID_HANDLE_VALUE;
+ OVERLAPPED Overlapped;
+
+ Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32);
+ Overlapped.Offset = (DWORD)ByteOffset;
+ Overlapped.hEvent = NULL;
+ if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, &Overlapped))
+ return false;
}
-
- // Create the file
- UniChar unicodeFileName[256];
- fileURL = CFURLCreateWithFileSystemPath(NULL, filePathCFString, kCFURLPOSIXPathStyle, false);
- CFStringRef fileNameCFString = CFURLCopyLastPathComponent(fileURL);
- CFStringGetCharacters(fileNameCFString, CFRangeMake(0, CFStringGetLength(fileNameCFString)),
- unicodeFileName);
- theErr = FSCreateFileUnicode(&theParentRef, CFStringGetLength(fileNameCFString), unicodeFileName,
- kFSCatInfoNone, NULL, &theFileRef, NULL);
- CFRelease(fileNameCFString);
- CFRelease(filePathCFString);
- CFRelease(fileURL);
- if (theErr != noErr)
+/*
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(ByteOffset != pStream->Base.File.FilePos)
{
- nLastError = theErr;
- return INVALID_HANDLE_VALUE;
+ LONG ByteOffsetHi = (LONG)(ByteOffset >> 32);
+
+ SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN);
+ pStream->Base.File.FilePos = ByteOffset;
}
- theErr = FSOpenDFCompat(&theFileRef, fsRdWrPerm, &fileRef);
- if(theErr != noErr)
+ // Read the data
+ if(dwBytesToRead != 0)
{
- nLastError = theErr;
- return INVALID_HANDLE_VALUE;
+ if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL))
+ return false;
}
-
- hFile = (HANDLE)(int)fileRef;
+*/
}
#endif
-#ifdef PLATFORM_LINUX
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
{
- intptr_t handle;
+ ssize_t bytes_read;
- handle = open(szFileName, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if(handle == -1)
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(ByteOffset != pStream->Base.File.FilePos)
{
- nLastError = errno;
- return INVALID_HANDLE_VALUE;
+ lseek((intptr_t)pStream->Base.File.hFile, (off_t)(ByteOffset), SEEK_SET);
+ pStream->Base.File.FilePos = ByteOffset;
}
- hFile = (HANDLE)handle;
+ // Perform the read operation
+ if(dwBytesToRead != 0)
+ {
+ bytes_read = read((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToRead);
+ if(bytes_read == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ dwBytesRead = (DWORD)(size_t)bytes_read;
+ }
}
#endif
- // Return the file handle
- return hFile;
+ // 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->Base.File.FilePos = ByteOffset + dwBytesRead;
+ if(dwBytesRead != dwBytesToRead)
+ SetLastError(ERROR_HANDLE_EOF);
+ return (dwBytesRead == dwBytesToRead);
}
-static HANDLE OpenExistingFile(
- const TCHAR * szFileName, // Name of the file to open
- bool bWriteAccess) // false = read-only, true = read/write
+/**
+ * \a pStream Pointer to an open stream
+ * \a pByteOffset Pointer to file byte offset. If NULL, writes to current position
+ * \a pvBuffer Pointer to data to be written
+ * \a dwBytesToWrite Number of bytes to write to the file
+ */
+
+static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
{
- HANDLE hFile = INVALID_HANDLE_VALUE; // Pre-set the file handle to INVALID_HANDLE_VALUE
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
+ DWORD dwBytesWritten = 0; // Must be set by platform-specific code
#ifdef PLATFORM_WINDOWS
{
- DWORD dwShareMode = FILE_SHARE_READ;
-
- if(dwGlobalFlags & SFILE_FLAG_ALLOW_WRITE_SHARE)
- dwShareMode |= FILE_SHARE_WRITE;
-
- hFile = CreateFile(szFileName,
- bWriteAccess ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ,
- dwShareMode,
- NULL,
- OPEN_EXISTING,
- 0,
- NULL);
- }
-#endif
+ // Note: StormLib no longer supports Windows 9x.
+ // Thus, we can use the OVERLAPPED structure to specify
+ // file offset to read from file. This allows us to skip
+ // one system call to SetFilePointer
-#ifdef PLATFORM_MAC
- {
- FSRef theFileRef;
- OSErr theErr;
- short fileRef;
- char permission = bWriteAccess ? fsRdWrPerm : fsRdPerm;
+ // Update the byte offset
+ pStream->Base.File.FilePos = ByteOffset;
- theErr = FSPathMakeRef((const UInt8 *)szFileName, &theFileRef, NULL);
- if(theErr != noErr)
+ // Read the data
+ if(dwBytesToWrite != 0)
{
- nLastError = theErr;
- return INVALID_HANDLE_VALUE;
- }
+ OVERLAPPED Overlapped;
- theErr = FSOpenDFCompat(&theFileRef, permission, &fileRef);
- if (theErr != noErr)
+ Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32);
+ Overlapped.Offset = (DWORD)ByteOffset;
+ Overlapped.hEvent = NULL;
+ if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, &Overlapped))
+ return false;
+ }
+/*
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(ByteOffset != pStream->Base.File.FilePos)
{
- nLastError = theErr;
- return INVALID_HANDLE_VALUE;
+ LONG ByteOffsetHi = (LONG)(ByteOffset >> 32);
+
+ SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN);
+ pStream->Base.File.FilePos = ByteOffset;
}
- hFile = (HANDLE)(int)fileRef;
+ // Read the data
+ if(dwBytesToWrite != 0)
+ {
+ if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL))
+ return false;
+ }
+*/
}
#endif
-#ifdef PLATFORM_LINUX
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
{
- int oflag = bWriteAccess ? O_RDWR : O_RDONLY;
- intptr_t handle;
+ ssize_t bytes_written;
- handle = open(szFileName, oflag | O_LARGEFILE);
- if(handle == -1)
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(ByteOffset != pStream->Base.File.FilePos)
{
- nLastError = errno;
- return INVALID_HANDLE_VALUE;
+ lseek((intptr_t)pStream->Base.File.hFile, (off_t)(ByteOffset), SEEK_SET);
+ pStream->Base.File.FilePos = ByteOffset;
}
- hFile = (HANDLE)handle;
+ // Perform the read operation
+ bytes_written = write((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToWrite);
+ if(bytes_written == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ dwBytesWritten = (DWORD)(size_t)bytes_written;
}
#endif
- // Return the file handle
- return hFile;
+ // Increment the current file position by number of bytes read
+ pStream->Base.File.FilePos = ByteOffset + dwBytesWritten;
+
+ // Also modify the file size, if needed
+ if(pStream->Base.File.FilePos > pStream->Base.File.FileSize)
+ pStream->Base.File.FileSize = pStream->Base.File.FilePos;
+
+ if(dwBytesWritten != dwBytesToWrite)
+ SetLastError(ERROR_DISK_FULL);
+ return (dwBytesWritten == dwBytesToWrite);
}
-static void CloseTheFile(HANDLE hFile)
+static bool BaseFile_GetPos(
+ TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG & ByteOffset) // Pointer to file byte offset
{
-#ifdef PLATFORM_WINDOWS
- CloseHandle(hFile);
-#endif
-
-#ifdef PLATFORM_MAC
- FSCloseFork((short)(long)hFile);
-#endif
+ ByteOffset = pStream->Base.File.FilePos;
+ return true;
+}
-#ifdef PLATFORM_LINUX
- close((intptr_t)hFile);
-#endif
+static bool BaseFile_GetSize(
+ TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG & FileSize) // Pointer where to store file size
+{
+ FileSize = pStream->Base.File.FileSize;
+ return true;
}
/**
- * Renames a file to another name.
- * Note that the "szNewFile" file usually exists when this function is called,
- * so the function must deal with it properly
+ * \a pStream Pointer to an open stream
+ * \a NewFileSize New size of the file
*/
-static bool RenameFile(const TCHAR * szExistingFile, const TCHAR * szNewFile)
+static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
{
#ifdef PLATFORM_WINDOWS
- // Delete the original stream file. Don't check the result value,
- // because if the file doesn't exist, it would fail
- DeleteFile(szNewFile);
+ {
+ LONG FileSizeHi = (LONG)(NewFileSize >> 32);
+ LONG FileSizeLo;
+ DWORD dwNewPos;
+ bool bResult;
- // Rename the new file to the old stream's file
- return (bool)MoveFile(szExistingFile, szNewFile);
-#endif
+ // Set the position at the new file size
+ dwNewPos = SetFilePointer(pStream->Base.File.hFile, (LONG)NewFileSize, &FileSizeHi, FILE_BEGIN);
+ if(dwNewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS)
+ return false;
-#ifdef PLATFORM_MAC
- OSErr theErr;
- FSRef fromFileRef;
- FSRef toFileRef;
-
- if (FSPathMakeRef((const UInt8 *)szNewFile, &toFileRef, NULL) == noErr)
- FSDeleteObject(&toFileRef);
-
- // Get the path to the old file
- theErr = FSPathMakeRef((const UInt8 *)szExistingFile, &fromFileRef, NULL);
- if (theErr != noErr)
- {
- nLastError = theErr;
- return false;
+ // Set the current file pointer as the end of the file
+ bResult = (bool)SetEndOfFile(pStream->Base.File.hFile);
+
+ // Restore the file position
+ FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32);
+ FileSizeLo = (LONG)(pStream->Base.File.FilePos);
+ SetFilePointer(pStream->Base.File.hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN);
+ return bResult;
}
+#endif
- // Get a CFString for the new file name
- CFStringRef newFileNameCFString = CFStringCreateWithCString(NULL, szNewFile, kCFStringEncodingUTF8);
- CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, newFileNameCFString, kCFURLPOSIXPathStyle, false);
- CFRelease(newFileNameCFString);
- newFileNameCFString = CFURLCopyLastPathComponent(fileURL);
- CFRelease(fileURL);
-
- // Convert CFString to Unicode and rename the file
- UniChar unicodeFileName[256];
- CFStringGetCharacters(newFileNameCFString, CFRangeMake(0, CFStringGetLength(newFileNameCFString)),
- unicodeFileName);
- theErr = FSRenameUnicode(&fromFileRef, CFStringGetLength(newFileNameCFString), unicodeFileName,
- kTextEncodingUnknown, NULL);
- if (theErr != noErr)
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
{
- CFRelease(newFileNameCFString);
- nLastError = theErr;
- return false;
+ if(ftruncate((intptr_t)pStream->Base.File.hFile, (off_t)NewFileSize) == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ return true;
}
-
- CFRelease(newFileNameCFString);
-
+#endif
+}
+
+static bool BaseFile_GetTime(TFileStream * pStream, ULONGLONG * pFileTime)
+{
+ *pFileTime = pStream->Base.File.FileTime;
return true;
+}
+
+// Renames the file pointed by pStream so that it contains data from pNewStream
+static bool BaseFile_Switch(TFileStream * pStream, TFileStream * pNewStream)
+{
+#ifdef PLATFORM_WINDOWS
+ // Delete the original stream file. Don't check the result value,
+ // because if the file doesn't exist, it would fail
+ DeleteFile(pStream->szFileName);
+
+ // Rename the new file to the old stream's file
+ return (bool)MoveFile(pNewStream->szFileName, pStream->szFileName);
#endif
-#ifdef PLATFORM_LINUX
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
// "rename" on Linux also works if the target file exists
- if(rename(szExistingFile, szNewFile) == -1)
+ if(rename(pNewStream->szFileName, pStream->szFileName) == -1)
{
nLastError = errno;
return false;
}
-
+
return true;
#endif
}
-//-----------------------------------------------------------------------------
-// Stream functions - normal file stream
-
-static bool File_GetPos(
- TFileStream * pStream, // Pointer to an open stream
- ULONGLONG & ByteOffset) // Pointer to file byte offset
+static void BaseFile_Close(TFileStream * pStream)
{
- ByteOffset = pStream->RawFilePos;
- return true;
+ if(pStream->Base.File.hFile != INVALID_HANDLE_VALUE)
+ {
+#ifdef PLATFORM_WINDOWS
+ CloseHandle(pStream->Base.File.hFile);
+#endif
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ close((intptr_t)pStream->Base.File.hFile);
+#endif
+ }
+
+ // Also invalidate the handle
+ pStream->Base.File.hFile = INVALID_HANDLE_VALUE;
}
-static bool File_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
+static bool BaseFile_Create(
+ TFileStream * pStream,
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
{
- DWORD dwBytesRead = 0; // Must be set by platform-specific code
+#ifdef PLATFORM_WINDOWS
+ {
+ DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
+
+ pStream->Base.File.hFile = CreateFile(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 the byte offset is not entered, use the current position
- if(pByteOffset == NULL)
- pByteOffset = &pStream->RawFilePos;
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ intptr_t handle;
+
+ handle = open(szFileName, O_RDWR | O_CREAT | O_TRUNC, 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->BaseGetPos = BaseFile_GetPos;
+ pStream->BaseGetSize = BaseFile_GetSize;
+ pStream->BaseSetSize = BaseFile_SetSize;
+ pStream->BaseSetSize = BaseFile_SetSize;
+ pStream->BaseGetTime = BaseFile_GetTime;
+ pStream->BaseClose = BaseFile_Close;
+
+ // Reset the file position
+ pStream->Base.File.FileSize = 0;
+ pStream->Base.File.FilePos = 0;
+ pStream->dwFlags = dwStreamFlags;
+ return true;
+}
+static bool BaseFile_Open(
+ TFileStream * pStream,
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
+{
#ifdef PLATFORM_WINDOWS
{
- // If the byte offset is different from the current file position,
- // we have to update the file position
- if(*pByteOffset != pStream->RawFilePos)
- {
- LONG ByteOffsetHi = (LONG)(*pByteOffset >> 32);
- LONG ByteOffsetLo = (LONG)(*pByteOffset);
+ ULARGE_INTEGER FileSize;
+ DWORD dwDesiredAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? GENERIC_READ : GENERIC_ALL;
+ DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
+
+ // Open the file
+ pStream->Base.File.hFile = CreateFile(szFileName,
+ dwDesiredAccess,
+ dwWriteShare | FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
+ return false;
- SetFilePointer(pStream->hFile, ByteOffsetLo, &ByteOffsetHi, FILE_BEGIN);
- pStream->RawFilePos = *pByteOffset;
- }
+ // Query the file size
+ FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart);
+ pStream->Base.File.FileSize = FileSize.QuadPart;
- // Read the data
- if(dwBytesToRead != 0)
- {
- if(!ReadFile(pStream->hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL))
- return false;
- }
+ // Query last write time
+ GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime);
}
#endif
-#ifdef PLATFORM_MAC
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
{
- ByteCount nBytesToRead = (ByteCount)dwBytesToRead;
- ByteCount nBytesRead = 0;
- OSErr theErr;
+ struct stat fileinfo;
+ int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR;
+ intptr_t handle;
- // If the byte offset is different from the current file position,
- // we have to update the file position
- if(*pByteOffset != pStream->RawFilePos)
+ // Open the file
+ handle = open(szFileName, oflag);
+ if(handle == -1)
{
- FSSetForkPosition((short)(long)pStream->hFile, fsFromStart, (SInt64)(*pByteOffset));
- pStream->RawFilePos = *pByteOffset;
+ nLastError = errno;
+ return false;
}
- // Read the data
- if(nBytesToRead != 0)
+ // Get the file size
+ if(fstat(handle, &fileinfo) == -1)
{
- theErr = FSReadFork((short)(long)pStream->hFile, fsAtMark, 0, nBytesToRead, pvBuffer, &nBytesRead);
- if (theErr != noErr && theErr != eofErr)
- {
- nLastError = theErr;
- return false;
- }
- dwBytesRead = (DWORD)nBytesRead;
+ 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
-#ifdef PLATFORM_LINUX
- {
- ssize_t bytes_read;
+ // Fill-in the entry points
+ pStream->BaseRead = BaseFile_Read;
+ pStream->BaseWrite = BaseFile_Write;
+ pStream->BaseGetPos = BaseFile_GetPos;
+ pStream->BaseGetSize = BaseFile_GetSize;
+ pStream->BaseSetSize = BaseFile_SetSize;
+ pStream->BaseGetTime = BaseFile_GetTime;
+ pStream->BaseClose = BaseFile_Close;
+
+ // Reset the file position
+ pStream->Base.File.FilePos = 0;
+ pStream->dwFlags = dwStreamFlags;
+ return true;
+}
- // If the byte offset is different from the current file position,
- // we have to update the file position
- if(*pByteOffset != pStream->RawFilePos)
- {
- lseek64((intptr_t)pStream->hFile, (off64_t)(*pByteOffset), SEEK_SET);
- pStream->RawFilePos = *pByteOffset;
- }
+//-----------------------------------------------------------------------------
+// Local functions - base memory-mapped file support
- // Perform the read operation
- if(dwBytesToRead != 0)
- {
- bytes_read = read((intptr_t)pStream->hFile, pvBuffer, (size_t)dwBytesToRead);
- if(bytes_read == -1)
- {
- nLastError = errno;
- return false;
- }
+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;
- dwBytesRead = (DWORD)(size_t)bytes_read;
- }
+ // 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);
}
-#endif
- // 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->RawFilePos = *pByteOffset + dwBytesRead;
- if(dwBytesRead != dwBytesToRead)
- SetLastError(ERROR_HANDLE_EOF);
- return (dwBytesRead == dwBytesToRead);
+ // Move the current file position
+ pStream->Base.Map.FilePos += dwBytesToRead;
+ return true;
}
-/**
- * \a pStream Pointer to an open stream
- * \a pByteOffset Pointer to file byte offset. If NULL, writes to current position
- * \a pvBuffer Pointer to data to be written
- * \a dwBytesToWrite Number of bytes to write to the file
- */
+static bool BaseMap_GetPos(
+ TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG & ByteOffset) // Pointer to file byte offset
+{
+ ByteOffset = pStream->Base.Map.FilePos;
+ return true;
+}
-static bool File_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
+static bool BaseMap_GetSize(
+ TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG & FileSize) // Pointer where to store file size
{
- DWORD dwBytesWritten = 0; // Must be set by platform-specific code
+ FileSize = pStream->Base.Map.FileSize;
+ return true;
+}
- // If the byte offset is not entered, use the current position
- if(pByteOffset == NULL)
- pByteOffset = &pStream->RawFilePos;
+static bool BaseMap_GetTime(TFileStream * pStream, ULONGLONG * pFileTime)
+{
+ *pFileTime = pStream->Base.Map.FileTime;
+ 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->Base.Map.FileSize);
+#endif
+
+ pStream->Base.Map.pbFile = NULL;
+}
+
+static bool BaseMap_Open(
+ TFileStream * pStream,
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
+{
#ifdef PLATFORM_WINDOWS
+
+ ULARGE_INTEGER FileSize;
+ HANDLE hFile;
+ HANDLE hMap;
+ bool bResult = false;
+
+ // Open the file for read access
+ hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if(hFile != NULL)
{
- // If the byte offset is different from the current file position,
- // we have to update the file position
- if(*pByteOffset != pStream->RawFilePos)
+ // Retrieve file size. Don't allow mapping file of a zero size.
+ FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart);
+ if(FileSize.QuadPart != 0)
{
- LONG ByteOffsetHi = (LONG)(*pByteOffset >> 32);
- LONG ByteOffsetLo = (LONG)(*pByteOffset);
+ // Retrieve file time
+ GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime);
+
+ // Now create mapping object
+ hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ if(hMap != NULL)
+ {
+ // Map the entire view into memory
+ // Note that this operation will fail if the file can't fit
+ // into usermode address space
+ pStream->Base.Map.pbFile = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+ if(pStream->Base.Map.pbFile != NULL)
+ {
+ pStream->Base.Map.FileSize = FileSize.QuadPart;
+ pStream->Base.Map.FilePos = 0;
+ bResult = true;
+ }
- SetFilePointer(pStream->hFile, ByteOffsetLo, &ByteOffsetHi, FILE_BEGIN);
- pStream->RawFilePos = *pByteOffset;
+ // Close the map handle
+ CloseHandle(hMap);
+ }
}
- // Read the data
- if(!WriteFile(pStream->hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL))
- return false;
+ // Close the file handle
+ CloseHandle(hFile);
}
+
+ // If the file is not there and is not available for random access,
+ // report error
+ if(bResult == false)
+ return false;
#endif
-#ifdef PLATFORM_MAC
- {
- ByteCount nBytesToWrite = (ByteCount)dwBytesToWrite;
- ByteCount nBytesWritten = 0;
- OSErr theErr;
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ struct stat fileinfo;
+ intptr_t handle;
+ bool bResult = false;
- // If the byte offset is different from the current file position,
- // we have to update the file position
- if(*pByteOffset != pStream->RawFilePos)
+ // Open the file
+ handle = open(szFileName, O_RDONLY);
+ if(handle != -1)
+ {
+ // Get the file size
+ if(fstat(handle, &fileinfo) != -1)
{
- FSSetForkPosition((short)(long)pStream->hFile, fsFromStart, (SInt64)(*pByteOffset));
- pStream->RawFilePos = *pByteOffset;
+ pStream->Base.Map.pbFile = (LPBYTE)mmap(NULL, (size_t)fileinfo.st_size, PROT_READ, MAP_PRIVATE, handle, 0);
+ if(pStream->Base.Map.pbFile != NULL)
+ {
+ // 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.Map.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime);
+ pStream->Base.Map.FileSize = (ULONGLONG)fileinfo.st_size;
+ pStream->Base.Map.FilePos = 0;
+ bResult = true;
+ }
}
+ close(handle);
+ }
- theErr = FSWriteFork((short)(long)pStream->hFile, fsAtMark, 0, nBytesToWrite, pvBuffer, &nBytesWritten);
- if (theErr != noErr)
- {
- nLastError = theErr;
- return false;
- }
- dwBytesWritten = (DWORD)nBytesWritten;
+ // Did the mapping fail?
+ if(bResult == false)
+ {
+ nLastError = errno;
+ return false;
}
#endif
-#ifdef PLATFORM_LINUX
+ // Fill-in entry points
+ pStream->BaseRead = BaseMap_Read;
+ pStream->BaseGetPos = BaseMap_GetPos;
+ pStream->BaseGetSize = BaseMap_GetSize;
+ pStream->BaseGetTime = BaseMap_GetTime;
+ pStream->BaseClose = BaseMap_Close;
+ pStream->dwFlags = dwStreamFlags;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - base HTTP file support
+
+static const TCHAR * BaseHttp_ExtractServerName(const TCHAR * szFileName, TCHAR * szServerName)
+{
+ // Check for HTTP
+ if(!_tcsnicmp(szFileName, _T("http://"), 7))
+ szFileName += 7;
+
+ // Cut off the server name
+ if(szServerName != NULL)
{
- ssize_t bytes_written;
+ while(szFileName[0] != 0 && szFileName[0] != _T('/'))
+ *szServerName++ = *szFileName++;
+ *szServerName = 0;
+ }
+ else
+ {
+ while(szFileName[0] != 0 && szFileName[0] != _T('/'))
+ *szFileName++;
+ }
- // If the byte offset is different from the current file position,
- // we have to update the file position
- if(*pByteOffset != pStream->RawFilePos)
- {
- lseek64((intptr_t)pStream->hFile, (off64_t)(*pByteOffset), SEEK_SET);
- pStream->RawFilePos = *pByteOffset;
- }
+ // Return the remainder
+ return szFileName;
+}
- // Perform the read operation
- bytes_written = write((intptr_t)pStream->hFile, pvBuffer, (size_t)dwBytesToWrite);
- if(bytes_written == -1)
+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
+ void * pvBuffer, // Pointer to data to be read
+ DWORD dwBytesToRead) // Number of bytes to read from the file
+{
+#ifdef PLATFORM_WINDOWS
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos;
+ DWORD dwTotalBytesRead = 0;
+
+ // Do we have to read anything at all?
+ if(dwBytesToRead != 0)
+ {
+ HINTERNET hRequest;
+ LPCTSTR szFileName;
+ LPBYTE pbBuffer = (LPBYTE)pvBuffer;
+ TCHAR szRangeRequest[0x80];
+ DWORD dwStartOffset = (DWORD)ByteOffset;
+ DWORD dwEndOffset = dwStartOffset + dwBytesToRead;
+ BYTE Buffer[0x200];
+
+ // Open HTTP request to the file
+ szFileName = BaseHttp_ExtractServerName(pStream->szFileName, NULL);
+ hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
+ if(hRequest != NULL)
{
- nLastError = errno;
- return false;
- }
+ // Add range request to the HTTP headers
+ // http://www.clevercomponents.com/articles/article015/resuming.asp
+ _stprintf(szRangeRequest, _T("Range: bytes=%d-%d"), dwStartOffset, dwEndOffset);
+ HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW);
- dwBytesWritten = (DWORD)(size_t)bytes_written;
+ // Send the request to the server
+ if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
+ {
+ while(dwTotalBytesRead < dwBytesToRead)
+ {
+ DWORD dwBlockBytesToRead = dwBytesToRead - dwTotalBytesRead;
+ DWORD dwBlockBytesRead = 0;
+
+ // Read the block from the file
+ if(dwBlockBytesToRead > sizeof(Buffer))
+ dwBlockBytesToRead = sizeof(Buffer);
+ InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead);
+
+ // Check for end
+ if(dwBlockBytesRead == 0)
+ break;
+
+ // Move buffers
+ dwTotalBytesRead += dwBlockBytesRead;
+ pbBuffer += dwBlockBytesRead;
+ }
+ }
+ InternetCloseHandle(hRequest);
+ }
}
-#endif
// Increment the current file position by number of bytes read
- pStream->RawFilePos = *pByteOffset + dwBytesWritten;
- if(dwBytesWritten != dwBytesToWrite)
- SetLastError(ERROR_DISK_FULL);
- return (dwBytesWritten == dwBytesToWrite);
+ pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead;
+
+ // If the number of bytes read doesn't match the required amount, return false
+ if(dwTotalBytesRead != dwBytesToRead)
+ SetLastError(ERROR_HANDLE_EOF);
+ return (dwTotalBytesRead == dwBytesToRead);
+
+#else
+
+ // Not supported
+ pStream = pStream;
+ pByteOffset = pByteOffset;
+ pvBuffer = pvBuffer;
+ dwBytesToRead = dwBytesToRead;
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+
+#endif
+}
+
+static bool BaseHttp_GetPos(
+ TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG & ByteOffset) // Pointer to file byte offset
+{
+ ByteOffset = pStream->Base.Http.FilePos;
+ return true;
}
-static bool File_GetSize(
+static bool BaseHttp_GetSize(
TFileStream * pStream, // Pointer to an open stream
ULONGLONG & FileSize) // Pointer where to store file size
{
-#ifdef PLATFORM_WINDOWS
- DWORD FileSizeHi = 0;
- DWORD FileSizeLo;
-
- FileSizeLo = GetFileSize(pStream->hFile, &FileSizeHi);
- if(FileSizeLo == INVALID_FILE_SIZE && GetLastError() != ERROR_SUCCESS)
- return false;
+ FileSize = pStream->Base.Http.FileSize;
+ return true;
+}
- FileSize = MAKE_OFFSET64(FileSizeHi, FileSizeLo);
+static bool BaseHttp_GetTime(TFileStream * pStream, ULONGLONG * pFileTime)
+{
+ *pFileTime = pStream->Base.Http.FileTime;
return true;
+}
+
+static void BaseHttp_Close(TFileStream * pStream)
+{
+#ifdef PLATFORM_WINDOWS
+ if(pStream->Base.Http.hConnect != NULL)
+ InternetCloseHandle(pStream->Base.Http.hConnect);
+ pStream->Base.Http.hConnect = NULL;
+
+ if(pStream->Base.Http.hInternet != NULL)
+ InternetCloseHandle(pStream->Base.Http.hInternet);
+ pStream->Base.Http.hInternet = NULL;
+#else
+ pStream = pStream;
#endif
+}
+
+static bool BaseHttp_Open(
+ TFileStream * pStream,
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
+{
+#ifdef PLATFORM_WINDOWS
+
+ HINTERNET hRequest;
+ DWORD dwTemp = 0;
+ bool bFileAvailable = false;
+ int nError = ERROR_SUCCESS;
-#ifdef PLATFORM_MAC
- SInt64 fileLength = 0;
- OSErr theErr;
+ // Don't connect to the internet
+ if(!InternetGetConnectedState(&dwTemp, 0))
+ nError = GetLastError();
- theErr = FSGetForkSize((short)(long)pStream->hFile, &fileLength);
- if(theErr != noErr)
+ // Initiate the connection to the internet
+ if(nError == ERROR_SUCCESS)
{
- nLastError = theErr;
- return false;
+ 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();
}
- FileSize = (ULONGLONG)fileLength;
- return true;
-#endif
+ // 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(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();
+ }
-#ifdef PLATFORM_LINUX
- struct stat64 fileinfo;
+ // 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(fstat64((intptr_t)pStream->hFile, &fileinfo) == -1)
+ // If the file is not there and is not available for random access,
+ // report error
+ if(bFileAvailable == false)
{
- nLastError = errno;
+ BaseHttp_Close(pStream);
return false;
}
- FileSize = (ULONGLONG)fileinfo.st_size;
+ // Fill-in entry points
+ pStream->BaseRead = BaseHttp_Read;
+ pStream->BaseGetPos = BaseHttp_GetPos;
+ pStream->BaseGetSize = BaseHttp_GetSize;
+ pStream->BaseGetTime = BaseHttp_GetTime;
+ pStream->BaseClose = BaseHttp_Close;
+ pStream->dwFlags = dwStreamFlags;
return true;
+
+#else
+
+ // Not supported
+ pStream = pStream;
+ szFileName = szFileName;
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+
#endif
}
-/**
- * \a pStream Pointer to an open stream
- * \a NewFileSize New size of the file
- */
-static bool File_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
+//-----------------------------------------------------------------------------
+// Local functions - linear stream support
+
+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
{
-#ifdef PLATFORM_WINDOWS
+ ULONGLONG ByteOffset;
+ ULONGLONG EndOffset;
+ LPBYTE pbBitmap;
+ DWORD BlockIndex;
+ DWORD ByteIndex;
+ DWORD BitMask;
+
+ // At this point, we must have a bitmap set
+ assert(pStream->pBitmap != NULL);
+
+ // If we have data map, we must check if the data block is present in the MPQ
+ if(dwBytesToRead != 0)
{
- LONG FileSizeHi = (LONG)(NewFileSize >> 32);
- LONG FileSizeLo = (LONG)(NewFileSize);
- DWORD dwNewPos;
- bool bResult;
+ DWORD BlockSize = pStream->pBitmap->BlockSize;
- // Set the position at the new file size
- dwNewPos = SetFilePointer(pStream->hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN);
- if(dwNewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS)
- return false;
+ // Get the offset where we read it from
+ if(pByteOffset == NULL)
+ pStream->BaseGetPos(pStream, ByteOffset);
+ else
+ ByteOffset = *pByteOffset;
+ EndOffset = ByteOffset + dwBytesToRead;
- // Set the current file pointer as the end of the file
- bResult = (bool)SetEndOfFile(pStream->hFile);
+ // If the start of the area is within the region
+ // protected by data map, check each block
+ if(ByteOffset < pStream->pBitmap->EndOffset)
+ {
+ // Cut the end of the stream protected by the data map
+ EndOffset = STORMLIB_MIN(EndOffset, pStream->pBitmap->EndOffset);
- // Restore the file position
- FileSizeHi = (LONG)(pStream->RawFilePos >> 32);
- FileSizeLo = (LONG)(pStream->RawFilePos);
- SetFilePointer(pStream->hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN);
- return bResult;
- }
-#endif
-
-#ifdef PLATFORM_MAC
- {
- OSErr theErr;
+ // Calculate the initial block index
+ BlockIndex = (DWORD)(ByteOffset / BlockSize);
+ pbBitmap = (LPBYTE)(pStream->pBitmap + 1);
- theErr = FSSetForkSize((short)(long)pStream->hFile, fsFromStart, (SInt64)NewFileSize);
- if(theErr != noErr)
- {
- nLastError = theErr;
- return false;
+ // Parse each block
+ while(ByteOffset < EndOffset)
+ {
+ // Prepare byte index and bit mask
+ ByteIndex = BlockIndex / 8;
+ BitMask = 1 << (BlockIndex & 0x07);
+
+ // If that bit is not set, it means that the block is not present
+ if((pbBitmap[ByteIndex] & BitMask) == 0)
+ {
+ SetLastError(ERROR_FILE_CORRUPT);
+ return false;
+ }
+
+ // Move to tne next block
+ ByteOffset += BlockSize;
+ BlockIndex++;
+ }
}
-
- return true;
}
-#endif
-#ifdef PLATFORM_LINUX
+ // Now if all tests passed, we can call the base read function
+ return pStream->BaseRead(pStream, pByteOffset, pvBuffer, dwBytesToRead);
+}
+
+static bool LinearStream_Switch(TLinearStream * pStream, TLinearStream * pNewStream)
+{
+ // 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->szFileName, pNewStream->dwFlags))
+ return false;
+
+ // We need to cleanup the new data stream
+ FileStream_Close(pNewStream);
+ return true;
+}
+
+static bool LinearStream_GetBitmap(
+ TLinearStream * pStream,
+ TFileBitmap * pBitmap,
+ DWORD Length,
+ LPDWORD LengthNeeded)
+{
+ DWORD TotalLength;
+ bool bResult = false;
+
+ // Assumed that we have bitmap now
+ assert(pStream->pBitmap != NULL);
+
+ // Give the bitmap length
+ TotalLength = sizeof(TFileBitmap) + pStream->pBitmap->BitmapSize;
+ if(LengthNeeded != NULL)
+ *LengthNeeded = TotalLength;
+
+ // Do we have enough space to fill at least the bitmap structure?
+ if(Length >= sizeof(TFileBitmap))
{
- if(ftruncate((intptr_t)pStream->hFile, (off_t)NewFileSize) == -1)
+ // Enough space for complete bitmap?
+ if(Length >= TotalLength)
{
- nLastError = errno;
- return false;
+ memcpy(pBitmap, pStream->pBitmap, TotalLength);
+ bResult = true;
+ }
+ else
+ {
+ memcpy(pBitmap, pStream->pBitmap, sizeof(TFileBitmap));
+ bResult = true;
}
-
- return true;
}
-#endif
+
+ return bResult;
}
-//-----------------------------------------------------------------------------
-// Stream functions - partial normal file stream
+static void LinearStream_Close(TLinearStream * pStream)
+{
+ // Free the data map, if any
+ if(pStream->pBitmap != NULL)
+ STORM_FREE(pStream->pBitmap);
+ pStream->pBitmap = NULL;
+
+ // Call the base class for closing the stream
+ return pStream->BaseClose(pStream);
+}
-/**
- * \a pStream Pointer to an open stream
- * \a ByteOffset File byte offset
- */
-static bool PartFile_GetPos(TPartFileStream * pStream, ULONGLONG & ByteOffset)
+static bool LinearStream_Open(TLinearStream * pStream)
{
- ByteOffset = pStream->VirtualPos;
+ // No extra work here really; just set entry points
+ pStream->StreamRead = pStream->BaseRead;
+ pStream->StreamWrite = pStream->BaseWrite;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamSetSize = pStream->BaseSetSize;
+ pStream->StreamGetTime = pStream->BaseGetTime;
+ pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap;
+ pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch;
+ pStream->StreamClose = (STREAM_CLOSE)LinearStream_Close;
return true;
}
-/**
- * \a pStream Pointer to an open stream
- * \a pByteOffset Pointer to file byte offset. If NULL, reads from the current position
- * \a pvBuffer Pointer to data to be read
- * \a dwBytesToRead Number of bytes to read from the file
- */
-static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead)
+//-----------------------------------------------------------------------------
+// Local functions - partial stream support
+
+static bool IsPartHeader(PPART_FILE_HEADER pPartHdr)
+{
+ // Version number must be 2
+ if(pPartHdr->PartialVersion == 2)
+ {
+ // GameBuildNumber must be an ASCII number
+ if(isdigit(pPartHdr->GameBuildNumber[0]) && isdigit(pPartHdr->GameBuildNumber[1]) && isdigit(pPartHdr->GameBuildNumber[2]))
+ {
+ // Block size must be power of 2
+ if((pPartHdr->BlockSize & (pPartHdr->BlockSize - 1)) == 0)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool PartialStream_Read(
+ TPartialStream * pStream,
+ ULONGLONG * pByteOffset,
+ void * pvBuffer,
+ DWORD dwBytesToRead)
{
ULONGLONG RawByteOffset;
LPBYTE pbBuffer = (LPBYTE)pvBuffer;
@@ -789,7 +1176,7 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo
// If the part is not present in the file, we fail the read
if((PartMap->Flags & 3) == 0)
{
- nFailReason = ERROR_CAN_NOT_COMPLETE;
+ nFailReason = ERROR_FILE_CORRUPT;
bResult = false;
break;
}
@@ -805,7 +1192,7 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo
RawByteOffset = MAKE_OFFSET64(PartMap->BlockOffsHi, PartMap->BlockOffsLo);
if(RawByteOffset == 0)
{
- nFailReason = ERROR_CAN_NOT_COMPLETE;
+ nFailReason = ERROR_FILE_CORRUPT;
bResult = false;
break;
}
@@ -816,9 +1203,9 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo
// Append the offset within the part
RawByteOffset += dwPartOffset;
- if(!File_Read(pStream, &RawByteOffset, pbBuffer, dwBytesInPart))
+ if(!pStream->BaseRead(pStream, &RawByteOffset, pbBuffer, dwBytesInPart))
{
- nFailReason = ERROR_CAN_NOT_COMPLETE;
+ nFailReason = ERROR_FILE_CORRUPT;
bResult = false;
break;
}
@@ -840,69 +1227,237 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo
return (dwBytesRead == dwBytesToRead);
}
-static bool PartFile_Write(
- TPartFileStream * pStream, // Pointer to an open stream
- ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
- const void * pvBuffer, // Pointer to data to be read
- DWORD dwBytesToRead) // Number of bytes to read from the file
+static bool PartialStream_GetPos(
+ TPartialStream * pStream,
+ ULONGLONG & ByteOffset)
{
- // Keep compiler happy
- dwBytesToRead = dwBytesToRead;
- pByteOffset = pByteOffset;
- pvBuffer = pvBuffer;
- pStream = pStream;
-
- // Not allowed
- return false;
+ ByteOffset = pStream->VirtualPos;
+ return true;
}
-static bool PartFile_GetSize(
- TPartFileStream * pStream, // Pointer to an open stream
+static bool PartialStream_GetSize(
+ TPartialStream * pStream, // Pointer to an open stream
ULONGLONG & FileSize) // Pointer where to store file size
{
FileSize = pStream->VirtualSize;
return true;
}
-static bool PartFile_SetSize(
- TPartFileStream * pStream, // Pointer to an open stream
- ULONGLONG NewSize) // new size of the file
+static bool PartialStream_GetBitmap(
+ TPartialStream * pStream,
+ TFileBitmap * pBitmap,
+ DWORD Length,
+ LPDWORD LengthNeeded)
{
- // Keep compiler happy
- pStream = pStream;
- NewSize = NewSize;
+ LPBYTE pbBitmap;
+ DWORD TotalLength;
+ DWORD BitmapSize = 0;
+ DWORD ByteOffset;
+ DWORD BitMask;
+ bool bResult = false;
- // Not allowed
- return false;
+ // Do we have stream bitmap?
+ BitmapSize = ((pStream->BlockCount - 1) / 8) + 1;
+
+ // Give the bitmap length
+ TotalLength = sizeof(TFileBitmap) + BitmapSize;
+ if(LengthNeeded != NULL)
+ *LengthNeeded = TotalLength;
+
+ // Do we have enough to fill at least the header?
+ if(Length >= sizeof(TFileBitmap))
+ {
+ // Fill the bitmap header
+ pBitmap->StartOffset = 0;
+ pBitmap->EndOffset = pStream->VirtualSize;
+ pBitmap->IsComplete = 1;
+ pBitmap->BitmapSize = BitmapSize;
+ pBitmap->BlockSize = pStream->BlockSize;
+ pBitmap->Reserved = 0;
+
+ // 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;
+ }
+
+ // Do we have enough space for supplying the bitmap?
+ if(Length >= TotalLength)
+ {
+ // Fill the file bitmap
+ pbBitmap = (LPBYTE)(pBitmap + 1);
+ for(DWORD i = 0; i < pStream->BlockCount; i++)
+ {
+ // Is the block there?
+ if(pStream->PartMap[i].Flags == 3)
+ {
+ ByteOffset = i / 8;
+ BitMask = 1 << (i & 7);
+ pbBitmap[ByteOffset] |= BitMask;
+ }
+ }
+ bResult = true;
+ }
+
+ return bResult;
}
-/*
- * Stream functions - encrypted stream
- *
- * Note: In original Starcraft II Installer.exe: Suffix derived from battle.net auth. code
- * Address of decryption routine: 0053A3D0 http://us.battle.net/static/mediakey/sc2-authenticationcode-enUS.txt
- * Pointer to decryptor object: ECX Numbers mean offset of 4-char group of auth code
- * Pointer to key: ECX+0x5C -0C- -1C--08- -18--04- -14--00- -10-
- */
-static const char * MpqeKey_Starcraft2_Install_enUS = "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG";
-static const char * MpqeKey_Starcraft2_Install_enGB = "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D";
-static const char * MpqeKey_Starcraft2_Install_deDE = "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V";
-static const char * MpqeKey_Starcraft2_Install_esES = "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P";
-static const char * MpqeKey_Starcraft2_Install_frFR = "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76";
-static const char * MpqeKey_Starcraft2_Install_itIT = "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B";
-static const char * MpqeKey_Starcraft2_Install_plPL = "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3";
-static const char * MpqeKey_Starcraft2_Install_ruRU = "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V";
+static void PartialStream_Close(TPartialStream * pStream)
+{
+ // Free the part map
+ if(pStream->PartMap != NULL)
+ STORM_FREE(pStream->PartMap);
+ pStream->PartMap = NULL;
+
+ // Clear variables
+ pStream->VirtualSize = 0;
+ pStream->VirtualPos = 0;
+
+ // 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;
+
+ // Sanity check
+ assert(pStream->BaseRead != NULL);
+ // Attempt to read PART file header
+ if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER)))
+ {
+ // We need to swap PART file header on big-endian platforms
+ BSWAP_PART_HEADER(&PartHdr);
+
+ // 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 = pStream->BaseGetTime;
+ pStream->StreamGetTime = pStream->BaseGetTime;
+ pStream->StreamGetBmp = (STREAM_GETBMP)PartialStream_GetBitmap;
+ pStream->StreamClose = (STREAM_CLOSE)PartialStream_Close;
+ return true;
+ }
+
+ // Free the part map
+ STORM_FREE(pStream->PartMap);
+ pStream->PartMap = NULL;
+ }
+ }
+ }
+
+ SetLastError(ERROR_BAD_FORMAT);
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - encrypted stream support
+
+// Note: Starcraft II - Installer.exe (4.1.1.4219): Suffix derived from battle.net auth. code
+// Address of decryption routine: 0053A3D0 http://us.battle.net/static/mediakey/sc2-authenticationcode-enUS.txt
+// Pointer to decryptor object: ECX Numbers mean offset of 4-char group of auth code (follows in comment)
+// Pointer to key: ECX+0x5C -0C- -1C--08- -18--04- -14--00- -10-
+static const char * MpqeKey_Starcraft2_Install_deDE = "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V"; // Y45MD3CAK4KXSSXHYD9VY64Z8EKJ4XFX
+static const char * MpqeKey_Starcraft2_Install_enGB = "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D"; // G8MN8UDG6NA2ANGY6A3DNY82HRGF29ZH
+static const char * MpqeKey_Starcraft2_Install_enSG = "expand 32-byte kWW5B0000F7HWFDU90000FWZSHLB20000BLRSW9RR00003ECE"; // W9RRHLB2FDU9WW5B3ECEBLRSFWZSF7HW
+static const char * MpqeKey_Starcraft2_Install_enUS = "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG"; // 3DH5RE5NVM5GTFD85LXGWT6FK859ETR5
+static const char * MpqeKey_Starcraft2_Install_esES = "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P"; // 8WLKUAXE94PFQU4Y249PAZ24N4R4XKTQ
+static const char * MpqeKey_Starcraft2_Install_esMX = "expand 32-byte kSQBR00004G54HGGX0000MF9GXX3V0000FFDXA34D0000FE5U"; // A34DXX3VHGGXSQBRFE5UFFDXMF9G4G54
+static const char * MpqeKey_Starcraft2_Install_frFR = "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76"; // ZG7J9K938HJEFWPQUA768MA2PFER6EAJ
+static const char * MpqeKey_Starcraft2_Install_itIT = "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B"; // NE7CUNNNTVAPXV7E3G2BSVBWGVMW8BL2
+static const char * MpqeKey_Starcraft2_Install_koKR = "expand 32-byte kQWK70000838FBM9Q0000WQDB2FTM0000MWAZ3V9E0000U6MA"; // 3V9E2FTMBM9QQWK7U6MAMWAZWQDB838F
+static const char * MpqeKey_Starcraft2_Install_plPL = "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3"; // 2NSFB8MELULJ83U6YHA3UP6K4MQD48L6
+static const char * MpqeKey_Starcraft2_Install_ptBR = "expand 32-byte kU8BM0000SW4EZ4CU00005F9CZ9EW0000CTY6QA2T0000B5WX"; // QA2TZ9EWZ4CUU8BMB5WXCTY65F9CSW4E
+static const char * MpqeKey_Starcraft2_Install_ruRU = "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V"; // VHB378W64BAT9SH7D68VV9NLQDK9YEGT
+static const char * MpqeKey_Starcraft2_Install_zhTW = "expand 32-byte k7KBN0000D9NEM6GC0000N3PLQJV400003BRDU3NF00009XQJ"; // U3NFQJV4M6GC7KBN9XQJ3BRDN3PLD9NE
+
+// Note: Diablo III: Agent.exe (1.0.0.954): Suffix derived from battle.net auth. code
+// Address of decryption routine: 00502b00 http://dist.blizzard.com/mediakey/d3-authenticationcode-enGB.txt
+// Pointer to decryptor object: ECX Numbers mean offset of 4-char group of auth code (follows in comment)
+// Pointer to key: ECX+0x5C -0C- -1C--08- -18--04- -14--00- -10-
+static const char * MpqeKey_Diablo3_Install_deDE = "expand 32-byte kEFH40000QRZKY3520000XC9MF6EJ0000CFH2UCMX0000XFRX"; // UCMXF6EJY352EFH4XFRXCFH2XC9MQRZK
+static const char * MpqeKey_Diablo3_Install_enGB = "expand 32-byte kXP4G0000PHBPRP7W0000J9UNHY4800007SL9MMKV0000HYBQ"; // MMKVHY48RP7WXP4GHYBQ7SL9J9UNPHBP
+static const char * MpqeKey_Diablo3_Install_enSG = "expand 32-byte kTZ9M00003CPPVGGL0000JYETWHQ70000FDCL8MXL0000QZQS"; // 8MXLWHQ7VGGLTZ9MQZQSFDCLJYET3CPP
+static const char * MpqeKey_Diablo3_Install_enUS = "expand 32-byte kGUNG0000WZSZXFE20000UAKP5TM60000HKQ9EJ2R00005QDG"; // EJ2R5TM6XFE2GUNG5QDGHKQ9UAKPWZSZ
+static const char * MpqeKey_Diablo3_Install_esES = "expand 32-byte kK65U0000HQQTZ6LN0000CLP4BE420000WZVMPBGF0000GJQ3"; // PBGFBE42Z6LNK65UGJQ3WZVMCLP4HQQT
+static const char * MpqeKey_Diablo3_Install_esMX = "expand 32-byte kW5P200008VU2TSGC0000JPEYJJS90000C47AX7SE00008EBS"; // X7SEJJS9TSGCW5P28EBSC47AJPEY8VU2
+static const char * MpqeKey_Diablo3_Install_frFR = "expand 32-byte kRY3D00007YA2YE6X0000XS4PQA8V0000ZDE45KVB0000LGC5"; // 5KVBQA8VYE6XRY3DLGC5ZDE4XS4P7YA2
+static const char * MpqeKey_Diablo3_Install_itIT = "expand 32-byte kVVY40000B2546EVN00005B8KD2K50000DWYT478J0000XX8T"; // 478JD2K56EVNVVY4XX8TDWYT5B8KB254
+static const char * MpqeKey_Diablo3_Install_koKR = "expand 32-byte k6YWH0000474ARZTN0000NVWDVNFQ0000VDH98TS40000E9CH"; // 8TS4VNFQRZTN6YWHE9CHVDH9NVWD474A
+static const char * MpqeKey_Diablo3_Install_plPL = "expand 32-byte k4ZJJ0000BLJBF4LZ0000A6GAZ32D00003AZQLJ520000XVKK"; // LJ52Z32DF4LZ4ZJJXVKK3AZQA6GABLJB
+static const char * MpqeKey_Diablo3_Install_ptBR = "expand 32-byte k545Y0000XYAGCUE20000WHE7HY2E0000JPVYK6BD0000KNLB"; // K6BDHY2ECUE2545YKNLBJPVYWHE7XYAG
+static const char * MpqeKey_Diablo3_Install_ruRU = "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //
+static const char * MpqeKey_Diablo3_Install_zhTW = "expand 32-byte kMRUC0000AA8HV3ZZ0000UX2TQTN80000A8CG6VWC0000ZXV8"; // 6VWCQTN8V3ZZMRUCZXV8A8CGUX2TAA8H
+static const char * MpqeKey_Diablo3_Install_zhCN = "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //
+
static const char * MpqKeyArray[] =
{
- MpqeKey_Starcraft2_Install_enUS,
- MpqeKey_Starcraft2_Install_enGB,
MpqeKey_Starcraft2_Install_deDE,
+ MpqeKey_Starcraft2_Install_enGB,
+ MpqeKey_Starcraft2_Install_enSG,
+ MpqeKey_Starcraft2_Install_enUS,
MpqeKey_Starcraft2_Install_esES,
+ MpqeKey_Starcraft2_Install_esMX,
MpqeKey_Starcraft2_Install_frFR,
MpqeKey_Starcraft2_Install_itIT,
+ MpqeKey_Starcraft2_Install_koKR,
MpqeKey_Starcraft2_Install_plPL,
+ MpqeKey_Starcraft2_Install_ptBR,
MpqeKey_Starcraft2_Install_ruRU,
+ MpqeKey_Starcraft2_Install_zhTW,
+
+ MpqeKey_Diablo3_Install_deDE,
+ MpqeKey_Diablo3_Install_enGB,
+ MpqeKey_Diablo3_Install_enSG,
+ MpqeKey_Diablo3_Install_enUS,
+ MpqeKey_Diablo3_Install_esES,
+ MpqeKey_Diablo3_Install_esMX,
+ MpqeKey_Diablo3_Install_frFR,
+ MpqeKey_Diablo3_Install_itIT,
+ MpqeKey_Diablo3_Install_koKR,
+ MpqeKey_Diablo3_Install_plPL,
+ MpqeKey_Diablo3_Install_ptBR,
+// MpqeKey_Diablo3_Install_ruRU,
+ MpqeKey_Diablo3_Install_zhTW,
+// MpqeKey_Diablo3_Install_zhCN,
+
NULL
};
@@ -1026,40 +1581,35 @@ static void DecryptFileChunk(
}
}
-
-static bool DetectFileKey(TEncryptedStream * pStream)
+static const char * DetectFileKey(LPBYTE pbEncryptedHeader)
{
ULONGLONG ByteOffset = 0;
- BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
BYTE FileHeader[MPQE_CHUNK_SIZE];
-
- // Load the chunk from the file
- if(!FileStream_Read(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader)))
- return false;
+ BYTE Key[MPQE_CHUNK_SIZE];
// We just try all known keys one by one
for(int i = 0; MpqKeyArray[i] != NULL; i++)
{
// Copy the key there
- memcpy(pStream->Key, MpqKeyArray[i], MPQE_CHUNK_SIZE);
- BSWAP_ARRAY32_UNSIGNED(pStream->Key, MPQE_CHUNK_SIZE);
+ memcpy(Key, MpqKeyArray[i], MPQE_CHUNK_SIZE);
+ BSWAP_ARRAY32_UNSIGNED(Key, MPQE_CHUNK_SIZE);
// Try to decrypt with the given key
- memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE);
- DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE);
+ memcpy(FileHeader, pbEncryptedHeader, MPQE_CHUNK_SIZE);
+ DecryptFileChunk((LPDWORD)FileHeader, Key, ByteOffset, MPQE_CHUNK_SIZE);
- // We check the decrypoted data
+ // 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;
+ return MpqKeyArray[i];
}
// Key not found, sorry
- return false;
+ return NULL;
}
-static bool EncryptedFile_Read(
+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
void * pvBuffer, // Pointer to data to be read
@@ -1075,10 +1625,10 @@ static bool EncryptedFile_Read(
bool bResult = false;
// Get the byte offset
- if(pByteOffset != NULL)
- ByteOffset = *pByteOffset;
+ if(pByteOffset == NULL)
+ pStream->BaseGetPos(pStream, ByteOffset);
else
- ByteOffset = pStream->RawFilePos;
+ ByteOffset = *pByteOffset;
// Cut it down to MPQE chunk size
StartOffset = ByteOffset;
@@ -1097,7 +1647,7 @@ static bool EncryptedFile_Read(
dwOffsetInCache = (DWORD)(ByteOffset - StartOffset);
// Read the file from the stream as-is
- if(File_Read(pStream, &StartOffset, pbMpqData, dwBytesToDecrypt))
+ if(pStream->BaseRead(pStream, &StartOffset, pbMpqData, dwBytesToDecrypt))
{
// Decrypt the data
DecryptFileChunk((LPDWORD)pbMpqData, pStream->Key, StartOffset, dwBytesToAllocate);
@@ -1119,31 +1669,42 @@ static bool EncryptedFile_Read(
return bResult;
}
-static bool EncryptedFile_Write(
- TEncryptedStream * pStream, // Pointer to an open stream
- ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
- const void * pvBuffer, // Pointer to data to be read
- DWORD dwBytesToRead) // Number of bytes to read from the file
+static bool EncryptedStream_Open(TEncryptedStream * pStream)
{
- // Keep compiler happy
- dwBytesToRead = dwBytesToRead;
- pByteOffset = pByteOffset;
- pvBuffer = pvBuffer;
- pStream = pStream;
+ ULONGLONG ByteOffset = 0;
+ BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
+ const char * szKey;
- // Not allowed
- return false;
-}
+ // Sanity check
+ assert(pStream->BaseRead != NULL);
-static bool EncryptedFile_SetSize(
- TEncryptedStream * pStream, // Pointer to an open stream
- ULONGLONG NewSize) // new size of the file
-{
- // Keep compiler happy
- pStream = pStream;
- NewSize = NewSize;
+ // 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
+ szKey = DetectFileKey(EncryptedHeader);
+ if(szKey != NULL)
+ {
+ // Copy the key for the file
+ memcpy(pStream->Key, szKey, MPQE_CHUNK_SIZE);
+ BSWAP_ARRAY32_UNSIGNED(pStream->Key, MPQE_CHUNK_SIZE);
+
+ // Assign functions
+ pStream->StreamRead = (STREAM_READ)EncryptedStream_Read;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamGetTime = pStream->BaseGetTime;
+ pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap;
+ 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;
+ }
- // Not allowed
+ // An unknown key
+ SetLastError(ERROR_UNKNOWN_FILE_KEY);
+ }
return false;
}
@@ -1151,7 +1712,7 @@ static bool EncryptedFile_SetSize(
// Public functions
/**
- * This function creates a new file for read or read-write access
+ * This function creates a new file for read-write access
*
* - If the current platform supports file sharing,
* the file must be created for read sharing (i.e. another application
@@ -1168,35 +1729,45 @@ static bool EncryptedFile_SetSize(
*/
TFileStream * FileStream_CreateFile(
- const TCHAR * szFileName) // Name of the file to create
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
{
- TFileStream * pStream = NULL;
- HANDLE hFile;
+ TFileStream * pStream;
- // Create the file
- hFile = CreateNewFile(szFileName);
- if(hFile != INVALID_HANDLE_VALUE)
+ // We only support creation of linear, local file
+ if((dwStreamFlags & (STREAM_PROVIDER_MASK | BASE_PROVIDER_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE))
{
- // Allocate the FileStream structure and fill it
- pStream = STORM_ALLOC(TFileStream, 1);
- if(pStream != NULL)
- {
- // Reset entire structure to zero
- memset(pStream, 0, sizeof(TFileStream));
-
- // Save file name and set function pointers
- _tcscpy(pStream->szFileName, szFileName);
- pStream->StreamGetPos = File_GetPos;
- pStream->StreamRead = File_Read;
- pStream->StreamWrite = File_Write;
- pStream->StreamGetSize = File_GetSize;
- pStream->StreamSetSize = File_SetSize;
- pStream->hFile = hFile;
- }
- else
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return NULL;
+ }
+
+ // Allocate file stream structure for linear stream
+ pStream = STORM_ALLOC(TFileStream, 1);
+ if(pStream != NULL)
+ {
+ // Reset entire structure to zero
+ memset(pStream, 0, sizeof(TFileStream));
+ _tcscpy(pStream->szFileName, szFileName);
+
+ // Attempt to create the disk file
+ if(BaseFile_Create(pStream, szFileName, dwStreamFlags))
{
- CloseTheFile(hFile);
+ // Fill the stream provider functions
+ pStream->StreamRead = pStream->BaseRead;
+ pStream->StreamWrite = pStream->BaseWrite;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamSetSize = pStream->BaseSetSize;
+ pStream->StreamGetTime = pStream->BaseGetTime;
+ pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap;;
+ pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch;
+ pStream->StreamClose = pStream->BaseClose;
+ return pStream;
}
+
+ // File create failed, delete the stream
+ STORM_FREE(pStream);
+ pStream = NULL;
}
// Return the stream
@@ -1212,185 +1783,106 @@ TFileStream * FileStream_CreateFile(
* - 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 TPartFileStream object if so.
+ * 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,
* including the TFileStream structure itself
*
* \a szFileName Name of the file to open
- * \a bWriteAccess false for read only, true for read+write
+ * \a dwStreamFlags specifies the provider and base storage type
*/
-TFileStream * FileStream_OpenRawFile(
- const TCHAR * szFileName, // Name of the file to create
- bool bWriteAccess) // false = read-only, true = read+write
+TFileStream * FileStream_OpenFile(
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
{
- TFileStream * pStream;
- HANDLE hFile;
-
- // Create the file
- hFile = OpenExistingFile(szFileName, bWriteAccess);
- if(hFile == INVALID_HANDLE_VALUE)
- return NULL;
+ TFileStream * pStream = NULL;
+ size_t StreamSize = 0;
+ bool bStreamResult = false;
+ bool bBaseResult = false;
- // Initialize the file as normal file stream
- pStream = STORM_ALLOC(TFileStream, 1);
- if(pStream != NULL)
+ // Allocate file stream for each stream provider
+ switch(dwStreamFlags & STREAM_PROVIDER_MASK)
{
- // Reset entire structure to zero
- memset(pStream, 0, sizeof(TFileStream));
-
- // Save file name and set function pointers
- _tcscpy(pStream->szFileName, szFileName);
- pStream->StreamGetPos = File_GetPos;
- pStream->StreamRead = File_Read;
- pStream->StreamWrite = File_Write;
- pStream->StreamGetSize = File_GetSize;
- pStream->StreamSetSize = File_SetSize;
- if(bWriteAccess == false)
- pStream->StreamFlags |= STREAM_FLAG_READ_ONLY;
- pStream->hFile = hFile;
- return pStream;
- }
+ case STREAM_PROVIDER_LINEAR: // Allocate structure for linear stream
+ StreamSize = sizeof(TLinearStream);
+ break;
- CloseTheFile(hFile);
- return NULL;
-}
+ case STREAM_PROVIDER_PARTIAL:
+ dwStreamFlags |= STREAM_FLAG_READ_ONLY;
+ StreamSize = sizeof(TPartialStream);
+ break;
-/**
- * Opens a file
- *
- * \a szFileName Name of the file to open
- * \a bWriteAccess false for read only, true for read+write
- */
+ case STREAM_PROVIDER_ENCRYPTED:
+ dwStreamFlags |= STREAM_FLAG_READ_ONLY;
+ StreamSize = sizeof(TEncryptedStream);
+ break;
-TFileStream * FileStream_OpenFile(const TCHAR * szFileName, bool bWriteAccess)
-{
- PART_FILE_HEADER PartHdr;
- ULONGLONG VirtualSize; // Size of the file stored in part file
- ULONGLONG ByteOffset = {0};
- TFileStream * pStream;
- size_t nStructLength;
- DWORD BlockCount;
+ default:
+ return NULL;
+ }
- // Open the file as normal stream
- pStream = FileStream_OpenRawFile(szFileName, bWriteAccess);
+ // Allocate the stream for each type
+ pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize);
if(pStream == NULL)
return NULL;
- // Attempt to read PART file header
- if(FileStream_Read(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER)))
- {
- // We need to swap PART file header on big-endian platforms
- BSWAP_PART_HEADER(&PartHdr);
-
- // Verify the PART file header
- if(IsPartHeader(&PartHdr))
- {
- TPartFileStream * pPartStream;
+ // Fill the stream structure with zeros
+ memset(pStream, 0, StreamSize);
+ _tcscpy(pStream->szFileName, szFileName);
- // Calculate the number of parts in the file
- VirtualSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo);
- BlockCount = (DWORD)((VirtualSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize);
+ // Now initialize the respective base provider
+ switch(dwStreamFlags & BASE_PROVIDER_MASK)
+ {
+ case BASE_PROVIDER_FILE:
+ bBaseResult = BaseFile_Open(pStream, szFileName, dwStreamFlags);
+ break;
- // Calculate the size of the entire structure
- // Note that we decrement number of parts by one,
- // because there already is one entry in the TPartFileStream structure
- nStructLength = sizeof(TPartFileStream) + (BlockCount - 1) * sizeof(PART_FILE_MAP_ENTRY);
- pPartStream = (TPartFileStream *)STORM_ALLOC(char, nStructLength);
- if(pPartStream != NULL)
- {
- // Initialize the part file stream
- memset(pPartStream, 0, nStructLength);
- memcpy(pPartStream, pStream, sizeof(TFileStream));
+ case BASE_PROVIDER_MAP:
+ dwStreamFlags |= STREAM_FLAG_READ_ONLY;
+ bBaseResult = BaseMap_Open(pStream, szFileName, dwStreamFlags);
+ break;
- // Load the block map
- if(!FileStream_Read(pPartStream, NULL, pPartStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY)))
- {
- FileStream_Close(pStream);
- STORM_FREE(pPartStream);
- return NULL;
- }
+ case BASE_PROVIDER_HTTP:
+ dwStreamFlags |= STREAM_FLAG_READ_ONLY;
+ bBaseResult = BaseHttp_Open(pStream, szFileName, dwStreamFlags);
+ break;
+ }
- // Swap the array of file map entries
- BSWAP_ARRAY32_UNSIGNED(pPartStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY));
+ // If we failed to open the base storage, fail the operation
+ if(bBaseResult == false)
+ {
+ STORM_FREE(pStream);
+ return NULL;
+ }
- // Set new function pointers
- pPartStream->StreamGetPos = (STREAM_GETPOS)PartFile_GetPos;
- pPartStream->StreamRead = (STREAM_READ)PartFile_Read;
- pPartStream->StreamWrite = (STREAM_WRITE)PartFile_Write;
- pPartStream->StreamGetSize = (STREAM_GETSIZE)PartFile_GetSize;
- pPartStream->StreamSetSize = (STREAM_SETSIZE)PartFile_SetSize;
- pPartStream->StreamFlags |= (STREAM_FLAG_READ_ONLY | STREAM_FLAG_PART_FILE);
+ // Now initialize the stream provider
+ switch(dwStreamFlags & STREAM_PROVIDER_MASK)
+ {
+ case STREAM_PROVIDER_LINEAR:
+ bStreamResult = LinearStream_Open((TLinearStream *)pStream);
+ break;
- // Fill the members of PART file stream
- pPartStream->VirtualSize = ((ULONGLONG)PartHdr.FileSizeHi) + PartHdr.FileSizeLo;
- pPartStream->VirtualPos = 0;
- pPartStream->BlockCount = BlockCount;
- pPartStream->BlockSize = PartHdr.BlockSize;
+ case STREAM_PROVIDER_PARTIAL:
+ bStreamResult = PartialStream_Open((TPartialStream *)pStream);
+ break;
- STORM_FREE(pStream);
- }
- return pPartStream;
- }
+ case STREAM_PROVIDER_ENCRYPTED:
+ bStreamResult = EncryptedStream_Open((TEncryptedStream *)pStream);
+ break;
}
- // If the file doesn't contain PART file header,
- // reset the file position to begin of the file
- FileStream_Read(pStream, &ByteOffset, NULL, 0);
- return pStream;
-}
-
-TFileStream * FileStream_OpenEncrypted(const TCHAR * szFileName)
-{
- TEncryptedStream * pEncryptedStream;
- TFileStream * pStream;
-
- // Open the file as raw stream
- pStream = FileStream_OpenRawFile(szFileName, false);
- if(pStream)
+ // If the operation failed, free the stream and set it to NULL
+ if(bStreamResult == false)
{
- // Allocate new stream for handling encryption
- pEncryptedStream = STORM_ALLOC(TEncryptedStream, 1);
- if(pEncryptedStream != NULL)
- {
- // Copy the file stream to the encrypted stream
- memset(pEncryptedStream, 0, sizeof(TEncryptedStream));
- memcpy(pEncryptedStream, pStream, sizeof(TFileStream));
-
- // Assign functions
- pEncryptedStream->StreamRead = (STREAM_READ)EncryptedFile_Read;
- pEncryptedStream->StreamWrite = (STREAM_WRITE)EncryptedFile_Write;
- pEncryptedStream->StreamSetSize = (STREAM_SETSIZE)EncryptedFile_SetSize;
- pEncryptedStream->StreamFlags |= (STREAM_FLAG_READ_ONLY | STREAM_FLAG_ENCRYPTED_FILE);
-
- // Get the file key
- if(DetectFileKey(pEncryptedStream))
- return pEncryptedStream;
-
- // Close the encrypted stream
- STORM_FREE(pEncryptedStream);
- pEncryptedStream = NULL;
- }
-
- FileStream_Close(pStream);
+ // Only close the base stream
+ pStream->BaseClose(pStream);
+ STORM_FREE(pStream);
pStream = NULL;
}
- SetLastError(ERROR_UNKNOWN_FILE_KEY);
- return NULL;
-}
-
-/**
- * This function returns the current file position
- * \a pStream
- * \a ByteOffset
- */
-bool FileStream_GetPos(TFileStream * pStream, ULONGLONG & ByteOffset)
-{
- assert(pStream->StreamGetPos != NULL);
- return pStream->StreamGetPos(pStream, ByteOffset);
+ return pStream;
}
/**
@@ -1432,67 +1924,22 @@ bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBu
*/
bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
{
- if(pStream->StreamFlags & STREAM_FLAG_READ_ONLY)
+ if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
return false;
- assert(pStream->StreamWrite != NULL);
+ assert(pStream->StreamWrite != NULL);
return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite);
}
-
/**
- * Returns the last write time of a file
- *
- * \a pStream Pointer to an open stream
- * \a pFileType Pointer where to store the file last write time
+ * This function returns the current file position
+ * \a pStream
+ * \a ByteOffset
*/
-bool FileStream_GetLastWriteTime(TFileStream * pStream, ULONGLONG * pFileTime)
+bool FileStream_GetPos(TFileStream * pStream, ULONGLONG & ByteOffset)
{
-#ifdef PLATFORM_WINDOWS
- FILETIME ft;
-
- if(!GetFileTime(pStream->hFile, NULL, NULL, &ft))
- return false;
-
- *pFileTime = MAKE_OFFSET64(ft.dwHighDateTime, ft.dwLowDateTime);
- return true;
-#endif
-
-#ifdef PLATFORM_MAC
- OSErr theErr;
- FSRef theFileRef;
- FSCatalogInfo theCatInfo;
-
- theErr = FSGetForkCBInfo((short)(long)pStream->hFile, 0, NULL, NULL, NULL, &theFileRef, NULL);
- if(theErr != noErr)
- {
- nLastError = theErr;
- return false;
- }
-
- theErr = FSGetCatalogInfo(&theFileRef, kFSCatInfoContentMod, &theCatInfo, NULL, NULL, NULL);
- if(theErr != noErr)
- {
- nLastError = theErr;
- return false;
- }
-
- ConvertUTCDateTimeToFileTime(&theCatInfo.contentModDate, pFileTime);
- return true;
-#endif
-
-#ifdef PLATFORM_LINUX
- struct stat file_stats;
-
- if(fstat((int)(size_t)pStream->hFile, &file_stats) == -1)
- {
- nLastError = errno;
- return false;
- }
-
- ConvertTimeTToFileTime(pFileTime, file_stats.st_mtime);
- return true;
-#endif
+ assert(pStream->StreamGetPos != NULL);
+ return pStream->StreamGetPos(pStream, ByteOffset);
}
/**
@@ -1515,14 +1962,26 @@ bool FileStream_GetSize(TFileStream * pStream, ULONGLONG & FileSize)
*/
bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
{
- if(pStream->StreamFlags & STREAM_FLAG_READ_ONLY)
+ if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
return false;
- assert(pStream->StreamSetSize != NULL);
+ assert(pStream->StreamSetSize != NULL);
return pStream->StreamSetSize(pStream, NewFileSize);
}
/**
+ * Returns the last write time of a file
+ *
+ * \a pStream Pointer to an open stream
+ * \a pFileType Pointer where to store the file last write time
+ */
+bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFileTime)
+{
+ assert(pStream->StreamGetTime != NULL);
+ return pStream->StreamGetTime(pStream, pFileTime);
+}
+
+/**
* Switches a stream with another. Used for final phase of archive compacting.
* Performs these steps:
*
@@ -1533,37 +1992,86 @@ bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
* \a pStream Pointer to an open stream
* \a pTempStream Temporary ("working") stream (created during archive compacting)
*/
-bool FileStream_MoveFile(TFileStream * pStream, TFileStream * pTempStream)
+bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream)
+{
+ if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ return false;
+
+ assert(pStream->StreamSwitch != NULL);
+ return pStream->StreamSwitch(pStream, pNewStream);
+}
+
+/**
+ * Returns the file name of the stream
+ *
+ * \a pStream Pointer to an open stream
+ */
+TCHAR * FileStream_GetFileName(TFileStream * pStream)
{
- bool bWriteAccess;
+ assert(pStream != NULL);
+ return pStream->szFileName;
+}
- // Close the handle to the temporary file
- CloseTheFile(pTempStream->hFile);
- pTempStream->hFile = INVALID_HANDLE_VALUE;
+/**
+ * Returns true if the stream is read-only
+ *
+ * \a pStream Pointer to an open stream
+ */
+bool FileStream_IsReadOnly(TFileStream * pStream)
+{
+ return (pStream->dwFlags & STREAM_FLAG_READ_ONLY) ? true : false;
+}
+
+/**
+ * This function enabled a linear stream to include data bitmap.
+ * Used by MPQs v 4.0 from WoW. Each file block is represented by
+ * a bit in the bitmap. 1 means the block is present, 0 means it's not.
+ *
+ * \a pStream Pointer to an open stream
+ * \a pBitmap Pointer to file bitmap
+ */
- // Close the handle to the source file
- CloseTheFile(pStream->hFile);
- pStream->hFile = INVALID_HANDLE_VALUE;
+bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap)
+{
+ TLinearStream * pLinearStream;
- // Rename the temp file to the final file
- if(!RenameFile(pTempStream->szFileName, pStream->szFileName))
+ // It must be a linear stream.
+ if((pStream->dwFlags & STREAM_PROVIDER_MASK) != STREAM_PROVIDER_LINEAR)
return false;
+ pLinearStream = (TLinearStream *)pStream;
- // Now open the renamed file again, and store its handle to the old stream
- bWriteAccess = (pStream->StreamFlags & STREAM_FLAG_READ_ONLY) ? false : true;
- pStream->hFile = OpenExistingFile(pStream->szFileName, bWriteAccess);
- if(pStream->hFile == INVALID_HANDLE_VALUE)
+ // Two bitmaps are not allowed
+ if(pLinearStream->pBitmap != NULL)
return false;
- // Delete the temporary file stream
- FileStream_Close(pTempStream);
+ // We need to change some entry points
+ pLinearStream->StreamRead = (STREAM_READ)LinearStream_Read;
+ pLinearStream->StreamGetBmp = (STREAM_GETBMP)LinearStream_GetBitmap;
- // The file position has been reset to zero by reopening the file
- pStream->RawFilePos = 0;
+ // Using data bitmap renders the stream to be read only.
+ pLinearStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+ pLinearStream->pBitmap = pBitmap;
return true;
}
/**
+ * 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, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded)
+{
+ assert(pStream->StreamGetBmp != NULL);
+ return pStream->StreamGetBmp(pStream, pBitmap, 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
@@ -1576,9 +2084,10 @@ void FileStream_Close(TFileStream * pStream)
// Check if the stream structure is allocated at all
if(pStream != NULL)
{
- // Close the file handle
- if(pStream->hFile != INVALID_HANDLE_VALUE)
- CloseTheFile(pStream->hFile);
+ // Close the stream provider.
+ // This will also close the base stream
+ assert(pStream->StreamClose != NULL);
+ pStream->StreamClose(pStream);
// Free the stream itself
STORM_FREE(pStream);
@@ -1587,275 +2096,199 @@ void FileStream_Close(TFileStream * pStream)
//-----------------------------------------------------------------------------
// main - for testing purposes
-/*
-int main(void)
+
+#ifdef __STORMLIB_TEST__
+int FileStream_Test(const TCHAR * szFileName, DWORD dwStreamFlags)
{
- ULONGLONG FilePos;
- ULONGLONG FileSize;
- TMPQFileTime * pFT;
- TFileStream * pTempStream;
TFileStream * pStream;
+ TMPQHeader MpqHeader;
+ ULONGLONG FilePos;
TMPQBlock * pBlock;
TMPQHash * pHash;
- TMPQHeader2 MpqHeader;
- char szString1[100] = "This is a single line\n\r";
- char szString2[100];
- char Buffer[0x80];
- DWORD dwLength = strlen(szString1);
- //
- // Test 1: Write to a stream
- //
+ InitializeMpqCryptography();
- pStream = FileStream_CreateFile("E:\\Stream.bin");
+ pStream = FileStream_OpenFile(szFileName, dwStreamFlags);
if(pStream == NULL)
+ return GetLastError();
+
+ // Read the MPQ header
+ FileStream_Read(pStream, NULL, &MpqHeader, MPQ_HEADER_SIZE_V2);
+ if(MpqHeader.dwID != ID_MPQ)
+ return ERROR_FILE_CORRUPT;
+
+ // Read the hash table
+ pHash = STORM_ALLOC(TMPQHash, MpqHeader.dwHashTableSize);
+ if(pHash != NULL)
{
- printf("Failed to create new file\n");
- return -1;
+ FilePos = MpqHeader.dwHashTablePos;
+ FileStream_Read(pStream, &FilePos, pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash));
+ DecryptMpqBlock(pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash), MPQ_KEY_HASH_TABLE);
+ STORM_FREE(pHash);
}
- for(int i = 0; i < 10; i++)
+ // Read the block table
+ pBlock = STORM_ALLOC(TMPQBlock, MpqHeader.dwBlockTableSize);
+ if(pBlock != NULL)
{
- if(!FileStream_Write(pStream, NULL, szString1, dwLength))
- {
- printf("Failed to write to the stream\n");
- return -1;
- }
+ FilePos = MpqHeader.dwBlockTablePos;
+ FileStream_Read(pStream, &FilePos, pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock));
+ DecryptMpqBlock(pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock), MPQ_KEY_BLOCK_TABLE);
+ STORM_FREE(pBlock);
}
+
FileStream_Close(pStream);
+ return ERROR_SUCCESS;
+}
+#endif
+
+/*
+int FileStream_Test()
+{
+ TFileStream * pStream;
+
+ InitializeMpqCryptography();
//
- // Test2: Read from the stream
+ // Test 1: Write to a stream
//
- pStream = FileStream_OpenFile("E:\\Stream.bin", false);
- if(pStream == NULL)
+ pStream = FileStream_CreateFile("E:\\Stream.bin", 0);
+ if(pStream != NULL)
{
- printf("Failed to open existing file\n");
- return -1;
- }
+ char szString1[100] = "This is a single line\n\r";
+ DWORD dwLength = strlen(szString1);
- // This call must end with an error
- if(FileStream_Write(pStream, NULL, "aaa", 3))
- {
- printf("Write succeeded while it should fail\n");
- return -1;
+ for(int i = 0; i < 10; i++)
+ {
+ if(!FileStream_Write(pStream, NULL, szString1, dwLength))
+ {
+ printf("Failed to write to the stream\n");
+ return ERROR_CAN_NOT_COMPLETE;
+ }
+ }
+ FileStream_Close(pStream);
}
- for(int i = 0; i < 10; i++)
+ //
+ // Test2: Read from the stream
+ //
+
+ pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
+ if(pStream != NULL)
{
- if(!FileStream_Read(pStream, NULL, szString2, dwLength))
+ char szString1[100] = "This is a single line\n\r";
+ char szString2[100];
+ DWORD dwLength = strlen(szString1);
+
+ // This call must end with an error
+ if(FileStream_Write(pStream, NULL, "aaa", 3))
{
- printf("Failed to read from the stream\n");
+ printf("Write succeeded while it should fail\n");
return -1;
}
- szString2[dwLength] = 0;
- if(strcmp(szString1, szString2))
+ for(int i = 0; i < 10; i++)
{
- printf("Data read from file are different from data written\n");
- return -1;
+ if(!FileStream_Read(pStream, NULL, szString2, dwLength))
+ {
+ printf("Failed to read from the stream\n");
+ return -1;
+ }
+
+ szString2[dwLength] = 0;
+ if(strcmp(szString1, szString2))
+ {
+ printf("Data read from file are different from data written\n");
+ return -1;
+ }
}
+ FileStream_Close(pStream);
}
- FileStream_Close(pStream);
//
// Test3: Open the temp stream, write some data and switch it to the original stream
//
- pStream = FileStream_OpenFile("E:\\Stream.bin", false);
- if(pStream == NULL)
- {
- printf("Failed to open existing file\n");
- return -1;
- }
-
- pTempStream = FileStream_CreateFile("E:\\TempStream.bin");
- if(pTempStream == NULL)
- {
- printf("Failed to create temp stream\n");
- return -1;
- }
-
- // Copy the original stream to the temp
- if(!FileStream_GetSize(pStream, &FileSize))
- {
- printf("Failed to get the file size\n");
- return -1;
- }
-
- while(FileSize.QuadPart != 0)
+ pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
+ if(pStream != NULL)
{
- DWORD dwBytesToRead = FileSize.LowPart;
+ TFileStream * pTempStream;
+ ULONGLONG FileSize;
- if(dwBytesToRead > sizeof(Buffer))
- dwBytesToRead = sizeof(Buffer);
-
- if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead))
+ pTempStream = FileStream_CreateFile("E:\\TempStream.bin", 0);
+ if(pTempStream == NULL)
{
- printf("CopyStream: Read source file failed\n");
+ printf("Failed to create temp stream\n");
return -1;
}
- if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead))
+ // Copy the original stream to the temp
+ if(!FileStream_GetSize(pStream, FileSize))
{
- printf("CopyStream: Write target file failed\n");
+ printf("Failed to get the file size\n");
return -1;
}
- FileSize.QuadPart -= dwBytesToRead;
- }
+ while(FileSize != 0)
+ {
+ DWORD dwBytesToRead = (DWORD)FileSize;
+ char Buffer[0x80];
- // Switch the streams
- // Note that the pTempStream is closed by the operation
- FileStream_MoveFile(pStream, pTempStream);
- FileStream_Close(pStream);
+ if(dwBytesToRead > sizeof(Buffer))
+ dwBytesToRead = sizeof(Buffer);
- //
- // Test4: Read from the stream again
- //
+ if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead))
+ {
+ printf("CopyStream: Read source file failed\n");
+ return -1;
+ }
- pStream = FileStream_OpenFile("E:\\Stream.bin", false);
- if(pStream == NULL)
- {
- printf("Failed to open existing file\n");
- return -1;
- }
+ if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead))
+ {
+ printf("CopyStream: Write target file failed\n");
+ return -1;
+ }
- for(int i = 0; i < 10; i++)
- {
- if(!FileStream_Read(pStream, NULL, szString2, dwLength))
- {
- printf("Failed to read from the stream\n");
- return -1;
+ FileSize -= dwBytesToRead;
}
- szString2[dwLength] = 0;
- if(strcmp(szString1, szString2))
- {
- printf("Data read from file are different from data written\n");
- return -1;
- }
+ // Switch the streams
+ // Note that the pTempStream is closed by the operation
+ FileStream_Switch(pStream, pTempStream);
+ FileStream_Close(pStream);
}
- FileStream_Close(pStream);
//
- // Test5: Open partial MPQ stream
+ // Test4: Read from the stream again
//
-// InitializeMpqCryptography();
- pStream = FileStream_OpenFile("e:\\Multimedia\\MPQs\\PartialMPQs\\patch.MPQ.part", false);
+ pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE);
if(pStream != NULL)
{
- // Read the MPQ header
- FileStream_Read(pStream, NULL, &MpqHeader, MPQ_HEADER_SIZE_V2);
-
- // Read the hash table
- pHash = STORM_ALLOC(TMPQHash, MpqHeader.dwHashTableSize);
- FilePos.HighPart = 0;
- FilePos.LowPart = MpqHeader.dwHashTablePos;
- FileStream_Read(pStream, &FilePos, pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash));
-
- //
- // At this point, the encrypted hash table should be like this:
- //
- // 416c7175 5ddfb61b 84bb75c8 c0399515
- // a9793eca 9ec773d7 ed8a54d6 74fb2adf
- // 6acd4ae5 b13816b5 ffad2341 2f2b2a54
- // 614339c7 5fd0adf0 62434e91 d62439e3
- // 8f317aa5 f12706d6 bd83d2ca 97d7f108
- // 7586d373 51d85b05 8540beca f37ef3d7
- // d931d4d6 d592aadf 9044e960 c4592e92
- // 47dc03f7 0982dea4 afb31943 7c3c7cec
- // 0c28fd0d bcbfb7df 4d13b6e4 b5b0ef31
- // e1a33b70 ec30e4b9 7aaa5e7a fb6d46ec
- // 61732791 55fe757e 8ba18b5d d5f93246
- // 6d275f38 a89b5781 c34189a9 654c6472
- // 07e1d4e1 814bc8ee c72d2730 815afd43
- // 40bd2a92 640a9391 d868f813 0f61b73d
- // 6d202746 2c5124ca 65db3ad0 5b1c3e39
- // b731013c 73776405 eac0c746 6e50c938
- // a4a7fd00 56db3805 6d6dbab7 44fed28a
- // 2383394b bf617bdd a3edfaa2 e7d3aaaf
- //
-
- // Decrypt the hash table
-// DecryptMpqBlock(pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash), MPQ_KEY_HASH_TABLE);
-
- //
- // At this point, the hash table should be like this:
- //
- // c750beb9 72c2538a 00000000 00000466
- // ffffffff ffffffff ffffffff ffffffff
- // 898fdc7a 18963b5d 00000000 000005e1
- // ffffffff ffffffff ffffffff ffffffff
- // e3c6fc32 d8afff2b 00000000 000001ea
- // ffffffff ffffffff ffffffff ffffffff
- // ffffffff ffffffff ffffffff ffffffff
- // ffffffff ffffffff ffffffff ffffffff
- // ffffffff ffffffff ffffffff ffffffff
- // ffffffff ffffffff ffffffff ffffffff
- // ffffffff ffffffff ffffffff ffffffff
- // 0fa4fd60 3fbe8626 00000000 0000076f
- // 9ee5bccf 031b277b 00000000 0000095c
- // f4e154c5 0aadd1c1 00000000 00000876
- // 9e1ce9e7 e12d575d 00000000 0000071d
- //
-
- // Read the block table
- pBlock = STORM_ALLOC(TMPQBlock, MpqHeader.dwBlockTableSize);
- FilePos.HighPart = 0;
- FilePos.LowPart = MpqHeader.dwBlockTablePos;
- FileStream_Read(pStream, &FilePos, pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock));
+ char szString1[100] = "This is a single line\n\r";
+ char szString2[100];
+ DWORD dwLength = strlen(szString1);
- //
- // At this point, the encrypted block table should be like this:
- //
- // 3d4867a7 ca0f533e f82c54d6 ed3c9dec
- // d8d607dc d9ad13ab f4588b46 8d058704
- // e8084fc8 63bc8064 b058c777 3683e9e3
- // 6c0da998 7703be0d 91ce3607 c14e29b9
- // 481b5c0d 42d902d2 8302acb7 e8f3e715
- // c9cdfc91 7cc38c15 ea3dfd22 ad20c856
- // b6450c7f 08522866 4cedb064 e03e3a86
- // 4509c7cc ddffbfc3 82fc8c66 e82a4424
- // afc4a982 23169037 5af6a3e2 34e1d24e
- // 362c9e34 846cfc3d 4c611fcd d645fe8f
- // f4061640 6d08d196 f330a975 66e30993
- // fd96a033 2b16def6 62ff30af 3e190b0b
- // 664a5b91 b8558235 fd631825 a7807be7
- // ec906b9b 76d8b32e 36f3ea0b 1b0f5391
- //
-
- // Decrypt the block table
-// DecryptMpqBlock(pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock), MPQ_KEY_BLOCK_TABLE);
-
- //
- // At this point, the block table should be like this:
- //
- // 0000002c 00078093 00116824 84000200
- // 000780bf 000002d5 00008044 84000200
- // 00078394 00001516 0000874c 84000200
- // 000798aa 00003797 0000af4e 84000200
- // 0007d041 000001db 00008044 84000200
- // 0007d21c 0000005e 0000005e 84000200
- // 0007d27a 000022fb 00009674 84000200
- // 0007f575 00002389 00009c64 84000200
- // 000818fe 000023cb 00009d58 84000200
- // 00083cc9 000024d9 0000a0d8 84000200
- // 000861a2 00002356 00009c70 84000200
- // 000884f8 000023d3 00009da4 84000200
- // 0008a8cb 000022d6 00009cd4 84000200
- // 0008cba1 00002339 00009714 84000200
- // 0008eeda 000023dc 00009b24 84000200
- // 000912b6 00002481 00009eac 84000200
- // 00093737 00002444 0000a028 84000200
- // 00095b7b 00002440 00009fc4 84000200
- //
+ for(int i = 0; i < 10; i++)
+ {
+ if(!FileStream_Read(pStream, NULL, szString2, dwLength))
+ {
+ printf("Failed to read from the stream\n");
+ return -1;
+ }
+ szString2[dwLength] = 0;
+ if(strcmp(szString1, szString2))
+ {
+ printf("Data read from file are different from data written\n");
+ return -1;
+ }
+ }
FileStream_Close(pStream);
}
return 0;
}
*/
+