diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-01-11 14:55:08 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-01-11 14:55:08 +0100 |
commit | 3a926f0228c68d7d91cf3946624d7859976440ec (patch) | |
tree | c4e7d36dc8157576929988cdfcf5bfd8262cd09c /src/FileStream.cpp | |
parent | df4b0c085478389c9a21a09521d46735a0109c8a (diff) |
Initial creation
Diffstat (limited to 'src/FileStream.cpp')
-rw-r--r-- | src/FileStream.cpp | 2294 |
1 files changed, 2294 insertions, 0 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp new file mode 100644 index 0000000..1a21170 --- /dev/null +++ b/src/FileStream.cpp @@ -0,0 +1,2294 @@ +/*****************************************************************************/ +/* FileStream.cpp Copyright (c) Ladislav Zezula 2010 */ +/*---------------------------------------------------------------------------*/ +/* File stream support for StormLib */ +/* */ +/* Windows support: Written by Ladislav Zezula */ +/* Mac support: Written by Sam Wilkins */ +/* Linux support: Written by Sam Wilkins and Ivan Komissarov */ +/* Big-endian: Written & debugged by Sam Wilkins */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 11.06.10 1.00 Lad Derived from StormPortMac.cpp and StormPortLinux.cpp */ +/*****************************************************************************/ + +#define __STORMLIB_SELF__ +#include "StormLib.h" +#include "StormCommon.h" +#include "FileStream.h" + +#ifdef _MSC_VER +#pragma comment(lib, "wininet.lib") +#endif + +//----------------------------------------------------------------------------- +// Local defines + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE ((HANDLE)-1) +#endif + +#ifdef _MSC_VER +#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) +#endif + +//----------------------------------------------------------------------------- +// Local functions - platform-specific functions + +#ifndef PLATFORM_WINDOWS +static int nLastError = ERROR_SUCCESS; + +int GetLastError() +{ + return nLastError; +} + +void SetLastError(int nError) +{ + nLastError = nError; +} +#endif + +#ifndef PLATFORM_LITTLE_ENDIAN +void ConvertPartHeader(void * partHeader) +{ + PPART_FILE_HEADER theHeader = (PPART_FILE_HEADER)partHeader; + + theHeader->PartialVersion = SwapUInt32(theHeader->PartialVersion); + theHeader->Flags = SwapUInt32(theHeader->Flags); + theHeader->FileSizeLo = SwapUInt32(theHeader->FileSizeLo); + theHeader->FileSizeHi = SwapUInt32(theHeader->FileSizeHi); + theHeader->BlockSize = SwapUInt32(theHeader->BlockSize); +} +#endif + +//----------------------------------------------------------------------------- +// Preparing file bitmap for a complete file of a given size + +#define DEFAULT_BLOCK_SIZE 0x4000 + +static bool Dummy_GetBitmap( + TFileStream * pStream, + TFileBitmap * pBitmap, + DWORD Length, + LPDWORD LengthNeeded) +{ + 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)) + { + memset(pBitmap, 0, sizeof(TFileBitmap)); + pBitmap->EndOffset = FileSize; + pBitmap->IsComplete = 1; + pBitmap->BitmapSize = BitmapSize; + pBitmap->BlockSize = DEFAULT_BLOCK_SIZE; + bResult = true; + } + + // 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; +} + +//----------------------------------------------------------------------------- +// Local functions - base file support + +static bool BaseFile_Read( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file +{ + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + DWORD dwBytesRead = 0; // Must be set by platform-specific code + +#ifdef PLATFORM_WINDOWS + { + // 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 + + // Update the byte offset + pStream->Base.File.FilePos = ByteOffset; + + // Read the data + if(dwBytesToRead != 0) + { + 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; + } +/* + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) + { + LONG ByteOffsetHi = (LONG)(ByteOffset >> 32); + + SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN); + pStream->Base.File.FilePos = ByteOffset; + } + + // Read the data + if(dwBytesToRead != 0) + { + if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL)) + return false; + } +*/ + } +#endif + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + { + ssize_t bytes_read; + + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) + { + lseek((intptr_t)pStream->Base.File.hFile, (off_t)(ByteOffset), SEEK_SET); + pStream->Base.File.FilePos = ByteOffset; + } + + // 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 + + // 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); +} + +/** + * \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) +{ + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + DWORD dwBytesWritten = 0; // Must be set by platform-specific code + +#ifdef PLATFORM_WINDOWS + { + // 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 + + // Update the byte offset + pStream->Base.File.FilePos = ByteOffset; + + // Read the data + if(dwBytesToWrite != 0) + { + OVERLAPPED Overlapped; + + 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) + { + LONG ByteOffsetHi = (LONG)(ByteOffset >> 32); + + SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN); + pStream->Base.File.FilePos = ByteOffset; + } + + // Read the data + if(dwBytesToWrite != 0) + { + if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL)) + return false; + } +*/ + } +#endif + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + { + ssize_t bytes_written; + + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) + { + lseek((intptr_t)pStream->Base.File.hFile, (off_t)(ByteOffset), SEEK_SET); + pStream->Base.File.FilePos = ByteOffset; + } + + // 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 + + // 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 bool BaseFile_GetPos( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset) // Pointer to file byte offset +{ + *pByteOffset = pStream->Base.File.FilePos; + return true; +} + +static bool BaseFile_GetSize( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pFileSize) // Pointer where to store file size +{ + *pFileSize = pStream->Base.File.FileSize; + return true; +} + +/** + * \a pStream Pointer to an open stream + * \a NewFileSize New size of the file + */ +static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) +{ +#ifdef PLATFORM_WINDOWS + { + LONG FileSizeHi = (LONG)(NewFileSize >> 32); + LONG FileSizeLo; + DWORD dwNewPos; + bool bResult; + + // 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; + + // 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 + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + { + if(ftruncate((intptr_t)pStream->Base.File.hFile, (off_t)NewFileSize) == -1) + { + nLastError = errno; + return false; + } + + return true; + } +#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 + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + // "rename" on Linux also works if the target file exists + if(rename(pNewStream->szFileName, pStream->szFileName) == -1) + { + nLastError = errno; + return false; + } + + return true; +#endif +} + +static void BaseFile_Close(TFileStream * pStream) +{ + 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 BaseFile_Create( + TFileStream * pStream, + const TCHAR * szFileName, + DWORD dwStreamFlags) +{ +#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 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 + { + 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; + + // Query the file size + FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart); + pStream->Base.File.FileSize = FileSize.QuadPart; + + // Query last write time + GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime); + } +#endif + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + { + struct stat fileinfo; + int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR; + intptr_t handle; + + // Open the file + handle = open(szFileName, oflag); + if(handle == -1) + { + nLastError = errno; + return false; + } + + // Get the file size + if(fstat(handle, &fileinfo) == -1) + { + nLastError = errno; + return false; + } + + // time_t is number of seconds since 1.1.1970, UTC. + // 1 second = 10000000 (decimal) in FILETIME + // Set the start to 1.1.1970 00:00:00 + pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); + pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size; + pStream->Base.File.hFile = (HANDLE)handle; + } +#endif + + // Fill-in the entry points + pStream->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; +} + +//----------------------------------------------------------------------------- +// Local functions - base memory-mapped file support + +static bool BaseMap_Read( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file +{ + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos; + + // Do we have to read anything at all? + if(dwBytesToRead != 0) + { + // Don't allow reading past file size + if((ByteOffset + dwBytesToRead) > pStream->Base.Map.FileSize) + return false; + + // Copy the required data + memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead); + } + + // Move the current file position + pStream->Base.Map.FilePos += dwBytesToRead; + return true; +} + +static bool BaseMap_GetPos( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset) // Pointer to file byte offset +{ + *pByteOffset = pStream->Base.Map.FilePos; + return true; +} + +static bool BaseMap_GetSize( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pFileSize) // Pointer where to store file size +{ + *pFileSize = pStream->Base.Map.FileSize; + return true; +} + +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) + { + // Retrieve file size. Don't allow mapping file of a zero size. + FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart); + if(FileSize.QuadPart != 0) + { + // 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; + } + + // Close the map handle + CloseHandle(hMap); + } + } + + // 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 + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + struct stat fileinfo; + intptr_t handle; + bool bResult = false; + + // Open the file + handle = open(szFileName, O_RDONLY); + if(handle != -1) + { + // Get the file size + if(fstat(handle, &fileinfo) != -1) + { + 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); + } + + // Did the mapping fail? + if(bResult == false) + { + nLastError = errno; + return false; + } +#endif + + // 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) + { + while(szFileName[0] != 0 && szFileName[0] != _T('/')) + *szServerName++ = *szFileName++; + *szServerName = 0; + } + else + { + while(szFileName[0] != 0 && szFileName[0] != _T('/')) + *szFileName++; + } + + // Return the remainder + return szFileName; +} + +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) + { + // 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); + + // 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); + } + } + + // Increment the current file position by number of bytes read + 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 * pByteOffset) // Pointer to file byte offset +{ + *pByteOffset = pStream->Base.Http.FilePos; + return true; +} + +static bool BaseHttp_GetSize( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pFileSize) // Pointer where to store file size +{ + *pFileSize = pStream->Base.Http.FileSize; + return true; +} + +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; + + // Don't connect to the internet + if(!InternetGetConnectedState(&dwTemp, 0)) + nError = GetLastError(); + + // Initiate the connection to the internet + if(nError == ERROR_SUCCESS) + { + pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0); + if(pStream->Base.Http.hInternet == NULL) + nError = GetLastError(); + } + + // Connect to the server + if(nError == ERROR_SUCCESS) + { + TCHAR szServerName[MAX_PATH]; + DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; + + // Initiate connection with the server + szFileName = BaseHttp_ExtractServerName(szFileName, szServerName); + pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, + szServerName, + INTERNET_DEFAULT_HTTP_PORT, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + dwFlags, + 0); + if(pStream->Base.Http.hConnect == NULL) + nError = GetLastError(); + } + + // Now try to query the file size + if(nError == ERROR_SUCCESS) + { + // Open HTTP request to the file + hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); + if(hRequest != NULL) + { + if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) + { + ULONGLONG FileTime = 0; + DWORD dwFileSize = 0; + DWORD dwDataSize; + DWORD dwIndex = 0; + + // Check if the MPQ has Last Modified field + dwDataSize = sizeof(ULONGLONG); + if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) + pStream->Base.Http.FileTime = FileTime; + + // Verify if the server supports random access + dwDataSize = sizeof(DWORD); + if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) + { + if(dwFileSize != 0) + { + pStream->Base.Http.FileSize = dwFileSize; + pStream->Base.Http.FilePos = 0; + bFileAvailable = true; + } + } + } + InternetCloseHandle(hRequest); + } + } + + // If the file is not there and is not available for random access, + // report error + if(bFileAvailable == false) + { + BaseHttp_Close(pStream); + return false; + } + + // 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 +} + +//----------------------------------------------------------------------------- +// 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 +{ + 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) + { + DWORD BlockSize = pStream->pBitmap->BlockSize; + + // Get the offset where we read it from + if(pByteOffset == NULL) + pStream->BaseGetPos(pStream, &ByteOffset); + else + ByteOffset = *pByteOffset; + EndOffset = ByteOffset + dwBytesToRead; + + // 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); + + // Calculate the initial block index + BlockIndex = (DWORD)(ByteOffset / BlockSize); + pbBitmap = (LPBYTE)(pStream->pBitmap + 1); + + // 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++; + } + } + } + + // 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)) + { + // Enough space for complete bitmap? + if(Length >= TotalLength) + { + memcpy(pBitmap, pStream->pBitmap, TotalLength); + bResult = true; + } + else + { + memcpy(pBitmap, pStream->pBitmap, sizeof(TFileBitmap)); + bResult = true; + } + } + + return bResult; +} + +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); +} + +static bool LinearStream_Open(TLinearStream * pStream) +{ + // 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; +} + +//----------------------------------------------------------------------------- +// 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; + DWORD dwBytesRemaining = dwBytesToRead; + DWORD dwPartOffset; + DWORD dwPartIndex; + DWORD dwBytesRead = 0; + DWORD dwBlockSize = pStream->BlockSize; + bool bResult = false; + int nFailReason = ERROR_HANDLE_EOF; // Why it failed if not enough bytes was read + + // If the byte offset is not entered, use the current position + if(pByteOffset == NULL) + pByteOffset = &pStream->VirtualPos; + + // Check if the file position is not at or beyond end of the file + if(*pByteOffset >= pStream->VirtualSize) + { + SetLastError(ERROR_HANDLE_EOF); + return false; + } + + // Get the part index where the read offset is + // Note that the part index should now be within the range, + // as read requests beyond-EOF are handled by the previous test + dwPartIndex = (DWORD)(*pByteOffset / pStream->BlockSize); + assert(dwPartIndex < pStream->BlockCount); + + // If the number of bytes remaining goes past + // the end of the file, cut them + if((*pByteOffset + dwBytesRemaining) > pStream->VirtualSize) + dwBytesRemaining = (DWORD)(pStream->VirtualSize - *pByteOffset); + + // Calculate the offset in the current part + dwPartOffset = (DWORD)(*pByteOffset) & (pStream->BlockSize - 1); + + // Read all data, one part at a time + while(dwBytesRemaining != 0) + { + PPART_FILE_MAP_ENTRY PartMap = pStream->PartMap + dwPartIndex; + DWORD dwBytesInPart; + + // If the part is not present in the file, we fail the read + if((PartMap->Flags & 3) == 0) + { + nFailReason = ERROR_FILE_CORRUPT; + bResult = false; + break; + } + + // If we are in the last part, we have to cut the number of bytes in the last part + if(dwPartIndex == pStream->BlockCount - 1) + dwBlockSize = (DWORD)pStream->VirtualSize & (pStream->BlockSize - 1); + + // Get the number of bytes reamining in the current part + dwBytesInPart = dwBlockSize - dwPartOffset; + + // Compute the raw file offset of the file part + RawByteOffset = MAKE_OFFSET64(PartMap->BlockOffsHi, PartMap->BlockOffsLo); + if(RawByteOffset == 0) + { + nFailReason = ERROR_FILE_CORRUPT; + bResult = false; + break; + } + + // If the number of bytes in part is too big, cut it + if(dwBytesInPart > dwBytesRemaining) + dwBytesInPart = dwBytesRemaining; + + // Append the offset within the part + RawByteOffset += dwPartOffset; + if(!pStream->BaseRead(pStream, &RawByteOffset, pbBuffer, dwBytesInPart)) + { + nFailReason = ERROR_FILE_CORRUPT; + bResult = false; + break; + } + + // Increment the file position + dwBytesRemaining -= dwBytesInPart; + dwBytesRead += dwBytesInPart; + pbBuffer += dwBytesInPart; + + // Move to the next file part + dwPartOffset = 0; + dwPartIndex++; + } + + // Move the file position by the number of bytes read + pStream->VirtualPos = *pByteOffset + dwBytesRead; + if(dwBytesRead != dwBytesToRead) + SetLastError(nFailReason); + return (dwBytesRead == dwBytesToRead); +} + +static bool PartialStream_GetPos( + TPartialStream * pStream, + ULONGLONG & ByteOffset) +{ + ByteOffset = pStream->VirtualPos; + return true; +} + +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 PartialStream_GetBitmap( + TPartialStream * pStream, + TFileBitmap * pBitmap, + DWORD Length, + LPDWORD LengthNeeded) +{ + LPBYTE pbBitmap; + DWORD TotalLength; + DWORD BitmapSize = 0; + DWORD ByteOffset; + DWORD BitMask; + bool bResult = 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; +} + +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 + +static const char * szKeyTemplate = "expand 32-byte k000000000000000000000000000000000000000000000000"; + +static const char * AuthCodeArray[] = +{ + // Diablo III: Agent.exe (1.0.0.954) + // Address of decryption routine: 00502b00 + // Pointer to decryptor object: ECX + // Pointer to key: ECX+0x5C + // Authentication code URL: http://dist.blizzard.com/mediakey/d3-authenticationcode-enGB.txt + // -0C- -1C--08- -18--04- -14--00- -10- + "UCMXF6EJY352EFH4XFRXCFH2XC9MQRZK", // Diablo III Installer (deDE): "expand 32-byte kEFH40000QRZKY3520000XC9MF6EJ0000CFH2UCMX0000XFRX" + "MMKVHY48RP7WXP4GHYBQ7SL9J9UNPHBP", // Diablo III Installer (enGB): "expand 32-byte kXP4G0000PHBPRP7W0000J9UNHY4800007SL9MMKV0000HYBQ" + "8MXLWHQ7VGGLTZ9MQZQSFDCLJYET3CPP", // Diablo III Installer (enSG): "expand 32-byte kTZ9M00003CPPVGGL0000JYETWHQ70000FDCL8MXL0000QZQS" + "EJ2R5TM6XFE2GUNG5QDGHKQ9UAKPWZSZ", // Diablo III Installer (enUS): "expand 32-byte kGUNG0000WZSZXFE20000UAKP5TM60000HKQ9EJ2R00005QDG" + "PBGFBE42Z6LNK65UGJQ3WZVMCLP4HQQT", // Diablo III Installer (esES): "expand 32-byte kK65U0000HQQTZ6LN0000CLP4BE420000WZVMPBGF0000GJQ3" + "X7SEJJS9TSGCW5P28EBSC47AJPEY8VU2", // Diablo III Installer (esMX): "expand 32-byte kW5P200008VU2TSGC0000JPEYJJS90000C47AX7SE00008EBS" + "5KVBQA8VYE6XRY3DLGC5ZDE4XS4P7YA2", // Diablo III Installer (frFR): "expand 32-byte kRY3D00007YA2YE6X0000XS4PQA8V0000ZDE45KVB0000LGC5" + "478JD2K56EVNVVY4XX8TDWYT5B8KB254", // Diablo III Installer (itIT): "expand 32-byte kVVY40000B2546EVN00005B8KD2K50000DWYT478J0000XX8T" + "8TS4VNFQRZTN6YWHE9CHVDH9NVWD474A", // Diablo III Installer (koKR): "expand 32-byte k6YWH0000474ARZTN0000NVWDVNFQ0000VDH98TS40000E9CH" + "LJ52Z32DF4LZ4ZJJXVKK3AZQA6GABLJB", // Diablo III Installer (plPL): "expand 32-byte k4ZJJ0000BLJBF4LZ0000A6GAZ32D00003AZQLJ520000XVKK" + "K6BDHY2ECUE2545YKNLBJPVYWHE7XYAG", // Diablo III Installer (ptBR): "expand 32-byte k545Y0000XYAGCUE20000WHE7HY2E0000JPVYK6BD0000KNLB" + "NDVW8GWLAYCRPGRNY8RT7ZZUQU63VLPR", // Diablo III Installer (ruRU): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + "6VWCQTN8V3ZZMRUCZXV8A8CGUX2TAA8H", // Diablo III Installer (zhTW): "expand 32-byte kMRUC0000AA8HV3ZZ0000UX2TQTN80000A8CG6VWC0000ZXV8" +// "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Diablo III Installer (zhCN): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + + // Note: Starcraft II (Wings of Liberty): Installer.exe (4.1.1.4219) + // Address of decryption routine: 0053A3D0 + // Pointer to decryptor object: ECX + // Pointer to key: ECX+0x5C + // Authentication code URL: http://dist.blizzard.com/mediakey/sc2-authenticationcode-enUS.txt + // -0C- -1C--08- -18--04- -14--00- -10- + "Y45MD3CAK4KXSSXHYD9VY64Z8EKJ4XFX", // SC2 Wings of Liberty (deDE): "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V" + "G8MN8UDG6NA2ANGY6A3DNY82HRGF29ZH", // SC2 Wings of Liberty (enGB): "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D" + "W9RRHLB2FDU9WW5B3ECEBLRSFWZSF7HW", // SC2 Wings of Liberty (enSG): "expand 32-byte kWW5B0000F7HWFDU90000FWZSHLB20000BLRSW9RR00003ECE" + "3DH5RE5NVM5GTFD85LXGWT6FK859ETR5", // SC2 Wings of Liberty (enUS): "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG" + "8WLKUAXE94PFQU4Y249PAZ24N4R4XKTQ", // SC2 Wings of Liberty (esES): "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P" + "A34DXX3VHGGXSQBRFE5UFFDXMF9G4G54", // SC2 Wings of Liberty (esMX): "expand 32-byte kSQBR00004G54HGGX0000MF9GXX3V0000FFDXA34D0000FE5U" + "ZG7J9K938HJEFWPQUA768MA2PFER6EAJ", // SC2 Wings of Liberty (frFR): "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76" + "NE7CUNNNTVAPXV7E3G2BSVBWGVMW8BL2", // SC2 Wings of Liberty (itIT): "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B" + "3V9E2FTMBM9QQWK7U6MAMWAZWQDB838F", // SC2 Wings of Liberty (koKR): "expand 32-byte kQWK70000838FBM9Q0000WQDB2FTM0000MWAZ3V9E0000U6MA" + "2NSFB8MELULJ83U6YHA3UP6K4MQD48L6", // SC2 Wings of Liberty (plPL): "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3" + "QA2TZ9EWZ4CUU8BMB5WXCTY65F9CSW4E", // SC2 Wings of Liberty (ptBR): "expand 32-byte kU8BM0000SW4EZ4CU00005F9CZ9EW0000CTY6QA2T0000B5WX" + "VHB378W64BAT9SH7D68VV9NLQDK9YEGT", // SC2 Wings of Liberty (ruRU): "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V" + "U3NFQJV4M6GC7KBN9XQJ3BRDN3PLD9NE", // SC2 Wings of Liberty (zhTW): "expand 32-byte k7KBN0000D9NEM6GC0000N3PLQJV400003BRDU3NF00009XQJ" + + NULL +}; + +static DWORD Rol32(DWORD dwValue, DWORD dwRolCount) +{ + DWORD dwShiftRight = 32 - dwRolCount; + + return (dwValue << dwRolCount) | (dwValue >> dwShiftRight); +} + +static void CreateKeyFromAuthCode( + LPBYTE pbKeyBuffer, + const char * szAuthCode) +{ + LPDWORD KeyPosition = (LPDWORD)(pbKeyBuffer + 0x10); + LPDWORD AuthCode32 = (LPDWORD)szAuthCode; + + memcpy(pbKeyBuffer, szKeyTemplate, MPQE_CHUNK_SIZE); + KeyPosition[0x00] = AuthCode32[0x03]; + KeyPosition[0x02] = AuthCode32[0x07]; + KeyPosition[0x03] = AuthCode32[0x02]; + KeyPosition[0x05] = AuthCode32[0x06]; + KeyPosition[0x06] = AuthCode32[0x01]; + KeyPosition[0x08] = AuthCode32[0x05]; + KeyPosition[0x09] = AuthCode32[0x00]; + KeyPosition[0x0B] = AuthCode32[0x04]; + BSWAP_ARRAY32_UNSIGNED(pbKeyBuffer, MPQE_CHUNK_SIZE); +} + +static void DecryptFileChunk( + DWORD * MpqData, + LPBYTE pbKey, + ULONGLONG ByteOffset, + DWORD dwLength) +{ + ULONGLONG ChunkOffset; + DWORD KeyShuffled[0x10]; + DWORD KeyMirror[0x10]; + DWORD RoundCount = 0x14; + + // Prepare the key + ChunkOffset = ByteOffset / MPQE_CHUNK_SIZE; + memcpy(KeyMirror, pbKey, MPQE_CHUNK_SIZE); + BSWAP_ARRAY32_UNSIGNED(KeyMirror, MPQE_CHUNK_SIZE); + KeyMirror[0x05] = (DWORD)(ChunkOffset >> 32); + KeyMirror[0x08] = (DWORD)(ChunkOffset); + + while(dwLength >= MPQE_CHUNK_SIZE) + { + // Shuffle the key - part 1 + KeyShuffled[0x0E] = KeyMirror[0x00]; + KeyShuffled[0x0C] = KeyMirror[0x01]; + KeyShuffled[0x05] = KeyMirror[0x02]; + KeyShuffled[0x0F] = KeyMirror[0x03]; + KeyShuffled[0x0A] = KeyMirror[0x04]; + KeyShuffled[0x07] = KeyMirror[0x05]; + KeyShuffled[0x0B] = KeyMirror[0x06]; + KeyShuffled[0x09] = KeyMirror[0x07]; + KeyShuffled[0x03] = KeyMirror[0x08]; + KeyShuffled[0x06] = KeyMirror[0x09]; + KeyShuffled[0x08] = KeyMirror[0x0A]; + KeyShuffled[0x0D] = KeyMirror[0x0B]; + KeyShuffled[0x02] = KeyMirror[0x0C]; + KeyShuffled[0x04] = KeyMirror[0x0D]; + KeyShuffled[0x01] = KeyMirror[0x0E]; + KeyShuffled[0x00] = KeyMirror[0x0F]; + + // Shuffle the key - part 2 + for(DWORD i = 0; i < RoundCount; i += 2) + { + KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x02]), 0x07); + KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0E]), 0x09); + KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x0A]), 0x0D); + KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x03]), 0x12); + + KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x04]), 0x07); + KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x0C]), 0x09); + KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x07]), 0x0D); + KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x06]), 0x12); + + KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x01]), 0x07); + KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x05]), 0x09); + KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x0B]), 0x0D); + KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x08]), 0x12); + + KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x00]), 0x07); + KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x0F]), 0x09); + KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x09]), 0x0D); + KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x0D]), 0x12); + + KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x09]), 0x07); + KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x0E]), 0x09); + KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x04]), 0x0D); + KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x08]), 0x12); + + KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x0A]), 0x07); + KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x0C]), 0x09); + KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x01]), 0x0D); + KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0D]), 0x12); + + KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x07]), 0x07); + KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x05]), 0x09); + KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x00]), 0x0D); + KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x03]), 0x12); + + KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x0B]), 0x07); + KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x0F]), 0x09); + KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x02]), 0x0D); + KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x06]), 0x12); + } + + // Decrypt one data chunk + BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE); + MpqData[0x00] = MpqData[0x00] ^ (KeyShuffled[0x0E] + KeyMirror[0x00]); + MpqData[0x01] = MpqData[0x01] ^ (KeyShuffled[0x04] + KeyMirror[0x0D]); + MpqData[0x02] = MpqData[0x02] ^ (KeyShuffled[0x08] + KeyMirror[0x0A]); + MpqData[0x03] = MpqData[0x03] ^ (KeyShuffled[0x09] + KeyMirror[0x07]); + MpqData[0x04] = MpqData[0x04] ^ (KeyShuffled[0x0A] + KeyMirror[0x04]); + MpqData[0x05] = MpqData[0x05] ^ (KeyShuffled[0x0C] + KeyMirror[0x01]); + MpqData[0x06] = MpqData[0x06] ^ (KeyShuffled[0x01] + KeyMirror[0x0E]); + MpqData[0x07] = MpqData[0x07] ^ (KeyShuffled[0x0D] + KeyMirror[0x0B]); + MpqData[0x08] = MpqData[0x08] ^ (KeyShuffled[0x03] + KeyMirror[0x08]); + MpqData[0x09] = MpqData[0x09] ^ (KeyShuffled[0x07] + KeyMirror[0x05]); + MpqData[0x0A] = MpqData[0x0A] ^ (KeyShuffled[0x05] + KeyMirror[0x02]); + MpqData[0x0B] = MpqData[0x0B] ^ (KeyShuffled[0x00] + KeyMirror[0x0F]); + MpqData[0x0C] = MpqData[0x0C] ^ (KeyShuffled[0x02] + KeyMirror[0x0C]); + MpqData[0x0D] = MpqData[0x0D] ^ (KeyShuffled[0x06] + KeyMirror[0x09]); + MpqData[0x0E] = MpqData[0x0E] ^ (KeyShuffled[0x0B] + KeyMirror[0x06]); + MpqData[0x0F] = MpqData[0x0F] ^ (KeyShuffled[0x0F] + KeyMirror[0x03]); + BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE); + + // Update byte offset in the key + KeyMirror[0x08]++; + if(KeyMirror[0x08] == 0) + KeyMirror[0x05]++; + + // Move pointers and decrease number of bytes to decrypt + MpqData += (MPQE_CHUNK_SIZE / sizeof(DWORD)); + dwLength -= MPQE_CHUNK_SIZE; + } +} + +static bool DetectFileKey(LPBYTE pbKeyBuffer, LPBYTE pbEncryptedHeader) +{ + ULONGLONG ByteOffset = 0; + BYTE FileHeader[MPQE_CHUNK_SIZE]; + + // We just try all known keys one by one + for(int i = 0; AuthCodeArray[i] != NULL; i++) + { + // Prepare they decryption key from game serial number + CreateKeyFromAuthCode(pbKeyBuffer, AuthCodeArray[i]); + + // Try to decrypt with the given key + memcpy(FileHeader, pbEncryptedHeader, MPQE_CHUNK_SIZE); + DecryptFileChunk((LPDWORD)FileHeader, pbKeyBuffer, ByteOffset, MPQE_CHUNK_SIZE); + + // We check the decrypted data + // All known encrypted MPQs have header at the begin of the file, + // so we check for MPQ signature there. + if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q') + return true; + } + + // Key not found, sorry + return false; +} + +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 + DWORD dwBytesToRead) // Number of bytes to read from the file +{ + ULONGLONG StartOffset; // Offset of the first byte to be read from the file + ULONGLONG ByteOffset; // Offset that the caller wants + ULONGLONG EndOffset; // End offset that is to be read from the file + DWORD dwBytesToAllocate; + DWORD dwBytesToDecrypt; + DWORD dwOffsetInCache; + LPBYTE pbMpqData = NULL; + bool bResult = false; + + // Get the byte offset + if(pByteOffset == NULL) + pStream->BaseGetPos(pStream, &ByteOffset); + else + ByteOffset = *pByteOffset; + + // Cut it down to MPQE chunk size + StartOffset = ByteOffset; + StartOffset = StartOffset & ~(MPQE_CHUNK_SIZE - 1); + EndOffset = ByteOffset + dwBytesToRead; + + // Calculate number of bytes to decrypt + dwBytesToDecrypt = (DWORD)(EndOffset - StartOffset); + dwBytesToAllocate = (dwBytesToDecrypt + (MPQE_CHUNK_SIZE - 1)) & ~(MPQE_CHUNK_SIZE - 1); + + // Allocate buffers for encrypted and decrypted data + pbMpqData = STORM_ALLOC(BYTE, dwBytesToAllocate); + if(pbMpqData) + { + // Get the offset of the desired data in the cache + dwOffsetInCache = (DWORD)(ByteOffset - StartOffset); + + // Read the file from the stream as-is + if(pStream->BaseRead(pStream, &StartOffset, pbMpqData, dwBytesToDecrypt)) + { + // Decrypt the data + DecryptFileChunk((LPDWORD)pbMpqData, pStream->Key, StartOffset, dwBytesToAllocate); + + // Copy the decrypted data + memcpy(pvBuffer, pbMpqData + dwOffsetInCache, dwBytesToRead); + bResult = true; + } + else + { + assert(false); + } + + // Free decryption buffer + STORM_FREE(pbMpqData); + } + + // Free buffers and exit + return bResult; +} + +static bool EncryptedStream_Open(TEncryptedStream * pStream) +{ + ULONGLONG ByteOffset = 0; + BYTE EncryptedHeader[MPQE_CHUNK_SIZE]; + + // Sanity check + assert(pStream->BaseRead != NULL); + + // Load one MPQE chunk and try to detect the file key + if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader))) + { + // Attempt to decrypt the MPQ header with all known keys + if(DetectFileKey(pStream->Key, EncryptedHeader)) + { + // Assign functions + pStream->StreamRead = (STREAM_READ)EncryptedStream_Read; + pStream->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; + } + + // An unknown key + SetLastError(ERROR_UNKNOWN_FILE_KEY); + } + return false; +} + +//----------------------------------------------------------------------------- +// Public functions + +/** + * 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 + * can open the file for read, but not for write) + * - If the file does not exist, the function must create new one + * - If the file exists, the function must rewrite it and set to zero size + * - The parameters of the function must be validate by the caller + * - 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 create + */ + +TFileStream * FileStream_CreateFile( + const TCHAR * szFileName, + DWORD dwStreamFlags) +{ + TFileStream * pStream; + + // We only support creation of linear, local file + if((dwStreamFlags & (STREAM_PROVIDER_MASK | BASE_PROVIDER_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE)) + { + 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)) + { + // 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 + return pStream; +} + +/** + * This function opens an existing file for read or read-write access + * - If the current platform supports file sharing, + * the file must be open for read sharing (i.e. another application + * can open the file for read, but not for write) + * - If the file does not exist, the function must return NULL + * - If the file exists but cannot be open, then function must return NULL + * - The parameters of the function must be validate by the caller + * - The function must check if the file is a PART file, + * and create TPartialStream object if so. + * - The function must initialize all stream function pointers in TFileStream + * - If the function fails from any reason, it must close all handles + * and free all memory that has been allocated in the process of stream creation, + * including the TFileStream structure itself + * + * \a szFileName Name of the file to open + * \a dwStreamFlags specifies the provider and base storage type + */ + +TFileStream * FileStream_OpenFile( + const TCHAR * szFileName, + DWORD dwStreamFlags) +{ + TFileStream * pStream = NULL; + size_t StreamSize = 0; + bool bStreamResult = false; + bool bBaseResult = false; + + // Allocate file stream for each stream provider + switch(dwStreamFlags & STREAM_PROVIDER_MASK) + { + case STREAM_PROVIDER_LINEAR: // Allocate structure for linear stream + StreamSize = sizeof(TLinearStream); + break; + + case STREAM_PROVIDER_PARTIAL: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + StreamSize = sizeof(TPartialStream); + break; + + case STREAM_PROVIDER_ENCRYPTED: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + StreamSize = sizeof(TEncryptedStream); + break; + + default: + return NULL; + } + + // Allocate the stream for each type + pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize); + if(pStream == NULL) + return NULL; + + // Fill the stream structure with zeros + memset(pStream, 0, StreamSize); + _tcscpy(pStream->szFileName, szFileName); + + // Now initialize the respective base provider + switch(dwStreamFlags & BASE_PROVIDER_MASK) + { + case BASE_PROVIDER_FILE: + bBaseResult = BaseFile_Open(pStream, szFileName, dwStreamFlags); + break; + + case BASE_PROVIDER_MAP: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + bBaseResult = BaseMap_Open(pStream, szFileName, dwStreamFlags); + break; + + case BASE_PROVIDER_HTTP: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + bBaseResult = BaseHttp_Open(pStream, szFileName, dwStreamFlags); + break; + } + + // If we failed to open the base storage, fail the operation + if(bBaseResult == false) + { + STORM_FREE(pStream); + return NULL; + } + + // Now initialize the stream provider + switch(dwStreamFlags & STREAM_PROVIDER_MASK) + { + case STREAM_PROVIDER_LINEAR: + bStreamResult = LinearStream_Open((TLinearStream *)pStream); + break; + + case STREAM_PROVIDER_PARTIAL: + bStreamResult = PartialStream_Open((TPartialStream *)pStream); + break; + + case STREAM_PROVIDER_ENCRYPTED: + bStreamResult = EncryptedStream_Open((TEncryptedStream *)pStream); + break; + } + + // If the operation failed, free the stream and set it to NULL + if(bStreamResult == false) + { + // Only close the base stream + pStream->BaseClose(pStream); + STORM_FREE(pStream); + pStream = NULL; + } + + return pStream; +} + +/** + * Reads data from the stream + * + * - Returns true if the read operation succeeded and all bytes have been read + * - Returns false if either read failed or not all bytes have been read + * - If the pByteOffset is NULL, the function must read the data from the current file position + * - The function can be called with dwBytesToRead = 0. In that case, pvBuffer is ignored + * and the function just adjusts file pointer. + * + * \a pStream Pointer to an open stream + * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position + * \a pvBuffer Pointer to data to be read + * \a dwBytesToRead Number of bytes to read from the file + * + * \returns + * - If the function reads the required amount of bytes, it returns true. + * - If the function reads less than required bytes, it returns false and GetLastError() returns ERROR_HANDLE_EOF + * - If the function fails, it reads false and GetLastError() returns an error code different from ERROR_HANDLE_EOF + */ +bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead) +{ + assert(pStream->StreamRead != NULL); + return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); +} + +/** + * This function writes data to the stream + * + * - Returns true if the write operation succeeded and all bytes have been written + * - Returns false if either write failed or not all bytes have been written + * - If the pByteOffset is NULL, the function must write the data to the current file position + * + * \a pStream Pointer to an open stream + * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position + * \a pvBuffer Pointer to data to be written + * \a dwBytesToWrite Number of bytes to write to the file + */ +bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) +{ + if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) + return false; + + assert(pStream->StreamWrite != NULL); + return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite); +} + +/** + * This function returns the current file position + * \a pStream + * \a ByteOffset + */ +bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) +{ + assert(pStream->StreamGetPos != NULL); + return pStream->StreamGetPos(pStream, pByteOffset); +} + +/** + * Returns the size of a file + * + * \a pStream Pointer to an open stream + * \a FileSize Pointer where to store the file size + */ +bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) +{ + assert(pStream->StreamGetSize != NULL); + return pStream->StreamGetSize(pStream, pFileSize); +} + +/** + * Sets the size of a file + * + * \a pStream Pointer to an open stream + * \a NewFileSize File size to set + */ +bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) +{ + if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) + return false; + + 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); +} + +/** + * Returns the stream flags + * + * \a pStream Pointer to an open stream + * \a pdwStreamFlags Pointer where to store the stream flags + */ +bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags) +{ + *pdwStreamFlags = pStream->dwFlags; + return true; +} + +/** + * Switches a stream with another. Used for final phase of archive compacting. + * Performs these steps: + * + * 1) Closes the handle to the existing MPQ + * 2) Renames the temporary MPQ to the original MPQ, overwrites existing one + * 3) Opens the MPQ stores the handle and stream position to the new stream structure + * + * \a pStream Pointer to an open stream + * \a pTempStream Temporary ("working") stream (created during archive compacting) + */ +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) +{ + assert(pStream != NULL); + return pStream->szFileName; +} + +/** + * 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 + */ + +bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap) +{ + TLinearStream * pLinearStream; + + // It must be a linear stream. + if((pStream->dwFlags & STREAM_PROVIDER_MASK) != STREAM_PROVIDER_LINEAR) + return false; + pLinearStream = (TLinearStream *)pStream; + + // Two bitmaps are not allowed + if(pLinearStream->pBitmap != NULL) + return false; + + // We need to change some entry points + pLinearStream->StreamRead = (STREAM_READ)LinearStream_Read; + pLinearStream->StreamGetBmp = (STREAM_GETBMP)LinearStream_GetBitmap; + + // 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 + * can be NULL, if there was an allocation failure during the process + * + * \a pStream Pointer to an open stream + */ +void FileStream_Close(TFileStream * pStream) +{ + // Check if the stream structure is allocated at all + if(pStream != NULL) + { + // 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); + } +} + +//----------------------------------------------------------------------------- +// main - for testing purposes + +#ifdef __STORMLIB_TEST__ +int FileStream_Test(const TCHAR * szFileName, DWORD dwStreamFlags) +{ + TFileStream * pStream; + TMPQHeader MpqHeader; + ULONGLONG FilePos; + TMPQBlock * pBlock; + TMPQHash * pHash; + + InitializeMpqCryptography(); + + 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) + { + 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); + } + + // Read the block table + pBlock = STORM_ALLOC(TMPQBlock, MpqHeader.dwBlockTableSize); + if(pBlock != NULL) + { + 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(); + + // + // Test 1: Write to a stream + // + + pStream = FileStream_CreateFile("E:\\Stream.bin", 0); + if(pStream != NULL) + { + char szString1[100] = "This is a single line\n\r"; + DWORD dwLength = strlen(szString1); + + 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); + } + + // + // Test2: Read from the stream + // + + pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + if(pStream != NULL) + { + 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("Write succeeded while it should fail\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; + } + + szString2[dwLength] = 0; + if(strcmp(szString1, szString2)) + { + printf("Data read from file are different from data written\n"); + return -1; + } + } + FileStream_Close(pStream); + } + + // + // Test3: Open the temp stream, write some data and switch it to the original stream + // + + pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + if(pStream != NULL) + { + TFileStream * pTempStream; + ULONGLONG FileSize; + + pTempStream = FileStream_CreateFile("E:\\TempStream.bin", 0); + 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 != 0) + { + DWORD dwBytesToRead = (DWORD)FileSize; + char Buffer[0x80]; + + if(dwBytesToRead > sizeof(Buffer)) + dwBytesToRead = sizeof(Buffer); + + if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead)) + { + printf("CopyStream: Read source file failed\n"); + return -1; + } + + if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead)) + { + printf("CopyStream: Write target file failed\n"); + return -1; + } + + FileSize -= dwBytesToRead; + } + + // Switch the streams + // Note that the pTempStream is closed by the operation + FileStream_Switch(pStream, pTempStream); + FileStream_Close(pStream); + } + + // + // Test4: Read from the stream again + // + + pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + if(pStream != NULL) + { + char szString1[100] = "This is a single line\n\r"; + char szString2[100]; + DWORD dwLength = strlen(szString1); + + 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; +} +*/ + |