diff options
author | Shauren <shauren.trinity@gmail.com> | 2012-07-05 14:16:44 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2012-07-05 14:16:44 +0200 |
commit | 32ba32c4ebe56ba931c8638460c24cd57ae29a75 (patch) | |
tree | 2f757648b85ec8989a7dfc573b954b9b4d718650 /dep/StormLib/src/FileStream.cpp | |
parent | c95905ddbb22e2b5b5362b790aa851ef10d4e27e (diff) | |
parent | ed6f3e2deff55f913f9646db5f540b7704088478 (diff) |
Merge branch '4.x' of github.com:TrinityCore/TrinityCore into 4.3.4
Diffstat (limited to 'dep/StormLib/src/FileStream.cpp')
-rw-r--r-- | dep/StormLib/src/FileStream.cpp | 2525 |
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; } */ + |