mirror of
https://github.com/mangosfour/server.git
synced 2025-12-16 04:37:00 +00:00
2294 lines
79 KiB
C++
2294 lines
79 KiB
C++
/*****************************************************************************/
|
|
/* 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;
|
|
}
|
|
*/
|
|
|