aboutsummaryrefslogtreecommitdiff
path: root/dep/StormLib/src/FileStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/StormLib/src/FileStream.cpp')
-rw-r--r--dep/StormLib/src/FileStream.cpp1861
1 files changed, 1861 insertions, 0 deletions
diff --git a/dep/StormLib/src/FileStream.cpp b/dep/StormLib/src/FileStream.cpp
new file mode 100644
index 00000000000..b8de102cd2e
--- /dev/null
+++ b/dep/StormLib/src/FileStream.cpp
@@ -0,0 +1,1861 @@
+/*****************************************************************************/
+/* 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"
+
+//-----------------------------------------------------------------------------
+// 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 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
+
+#ifndef PLATFORM_WINDOWS
+static int nLastError = ERROR_SUCCESS;
+
+int GetLastError()
+{
+ return nLastError;
+}
+
+void SetLastError(int nError)
+{
+ nLastError = 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->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;
+
+ *pFT = intTime;
+}
+
+static OSErr FSOpenDFCompat(FSRef *ref, char permission, short *refNum)
+{
+ HFSUniStr255 forkName;
+ OSErr theErr;
+ Boolean isFolder, wasChanged;
+
+ theErr = FSResolveAliasFile(ref, true, &isFolder, &wasChanged);
+ if (theErr != noErr)
+ {
+ return theErr;
+ }
+
+ 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);
+}
+#endif
+
+static HANDLE CreateNewFile(
+ const TCHAR * szFileName) // Name of the file to open
+{
+ HANDLE hFile = INVALID_HANDLE_VALUE; // Pre-set the file handle to INVALID_HANDLE_VALUE
+
+#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
+
+#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)
+ {
+ nLastError = theErr;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ // 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)
+ {
+ nLastError = theErr;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ theErr = FSOpenDFCompat(&theFileRef, fsRdWrPerm, &fileRef);
+ if(theErr != noErr)
+ {
+ nLastError = theErr;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ hFile = (HANDLE)(int)fileRef;
+ }
+#endif
+
+#ifdef 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 INVALID_HANDLE_VALUE;
+ }
+
+ hFile = (HANDLE)handle;
+ }
+#endif
+
+ // Return the file handle
+ return hFile;
+}
+
+static HANDLE OpenExistingFile(
+ const TCHAR * szFileName, // Name of the file to open
+ bool bWriteAccess) // false = read-only, true = read/write
+{
+ HANDLE hFile = INVALID_HANDLE_VALUE; // Pre-set the file handle to INVALID_HANDLE_VALUE
+
+#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
+
+#ifdef PLATFORM_MAC
+ {
+ FSRef theFileRef;
+ OSErr theErr;
+ short fileRef;
+ char permission = bWriteAccess ? fsRdWrPerm : fsRdPerm;
+
+ theErr = FSPathMakeRef((const UInt8 *)szFileName, &theFileRef, NULL);
+ if(theErr != noErr)
+ {
+ nLastError = theErr;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ theErr = FSOpenDFCompat(&theFileRef, permission, &fileRef);
+ if (theErr != noErr)
+ {
+ nLastError = theErr;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ hFile = (HANDLE)(int)fileRef;
+ }
+#endif
+
+#ifdef PLATFORM_LINUX
+ {
+ int oflag = bWriteAccess ? O_RDWR : O_RDONLY;
+ intptr_t handle;
+
+ handle = open(szFileName, oflag | O_LARGEFILE);
+ if(handle == -1)
+ {
+ nLastError = errno;
+ return INVALID_HANDLE_VALUE;
+ }
+
+ hFile = (HANDLE)handle;
+ }
+#endif
+
+ // Return the file handle
+ return hFile;
+}
+
+static void CloseTheFile(HANDLE hFile)
+{
+#ifdef PLATFORM_WINDOWS
+ CloseHandle(hFile);
+#endif
+
+#ifdef PLATFORM_MAC
+ FSCloseFork((short)(long)hFile);
+#endif
+
+#ifdef PLATFORM_LINUX
+ close((intptr_t)hFile);
+#endif
+}
+
+/**
+ * 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
+ */
+static bool RenameFile(const TCHAR * szExistingFile, const TCHAR * szNewFile)
+{
+#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);
+
+ // Rename the new file to the old stream's file
+ return (bool)MoveFile(szExistingFile, szNewFile);
+#endif
+
+#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;
+ }
+
+ // 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)
+ {
+ CFRelease(newFileNameCFString);
+ nLastError = theErr;
+ return false;
+ }
+
+ CFRelease(newFileNameCFString);
+
+ return true;
+#endif
+
+#ifdef PLATFORM_LINUX
+ // "rename" on Linux also works if the target file exists
+ if(rename(szExistingFile, szNewFile) == -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
+{
+ ByteOffset = pStream->RawFilePos;
+ return true;
+}
+
+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
+{
+ DWORD dwBytesRead = 0; // Must be set by platform-specific code
+
+ // If the byte offset is not entered, use the current position
+ if(pByteOffset == NULL)
+ pByteOffset = &pStream->RawFilePos;
+
+#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);
+
+ SetFilePointer(pStream->hFile, ByteOffsetLo, &ByteOffsetHi, FILE_BEGIN);
+ pStream->RawFilePos = *pByteOffset;
+ }
+
+ // Read the data
+ if(dwBytesToRead != 0)
+ {
+ if(!ReadFile(pStream->hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL))
+ return false;
+ }
+ }
+#endif
+
+#ifdef PLATFORM_MAC
+ {
+ ByteCount nBytesToRead = (ByteCount)dwBytesToRead;
+ ByteCount nBytesRead = 0;
+ OSErr theErr;
+
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(*pByteOffset != pStream->RawFilePos)
+ {
+ FSSetForkPosition((short)(long)pStream->hFile, fsFromStart, (SInt64)(*pByteOffset));
+ pStream->RawFilePos = *pByteOffset;
+ }
+
+ // Read the data
+ if(nBytesToRead != 0)
+ {
+ theErr = FSReadFork((short)(long)pStream->hFile, fsAtMark, 0, nBytesToRead, pvBuffer, &nBytesRead);
+ if (theErr != noErr && theErr != eofErr)
+ {
+ nLastError = theErr;
+ return false;
+ }
+ dwBytesRead = (DWORD)nBytesRead;
+ }
+ }
+#endif
+
+#ifdef 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(*pByteOffset != pStream->RawFilePos)
+ {
+ lseek64((intptr_t)pStream->hFile, (off64_t)(*pByteOffset), SEEK_SET);
+ pStream->RawFilePos = *pByteOffset;
+ }
+
+ // 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;
+ }
+
+ 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->RawFilePos = *pByteOffset + 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 File_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
+{
+ DWORD dwBytesWritten = 0; // Must be set by platform-specific code
+
+ // If the byte offset is not entered, use the current position
+ if(pByteOffset == NULL)
+ pByteOffset = &pStream->RawFilePos;
+
+#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);
+
+ SetFilePointer(pStream->hFile, ByteOffsetLo, &ByteOffsetHi, FILE_BEGIN);
+ pStream->RawFilePos = *pByteOffset;
+ }
+
+ // Read the data
+ if(!WriteFile(pStream->hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL))
+ return false;
+ }
+#endif
+
+#ifdef PLATFORM_MAC
+ {
+ ByteCount nBytesToWrite = (ByteCount)dwBytesToWrite;
+ ByteCount nBytesWritten = 0;
+ OSErr theErr;
+
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(*pByteOffset != pStream->RawFilePos)
+ {
+ FSSetForkPosition((short)(long)pStream->hFile, fsFromStart, (SInt64)(*pByteOffset));
+ pStream->RawFilePos = *pByteOffset;
+ }
+
+ theErr = FSWriteFork((short)(long)pStream->hFile, fsAtMark, 0, nBytesToWrite, pvBuffer, &nBytesWritten);
+ if (theErr != noErr)
+ {
+ nLastError = theErr;
+ return false;
+ }
+ dwBytesWritten = (DWORD)nBytesWritten;
+ }
+#endif
+
+#ifdef 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(*pByteOffset != pStream->RawFilePos)
+ {
+ lseek64((intptr_t)pStream->hFile, (off64_t)(*pByteOffset), SEEK_SET);
+ pStream->RawFilePos = *pByteOffset;
+ }
+
+ // Perform the read operation
+ bytes_written = write((intptr_t)pStream->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->RawFilePos = *pByteOffset + dwBytesWritten;
+ if(dwBytesWritten != dwBytesToWrite)
+ SetLastError(ERROR_DISK_FULL);
+ return (dwBytesWritten == dwBytesToWrite);
+}
+
+static bool File_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 = MAKE_OFFSET64(FileSizeHi, FileSizeLo);
+ return true;
+#endif
+
+#ifdef PLATFORM_MAC
+ SInt64 fileLength = 0;
+ OSErr theErr;
+
+ theErr = FSGetForkSize((short)(long)pStream->hFile, &fileLength);
+ if(theErr != noErr)
+ {
+ nLastError = theErr;
+ return false;
+ }
+
+ FileSize = (ULONGLONG)fileLength;
+ return true;
+#endif
+
+#ifdef PLATFORM_LINUX
+ struct stat64 fileinfo;
+
+ if(fstat64((intptr_t)pStream->hFile, &fileinfo) == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ FileSize = (ULONGLONG)fileinfo.st_size;
+ return true;
+#endif
+}
+
+/**
+ * \a pStream Pointer to an open stream
+ * \a NewFileSize New size of the file
+ */
+static bool File_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
+{
+#ifdef PLATFORM_WINDOWS
+ {
+ LONG FileSizeHi = (LONG)(NewFileSize >> 32);
+ LONG FileSizeLo = (LONG)(NewFileSize);
+ DWORD dwNewPos;
+ bool bResult;
+
+ // 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;
+
+ // Set the current file pointer as the end of the file
+ bResult = (bool)SetEndOfFile(pStream->hFile);
+
+ // 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;
+
+ theErr = FSSetForkSize((short)(long)pStream->hFile, fsFromStart, (SInt64)NewFileSize);
+ if(theErr != noErr)
+ {
+ nLastError = theErr;
+ return false;
+ }
+
+ return true;
+ }
+#endif
+
+#ifdef PLATFORM_LINUX
+ {
+ if(ftruncate((intptr_t)pStream->hFile, (off_t)NewFileSize) == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ return true;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Stream functions - partial normal file stream
+
+/**
+ * \a pStream Pointer to an open stream
+ * \a ByteOffset File byte offset
+ */
+static bool PartFile_GetPos(TPartFileStream * pStream, ULONGLONG & ByteOffset)
+{
+ ByteOffset = pStream->VirtualPos;
+ 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)
+{
+ 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_CAN_NOT_COMPLETE;
+ 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_CAN_NOT_COMPLETE;
+ 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(!File_Read(pStream, &RawByteOffset, pbBuffer, dwBytesInPart))
+ {
+ nFailReason = ERROR_CAN_NOT_COMPLETE;
+ 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 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
+{
+ // Keep compiler happy
+ dwBytesToRead = dwBytesToRead;
+ pByteOffset = pByteOffset;
+ pvBuffer = pvBuffer;
+ pStream = pStream;
+
+ // Not allowed
+ return false;
+}
+
+static bool PartFile_GetSize(
+ TPartFileStream * 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
+{
+ // Keep compiler happy
+ pStream = pStream;
+ NewSize = NewSize;
+
+ // Not allowed
+ return false;
+}
+
+/*
+ * 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 const char * MpqKeyArray[] =
+{
+ MpqeKey_Starcraft2_Install_enUS,
+ MpqeKey_Starcraft2_Install_enGB,
+ MpqeKey_Starcraft2_Install_deDE,
+ MpqeKey_Starcraft2_Install_esES,
+ MpqeKey_Starcraft2_Install_frFR,
+ MpqeKey_Starcraft2_Install_itIT,
+ MpqeKey_Starcraft2_Install_plPL,
+ MpqeKey_Starcraft2_Install_ruRU,
+ NULL
+};
+
+static DWORD Rol32(DWORD dwValue, DWORD dwRolCount)
+{
+ DWORD dwShiftRight = 32 - dwRolCount;
+
+ return (dwValue << dwRolCount) | (dwValue >> dwShiftRight);
+}
+
+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(TEncryptedStream * pStream)
+{
+ 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;
+
+ // 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);
+
+ // Try to decrypt with the given key
+ memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE);
+ DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE);
+
+ // We check the decrypoted 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 EncryptedFile_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)
+ ByteOffset = *pByteOffset;
+ else
+ ByteOffset = pStream->RawFilePos;
+
+ // 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(File_Read(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 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
+{
+ // Keep compiler happy
+ dwBytesToRead = dwBytesToRead;
+ pByteOffset = pByteOffset;
+ pvBuffer = pvBuffer;
+ pStream = pStream;
+
+ // Not allowed
+ return false;
+}
+
+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;
+
+ // Not allowed
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+/**
+ * This function creates a new file for read or 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) // Name of the file to create
+{
+ TFileStream * pStream = NULL;
+ HANDLE hFile;
+
+ // Create the file
+ hFile = CreateNewFile(szFileName);
+ if(hFile != INVALID_HANDLE_VALUE)
+ {
+ // 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
+ {
+ CloseTheFile(hFile);
+ }
+ }
+
+ // 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 TPartFileStream 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
+ */
+
+TFileStream * FileStream_OpenRawFile(
+ const TCHAR * szFileName, // Name of the file to create
+ bool bWriteAccess) // false = read-only, true = read+write
+{
+ TFileStream * pStream;
+ HANDLE hFile;
+
+ // Create the file
+ hFile = OpenExistingFile(szFileName, bWriteAccess);
+ if(hFile == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ // Initialize the file as normal file stream
+ 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;
+ if(bWriteAccess == false)
+ pStream->StreamFlags |= STREAM_FLAG_READ_ONLY;
+ pStream->hFile = hFile;
+ return pStream;
+ }
+
+ CloseTheFile(hFile);
+ return NULL;
+}
+
+/**
+ * Opens a file
+ *
+ * \a szFileName Name of the file to open
+ * \a bWriteAccess false for read only, true for read+write
+ */
+
+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;
+
+ // Open the file as normal stream
+ pStream = FileStream_OpenRawFile(szFileName, bWriteAccess);
+ 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;
+
+ // Calculate the number of parts in the file
+ VirtualSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo);
+ BlockCount = (DWORD)((VirtualSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize);
+
+ // 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));
+
+ // 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;
+ }
+
+ // Swap the array of file map entries
+ BSWAP_ARRAY32_UNSIGNED(pPartStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY));
+
+ // 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);
+
+ // Fill the members of PART file stream
+ pPartStream->VirtualSize = ((ULONGLONG)PartHdr.FileSizeHi) + PartHdr.FileSizeLo;
+ pPartStream->VirtualPos = 0;
+ pPartStream->BlockCount = BlockCount;
+ pPartStream->BlockSize = PartHdr.BlockSize;
+
+ STORM_FREE(pStream);
+ }
+ return pPartStream;
+ }
+ }
+
+ // 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)
+ {
+ // 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);
+ 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);
+}
+
+/**
+ * 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->StreamFlags & STREAM_FLAG_READ_ONLY)
+ return false;
+ 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
+ */
+bool FileStream_GetLastWriteTime(TFileStream * pStream, ULONGLONG * pFileTime)
+{
+#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
+}
+
+/**
+ * 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 & FileSize)
+{
+ assert(pStream->StreamGetSize != NULL);
+ return pStream->StreamGetSize(pStream, FileSize);
+}
+
+/**
+ * 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->StreamFlags & STREAM_FLAG_READ_ONLY)
+ return false;
+ assert(pStream->StreamSetSize != NULL);
+
+ return pStream->StreamSetSize(pStream, NewFileSize);
+}
+
+/**
+ * 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_MoveFile(TFileStream * pStream, TFileStream * pTempStream)
+{
+ bool bWriteAccess;
+
+ // Close the handle to the temporary file
+ CloseTheFile(pTempStream->hFile);
+ pTempStream->hFile = INVALID_HANDLE_VALUE;
+
+ // Close the handle to the source file
+ CloseTheFile(pStream->hFile);
+ pStream->hFile = INVALID_HANDLE_VALUE;
+
+ // Rename the temp file to the final file
+ if(!RenameFile(pTempStream->szFileName, pStream->szFileName))
+ return false;
+
+ // 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)
+ return false;
+
+ // Delete the temporary file stream
+ FileStream_Close(pTempStream);
+
+ // The file position has been reset to zero by reopening the file
+ pStream->RawFilePos = 0;
+ return true;
+}
+
+/**
+ * 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 file handle
+ if(pStream->hFile != INVALID_HANDLE_VALUE)
+ CloseTheFile(pStream->hFile);
+
+ // Free the stream itself
+ STORM_FREE(pStream);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// main - for testing purposes
+/*
+int main(void)
+{
+ ULONGLONG FilePos;
+ ULONGLONG FileSize;
+ TMPQFileTime * pFT;
+ TFileStream * pTempStream;
+ TFileStream * pStream;
+ 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
+ //
+
+ pStream = FileStream_CreateFile("E:\\Stream.bin");
+ if(pStream == NULL)
+ {
+ printf("Failed to create new file\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 -1;
+ }
+ }
+ FileStream_Close(pStream);
+
+ //
+ // Test2: Read from the stream
+ //
+
+ pStream = FileStream_OpenFile("E:\\Stream.bin", false);
+ if(pStream == NULL)
+ {
+ printf("Failed to open existing file\n");
+ return -1;
+ }
+
+ // 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", 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)
+ {
+ DWORD dwBytesToRead = FileSize.LowPart;
+
+ 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.QuadPart -= dwBytesToRead;
+ }
+
+ // Switch the streams
+ // Note that the pTempStream is closed by the operation
+ FileStream_MoveFile(pStream, pTempStream);
+ FileStream_Close(pStream);
+
+ //
+ // Test4: Read from the stream again
+ //
+
+ pStream = FileStream_OpenFile("E:\\Stream.bin", false);
+ if(pStream == NULL)
+ {
+ printf("Failed to open existing file\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);
+
+ //
+ // Test5: Open partial MPQ stream
+ //
+
+// InitializeMpqCryptography();
+ pStream = FileStream_OpenFile("e:\\Multimedia\\MPQs\\PartialMPQs\\patch.MPQ.part", false);
+ 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));
+
+ //
+ // 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
+ //
+
+ FileStream_Close(pStream);
+ }
+
+ return 0;
+}
+*/