17 changed files with 14987 additions and 0 deletions
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 1999-2013 Ladislav Zezula |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
||||
@ -0,0 +1,11 @@
|
||||
This is a slightly modified version of ladislav-zezula/StormLib@4ad0bff21da0163917e1552960e6a43679586b89 |
||||
|
||||
Cosmetic changes: |
||||
* Remove all unused files. |
||||
* Comment out unnecessary bits with `#ifndef FULL`. |
||||
|
||||
Implementation changes: |
||||
* Redefine `bool` to `BOOL`. |
||||
* Use `stdcall` calling convention. |
||||
* Use external Storm error handling (`SErrGetLastError()` etc.) |
||||
* Convert pathnames to Unix-style slashes. |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,217 @@
|
||||
/*****************************************************************************/ |
||||
/* FileStream.h Copyright (c) Ladislav Zezula 2012 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Description: Definitions for FileStream object */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* 14.04.12 1.00 Lad The first version of FileStream.h */ |
||||
/*****************************************************************************/ |
||||
|
||||
#ifndef __FILESTREAM_H__ |
||||
#define __FILESTREAM_H__ |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Function prototypes
|
||||
|
||||
typedef void (*STREAM_INIT)( |
||||
struct TFileStream * pStream // Pointer to an unopened stream
|
||||
); |
||||
|
||||
typedef bool (*STREAM_CREATE)( |
||||
struct TFileStream * pStream // Pointer to an unopened stream
|
||||
); |
||||
|
||||
typedef bool (*STREAM_OPEN)( |
||||
struct TFileStream * pStream, // Pointer to an unopened stream
|
||||
const TCHAR * szFileName, // Pointer to file name to be open
|
||||
DWORD dwStreamFlags // Stream flags
|
||||
); |
||||
|
||||
typedef bool (*STREAM_READ)( |
||||
struct 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
|
||||
); |
||||
|
||||
typedef bool (*STREAM_WRITE)( |
||||
struct TFileStream * pStream, // Pointer to an open stream
|
||||
ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it writes to the current position
|
||||
const void * pvBuffer, // Pointer to data to be written
|
||||
DWORD dwBytesToWrite // Number of bytes to read from the file
|
||||
); |
||||
|
||||
typedef bool (*STREAM_RESIZE)( |
||||
struct TFileStream * pStream, // Pointer to an open stream
|
||||
ULONGLONG FileSize // New size for the file, in bytes
|
||||
); |
||||
|
||||
typedef bool (*STREAM_GETSIZE)( |
||||
struct TFileStream * pStream, // Pointer to an open stream
|
||||
ULONGLONG * pFileSize // Receives the file size, in bytes
|
||||
); |
||||
|
||||
typedef bool (*STREAM_GETPOS)( |
||||
struct TFileStream * pStream, // Pointer to an open stream
|
||||
ULONGLONG * pByteOffset // Pointer to store current file position
|
||||
); |
||||
|
||||
typedef void (*STREAM_CLOSE)( |
||||
struct TFileStream * pStream // Pointer to an open stream
|
||||
); |
||||
|
||||
typedef bool (*BLOCK_READ)( |
||||
struct TFileStream * pStream, // Pointer to a block-oriented stream
|
||||
ULONGLONG StartOffset, // Byte offset of start of the block array
|
||||
ULONGLONG EndOffset, // End offset (either end of the block or end of the file)
|
||||
LPBYTE BlockBuffer, // Pointer to block-aligned buffer
|
||||
DWORD BytesNeeded, // Number of bytes that are really needed
|
||||
bool bAvailable // true if the block is available
|
||||
); |
||||
|
||||
typedef bool (*BLOCK_CHECK)( |
||||
struct TFileStream * pStream, // Pointer to a block-oriented stream
|
||||
ULONGLONG BlockOffset // Offset of the file to check
|
||||
); |
||||
|
||||
typedef void (*BLOCK_SAVEMAP)( |
||||
struct TFileStream * pStream // Pointer to a block-oriented stream
|
||||
); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local structures - partial file structure and bitmap footer
|
||||
|
||||
#define ID_FILE_BITMAP_FOOTER 0x33767470 // Signature of the file bitmap footer ('ptv3')
|
||||
#define DEFAULT_BLOCK_SIZE 0x00004000 // Default size of the stream block
|
||||
#define DEFAULT_BUILD_NUMBER 10958 // Build number for newly created partial MPQs
|
||||
|
||||
typedef struct _PART_FILE_HEADER |
||||
{ |
||||
DWORD PartialVersion; // Always set to 2
|
||||
char GameBuildNumber[0x20]; // Minimum build number of the game that can use this MPQ
|
||||
DWORD Flags; // Flags (details unknown)
|
||||
DWORD FileSizeLo; // Low 32 bits of the contained file size
|
||||
DWORD FileSizeHi; // High 32 bits of the contained 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 LargeValueLo; // 64-bit value, meaning is unknown
|
||||
DWORD LargeValueHi; |
||||
|
||||
} PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY; |
||||
|
||||
typedef struct _FILE_BITMAP_FOOTER |
||||
{ |
||||
DWORD Signature; // 'ptv3' (ID_FILE_BITMAP_FOOTER)
|
||||
DWORD Version; // Unknown, seems to always have value of 3 (version?)
|
||||
DWORD BuildNumber; // Game build number for that MPQ
|
||||
DWORD MapOffsetLo; // Low 32-bits of the offset of the bit map
|
||||
DWORD MapOffsetHi; // High 32-bits of the offset of the bit map
|
||||
DWORD BlockSize; // Size of one block (usually 0x4000 bytes)
|
||||
|
||||
} FILE_BITMAP_FOOTER, *PFILE_BITMAP_FOOTER; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structure for file stream
|
||||
|
||||
union TBaseProviderData |
||||
{ |
||||
struct |
||||
{ |
||||
ULONGLONG FileSize; // Size of the file
|
||||
ULONGLONG FilePos; // Current file position
|
||||
ULONGLONG FileTime; // Last write time
|
||||
HANDLE hFile; // File handle
|
||||
} File; |
||||
|
||||
struct |
||||
{ |
||||
ULONGLONG FileSize; // Size of the file
|
||||
ULONGLONG FilePos; // Current file position
|
||||
ULONGLONG FileTime; // Last write time
|
||||
LPBYTE pbFile; // Pointer to mapped view
|
||||
} Map; |
||||
|
||||
struct |
||||
{ |
||||
ULONGLONG FileSize; // Size of the file
|
||||
ULONGLONG FilePos; // Current file position
|
||||
ULONGLONG FileTime; // Last write time
|
||||
HANDLE hInternet; // Internet handle
|
||||
HANDLE hConnect; // Connection to the internet server
|
||||
} Http; |
||||
}; |
||||
|
||||
struct TFileStream |
||||
{ |
||||
// Stream provider functions
|
||||
STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly.
|
||||
STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly.
|
||||
STREAM_RESIZE StreamResize; // Pointer to function changing file size
|
||||
STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size
|
||||
STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position
|
||||
STREAM_CLOSE StreamClose; // Pointer to function closing the stream
|
||||
|
||||
// Block-oriented functions
|
||||
BLOCK_READ BlockRead; // Pointer to function reading one or more blocks
|
||||
BLOCK_CHECK BlockCheck; // Pointer to function checking whether the block is present
|
||||
|
||||
// Base provider functions
|
||||
STREAM_CREATE BaseCreate; // Pointer to base create function
|
||||
STREAM_OPEN BaseOpen; // Pointer to base open function
|
||||
STREAM_READ BaseRead; // Read from the stream
|
||||
STREAM_WRITE BaseWrite; // Write to the stream
|
||||
STREAM_RESIZE BaseResize; // Pointer to function changing file size
|
||||
STREAM_GETSIZE BaseGetSize; // Pointer to function returning file size
|
||||
STREAM_GETPOS BaseGetPos; // Pointer to function that returns current file position
|
||||
STREAM_CLOSE BaseClose; // Pointer to function closing the stream
|
||||
|
||||
// Base provider data (file size, file position)
|
||||
TBaseProviderData Base; |
||||
|
||||
// Stream provider data
|
||||
TFileStream * pMaster; // Master stream (e.g. MPQ on a web server)
|
||||
TCHAR * szFileName; // File name (self-relative pointer)
|
||||
|
||||
ULONGLONG StreamSize; // Stream size (can be less than file size)
|
||||
ULONGLONG StreamPos; // Stream position
|
||||
DWORD BuildNumber; // Game build number
|
||||
DWORD dwFlags; // Stream flags
|
||||
|
||||
// Followed by stream provider data, with variable length
|
||||
}; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structures for block-oriented stream
|
||||
|
||||
struct TBlockStream : public TFileStream |
||||
{ |
||||
SFILE_DOWNLOAD_CALLBACK pfnCallback; // Callback for downloading
|
||||
void * FileBitmap; // Array of bits for file blocks
|
||||
void * UserData; // User data to be passed to the download callback
|
||||
DWORD BitmapSize; // Size of the file bitmap (in bytes)
|
||||
DWORD BlockSize; // Size of one block, in bytes
|
||||
DWORD BlockCount; // Number of data blocks in the file
|
||||
DWORD IsComplete; // If nonzero, no blocks are missing
|
||||
DWORD IsModified; // nonzero if the bitmap has been modified
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structure for encrypted stream
|
||||
|
||||
#define MPQE_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted
|
||||
|
||||
struct TEncryptedStream : public TBlockStream |
||||
{ |
||||
BYTE Key[MPQE_CHUNK_SIZE]; // File key
|
||||
}; |
||||
|
||||
#endif // __FILESTREAM_H__
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,618 @@
|
||||
/*****************************************************************************/ |
||||
/* SBaseSubTypes.cpp Copyright (c) Ladislav Zezula 2013 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Conversion routines for archive formats that are similar to MPQ format */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* 02.11.11 1.00 Lad The first version of SBaseSubTypes.cpp */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define __STORMLIB_SELF__ |
||||
#include "StormLib.h" |
||||
#include "StormCommon.h" |
||||
|
||||
/*****************************************************************************/ |
||||
/* */ |
||||
/* Support for SQP file format (War of the Immortals) */ |
||||
/* */ |
||||
/*****************************************************************************/ |
||||
|
||||
typedef struct _TSQPHeader |
||||
{ |
||||
// The ID_MPQ ('MPQ\x1A') signature
|
||||
DWORD dwID;
|
||||
|
||||
// Size of the archive header
|
||||
DWORD dwHeaderSize;
|
||||
|
||||
// 32-bit size of MPQ archive
|
||||
DWORD dwArchiveSize; |
||||
|
||||
// Offset to the beginning of the hash table, relative to the beginning of the archive.
|
||||
DWORD dwHashTablePos; |
||||
|
||||
// Offset to the beginning of the block table, relative to the beginning of the archive.
|
||||
DWORD dwBlockTablePos; |
||||
|
||||
// Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for
|
||||
// the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
|
||||
DWORD dwHashTableSize; |
||||
|
||||
// Number of entries in the block table
|
||||
DWORD dwBlockTableSize; |
||||
|
||||
// Must be zero for SQP files
|
||||
USHORT wFormatVersion; |
||||
|
||||
// Power of two exponent specifying the number of 512-byte disk sectors in each file sector
|
||||
// in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize.
|
||||
USHORT wSectorSize; |
||||
|
||||
} TSQPHeader; |
||||
|
||||
typedef struct _TSQPHash |
||||
{ |
||||
// Most likely the lcLocale+wPlatform.
|
||||
DWORD dwAlwaysZero; |
||||
|
||||
// If the hash table entry is valid, this is the index into the block table of the file.
|
||||
// Otherwise, one of the following two values:
|
||||
// - FFFFFFFFh: Hash table entry is empty, and has always been empty.
|
||||
// Terminates searches for a given file.
|
||||
// - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
|
||||
// Does not terminate searches for a given file.
|
||||
DWORD dwBlockIndex; |
||||
|
||||
// The hash of the file path, using method A.
|
||||
DWORD dwName1; |
||||
|
||||
// The hash of the file path, using method B.
|
||||
DWORD dwName2; |
||||
|
||||
} TSQPHash; |
||||
|
||||
typedef struct _TSQPBlock |
||||
{ |
||||
// Offset of the beginning of the file, relative to the beginning of the archive.
|
||||
DWORD dwFilePos; |
||||
|
||||
// Flags for the file. See MPQ_FILE_XXXX constants
|
||||
DWORD dwFlags;
|
||||
|
||||
// Compressed file size
|
||||
DWORD dwCSize; |
||||
|
||||
// Uncompressed file size
|
||||
DWORD dwFSize;
|
||||
|
||||
} TSQPBlock; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions - SQP file format
|
||||
|
||||
// This function converts SQP file header into MPQ file header
|
||||
int ConvertSqpHeaderToFormat4( |
||||
TMPQArchive * ha, |
||||
ULONGLONG FileSize, |
||||
DWORD dwFlags) |
||||
{ |
||||
TSQPHeader * pSqpHeader = (TSQPHeader *)ha->HeaderData; |
||||
TMPQHeader Header; |
||||
|
||||
// SQP files from War of the Immortal use MPQ file format with slightly
|
||||
// modified structure. These fields have different position:
|
||||
//
|
||||
// Offset TMPQHeader TSQPHeader
|
||||
// ------ ---------- -----------
|
||||
// 000C wFormatVersion dwHashTablePos (lo)
|
||||
// 000E wSectorSize dwHashTablePos (hi)
|
||||
// 001C dwBlockTableSize (lo) wBlockSize
|
||||
// 001E dwHashTableSize (hi) wFormatVersion
|
||||
|
||||
// Can't open the archive with certain flags
|
||||
if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) |
||||
return ERROR_FILE_CORRUPT; |
||||
|
||||
// The file must not be greater than 4 GB
|
||||
if((FileSize >> 0x20) != 0) |
||||
return ERROR_FILE_CORRUPT; |
||||
|
||||
// Translate the SQP header into a MPQ header
|
||||
memset(&Header, 0, sizeof(TMPQHeader)); |
||||
Header.dwID = BSWAP_INT32_UNSIGNED(pSqpHeader->dwID); |
||||
Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHeaderSize); |
||||
Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwArchiveSize); |
||||
Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTablePos); |
||||
Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTablePos); |
||||
Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTableSize); |
||||
Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTableSize); |
||||
Header.wFormatVersion = BSWAP_INT16_UNSIGNED(pSqpHeader->wFormatVersion); |
||||
Header.wSectorSize = BSWAP_INT16_UNSIGNED(pSqpHeader->wSectorSize); |
||||
|
||||
// Verify the SQP header
|
||||
if(Header.dwID == ID_MPQ && Header.dwHeaderSize == sizeof(TSQPHeader) && Header.dwArchiveSize == FileSize) |
||||
{ |
||||
// Check for fixed values of version and sector size
|
||||
if(Header.wFormatVersion == MPQ_FORMAT_VERSION_1 && Header.wSectorSize == 3) |
||||
{ |
||||
// Initialize the fields of 3.0 header
|
||||
Header.ArchiveSize64 = Header.dwArchiveSize; |
||||
Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash); |
||||
Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock); |
||||
|
||||
// Copy the converted MPQ header back
|
||||
memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader)); |
||||
|
||||
// Mark this file as SQP file
|
||||
ha->pfnHashString = HashStringSlash; |
||||
ha->dwFlags |= MPQ_FLAG_READ_ONLY; |
||||
ha->dwSubType = MPQ_SUBTYPE_SQP; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
} |
||||
|
||||
return ERROR_FILE_CORRUPT; |
||||
} |
||||
|
||||
void * LoadSqpTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize, DWORD dwKey) |
||||
{ |
||||
ULONGLONG ByteOffset; |
||||
LPBYTE pbSqpTable; |
||||
|
||||
// Allocate buffer for the table
|
||||
pbSqpTable = STORM_ALLOC(BYTE, cbTableSize); |
||||
if(pbSqpTable != NULL) |
||||
{ |
||||
// Load the table
|
||||
ByteOffset = ha->MpqPos + dwByteOffset; |
||||
if(FileStream_Read(ha->pStream, &ByteOffset, pbSqpTable, cbTableSize)) |
||||
{ |
||||
// Decrypt the SQP table
|
||||
DecryptMpqBlock(pbSqpTable, cbTableSize, dwKey); |
||||
return pbSqpTable; |
||||
} |
||||
|
||||
// Free the table
|
||||
STORM_FREE(pbSqpTable); |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
TMPQHash * LoadSqpHashTable(TMPQArchive * ha) |
||||
{ |
||||
TMPQHeader * pHeader = ha->pHeader; |
||||
TSQPHash * pSqpHashTable; |
||||
TSQPHash * pSqpHashEnd; |
||||
TSQPHash * pSqpHash; |
||||
TMPQHash * pMpqHash; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Load the hash table
|
||||
pSqpHashTable = (TSQPHash *)LoadSqpTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TSQPHash), MPQ_KEY_HASH_TABLE); |
||||
if(pSqpHashTable != NULL) |
||||
{ |
||||
// Parse the entire hash table and convert it to MPQ hash table
|
||||
pSqpHashEnd = pSqpHashTable + pHeader->dwHashTableSize; |
||||
pMpqHash = (TMPQHash *)pSqpHashTable; |
||||
for(pSqpHash = pSqpHashTable; pSqpHash < pSqpHashEnd; pSqpHash++, pMpqHash++) |
||||
{ |
||||
// Ignore free entries
|
||||
if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE) |
||||
{ |
||||
// Check block index against the size of the block table
|
||||
if(pHeader->dwBlockTableSize <= MPQ_BLOCK_INDEX(pSqpHash) && pSqpHash->dwBlockIndex < HASH_ENTRY_DELETED) |
||||
nError = ERROR_FILE_CORRUPT; |
||||
|
||||
// We do not support nonzero locale and platform ID
|
||||
if(pSqpHash->dwAlwaysZero != 0 && pSqpHash->dwAlwaysZero != HASH_ENTRY_FREE) |
||||
nError = ERROR_FILE_CORRUPT; |
||||
|
||||
// Store the file name hash
|
||||
pMpqHash->dwName1 = pSqpHash->dwName1; |
||||
pMpqHash->dwName2 = pSqpHash->dwName2; |
||||
|
||||
// Store the rest. Note that this must be done last,
|
||||
// because block index corresponds to pMpqHash->dwName2
|
||||
pMpqHash->dwBlockIndex = MPQ_BLOCK_INDEX(pSqpHash); |
||||
pMpqHash->Platform = 0; |
||||
pMpqHash->lcLocale = 0; |
||||
} |
||||
} |
||||
|
||||
// If an error occured, we need to free the hash table
|
||||
if(nError != ERROR_SUCCESS) |
||||
{ |
||||
STORM_FREE(pSqpHashTable); |
||||
pSqpHashTable = NULL; |
||||
} |
||||
} |
||||
|
||||
// Return the converted hash table (or NULL on failure)
|
||||
return (TMPQHash *)pSqpHashTable; |
||||
} |
||||
|
||||
// Loads the SQP Block table and converts it to a MPQ block table
|
||||
TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha) |
||||
{ |
||||
TMPQHeader * pHeader = ha->pHeader; |
||||
TSQPBlock * pSqpBlockTable; |
||||
TSQPBlock * pSqpBlockEnd; |
||||
TSQPBlock * pSqpBlock; |
||||
TMPQBlock * pMpqBlock; |
||||
DWORD dwFlags; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Load the hash table
|
||||
pSqpBlockTable = (TSQPBlock *)LoadSqpTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TSQPBlock), MPQ_KEY_BLOCK_TABLE); |
||||
if(pSqpBlockTable != NULL) |
||||
{ |
||||
// Parse the entire hash table and convert it to MPQ hash table
|
||||
pSqpBlockEnd = pSqpBlockTable + pHeader->dwBlockTableSize; |
||||
pMpqBlock = (TMPQBlock *)pSqpBlockTable; |
||||
for(pSqpBlock = pSqpBlockTable; pSqpBlock < pSqpBlockEnd; pSqpBlock++, pMpqBlock++) |
||||
{ |
||||
// Check for valid flags
|
||||
if(pSqpBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) |
||||
nError = ERROR_FILE_CORRUPT; |
||||
|
||||
// Convert SQP block table entry to MPQ block table entry
|
||||
dwFlags = pSqpBlock->dwFlags; |
||||
pMpqBlock->dwCSize = pSqpBlock->dwCSize; |
||||
pMpqBlock->dwFSize = pSqpBlock->dwFSize; |
||||
pMpqBlock->dwFlags = dwFlags; |
||||
} |
||||
|
||||
// If an error occured, we need to free the hash table
|
||||
if(nError != ERROR_SUCCESS) |
||||
{ |
||||
STORM_FREE(pSqpBlockTable); |
||||
pSqpBlockTable = NULL; |
||||
} |
||||
} |
||||
|
||||
// Return the converted hash table (or NULL on failure)
|
||||
return (TMPQBlock *)pSqpBlockTable; |
||||
} |
||||
|
||||
/*****************************************************************************/ |
||||
/* */ |
||||
/* Support for MPK file format (Longwu Online) */ |
||||
/* */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define MPK_FILE_UNKNOWN_0001 0x00000001 // Seems to be always present
|
||||
#define MPK_FILE_UNKNOWN_0010 0x00000010 // Seems to be always present
|
||||
#define MPK_FILE_COMPRESSED 0x00000100 // Indicates a compressed file
|
||||
#define MPK_FILE_UNKNOWN_2000 0x00002000 // Seems to be always present
|
||||
#define MPK_FILE_EXISTS 0x01000000 // Seems to be always present
|
||||
|
||||
typedef struct _TMPKHeader |
||||
{ |
||||
// The ID_MPK ('MPK\x1A') signature
|
||||
DWORD dwID; |
||||
|
||||
// Contains '2000'
|
||||
DWORD dwVersion; |
||||
|
||||
// 32-bit size of the archive
|
||||
DWORD dwArchiveSize; |
||||
|
||||
// Size of the archive header
|
||||
DWORD dwHeaderSize; |
||||
|
||||
DWORD dwHashTablePos; |
||||
DWORD dwHashTableSize; |
||||
DWORD dwBlockTablePos; |
||||
DWORD dwBlockTableSize; |
||||
DWORD dwUnknownPos; |
||||
DWORD dwUnknownSize; |
||||
} TMPKHeader; |
||||
|
||||
|
||||
typedef struct _TMPKHash |
||||
{ |
||||
// The hash of the file path, using method A.
|
||||
DWORD dwName1; |
||||
|
||||
// The hash of the file path, using method B.
|
||||
DWORD dwName2; |
||||
|
||||
// The hash of the file path, using method C.
|
||||
DWORD dwName3; |
||||
|
||||
// If the hash table entry is valid, this is the index into the block table of the file.
|
||||
// Otherwise, one of the following two values:
|
||||
// - FFFFFFFFh: Hash table entry is empty, and has always been empty.
|
||||
// Terminates searches for a given file.
|
||||
// - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
|
||||
// Does not terminate searches for a given file.
|
||||
DWORD dwBlockIndex; |
||||
|
||||
} TMPKHash; |
||||
|
||||
typedef struct _TMPKBlock |
||||
{ |
||||
DWORD dwFlags; // 0x1121 - Compressed , 0x1120 - Not compressed
|
||||
DWORD dwFilePos; // Offset of the beginning of the file, relative to the beginning of the archive.
|
||||
DWORD dwFSize; // Uncompressed file size
|
||||
DWORD dwCSize; // Compressed file size
|
||||
DWORD dwUnknown; // 0x86364E6D
|
||||
} TMPKBlock; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local variables - MPK file format
|
||||
|
||||
static const unsigned char MpkDecryptionKey[512] = |
||||
{ |
||||
0x60, 0x20, 0x29, 0xE1, 0x01, 0xCE, 0xAA, 0xFE, 0xA3, 0xAB, 0x8E, 0x30, 0xAF, 0x02, 0xD1, 0x7D, |
||||
0x41, 0x24, 0x06, 0xBD, 0xAE, 0xBE, 0x43, 0xC3, 0xBA, 0xB7, 0x08, 0x13, 0x51, 0xCF, 0xF8, 0xF7, |
||||
0x25, 0x42, 0xA5, 0x4A, 0xDA, 0x0F, 0x52, 0x1C, 0x90, 0x3B, 0x63, 0x49, 0x36, 0xF6, 0xDD, 0x1B, |
||||
0xEA, 0x58, 0xD4, 0x40, 0x70, 0x61, 0x55, 0x09, 0xCD, 0x0B, 0xA2, 0x4B, 0x68, 0x2C, 0x8A, 0xF1, |
||||
0x3C, 0x3A, 0x65, 0xBB, 0xA1, 0xA8, 0x23, 0x97, 0xFD, 0x15, 0x00, 0x94, 0x88, 0x33, 0x59, 0xE9, |
||||
0xFB, 0x69, 0x21, 0xEF, 0x85, 0x5B, 0x57, 0x6C, 0xFA, 0xB5, 0xEE, 0xB8, 0x71, 0xDC, 0xB1, 0x38, |
||||
0x0C, 0x0A, 0x5C, 0x56, 0xC9, 0xB4, 0x84, 0x17, 0x1E, 0xE5, 0xD3, 0x5A, 0xCC, 0xFC, 0x11, 0x86, |
||||
0x7F, 0x45, 0x4F, 0x54, 0xC8, 0x8D, 0x73, 0x89, 0x79, 0x5D, 0xB3, 0xBF, 0xB9, 0xE3, 0x93, 0xE4, |
||||
0x6F, 0x35, 0x2D, 0x46, 0xF2, 0x76, 0xC5, 0x7E, 0xE2, 0xA4, 0xE6, 0xD9, 0x6E, 0x48, 0x34, 0x2B, |
||||
0xC6, 0x5F, 0xBC, 0xA0, 0x6D, 0x0D, 0x47, 0x6B, 0x95, 0x96, 0x92, 0x91, 0xB2, 0x27, 0xEB, 0x9E, |
||||
0xEC, 0x8F, 0xDF, 0x9C, 0x74, 0x99, 0x64, 0xF5, 0xFF, 0x28, 0xB6, 0x37, 0xF3, 0x7C, 0x81, 0x03, |
||||
0x44, 0x62, 0x1F, 0xDB, 0x04, 0x7B, 0xB0, 0x9B, 0x31, 0xA7, 0xDE, 0x78, 0x9F, 0xAD, 0x0E, 0x3F, |
||||
0x3E, 0x4D, 0xC7, 0xD7, 0x39, 0x19, 0x5E, 0xC2, 0xD0, 0xAC, 0xE8, 0x1A, 0x87, 0x8B, 0x07, 0x05, |
||||
0x22, 0xED, 0x72, 0x2E, 0x1D, 0xC1, 0xA9, 0xD6, 0xE0, 0x83, 0xD5, 0xD8, 0xCB, 0x80, 0xF0, 0x66, |
||||
0x7A, 0x9D, 0x50, 0xF9, 0x10, 0x4E, 0x16, 0x14, 0x77, 0x75, 0x6A, 0x67, 0xD2, 0xC0, 0xA6, 0xC4, |
||||
0x53, 0x8C, 0x32, 0xCA, 0x82, 0x2A, 0x18, 0x9A, 0xF4, 0x4C, 0x3D, 0x26, 0x12, 0xE7, 0x98, 0x2F, |
||||
0x4A, 0x04, 0x0D, 0xAF, 0xB4, 0xCF, 0x12, 0xCE, 0x1A, 0x37, 0x61, 0x39, 0x60, 0x95, 0xBE, 0x25, |
||||
0xE4, 0x6E, 0xFC, 0x1B, 0xE7, 0x49, 0xE6, 0x67, 0xF6, 0xC5, 0xCB, 0x2F, 0x27, 0xD4, 0x68, 0xB2, |
||||
0x01, 0x52, 0xD0, 0x46, 0x11, 0x20, 0xFB, 0x9D, 0xA9, 0x02, 0xF5, 0x8F, 0x3D, 0x82, 0xD3, 0xFF, |
||||
0x0B, 0xB8, 0xF2, 0x4D, 0x8E, 0x81, 0x2C, 0xAB, 0x5F, 0xC4, 0x41, 0x29, 0x40, 0xFA, 0xC0, 0xBF, |
||||
0x33, 0x10, 0x21, 0x16, 0xB0, 0x71, 0x83, 0x96, 0x8D, 0x2B, 0x23, 0x3B, 0xF9, 0xC1, 0xE5, 0x72, |
||||
0xE2, 0x1C, 0x26, 0xF0, 0x73, 0x36, 0x63, 0x56, 0x31, 0x4E, 0x6B, 0x55, 0x62, 0x79, 0xC6, 0x91, |
||||
0x00, 0x35, 0xB1, 0x2A, 0xA6, 0x42, 0xDF, 0xEB, 0x3C, 0x51, 0xEA, 0x97, 0x57, 0x94, 0x8C, 0x80, |
||||
0x34, 0x5C, 0xD2, 0x76, 0xA4, 0xE9, 0x85, 0xE8, 0xBB, 0x78, 0xE0, 0xB5, 0xAD, 0x0F, 0x87, 0x70, |
||||
0xDD, 0xAE, 0xF4, 0xD9, 0x66, 0x54, 0x6F, 0xCC, 0x4C, 0x77, 0x3E, 0xCD, 0xF1, 0x75, 0x0A, 0xA1, |
||||
0x28, 0x9B, 0x9A, 0x7E, 0x4B, 0x98, 0x99, 0x47, 0xFE, 0xA5, 0xF7, 0xB7, 0xA3, 0xE1, 0x9F, 0xBC, |
||||
0x93, 0x44, 0x3A, 0x08, 0x89, 0x22, 0xEE, 0xB9, 0x45, 0xD6, 0x06, 0x09, 0xC9, 0xBD, 0x14, 0x0C, |
||||
0xB6, 0x5E, 0x9C, 0x7A, 0x65, 0x59, 0xAA, 0x19, 0x5B, 0x7C, 0x18, 0x43, 0x92, 0x13, 0x15, 0x7B, |
||||
0xED, 0xD5, 0xC7, 0x17, 0xEF, 0x86, 0x90, 0xC2, 0x74, 0x64, 0xF3, 0xDC, 0x6C, 0x38, 0x05, 0x1D, |
||||
0xC8, 0x0E, 0xEC, 0x6A, 0x32, 0xDA, 0xD7, 0xC3, 0xDB, 0x8B, 0x24, 0xB3, 0x5D, 0x2E, 0xBA, 0xA2, |
||||
0xD8, 0x03, 0x88, 0x7D, 0x7F, 0x69, 0x8A, 0xFD, 0xCA, 0x4F, 0x30, 0x9E, 0xA0, 0xD1, 0x5A, 0x53, |
||||
0xDE, 0x3F, 0x84, 0xAC, 0xF8, 0xA7, 0x2D, 0x1F, 0x1E, 0xE3, 0x58, 0x50, 0x6D, 0x48, 0x07, 0xA8 |
||||
}; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions - MPK file format
|
||||
|
||||
// This function converts MPK file header into MPQ file header
|
||||
int ConvertMpkHeaderToFormat4( |
||||
TMPQArchive * ha, |
||||
ULONGLONG FileSize, |
||||
DWORD dwFlags) |
||||
{ |
||||
TMPKHeader * pMpkHeader = (TMPKHeader *)ha->HeaderData; |
||||
TMPQHeader Header; |
||||
|
||||
// Can't open the archive with certain flags
|
||||
if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) |
||||
return ERROR_FILE_CORRUPT; |
||||
|
||||
// Translate the MPK header into a MPQ header
|
||||
// Note: Hash table size and block table size are in bytes, not in entries
|
||||
memset(&Header, 0, sizeof(TMPQHeader)); |
||||
Header.dwID = BSWAP_INT32_UNSIGNED(pMpkHeader->dwID); |
||||
Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwArchiveSize); |
||||
Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHeaderSize); |
||||
Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTablePos); |
||||
Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTableSize) / sizeof(TMPKHash); |
||||
Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTablePos); |
||||
Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTableSize) / sizeof(TMPKBlock); |
||||
// Header.dwUnknownPos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownPos);
|
||||
// Header.dwUnknownSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownSize);
|
||||
assert(Header.dwHeaderSize == sizeof(TMPKHeader)); |
||||
|
||||
// Verify the MPK header
|
||||
if(Header.dwID == ID_MPK && Header.dwHeaderSize == sizeof(TMPKHeader) && Header.dwArchiveSize == (DWORD)FileSize) |
||||
{ |
||||
// The header ID must be ID_MPQ
|
||||
Header.dwID = ID_MPQ; |
||||
Header.wFormatVersion = MPQ_FORMAT_VERSION_1; |
||||
Header.wSectorSize = 3; |
||||
|
||||
// Initialize the fields of 3.0 header
|
||||
Header.ArchiveSize64 = Header.dwArchiveSize; |
||||
Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash); |
||||
Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock); |
||||
|
||||
// Copy the converted MPQ header back
|
||||
memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader)); |
||||
|
||||
// Mark this file as MPK file
|
||||
ha->pfnHashString = HashStringLower; |
||||
ha->dwFlags |= MPQ_FLAG_READ_ONLY; |
||||
ha->dwSubType = MPQ_SUBTYPE_MPK; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
return ERROR_FILE_CORRUPT; |
||||
} |
||||
|
||||
// Attempts to search a free hash entry in the hash table being converted.
|
||||
// The created hash table must always be of nonzero size,
|
||||
// should have no duplicated items and no deleted entries
|
||||
TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD dwStartIndex) |
||||
{ |
||||
TMPQHash * pHash; |
||||
DWORD dwIndex; |
||||
|
||||
// Set the initial index
|
||||
dwStartIndex = dwIndex = (dwStartIndex & (dwHashTableSize - 1)); |
||||
assert(dwHashTableSize != 0); |
||||
|
||||
// Search the hash table and return the found entries in the following priority:
|
||||
for(;;) |
||||
{ |
||||
// We are not expecting to find matching entry in the hash table being built
|
||||
// We are not expecting to find deleted entry either
|
||||
pHash = pHashTable + dwIndex; |
||||
|
||||
// If we found a free entry, we need to stop searching
|
||||
if(pHash->dwBlockIndex == HASH_ENTRY_FREE) |
||||
return pHash; |
||||
|
||||
// Move to the next hash entry.
|
||||
// If we reached the starting entry, it's failure.
|
||||
dwIndex = (dwIndex + 1) & (dwHashTableSize - 1); |
||||
if(dwIndex == dwStartIndex) |
||||
break; |
||||
} |
||||
|
||||
// We haven't found anything
|
||||
assert(false); |
||||
return NULL; |
||||
} |
||||
|
||||
void DecryptMpkTable(void * pvMpkTable, size_t cbSize) |
||||
{ |
||||
LPBYTE pbMpkTable = (LPBYTE)pvMpkTable; |
||||
|
||||
for(size_t i = 0; i < cbSize; i++) |
||||
pbMpkTable[i] = MpkDecryptionKey[pbMpkTable[i]]; |
||||
} |
||||
|
||||
void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize) |
||||
{ |
||||
ULONGLONG ByteOffset; |
||||
LPBYTE pbMpkTable = NULL; |
||||
|
||||
// Allocate space for the table
|
||||
pbMpkTable = STORM_ALLOC(BYTE, cbTableSize); |
||||
if(pbMpkTable != NULL) |
||||
{ |
||||
// Load and the MPK hash table
|
||||
ByteOffset = ha->MpqPos + dwByteOffset; |
||||
if(FileStream_Read(ha->pStream, &ByteOffset, pbMpkTable, cbTableSize)) |
||||
{ |
||||
// Decrypt the table
|
||||
DecryptMpkTable(pbMpkTable, cbTableSize); |
||||
return pbMpkTable; |
||||
} |
||||
|
||||
// Free the MPK table
|
||||
STORM_FREE(pbMpkTable); |
||||
pbMpkTable = NULL; |
||||
} |
||||
|
||||
// Return the table
|
||||
return pbMpkTable; |
||||
} |
||||
|
||||
TMPQHash * LoadMpkHashTable(TMPQArchive * ha) |
||||
{ |
||||
TMPQHeader * pHeader = ha->pHeader; |
||||
TMPQHash * pHashTable = NULL; |
||||
TMPKHash * pMpkHash; |
||||
TMPQHash * pHash = NULL; |
||||
DWORD dwHashTableSize = pHeader->dwHashTableSize; |
||||
|
||||
// MPKs use different hash table searching.
|
||||
// Instead of using MPQ_HASH_TABLE_INDEX hash as index,
|
||||
// they store the value directly in the hash table.
|
||||
// Also for faster searching, the hash table is sorted ascending by the value
|
||||
|
||||
// Load and decrypt the MPK hash table.
|
||||
pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash)); |
||||
if(pMpkHash != NULL) |
||||
{ |
||||
// Calculate the hash table size as if it was real MPQ hash table
|
||||
pHeader->dwHashTableSize = GetNearestPowerOfTwo(pHeader->dwHashTableSize); |
||||
pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); |
||||
|
||||
// Now allocate table that will serve like a true MPQ hash table,
|
||||
// so we translate the MPK hash table to MPQ hash table
|
||||
pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); |
||||
if(pHashTable != NULL) |
||||
{ |
||||
// Set the entire hash table to free
|
||||
memset(pHashTable, 0xFF, (size_t)pHeader->HashTableSize64); |
||||
|
||||
// Copy the MPK hash table into MPQ hash table
|
||||
for(DWORD i = 0; i < dwHashTableSize; i++) |
||||
{ |
||||
// Finds the free hash entry in the hash table
|
||||
// We don't expect any errors here, because we are putting files to empty hash table
|
||||
pHash = FindFreeHashEntry(pHashTable, pHeader->dwHashTableSize, pMpkHash[i].dwName1); |
||||
assert(pHash->dwBlockIndex == HASH_ENTRY_FREE); |
||||
|
||||
// Copy the MPK hash entry to the hash table
|
||||
pHash->dwBlockIndex = pMpkHash[i].dwBlockIndex; |
||||
pHash->Platform = 0; |
||||
pHash->lcLocale = 0; |
||||
pHash->dwName1 = pMpkHash[i].dwName2; |
||||
pHash->dwName2 = pMpkHash[i].dwName3; |
||||
} |
||||
} |
||||
|
||||
// Free the temporary hash table
|
||||
STORM_FREE(pMpkHash); |
||||
} |
||||
|
||||
return pHashTable; |
||||
} |
||||
|
||||
static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags) |
||||
{ |
||||
DWORD dwMpqFlags = MPQ_FILE_EXISTS; |
||||
|
||||
// Check for flags that are always present
|
||||
assert((dwMpkFlags & MPK_FILE_UNKNOWN_0001) != 0); |
||||
assert((dwMpkFlags & MPK_FILE_UNKNOWN_0010) != 0); |
||||
assert((dwMpkFlags & MPK_FILE_UNKNOWN_2000) != 0); |
||||
assert((dwMpkFlags & MPK_FILE_EXISTS) != 0); |
||||
|
||||
// Append the compressed flag
|
||||
dwMpqFlags |= (dwMpkFlags & MPK_FILE_COMPRESSED) ? MPQ_FILE_COMPRESS : 0; |
||||
|
||||
// All files in the MPQ seem to be single unit files
|
||||
dwMpqFlags |= MPQ_FILE_ENCRYPTED | MPQ_FILE_SINGLE_UNIT; |
||||
|
||||
return dwMpqFlags; |
||||
} |
||||
|
||||
TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha) |
||||
{ |
||||
TMPQHeader * pHeader = ha->pHeader; |
||||
TMPKBlock * pMpkBlockTable; |
||||
TMPKBlock * pMpkBlockEnd; |
||||
TMPQBlock * pBlockTable = NULL; |
||||
TMPKBlock * pMpkBlock; |
||||
TMPQBlock * pMpqBlock; |
||||
|
||||
// Load and decrypt the MPK block table
|
||||
pMpkBlockTable = pMpkBlock = (TMPKBlock *)LoadMpkTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TMPKBlock)); |
||||
if(pMpkBlockTable != NULL) |
||||
{ |
||||
// Allocate buffer for MPQ-like block table
|
||||
pBlockTable = pMpqBlock = STORM_ALLOC(TMPQBlock, pHeader->dwBlockTableSize); |
||||
if(pBlockTable != NULL) |
||||
{ |
||||
// Convert the MPK block table to MPQ block table
|
||||
pMpkBlockEnd = pMpkBlockTable + pHeader->dwBlockTableSize; |
||||
while(pMpkBlock < pMpkBlockEnd) |
||||
{ |
||||
// Translate the MPK block table entry to MPQ block table entry
|
||||
pMpqBlock->dwFilePos = pMpkBlock->dwFilePos; |
||||
pMpqBlock->dwCSize = pMpkBlock->dwCSize; |
||||
pMpqBlock->dwFSize = pMpkBlock->dwFSize; |
||||
pMpqBlock->dwFlags = ConvertMpkFlagsToMpqFlags(pMpkBlock->dwFlags); |
||||
|
||||
// Move both
|
||||
pMpkBlock++; |
||||
pMpqBlock++; |
||||
} |
||||
} |
||||
|
||||
// Free the MPK block table
|
||||
STORM_FREE(pMpkBlockTable); |
||||
} |
||||
|
||||
return pBlockTable; |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,64 @@
|
||||
/*****************************************************************************/ |
||||
/* SFileExtractFile.cpp Copyright (c) Ladislav Zezula 2003 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Simple extracting utility */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* 20.06.03 1.00 Lad The first version of SFileExtractFile.cpp */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define __STORMLIB_SELF__ |
||||
#include "StormLib.h" |
||||
#include "StormCommon.h" |
||||
|
||||
bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR * szExtracted, DWORD dwSearchScope) |
||||
{ |
||||
TFileStream * pLocalFile = NULL; |
||||
HANDLE hMpqFile = NULL; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Open the MPQ file
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
if(!SFileOpenFileEx(hMpq, szToExtract, dwSearchScope, &hMpqFile)) |
||||
nError = GetLastError(); |
||||
} |
||||
|
||||
// Create the local file
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
pLocalFile = FileStream_CreateFile(szExtracted, 0); |
||||
if(pLocalFile == NULL) |
||||
nError = GetLastError(); |
||||
} |
||||
|
||||
// Copy the file's content
|
||||
while(nError == ERROR_SUCCESS) |
||||
{ |
||||
char szBuffer[0x1000]; |
||||
DWORD dwTransferred = 0; |
||||
|
||||
// dwTransferred is only set to nonzero if something has been read.
|
||||
// nError can be ERROR_SUCCESS or ERROR_HANDLE_EOF
|
||||
if(!SFileReadFile(hMpqFile, szBuffer, sizeof(szBuffer), &dwTransferred, NULL)) |
||||
nError = GetLastError(); |
||||
if(nError == ERROR_HANDLE_EOF) |
||||
nError = ERROR_SUCCESS; |
||||
if(dwTransferred == 0) |
||||
break; |
||||
|
||||
// If something has been actually read, write it
|
||||
if(!FileStream_Write(pLocalFile, NULL, szBuffer, dwTransferred)) |
||||
nError = GetLastError(); |
||||
} |
||||
|
||||
// Close the files
|
||||
if(hMpqFile != NULL) |
||||
SFileCloseFile(hMpqFile); |
||||
if(pLocalFile != NULL) |
||||
FileStream_Close(pLocalFile); |
||||
if(nError != ERROR_SUCCESS) |
||||
SetLastError(nError); |
||||
return (nError == ERROR_SUCCESS); |
||||
} |
||||
@ -0,0 +1,481 @@
|
||||
/*****************************************************************************/ |
||||
/* SFileFindFile.cpp Copyright (c) Ladislav Zezula 2003 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* A module for file searching within MPQs */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* 25.03.03 1.00 Lad The first version of SFileFindFile.cpp */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define __STORMLIB_SELF__ |
||||
#include "StormLib.h" |
||||
#include "StormCommon.h" |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private structure used for file search (search handle)
|
||||
|
||||
// Used by searching in MPQ archives
|
||||
struct TMPQSearch |
||||
{ |
||||
TMPQArchive * ha; // Handle to MPQ, where the search runs
|
||||
TFileEntry ** pSearchTable; // Table for files that have been already found
|
||||
DWORD dwSearchTableItems; // Number of items in the search table
|
||||
DWORD dwNextIndex; // Next file index to be checked
|
||||
DWORD dwFlagMask; // For checking flag mask
|
||||
char szSearchMask[1]; // Search mask (variable length)
|
||||
}; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local functions
|
||||
|
||||
static TMPQSearch * IsValidSearchHandle(HANDLE hFind) |
||||
{ |
||||
TMPQSearch * hs = (TMPQSearch *)hFind; |
||||
|
||||
if(hs != NULL && IsValidMpqHandle(hs->ha)) |
||||
return hs; |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
bool CheckWildCard(const char * szString, const char * szWildCard) |
||||
{ |
||||
const char * szWildCardPtr; |
||||
|
||||
for(;;) |
||||
{ |
||||
// If there is '?' in the wildcard, we skip one char
|
||||
while(szWildCard[0] == '?') |
||||
{ |
||||
if(szString[0] == 0) |
||||
return false; |
||||
|
||||
szWildCard++; |
||||
szString++; |
||||
} |
||||
|
||||
// Handle '*'
|
||||
szWildCardPtr = szWildCard; |
||||
if(szWildCardPtr[0] != 0) |
||||
{ |
||||
if(szWildCardPtr[0] == '*') |
||||
{ |
||||
szWildCardPtr++; |
||||
|
||||
if(szWildCardPtr[0] == '*') |
||||
continue; |
||||
|
||||
if(szWildCardPtr[0] == 0) |
||||
return true; |
||||
|
||||
if(AsciiToUpperTable[szWildCardPtr[0]] == AsciiToUpperTable[szString[0]]) |
||||
{ |
||||
if(CheckWildCard(szString, szWildCardPtr)) |
||||
return true; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
if(AsciiToUpperTable[szWildCardPtr[0]] != AsciiToUpperTable[szString[0]]) |
||||
return false; |
||||
|
||||
szWildCard = szWildCardPtr + 1; |
||||
} |
||||
|
||||
if(szString[0] == 0) |
||||
return false; |
||||
szString++; |
||||
} |
||||
else |
||||
{ |
||||
return (szString[0] == 0) ? true : false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static DWORD GetSearchTableItems(TMPQArchive * ha) |
||||
{ |
||||
DWORD dwMergeItems = 0; |
||||
|
||||
// Loop over all patches
|
||||
while(ha != NULL) |
||||
{ |
||||
// Append the number of files
|
||||
dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount |
||||
: ha->pHeader->dwBlockTableSize; |
||||
// Move to the patched archive
|
||||
ha = ha->haPatch; |
||||
} |
||||
|
||||
// Return the double size of number of items
|
||||
return (dwMergeItems | 1); |
||||
} |
||||
|
||||
static bool FileWasFoundBefore( |
||||
TMPQArchive * ha, |
||||
TMPQSearch * hs, |
||||
TFileEntry * pFileEntry) |
||||
{ |
||||
TFileEntry * pEntry; |
||||
char * szRealFileName = pFileEntry->szFileName; |
||||
DWORD dwStartIndex; |
||||
DWORD dwNameHash; |
||||
DWORD dwIndex; |
||||
|
||||
if(hs->pSearchTable != NULL && szRealFileName != NULL) |
||||
{ |
||||
// If we are in patch MPQ, we check if patch prefix matches
|
||||
// and then trim the patch prefix
|
||||
if(ha->pPatchPrefix != NULL) |
||||
{ |
||||
// If the patch prefix doesn't fit, we pretend that the file
|
||||
// was there before and it will be skipped
|
||||
if(_strnicmp(szRealFileName, ha->pPatchPrefix->szPatchPrefix, ha->pPatchPrefix->nLength)) |
||||
return true; |
||||
|
||||
szRealFileName += ha->pPatchPrefix->nLength; |
||||
} |
||||
|
||||
// Calculate the hash to the table
|
||||
dwNameHash = ha->pfnHashString(szRealFileName, MPQ_HASH_NAME_A); |
||||
dwStartIndex = dwIndex = (dwNameHash % hs->dwSearchTableItems); |
||||
|
||||
// The file might have been found before
|
||||
// only if this is not the first MPQ being searched
|
||||
if(ha->haBase != NULL) |
||||
{ |
||||
// Enumerate all entries in the search table
|
||||
for(;;) |
||||
{ |
||||
// Get the file entry at that position
|
||||
pEntry = hs->pSearchTable[dwIndex]; |
||||
if(pEntry == NULL) |
||||
break; |
||||
|
||||
if(pEntry->szFileName != NULL) |
||||
{ |
||||
// Does the name match?
|
||||
if(!_stricmp(pEntry->szFileName, szRealFileName)) |
||||
return true; |
||||
} |
||||
|
||||
// Move to the next entry
|
||||
dwIndex = (dwIndex + 1) % hs->dwSearchTableItems; |
||||
if(dwIndex == dwStartIndex) |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Put the entry to the table for later use
|
||||
hs->pSearchTable[dwIndex] = pFileEntry; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry) |
||||
{ |
||||
TFileEntry * pPatchEntry = pFileEntry; |
||||
TFileEntry * pTempEntry; |
||||
char szFileName[MAX_PATH+1]; |
||||
|
||||
// Can't find patch entry for a file that doesn't have name
|
||||
if(pFileEntry->szFileName != NULL && pFileEntry->szFileName[0] != 0) |
||||
{ |
||||
// Go while there are patches
|
||||
while(ha->haPatch != NULL) |
||||
{ |
||||
// Move to the patch archive
|
||||
ha = ha->haPatch; |
||||
szFileName[0] = 0; |
||||
|
||||
// Prepare the prefix for the file name
|
||||
if(ha->pPatchPrefix && ha->pPatchPrefix->nLength) |
||||
StringCopy(szFileName, _countof(szFileName), ha->pPatchPrefix->szPatchPrefix); |
||||
StringCat(szFileName, _countof(szFileName), pFileEntry->szFileName); |
||||
|
||||
// Try to find the file there
|
||||
pTempEntry = GetFileEntryExact(ha, szFileName, 0, NULL); |
||||
if(pTempEntry != NULL) |
||||
pPatchEntry = pTempEntry; |
||||
} |
||||
} |
||||
|
||||
// Return the found patch entry
|
||||
return pPatchEntry; |
||||
} |
||||
|
||||
static bool DoMPQSearch_FileEntry( |
||||
TMPQSearch * hs, |
||||
SFILE_FIND_DATA * lpFindFileData, |
||||
TMPQArchive * ha, |
||||
TMPQHash * pHashEntry, |
||||
TFileEntry * pFileEntry) |
||||
{ |
||||
TFileEntry * pPatchEntry; |
||||
HANDLE hFile = NULL; |
||||
const char * szFileName; |
||||
size_t nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0; |
||||
DWORD dwBlockIndex; |
||||
char szNameBuff[MAX_PATH]; |
||||
|
||||
// Is it a file but not a patch file?
|
||||
if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS) |
||||
{ |
||||
// Now we have to check if this file was not enumerated before
|
||||
if(!FileWasFoundBefore(ha, hs, pFileEntry)) |
||||
{ |
||||
// if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy"))
|
||||
// DebugBreak();
|
||||
|
||||
// Find a patch to this file
|
||||
// Note: This either succeeds or returns pFileEntry
|
||||
pPatchEntry = FindPatchEntry(ha, pFileEntry); |
||||
|
||||
// Prepare the block index
|
||||
dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); |
||||
|
||||
// Get the file name. If it's not known, we will create pseudo-name
|
||||
szFileName = pFileEntry->szFileName; |
||||
if(szFileName == NULL) |
||||
{ |
||||
// Open the file by its pseudo-name.
|
||||
sprintf(szNameBuff, "File%08u.xxx", (unsigned int)dwBlockIndex); |
||||
if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile)) |
||||
{ |
||||
SFileGetFileName(hFile, szNameBuff); |
||||
szFileName = szNameBuff; |
||||
SFileCloseFile(hFile); |
||||
} |
||||
} |
||||
|
||||
// If the file name is still NULL, we cannot include the file to search results
|
||||
if(szFileName != NULL) |
||||
{ |
||||
// Check the file name against the wildcard
|
||||
if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) |
||||
{ |
||||
// Fill the found entry. hash entry and block index are taken from the base MPQ
|
||||
lpFindFileData->dwHashIndex = HASH_ENTRY_FREE; |
||||
lpFindFileData->dwBlockIndex = dwBlockIndex; |
||||
lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; |
||||
lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; |
||||
lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; |
||||
lpFindFileData->lcLocale = 0; // pPatchEntry->lcLocale;
|
||||
|
||||
// Fill the filetime
|
||||
lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); |
||||
lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); |
||||
|
||||
// Fill-in the entries from hash table entry, if given
|
||||
if(pHashEntry != NULL) |
||||
{ |
||||
lpFindFileData->dwHashIndex = (DWORD)(pHashEntry - ha->pHashTable); |
||||
lpFindFileData->lcLocale = pHashEntry->lcLocale; |
||||
} |
||||
|
||||
// Fill the file name and plain file name
|
||||
StringCopy(lpFindFileData->cFileName, _countof(lpFindFileData->cFileName), szFileName + nPrefixLength); |
||||
lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Either not a valid item or was found before
|
||||
return false; |
||||
} |
||||
|
||||
static int DoMPQSearch_HashTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha) |
||||
{ |
||||
TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; |
||||
TMPQHash * pHash; |
||||
|
||||
// Parse the file table
|
||||
for(pHash = ha->pHashTable + hs->dwNextIndex; pHash < pHashTableEnd; pHash++) |
||||
{ |
||||
// Increment the next index for subsequent search
|
||||
hs->dwNextIndex++; |
||||
|
||||
// Does this hash table entry point to a proper block table entry?
|
||||
if(IsValidHashEntry(ha, pHash)) |
||||
{ |
||||
// Check if this file entry should be included in the search result
|
||||
if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + MPQ_BLOCK_INDEX(pHash))) |
||||
return ERROR_SUCCESS; |
||||
} |
||||
} |
||||
|
||||
// No more files
|
||||
return ERROR_NO_MORE_FILES; |
||||
} |
||||
|
||||
static int DoMPQSearch_FileTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha) |
||||
{ |
||||
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; |
||||
TFileEntry * pFileEntry; |
||||
|
||||
// Parse the file table
|
||||
for(pFileEntry = ha->pFileTable + hs->dwNextIndex; pFileEntry < pFileTableEnd; pFileEntry++) |
||||
{ |
||||
// Increment the next index for subsequent search
|
||||
hs->dwNextIndex++; |
||||
|
||||
// Check if this file entry should be included in the search result
|
||||
if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, NULL, pFileEntry)) |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
// No more files
|
||||
return ERROR_NO_MORE_FILES; |
||||
} |
||||
|
||||
// Performs one MPQ search
|
||||
static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) |
||||
{ |
||||
TMPQArchive * ha = hs->ha; |
||||
int nError; |
||||
|
||||
// Start searching with base MPQ
|
||||
while(ha != NULL) |
||||
{ |
||||
// If the archive has hash table, we need to use hash table
|
||||
// in order to catch hash table index and file locale.
|
||||
// Note: If multiple hash table entries, point to the same block entry,
|
||||
// we need, to report them all
|
||||
nError = (ha->pHashTable != NULL) ? DoMPQSearch_HashTable(hs, lpFindFileData, ha) |
||||
: DoMPQSearch_FileTable(hs, lpFindFileData, ha); |
||||
if(nError == ERROR_SUCCESS) |
||||
return nError; |
||||
|
||||
// If there is no more patches in the chain, stop it.
|
||||
// This also keeps hs->ha non-NULL, which is required
|
||||
// for freeing the handle later
|
||||
if(ha->haPatch == NULL) |
||||
break; |
||||
|
||||
// Move to the next patch in the patch chain
|
||||
hs->ha = ha = ha->haPatch; |
||||
hs->dwNextIndex = 0; |
||||
} |
||||
|
||||
// No more files found, return error
|
||||
return ERROR_NO_MORE_FILES; |
||||
} |
||||
|
||||
static void FreeMPQSearch(TMPQSearch *& hs) |
||||
{ |
||||
if(hs != NULL) |
||||
{ |
||||
if(hs->pSearchTable != NULL) |
||||
STORM_FREE(hs->pSearchTable); |
||||
STORM_FREE(hs); |
||||
hs = NULL; |
||||
} |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
|
||||
HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DATA * lpFindFileData, const TCHAR * szListFile) |
||||
{ |
||||
TMPQArchive * ha = (TMPQArchive *)hMpq; |
||||
TMPQSearch * hs = NULL; |
||||
size_t nSize = 0; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Check for the valid parameters
|
||||
if(!IsValidMpqHandle(hMpq)) |
||||
nError = ERROR_INVALID_HANDLE; |
||||
if(szMask == NULL || lpFindFileData == NULL) |
||||
nError = ERROR_INVALID_PARAMETER; |
||||
|
||||
#ifdef FULL |
||||
// Include the listfile into the MPQ's internal listfile
|
||||
// Note that if the listfile name is NULL, do nothing because the
|
||||
// internal listfile is always included.
|
||||
if(nError == ERROR_SUCCESS && szListFile != NULL && *szListFile != 0) |
||||
nError = SFileAddListFile((HANDLE)ha, szListFile); |
||||
#endif |
||||
|
||||
// Allocate the structure for MPQ search
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
nSize = sizeof(TMPQSearch) + strlen(szMask) + 1; |
||||
if((hs = (TMPQSearch *)STORM_ALLOC(char, nSize)) == NULL) |
||||
nError = ERROR_NOT_ENOUGH_MEMORY; |
||||
} |
||||
|
||||
// Perform the first search
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
memset(hs, 0, sizeof(TMPQSearch)); |
||||
strcpy(hs->szSearchMask, szMask); |
||||
hs->dwFlagMask = MPQ_FILE_EXISTS; |
||||
hs->ha = ha; |
||||
|
||||
// If the archive is patched archive, we have to create a merge table
|
||||
// to prevent files being repeated
|
||||
if(ha->haPatch != NULL) |
||||
{ |
||||
hs->dwSearchTableItems = GetSearchTableItems(ha); |
||||
hs->pSearchTable = STORM_ALLOC(TFileEntry *, hs->dwSearchTableItems); |
||||
hs->dwFlagMask = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE; |
||||
if(hs->pSearchTable != NULL) |
||||
memset(hs->pSearchTable, 0, hs->dwSearchTableItems * sizeof(TFileEntry *)); |
||||
else |
||||
nError = ERROR_NOT_ENOUGH_MEMORY; |
||||
} |
||||
} |
||||
|
||||
// Perform first item searching
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
nError = DoMPQSearch(hs, lpFindFileData); |
||||
} |
||||
|
||||
// Cleanup
|
||||
if(nError != ERROR_SUCCESS) |
||||
{ |
||||
FreeMPQSearch(hs); |
||||
SetLastError(nError); |
||||
} |
||||
|
||||
// Return the result value
|
||||
return (HANDLE)hs; |
||||
} |
||||
|
||||
bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) |
||||
{ |
||||
TMPQSearch * hs = IsValidSearchHandle(hFind); |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Check the parameters
|
||||
if(hs == NULL) |
||||
nError = ERROR_INVALID_HANDLE; |
||||
if(lpFindFileData == NULL) |
||||
nError = ERROR_INVALID_PARAMETER; |
||||
|
||||
if(nError == ERROR_SUCCESS) |
||||
nError = DoMPQSearch(hs, lpFindFileData); |
||||
|
||||
if(nError != ERROR_SUCCESS) |
||||
SetLastError(nError); |
||||
return (nError == ERROR_SUCCESS); |
||||
} |
||||
|
||||
bool WINAPI SFileFindClose(HANDLE hFind) |
||||
{ |
||||
TMPQSearch * hs = IsValidSearchHandle(hFind); |
||||
|
||||
// Check the parameters
|
||||
if(hs == NULL) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return false; |
||||
} |
||||
|
||||
FreeMPQSearch(hs); |
||||
return true; |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,618 @@
|
||||
/*****************************************************************************/ |
||||
/* SFileOpenArchive.cpp Copyright Ladislav Zezula 1999 */ |
||||
/* */ |
||||
/* Author : Ladislav Zezula */ |
||||
/* E-mail : ladik@zezula.net */ |
||||
/* WWW : www.zezula.net */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Archive functions of Storm.dll */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* xx.xx.xx 1.00 Lad The first version of SFileOpenArchive.cpp */ |
||||
/* 19.11.03 1.01 Dan Big endian handling */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define __STORMLIB_SELF__ |
||||
#include "StormLib.h" |
||||
#include "StormCommon.h" |
||||
|
||||
#define HEADER_SEARCH_BUFFER_SIZE 0x1000 |
||||
|
||||
/*****************************************************************************/ |
||||
/* Local functions */ |
||||
/*****************************************************************************/ |
||||
|
||||
static bool IsAviFile(DWORD * HeaderData) |
||||
{ |
||||
DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderData[0]); |
||||
DWORD DwordValue2 = BSWAP_INT32_UNSIGNED(HeaderData[2]); |
||||
DWORD DwordValue3 = BSWAP_INT32_UNSIGNED(HeaderData[3]); |
||||
|
||||
// Test for 'RIFF', 'AVI ' or 'LIST'
|
||||
return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C); |
||||
} |
||||
|
||||
static bool IsWarcraft3Map(DWORD * HeaderData) |
||||
{ |
||||
DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderData[0]); |
||||
DWORD DwordValue1 = BSWAP_INT32_UNSIGNED(HeaderData[1]); |
||||
|
||||
return (DwordValue0 == 0x57334D48 && DwordValue1 == 0x00000000); |
||||
} |
||||
|
||||
static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData) |
||||
{ |
||||
TMPQUserData * pUserData; |
||||
|
||||
// BSWAP the source data and copy them to our buffer
|
||||
BSWAP_ARRAY32_UNSIGNED(&pvUserData, sizeof(TMPQUserData)); |
||||
pUserData = (TMPQUserData *)pvUserData; |
||||
|
||||
// Check the sizes
|
||||
if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs) |
||||
{ |
||||
// Move to the position given by the userdata
|
||||
ByteOffset += pUserData->dwHeaderOffs; |
||||
|
||||
// The MPQ header should be within range of the file size
|
||||
if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize) |
||||
{ |
||||
// Note: We should verify if there is the MPQ header.
|
||||
// However, the header could be at any position below that
|
||||
// that is multiplier of 0x200
|
||||
return (TMPQUserData *)pvUserData; |
||||
} |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
// This function gets the right positions of the hash table and the block table.
|
||||
static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize) |
||||
{ |
||||
TMPQHeader * pHeader = ha->pHeader; |
||||
ULONGLONG ByteOffset; |
||||
|
||||
// Check the begin of HET table
|
||||
if(pHeader->HetTablePos64) |
||||
{ |
||||
ByteOffset = ha->MpqPos + pHeader->HetTablePos64; |
||||
if(ByteOffset > FileSize) |
||||
return ERROR_BAD_FORMAT; |
||||
} |
||||
|
||||
// Check the begin of BET table
|
||||
if(pHeader->BetTablePos64) |
||||
{ |
||||
ByteOffset = ha->MpqPos + pHeader->BetTablePos64; |
||||
if(ByteOffset > FileSize) |
||||
return ERROR_BAD_FORMAT; |
||||
} |
||||
|
||||
// Check the begin of hash table
|
||||
if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) |
||||
{ |
||||
ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos)); |
||||
if(ByteOffset > FileSize) |
||||
return ERROR_BAD_FORMAT; |
||||
} |
||||
|
||||
// Check the begin of block table
|
||||
if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) |
||||
{ |
||||
ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos)); |
||||
if(ByteOffset > FileSize) |
||||
return ERROR_BAD_FORMAT; |
||||
} |
||||
|
||||
// Check the begin of hi-block table
|
||||
if(pHeader->HiBlockTablePos64 != 0) |
||||
{ |
||||
ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64; |
||||
if(ByteOffset > FileSize) |
||||
return ERROR_BAD_FORMAT; |
||||
} |
||||
|
||||
// All OK.
|
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
|
||||
/*****************************************************************************/ |
||||
/* Public functions */ |
||||
/*****************************************************************************/ |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileGetLocale and SFileSetLocale
|
||||
// Set the locale for all newly opened files
|
||||
|
||||
LCID WINAPI SFileGetLocale() |
||||
{ |
||||
return lcFileLocale; |
||||
} |
||||
|
||||
LCID WINAPI SFileSetLocale(LCID lcNewLocale) |
||||
{ |
||||
lcFileLocale = lcNewLocale; |
||||
return lcFileLocale; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileOpenArchive
|
||||
//
|
||||
// szFileName - MPQ archive file name to open
|
||||
// dwPriority - When SFileOpenFileEx called, this contains the search priority for searched archives
|
||||
// dwFlags - See MPQ_OPEN_XXX in StormLib.h
|
||||
// phMpq - Pointer to store open archive handle
|
||||
|
||||
bool WINAPI SFileOpenArchive( |
||||
const TCHAR * szMpqName, |
||||
DWORD dwPriority, |
||||
DWORD dwFlags, |
||||
HANDLE * phMpq) |
||||
{ |
||||
TMPQUserData * pUserData; |
||||
TFileStream * pStream = NULL; // Open file stream
|
||||
TMPQArchive * ha = NULL; // Archive handle
|
||||
TFileEntry * pFileEntry; |
||||
ULONGLONG FileSize = 0; // Size of the file
|
||||
LPBYTE pbHeaderBuffer = NULL; // Buffer for searching MPQ header
|
||||
DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK); |
||||
bool bIsWarcraft3Map = false; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Verify the parameters
|
||||
if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) |
||||
{ |
||||
SetLastError(ERROR_INVALID_PARAMETER); |
||||
return false; |
||||
} |
||||
|
||||
// One time initialization of MPQ cryptography
|
||||
InitializeMpqCryptography(); |
||||
dwPriority = dwPriority; |
||||
|
||||
// If not forcing MPQ v 1.0, also use file bitmap
|
||||
dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP; |
||||
|
||||
#ifndef FULL |
||||
char translatedName[260]; |
||||
TranslateFileName(translatedName, sizeof(translatedName), szMpqName); |
||||
#endif |
||||
|
||||
// Open the MPQ archive file
|
||||
pStream = FileStream_OpenFile(translatedName, dwStreamFlags); |
||||
if(pStream == NULL) |
||||
return false; |
||||
|
||||
// Check the file size. There must be at least 0x20 bytes
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
FileStream_GetSize(pStream, &FileSize); |
||||
if(FileSize < MPQ_HEADER_SIZE_V1) |
||||
nError = ERROR_BAD_FORMAT; |
||||
} |
||||
|
||||
// Allocate the MPQhandle
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) |
||||
nError = ERROR_NOT_ENOUGH_MEMORY; |
||||
} |
||||
|
||||
// Allocate buffer for searching MPQ header
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
pbHeaderBuffer = STORM_ALLOC(BYTE, HEADER_SEARCH_BUFFER_SIZE); |
||||
if(pbHeaderBuffer == NULL) |
||||
nError = ERROR_NOT_ENOUGH_MEMORY; |
||||
} |
||||
|
||||
// Find the position of MPQ header
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
ULONGLONG SearchOffset = 0; |
||||
ULONGLONG EndOfSearch = FileSize; |
||||
DWORD dwStrmFlags = 0; |
||||
DWORD dwHeaderSize; |
||||
DWORD dwHeaderID; |
||||
bool bSearchComplete = false; |
||||
|
||||
memset(ha, 0, sizeof(TMPQArchive)); |
||||
ha->pfnHashString = HashStringSlash; |
||||
ha->pStream = pStream; |
||||
pStream = NULL; |
||||
|
||||
// Set the archive read only if the stream is read-only
|
||||
FileStream_GetFlags(ha->pStream, &dwStrmFlags); |
||||
ha->dwFlags |= (dwStrmFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0; |
||||
|
||||
// Also remember if we shall check sector CRCs when reading file
|
||||
ha->dwFlags |= (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ? MPQ_FLAG_CHECK_SECTOR_CRC : 0; |
||||
|
||||
// Also remember if this MPQ is a patch
|
||||
ha->dwFlags |= (dwFlags & MPQ_OPEN_PATCH) ? MPQ_FLAG_PATCH : 0; |
||||
|
||||
// Limit the header searching to about 130 MB of data
|
||||
if(EndOfSearch > 0x08000000) |
||||
EndOfSearch = 0x08000000; |
||||
|
||||
// Find the offset of MPQ header within the file
|
||||
while(bSearchComplete == false && SearchOffset < EndOfSearch) |
||||
{ |
||||
// Always read at least 0x1000 bytes for performance.
|
||||
// This is what Storm.dll (2002) does.
|
||||
DWORD dwBytesAvailable = HEADER_SEARCH_BUFFER_SIZE; |
||||
DWORD dwInBufferOffset = 0; |
||||
|
||||
// Cut the bytes available, if needed
|
||||
if((FileSize - SearchOffset) < HEADER_SEARCH_BUFFER_SIZE) |
||||
dwBytesAvailable = (DWORD)(FileSize - SearchOffset); |
||||
|
||||
// Read the eventual MPQ header
|
||||
if(!FileStream_Read(ha->pStream, &SearchOffset, pbHeaderBuffer, dwBytesAvailable)) |
||||
{ |
||||
nError = GetLastError(); |
||||
break; |
||||
} |
||||
|
||||
// There are AVI files from Warcraft III with 'MPQ' extension.
|
||||
if(SearchOffset == 0) |
||||
{ |
||||
if(IsAviFile((DWORD *)pbHeaderBuffer)) |
||||
{ |
||||
nError = ERROR_AVI_FILE; |
||||
break; |
||||
} |
||||
|
||||
bIsWarcraft3Map = IsWarcraft3Map((DWORD *)pbHeaderBuffer); |
||||
} |
||||
|
||||
// Search the header buffer
|
||||
while(dwInBufferOffset < dwBytesAvailable) |
||||
{ |
||||
// Copy the data from the potential header buffer to the MPQ header
|
||||
memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData)); |
||||
|
||||
// If there is the MPQ user data, process it
|
||||
// Note that Warcraft III does not check for user data, which is abused by many map protectors
|
||||
dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]); |
||||
if(bIsWarcraft3Map == false && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) |
||||
{ |
||||
if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA) |
||||
{ |
||||
// Verify if this looks like a valid user data
|
||||
pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); |
||||
if(pUserData != NULL) |
||||
{ |
||||
// Fill the user data header
|
||||
ha->UserDataPos = SearchOffset; |
||||
ha->pUserData = &ha->UserData; |
||||
memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); |
||||
|
||||
// Continue searching from that position
|
||||
SearchOffset += ha->pUserData->dwHeaderOffs; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// There must be MPQ header signature. Note that STORM.dll from Warcraft III actually
|
||||
// tests the MPQ header size. It must be at least 0x20 bytes in order to load it
|
||||
// Abused by Spazzler Map protector. Note that the size check is not present
|
||||
// in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway.
|
||||
dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]); |
||||
if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1) |
||||
{ |
||||
// Now convert the header to version 4
|
||||
nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags, bIsWarcraft3Map); |
||||
bSearchComplete = true; |
||||
break; |
||||
} |
||||
|
||||
// Check for MPK archives (Longwu Online - MPQ fork)
|
||||
if(bIsWarcraft3Map == false && dwHeaderID == ID_MPK) |
||||
{ |
||||
// Now convert the MPK header to MPQ Header version 4
|
||||
nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags); |
||||
bSearchComplete = true; |
||||
break; |
||||
} |
||||
|
||||
// If searching for the MPQ header is disabled, return an error
|
||||
if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH) |
||||
{ |
||||
nError = ERROR_NOT_SUPPORTED; |
||||
bSearchComplete = true; |
||||
break; |
||||
} |
||||
|
||||
// Move the pointers
|
||||
SearchOffset += 0x200; |
||||
dwInBufferOffset += 0x200; |
||||
} |
||||
} |
||||
|
||||
// Did we identify one of the supported headers?
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
// Set the user data position to the MPQ header, if none
|
||||
if(ha->pUserData == NULL) |
||||
ha->UserDataPos = SearchOffset; |
||||
|
||||
// Set the position of the MPQ header
|
||||
ha->pHeader = (TMPQHeader *)ha->HeaderData; |
||||
ha->MpqPos = SearchOffset; |
||||
ha->FileSize = FileSize; |
||||
|
||||
// Sector size must be nonzero.
|
||||
if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0) |
||||
nError = ERROR_BAD_FORMAT; |
||||
} |
||||
} |
||||
|
||||
// Fix table positions according to format
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
// Dump the header
|
||||
// DumpMpqHeader(ha->pHeader);
|
||||
|
||||
// W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data,
|
||||
// and ignores the MPQ format version as well. The trick is to
|
||||
// fake MPQ format 2, with an improper hi-word position of hash table and block table
|
||||
// We can overcome such protectors by forcing opening the archive as MPQ v 1.0
|
||||
if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) |
||||
{ |
||||
ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; |
||||
ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; |
||||
ha->dwFlags |= MPQ_FLAG_READ_ONLY; |
||||
ha->pUserData = NULL; |
||||
} |
||||
|
||||
// Anti-overflow. If the hash table size in the header is
|
||||
// higher than 0x10000000, it would overflow in 32-bit version
|
||||
// Observed in the malformed Warcraft III maps
|
||||
// Example map: MPQ_2016_v1_ProtectedMap_TableSizeOverflow.w3x
|
||||
ha->pHeader->dwBlockTableSize = (ha->pHeader->dwBlockTableSize & BLOCK_INDEX_MASK); |
||||
ha->pHeader->dwHashTableSize = (ha->pHeader->dwHashTableSize & BLOCK_INDEX_MASK); |
||||
|
||||
// Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode
|
||||
if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) |
||||
ha->dwFlags |= MPQ_FLAG_READ_ONLY; |
||||
|
||||
// Remember whether whis is a map for Warcraft III
|
||||
if(bIsWarcraft3Map) |
||||
ha->dwFlags |= MPQ_FLAG_WAR3_MAP; |
||||
|
||||
// Set the size of file sector
|
||||
ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); |
||||
|
||||
// Verify if any of the tables doesn't start beyond the end of the file
|
||||
nError = VerifyMpqTablePositions(ha, FileSize); |
||||
} |
||||
|
||||
// Read the hash table. Ignore the result, as hash table is no longer required
|
||||
// Read HET table. Ignore the result, as HET table is no longer required
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
nError = LoadAnyHashTable(ha); |
||||
} |
||||
|
||||
// Now, build the file table. It will be built by combining
|
||||
// the block table, BET table, hi-block table, (attributes) and (listfile).
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
nError = BuildFileTable(ha); |
||||
} |
||||
|
||||
#ifdef FULL |
||||
// Load the internal listfile and include it to the file table
|
||||
if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) |
||||
{ |
||||
// Quick check for (listfile)
|
||||
pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL); |
||||
if(pFileEntry != NULL) |
||||
{ |
||||
// Ignore result of the operation. (listfile) is optional.
|
||||
SFileAddListFile((HANDLE)ha, NULL); |
||||
ha->dwFileFlags1 = pFileEntry->dwFlags; |
||||
} |
||||
} |
||||
|
||||
// Load the "(attributes)" file and merge it to the file table
|
||||
if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0 && (ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0) |
||||
{ |
||||
// Quick check for (attributes)
|
||||
pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); |
||||
if(pFileEntry != NULL) |
||||
{ |
||||
// Ignore result of the operation. (attributes) is optional.
|
||||
SAttrLoadAttributes(ha); |
||||
ha->dwFileFlags2 = pFileEntry->dwFlags; |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
// Remember whether the archive has weak signature. Only for MPQs format 1.0.
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
// Quick check for (signature)
|
||||
pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL); |
||||
if(pFileEntry != NULL) |
||||
{ |
||||
// Just remember that the archive is weak-signed
|
||||
assert((pFileEntry->dwFlags & MPQ_FILE_EXISTS) != 0); |
||||
ha->dwFileFlags3 = pFileEntry->dwFlags; |
||||
} |
||||
|
||||
// Finally, set the MPQ_FLAG_READ_ONLY if the MPQ was found malformed
|
||||
ha->dwFlags |= (ha->dwFlags & MPQ_FLAG_MALFORMED) ? MPQ_FLAG_READ_ONLY : 0; |
||||
} |
||||
|
||||
// Cleanup and exit
|
||||
if(nError != ERROR_SUCCESS) |
||||
{ |
||||
FileStream_Close(pStream); |
||||
FreeArchiveHandle(ha); |
||||
SetLastError(nError); |
||||
ha = NULL; |
||||
} |
||||
|
||||
// Free the header buffer
|
||||
if(pbHeaderBuffer != NULL) |
||||
STORM_FREE(pbHeaderBuffer); |
||||
if(phMpq != NULL) |
||||
*phMpq = ha; |
||||
return (nError == ERROR_SUCCESS); |
||||
} |
||||
|
||||
|
||||
#ifdef FULL |
||||
//-----------------------------------------------------------------------------
|
||||
// bool WINAPI SFileSetDownloadCallback(HANDLE, SFILE_DOWNLOAD_CALLBACK, void *);
|
||||
//
|
||||
// Sets a callback that is called when content is downloaded from the master MPQ
|
||||
//
|
||||
|
||||
bool WINAPI SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK DownloadCB, void * pvUserData) |
||||
{ |
||||
TMPQArchive * ha = (TMPQArchive *)hMpq; |
||||
|
||||
// Do nothing if 'hMpq' is bad parameter
|
||||
if(!IsValidMpqHandle(hMpq)) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return false; |
||||
} |
||||
|
||||
return FileStream_SetCallback(ha->pStream, DownloadCB, pvUserData); |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// bool SFileFlushArchive(HANDLE hMpq)
|
||||
//
|
||||
// Saves all dirty data into MPQ archive.
|
||||
// Has similar effect like SFileCloseArchive, but the archive is not closed.
|
||||
// Use on clients who keep MPQ archive open even for write operations,
|
||||
// and terminating without calling SFileCloseArchive might corrupt the archive.
|
||||
//
|
||||
|
||||
bool WINAPI SFileFlushArchive(HANDLE hMpq) |
||||
{ |
||||
TMPQArchive * ha; |
||||
int nResultError = ERROR_SUCCESS; |
||||
int nError; |
||||
|
||||
// Do nothing if 'hMpq' is bad parameter
|
||||
if((ha = IsValidMpqHandle(hMpq)) == NULL) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return false; |
||||
} |
||||
|
||||
// Only if the MPQ was changed
|
||||
if(ha->dwFlags & MPQ_FLAG_CHANGED) |
||||
{ |
||||
// Indicate that we are saving MPQ internal structures
|
||||
ha->dwFlags |= MPQ_FLAG_SAVING_TABLES; |
||||
|
||||
// Defragment the file table. This will allow us to put the internal files to the end
|
||||
DefragmentFileTable(ha); |
||||
|
||||
//
|
||||
// Create each internal file
|
||||
// Note that the (signature) file is usually before (listfile) in the file table
|
||||
//
|
||||
|
||||
if(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW) |
||||
{ |
||||
#ifdef FULL |
||||
nError = SSignFileCreate(ha); |
||||
if(nError != ERROR_SUCCESS) |
||||
nResultError = nError; |
||||
#else |
||||
assert(0); |
||||
#endif |
||||
} |
||||
|
||||
if(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW) |
||||
{ |
||||
nError = SListFileSaveToMpq(ha); |
||||
if(nError != ERROR_SUCCESS) |
||||
nResultError = nError; |
||||
} |
||||
|
||||
if(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW) |
||||
{ |
||||
nError = SAttrFileSaveToMpq(ha); |
||||
if(nError != ERROR_SUCCESS) |
||||
nResultError = nError; |
||||
} |
||||
|
||||
// Save HET table, BET table, hash table, block table, hi-block table
|
||||
if(ha->dwFlags & MPQ_FLAG_CHANGED) |
||||
{ |
||||
// Rebuild the HET table
|
||||
if(ha->pHetTable != NULL) |
||||
RebuildHetTable(ha); |
||||
|
||||
// Save all MPQ tables first
|
||||
nError = SaveMPQTables(ha); |
||||
if(nError != ERROR_SUCCESS) |
||||
nResultError = nError; |
||||
|
||||
// If the archive has weak signature, we need to finish it
|
||||
if(ha->dwFileFlags3 != 0) |
||||
{ |
||||
#ifdef FULL |
||||
nError = SSignFileFinish(ha); |
||||
if(nError != ERROR_SUCCESS) |
||||
nResultError = nError; |
||||
#else |
||||
assert(0); |
||||
#endif |
||||
} |
||||
} |
||||
|
||||
// We are no longer saving internal MPQ structures
|
||||
ha->dwFlags &= ~MPQ_FLAG_SAVING_TABLES; |
||||
} |
||||
|
||||
// Return the error
|
||||
if(nResultError != ERROR_SUCCESS) |
||||
SetLastError(nResultError); |
||||
return (nResultError == ERROR_SUCCESS); |
||||
} |
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// bool SFileCloseArchive(HANDLE hMpq);
|
||||
//
|
||||
|
||||
bool WINAPI SFileCloseArchive(HANDLE hMpq) |
||||
{ |
||||
TMPQArchive * ha = IsValidMpqHandle(hMpq); |
||||
bool bResult = true; |
||||
|
||||
// Only if the handle is valid
|
||||
if(ha == NULL) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return false; |
||||
} |
||||
|
||||
// Invalidate the add file callback so it won't be called
|
||||
// when saving (listfile) and (attributes)
|
||||
ha->pfnAddFileCB = NULL; |
||||
ha->pvAddFileUserData = NULL; |
||||
|
||||
#ifdef FULL |
||||
// Flush all unsaved data to the storage
|
||||
bResult = SFileFlushArchive(hMpq); |
||||
#endif |
||||
|
||||
// Free all memory used by MPQ archive
|
||||
FreeArchiveHandle(ha); |
||||
return bResult; |
||||
} |
||||
@ -0,0 +1,396 @@
|
||||
/*****************************************************************************/ |
||||
/* SFileOpenFileEx.cpp Copyright (c) Ladislav Zezula 2003 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Description : */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* xx.xx.99 1.00 Lad The first version of SFileOpenFileEx.cpp */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define __STORMLIB_SELF__ |
||||
#include "StormLib.h" |
||||
#include "StormCommon.h" |
||||
|
||||
/*****************************************************************************/ |
||||
/* Local functions */ |
||||
/*****************************************************************************/ |
||||
|
||||
static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex) |
||||
{ |
||||
TMPQHash * pHashTableEnd; |
||||
TMPQHash * pHash; |
||||
DWORD dwFirstIndex = HASH_ENTRY_FREE; |
||||
|
||||
// Should only be called if the archive has hash table
|
||||
assert(ha->pHashTable != NULL); |
||||
|
||||
// Multiple hash table entries can point to the file table entry.
|
||||
// We need to search all of them
|
||||
pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; |
||||
for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++) |
||||
{ |
||||
if(MPQ_BLOCK_INDEX(pHash) == dwFileIndex) |
||||
{ |
||||
// Duplicate hash entry found
|
||||
if(dwFirstIndex != HASH_ENTRY_FREE) |
||||
return HASH_ENTRY_FREE; |
||||
dwFirstIndex = (DWORD)(pHash - ha->pHashTable); |
||||
} |
||||
} |
||||
|
||||
// Return the hash table entry index
|
||||
return dwFirstIndex; |
||||
} |
||||
|
||||
static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer) |
||||
{ |
||||
TMPQNamePrefix * pPrefix; |
||||
|
||||
// Are there patches in the current MPQ?
|
||||
if(ha->dwFlags & MPQ_FLAG_PATCH) |
||||
{ |
||||
// The patch prefix must be already known here
|
||||
assert(ha->pPatchPrefix != NULL); |
||||
pPrefix = ha->pPatchPrefix; |
||||
|
||||
// The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY"
|
||||
// We need to remove the "OldWorld\\" prefix
|
||||
if(!_strnicmp(szFileName, "OldWorld\\", 9)) |
||||
szFileName += 9; |
||||
|
||||
// Create the file name from the known patch entry
|
||||
memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength); |
||||
strcpy(szBuffer + pPrefix->nLength, szFileName); |
||||
szFileName = szBuffer; |
||||
} |
||||
|
||||
return szFileName; |
||||
} |
||||
|
||||
static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile) |
||||
{ |
||||
TFileStream * pStream; |
||||
TMPQFile * hf = NULL; |
||||
TCHAR szFileNameT[MAX_PATH]; |
||||
|
||||
// Convert the file name to UNICODE (if needed)
|
||||
StringCopy(szFileNameT, _countof(szFileNameT), szFileName); |
||||
|
||||
// Open the file and create the TMPQFile structure
|
||||
pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY); |
||||
if(pStream != NULL) |
||||
{ |
||||
// Allocate and initialize file handle
|
||||
hf = CreateFileHandle(NULL, NULL); |
||||
if(hf != NULL) |
||||
{ |
||||
hf->pStream = pStream; |
||||
*PtrFile = hf; |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
FileStream_Close(pStream); |
||||
SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
||||
} |
||||
} |
||||
*PtrFile = NULL; |
||||
return false; |
||||
} |
||||
|
||||
bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile) |
||||
{ |
||||
TMPQArchive * haBase = NULL; |
||||
TMPQArchive * ha = (TMPQArchive *)hMpq; |
||||
TFileEntry * pFileEntry; |
||||
TMPQFile * hfPatch; // Pointer to patch file
|
||||
TMPQFile * hfBase = NULL; // Pointer to base open file
|
||||
TMPQFile * hf = NULL; |
||||
HANDLE hPatchFile; |
||||
char szNameBuffer[MAX_PATH]; |
||||
|
||||
// First of all, find the latest archive where the file is in base version
|
||||
// (i.e. where the original, unpatched version of the file exists)
|
||||
while(ha != NULL) |
||||
{ |
||||
// If the file is there, then we remember the archive
|
||||
pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL); |
||||
if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) |
||||
haBase = ha; |
||||
|
||||
// Move to the patch archive
|
||||
ha = ha->haPatch; |
||||
} |
||||
|
||||
// If we couldn't find the base file in any of the patches, it doesn't exist
|
||||
if((ha = haBase) == NULL) |
||||
{ |
||||
SetLastError(ERROR_FILE_NOT_FOUND); |
||||
return false; |
||||
} |
||||
|
||||
// Now open the base file
|
||||
if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) |
||||
{ |
||||
// The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
|
||||
assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0); |
||||
hf = hfBase; |
||||
|
||||
// Now open all patches and attach them on top of the base file
|
||||
for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) |
||||
{ |
||||
// Prepare the file name with a correct prefix
|
||||
if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) |
||||
{ |
||||
// Remember the new version
|
||||
hfPatch = (TMPQFile *)hPatchFile; |
||||
|
||||
// We should not find patch file
|
||||
assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0); |
||||
|
||||
// Attach the patch to the base file
|
||||
hf->hfPatch = hfPatch; |
||||
hf = hfPatch; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Give the updated base MPQ
|
||||
if(PtrFile != NULL) |
||||
*PtrFile = (HANDLE)hfBase; |
||||
return (hfBase != NULL); |
||||
} |
||||
|
||||
/*****************************************************************************/ |
||||
/* Public functions */ |
||||
/*****************************************************************************/ |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileEnumLocales enums all locale versions within MPQ.
|
||||
// Functions fills all available language identifiers on a file into the buffer
|
||||
// pointed by plcLocales. There must be enough entries to copy the localed,
|
||||
// otherwise the function returns ERROR_INSUFFICIENT_BUFFER.
|
||||
|
||||
int WINAPI SFileEnumLocales( |
||||
HANDLE hMpq, |
||||
const char * szFileName, |
||||
LCID * PtrLocales, |
||||
LPDWORD PtrMaxLocales, |
||||
DWORD dwSearchScope) |
||||
{ |
||||
TMPQArchive * ha = (TMPQArchive *)hMpq; |
||||
TMPQHash * pFirstHash; |
||||
TMPQHash * pHash; |
||||
DWORD dwFileIndex = 0; |
||||
DWORD dwMaxLocales; |
||||
DWORD dwLocales = 0; |
||||
|
||||
// Test the parameters
|
||||
if(!IsValidMpqHandle(hMpq)) |
||||
return ERROR_INVALID_HANDLE; |
||||
if(szFileName == NULL || *szFileName == 0) |
||||
return ERROR_INVALID_PARAMETER; |
||||
if(ha->pHashTable == NULL) |
||||
return ERROR_NOT_SUPPORTED; |
||||
if(PtrMaxLocales == NULL) |
||||
return ERROR_INVALID_PARAMETER; |
||||
if(IsPseudoFileName(szFileName, &dwFileIndex)) |
||||
return ERROR_INVALID_PARAMETER; |
||||
|
||||
// Keep compiler happy
|
||||
dwMaxLocales = PtrMaxLocales[0]; |
||||
dwSearchScope = dwSearchScope; |
||||
|
||||
// Parse all files with that name
|
||||
pFirstHash = pHash = GetFirstHashEntry(ha, szFileName); |
||||
while(pHash != NULL) |
||||
{ |
||||
// Put the locales to the buffer
|
||||
if(PtrLocales != NULL && dwLocales < dwMaxLocales) |
||||
*PtrLocales++ = pHash->lcLocale; |
||||
dwLocales++; |
||||
|
||||
// Get the next locale
|
||||
pHash = GetNextHashEntry(ha, pFirstHash, pHash); |
||||
} |
||||
|
||||
// Give the caller the number of locales and return
|
||||
PtrMaxLocales[0] = dwLocales; |
||||
return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileOpenFileEx
|
||||
//
|
||||
// hMpq - Handle of opened MPQ archive
|
||||
// szFileName - Name of file to open
|
||||
// dwSearchScope - Where to search
|
||||
// PtrFile - Pointer to store opened file handle
|
||||
|
||||
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile) |
||||
{ |
||||
TMPQArchive * ha = IsValidMpqHandle(hMpq); |
||||
TFileEntry * pFileEntry = NULL; |
||||
TMPQFile * hf = NULL; |
||||
DWORD dwHashIndex = HASH_ENTRY_FREE; |
||||
DWORD dwFileIndex = 0; |
||||
bool bOpenByIndex = false; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Don't accept NULL pointer to file handle
|
||||
if(szFileName == NULL || *szFileName == 0) |
||||
nError = ERROR_INVALID_PARAMETER; |
||||
|
||||
// When opening a file from MPQ, the handle must be valid
|
||||
if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL) |
||||
nError = ERROR_INVALID_HANDLE; |
||||
|
||||
// When not checking for existence, the pointer to file handle must be valid
|
||||
if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL) |
||||
nError = ERROR_INVALID_PARAMETER; |
||||
|
||||
// Prepare the file opening
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
switch(dwSearchScope) |
||||
{ |
||||
case SFILE_OPEN_FROM_MPQ: |
||||
case SFILE_OPEN_BASE_FILE: |
||||
case SFILE_OPEN_CHECK_EXISTS: |
||||
|
||||
// If this MPQ has no patches, open the file from this MPQ directly
|
||||
if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE) |
||||
{ |
||||
pFileEntry = GetFileEntryLocale2(ha, szFileName, lcFileLocale, &dwHashIndex); |
||||
} |
||||
|
||||
// If this MPQ is a patched archive, open the file as patched
|
||||
else |
||||
{ |
||||
return OpenPatchedFile(hMpq, szFileName, PtrFile); |
||||
} |
||||
break; |
||||
|
||||
case SFILE_OPEN_ANY_LOCALE: |
||||
|
||||
// This open option is reserved for opening MPQ internal listfile.
|
||||
// No argument validation. Tries to open file with neutral locale first,
|
||||
// then any other available.
|
||||
pFileEntry = GetFileEntryLocale2(ha, szFileName, 0, &dwHashIndex); |
||||
break; |
||||
|
||||
case SFILE_OPEN_LOCAL_FILE: |
||||
|
||||
// Open a local file
|
||||
return OpenLocalFile(szFileName, PtrFile);
|
||||
|
||||
default: |
||||
|
||||
// Don't accept any other value
|
||||
nError = ERROR_INVALID_PARAMETER; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Check whether the file really exists in the MPQ
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
if(pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) |
||||
{ |
||||
// Check the pseudo-file name
|
||||
if((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true) |
||||
{ |
||||
// Get the file entry for the file
|
||||
if(dwFileIndex < ha->dwFileTableSize) |
||||
{ |
||||
pFileEntry = ha->pFileTable + dwFileIndex; |
||||
} |
||||
} |
||||
|
||||
nError = ERROR_FILE_NOT_FOUND; |
||||
} |
||||
|
||||
// Ignore unknown loading flags (example: MPQ_2016_v1_WME4_4.w3x)
|
||||
// if(pFileEntry != NULL && pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS)
|
||||
// nError = ERROR_NOT_SUPPORTED;
|
||||
} |
||||
|
||||
// Did the caller just wanted to know if the file exists?
|
||||
if(nError == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS) |
||||
{ |
||||
// Allocate file handle
|
||||
hf = CreateFileHandle(ha, pFileEntry); |
||||
if(hf != NULL) |
||||
{ |
||||
// Get the hash index for the file
|
||||
if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE) |
||||
dwHashIndex = FindHashIndex(ha, dwFileIndex); |
||||
if(dwHashIndex != HASH_ENTRY_FREE) |
||||
hf->pHashEntry = ha->pHashTable + dwHashIndex; |
||||
hf->dwHashIndex = dwHashIndex; |
||||
|
||||
// If the MPQ has sector CRC enabled, enable if for the file
|
||||
if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC) |
||||
hf->bCheckSectorCRCs = true; |
||||
|
||||
// If we know the real file name, copy it to the file entry
|
||||
if(bOpenByIndex == false) |
||||
{ |
||||
// If there is no file name yet, allocate it
|
||||
AllocateFileName(ha, pFileEntry, szFileName); |
||||
|
||||
// If the file is encrypted, we should detect the file key
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) |
||||
{ |
||||
hf->dwFileKey = DecryptFileKey(szFileName, |
||||
pFileEntry->ByteOffset, |
||||
pFileEntry->dwFileSize, |
||||
pFileEntry->dwFlags); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
nError = ERROR_NOT_ENOUGH_MEMORY; |
||||
} |
||||
} |
||||
|
||||
// Give the file entry
|
||||
if(PtrFile != NULL) |
||||
PtrFile[0] = hf; |
||||
|
||||
// Return error code
|
||||
if(nError != ERROR_SUCCESS) |
||||
SetLastError(nError); |
||||
return (nError == ERROR_SUCCESS); |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileHasFile
|
||||
//
|
||||
// hMpq - Handle of opened MPQ archive
|
||||
// szFileName - Name of file to look for
|
||||
|
||||
bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) |
||||
{ |
||||
return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL); |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// bool WINAPI SFileCloseFile(HANDLE hFile);
|
||||
|
||||
bool WINAPI SFileCloseFile(HANDLE hFile) |
||||
{ |
||||
TMPQFile * hf = (TMPQFile *)hFile; |
||||
|
||||
if(!IsValidFileHandle(hFile)) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return false; |
||||
} |
||||
|
||||
// Free the structure
|
||||
FreeFileHandle(hf); |
||||
return true; |
||||
} |
||||
@ -0,0 +1,907 @@
|
||||
/*****************************************************************************/ |
||||
/* SFileReadFile.cpp Copyright (c) Ladislav Zezula 2003 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Description : */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* xx.xx.99 1.00 Lad The first version of SFileReadFile.cpp */ |
||||
/* 24.03.99 1.00 Lad Added the SFileGetFileInfo function */ |
||||
/*****************************************************************************/ |
||||
|
||||
#define __STORMLIB_SELF__ |
||||
#include "StormLib.h" |
||||
#include "StormCommon.h" |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local functions
|
||||
|
||||
// hf - MPQ File handle.
|
||||
// pbBuffer - Pointer to target buffer to store sectors.
|
||||
// dwByteOffset - Position of sector in the file (relative to file begin)
|
||||
// dwBytesToRead - Number of bytes to read. Must be multiplier of sector size.
|
||||
// pdwBytesRead - Stored number of bytes loaded
|
||||
static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead) |
||||
{ |
||||
ULONGLONG RawFilePos; |
||||
TMPQArchive * ha = hf->ha; |
||||
TFileEntry * pFileEntry = hf->pFileEntry; |
||||
LPBYTE pbRawSector = NULL; |
||||
LPBYTE pbOutSector = pbBuffer; |
||||
LPBYTE pbInSector = pbBuffer; |
||||
DWORD dwRawBytesToRead; |
||||
DWORD dwRawSectorOffset = dwByteOffset; |
||||
DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize; |
||||
DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize; |
||||
DWORD dwSectorsDone = 0; |
||||
DWORD dwBytesRead = 0; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Note that dwByteOffset must be aligned to size of one sector
|
||||
// Note that dwBytesToRead must be a multiplier of one sector size
|
||||
// This is local function, so we won't check if that's true.
|
||||
// Note that files stored in single units are processed by a separate function
|
||||
|
||||
// If there is not enough bytes remaining, cut dwBytesToRead
|
||||
if((dwByteOffset + dwBytesToRead) > hf->dwDataSize) |
||||
dwBytesToRead = hf->dwDataSize - dwByteOffset; |
||||
dwRawBytesToRead = dwBytesToRead; |
||||
|
||||
// Perform all necessary work to do with compressed files
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) |
||||
{ |
||||
// If the sector positions are not loaded yet, do it
|
||||
if(hf->SectorOffsets == NULL) |
||||
{ |
||||
nError = AllocateSectorOffsets(hf, true); |
||||
if(nError != ERROR_SUCCESS || hf->SectorOffsets == NULL) |
||||
return nError; |
||||
} |
||||
|
||||
// If the sector checksums are not loaded yet, load them now.
|
||||
if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->bLoadedSectorCRCs == false) |
||||
{ |
||||
//
|
||||
// Sector CRCs is plain crap feature. It is almost never present,
|
||||
// often it's empty, or the end offset of sector CRCs is zero.
|
||||
// We only try to load sector CRCs once, and regardless if it fails
|
||||
// or not, we won't try that again for the given file.
|
||||
//
|
||||
|
||||
AllocateSectorChecksums(hf, true); |
||||
hf->bLoadedSectorCRCs = true; |
||||
} |
||||
|
||||
// TODO: If the raw data MD5s are not loaded yet, load them now
|
||||
// Only do it if the MPQ is of format 4.0
|
||||
// if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0)
|
||||
// {
|
||||
// nError = AllocateRawMD5s(hf, true);
|
||||
// if(nError != ERROR_SUCCESS)
|
||||
// return nError;
|
||||
// }
|
||||
|
||||
// Assign the temporary buffer as target for read operation
|
||||
dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex]; |
||||
dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset; |
||||
|
||||
// If the file is compressed, also allocate secondary buffer
|
||||
pbInSector = pbRawSector = STORM_ALLOC(BYTE, dwRawBytesToRead); |
||||
if(pbRawSector == NULL) |
||||
return ERROR_NOT_ENOUGH_MEMORY; |
||||
} |
||||
|
||||
// Calculate raw file offset where the sector(s) are stored.
|
||||
RawFilePos = CalculateRawSectorOffset(hf, dwRawSectorOffset); |
||||
|
||||
// Set file pointer and read all required sectors
|
||||
if(FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead)) |
||||
{ |
||||
// Now we have to decrypt and decompress all file sectors that have been loaded
|
||||
for(DWORD i = 0; i < dwSectorsToRead; i++) |
||||
{ |
||||
DWORD dwRawBytesInThisSector = ha->dwSectorSize; |
||||
DWORD dwBytesInThisSector = ha->dwSectorSize; |
||||
DWORD dwIndex = dwSectorIndex + i; |
||||
|
||||
// If there is not enough bytes in the last sector,
|
||||
// cut the number of bytes in this sector
|
||||
if(dwRawBytesInThisSector > dwBytesToRead) |
||||
dwRawBytesInThisSector = dwBytesToRead; |
||||
if(dwBytesInThisSector > dwBytesToRead) |
||||
dwBytesInThisSector = dwBytesToRead; |
||||
|
||||
// If the file is compressed, we have to adjust the raw sector size
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) |
||||
dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex]; |
||||
|
||||
// If the file is encrypted, we have to decrypt the sector
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) |
||||
{ |
||||
BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); |
||||
|
||||
// If we don't know the key, try to detect it by file content
|
||||
if(hf->dwFileKey == 0) |
||||
{ |
||||
hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector, hf->dwDataSize); |
||||
if(hf->dwFileKey == 0) |
||||
{ |
||||
nError = ERROR_UNKNOWN_FILE_KEY; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex); |
||||
BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); |
||||
} |
||||
|
||||
#ifdef FULL |
||||
// If the file has sector CRC check turned on, perform it
|
||||
if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL) |
||||
{ |
||||
DWORD dwAdlerExpected = hf->SectorChksums[dwIndex]; |
||||
DWORD dwAdlerValue = 0; |
||||
|
||||
// We can only check sector CRC when it's not zero
|
||||
// Neither can we check it if it's 0xFFFFFFFF.
|
||||
if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF) |
||||
{ |
||||
dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector); |
||||
if(dwAdlerValue != dwAdlerExpected) |
||||
{ |
||||
nError = ERROR_CHECKSUM_ERROR; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
// If the sector is really compressed, decompress it.
|
||||
// WARNING : Some sectors may not be compressed, it can be determined only
|
||||
// by comparing uncompressed and compressed size !!!
|
||||
if(dwRawBytesInThisSector < dwBytesInThisSector) |
||||
{ |
||||
int cbOutSector = dwBytesInThisSector; |
||||
int cbInSector = dwRawBytesInThisSector; |
||||
int nResult = 0; |
||||
|
||||
// Is the file compressed by Blizzard's multiple compression ?
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) |
||||
{ |
||||
// Remember the last used compression
|
||||
hf->dwCompression0 = pbInSector[0]; |
||||
|
||||
// Decompress the data
|
||||
if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) |
||||
nResult = SCompDecompress2(pbOutSector, &cbOutSector, pbInSector, cbInSector); |
||||
else |
||||
nResult = SCompDecompress(pbOutSector, &cbOutSector, pbInSector, cbInSector); |
||||
} |
||||
|
||||
// Is the file compressed by PKWARE Data Compression Library ?
|
||||
else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) |
||||
{ |
||||
nResult = SCompExplode(pbOutSector, &cbOutSector, pbInSector, cbInSector); |
||||
} |
||||
|
||||
// Did the decompression fail ?
|
||||
if(nResult == 0) |
||||
{ |
||||
nError = ERROR_FILE_CORRUPT; |
||||
break; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
if(pbOutSector != pbInSector) |
||||
memcpy(pbOutSector, pbInSector, dwBytesInThisSector); |
||||
} |
||||
|
||||
// Move pointers
|
||||
dwBytesToRead -= dwBytesInThisSector; |
||||
dwByteOffset += dwBytesInThisSector; |
||||
dwBytesRead += dwBytesInThisSector; |
||||
pbOutSector += dwBytesInThisSector; |
||||
pbInSector += dwRawBytesInThisSector; |
||||
dwSectorsDone++; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
nError = GetLastError(); |
||||
} |
||||
|
||||
// Free all used buffers
|
||||
if(pbRawSector != NULL) |
||||
STORM_FREE(pbRawSector); |
||||
|
||||
// Give the caller thenumber of bytes read
|
||||
*pdwBytesRead = dwBytesRead; |
||||
return nError; |
||||
} |
||||
|
||||
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) |
||||
{ |
||||
ULONGLONG RawFilePos = hf->RawFilePos; |
||||
TMPQArchive * ha = hf->ha; |
||||
TFileEntry * pFileEntry = hf->pFileEntry; |
||||
LPBYTE pbCompressed = NULL; |
||||
LPBYTE pbRawData; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// If the file buffer is not allocated yet, do it.
|
||||
if(hf->pbFileSector == NULL) |
||||
{ |
||||
nError = AllocateSectorBuffer(hf); |
||||
if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL) |
||||
return nError; |
||||
} |
||||
|
||||
// If the file is a patch file, adjust raw data offset
|
||||
if(hf->pPatchInfo != NULL) |
||||
RawFilePos += hf->pPatchInfo->dwLength; |
||||
pbRawData = hf->pbFileSector; |
||||
|
||||
// If the file sector is not loaded yet, do it
|
||||
if(hf->dwSectorOffs != 0) |
||||
{ |
||||
// Is the file compressed?
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) |
||||
{ |
||||
// Allocate space for compressed data
|
||||
pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); |
||||
if(pbCompressed == NULL) |
||||
return ERROR_NOT_ENOUGH_MEMORY; |
||||
pbRawData = pbCompressed; |
||||
} |
||||
|
||||
// Load the raw (compressed, encrypted) data
|
||||
if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) |
||||
{ |
||||
STORM_FREE(pbCompressed); |
||||
return GetLastError(); |
||||
} |
||||
|
||||
// If the file is encrypted, we have to decrypt the data first
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) |
||||
{ |
||||
BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); |
||||
DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); |
||||
BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); |
||||
} |
||||
|
||||
// If the file is compressed, we have to decompress it now
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) |
||||
{ |
||||
int cbOutBuffer = (int)hf->dwDataSize; |
||||
int cbInBuffer = (int)pFileEntry->dwCmpSize; |
||||
int nResult = 0; |
||||
|
||||
//
|
||||
// If the file is an incremental patch, the size of compressed data
|
||||
// is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo)
|
||||
//
|
||||
// In "wow-update-12694.MPQ" from Wow-Cataclysm BETA:
|
||||
//
|
||||
// File CmprSize DcmpSize DataSize Compressed?
|
||||
// -------------------------------------- ---------- -------- -------- ---------------
|
||||
// esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes
|
||||
// deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No
|
||||
//
|
||||
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) |
||||
cbInBuffer = cbInBuffer - sizeof(TPatchInfo); |
||||
|
||||
// Is the file compressed by Blizzard's multiple compression ?
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) |
||||
{ |
||||
// Remember the last used compression
|
||||
hf->dwCompression0 = pbRawData[0]; |
||||
|
||||
// Decompress the file
|
||||
if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) |
||||
nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); |
||||
else |
||||
nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); |
||||
} |
||||
|
||||
// Is the file compressed by PKWARE Data Compression Library ?
|
||||
// Note: Single unit files compressed with IMPLODE are not supported by Blizzard
|
||||
else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) |
||||
nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); |
||||
|
||||
nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; |
||||
} |
||||
else |
||||
{ |
||||
if(hf->pbFileSector != NULL && pbRawData != hf->pbFileSector) |
||||
memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); |
||||
} |
||||
|
||||
// Free the decompression buffer.
|
||||
if(pbCompressed != NULL) |
||||
STORM_FREE(pbCompressed); |
||||
|
||||
// The file sector is now properly loaded
|
||||
hf->dwSectorOffs = 0; |
||||
} |
||||
|
||||
// At this moment, we have the file loaded into the file buffer.
|
||||
// Copy as much as the caller wants
|
||||
if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) |
||||
{ |
||||
// File position is greater or equal to file size ?
|
||||
if(dwFilePos >= hf->dwDataSize) |
||||
{ |
||||
*pdwBytesRead = 0; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
// If not enough bytes remaining in the file, cut them
|
||||
if((hf->dwDataSize - dwFilePos) < dwToRead) |
||||
dwToRead = (hf->dwDataSize - dwFilePos); |
||||
|
||||
// Copy the bytes
|
||||
memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); |
||||
|
||||
// Give the number of bytes read
|
||||
*pdwBytesRead = dwToRead; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
// An error, sorry
|
||||
return ERROR_CAN_NOT_COMPLETE; |
||||
} |
||||
|
||||
static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) |
||||
{ |
||||
ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C)
|
||||
TMPQArchive * ha = hf->ha; |
||||
TFileEntry * pFileEntry = hf->pFileEntry; |
||||
LPBYTE pbCompressed = NULL; |
||||
LPBYTE pbRawData = hf->pbFileSector; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// We do not support patch files in MPK archives
|
||||
assert(hf->pPatchInfo == NULL); |
||||
|
||||
// If the file buffer is not allocated yet, do it.
|
||||
if(hf->pbFileSector == NULL) |
||||
{ |
||||
nError = AllocateSectorBuffer(hf); |
||||
if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL) |
||||
return nError; |
||||
pbRawData = hf->pbFileSector; |
||||
} |
||||
|
||||
// If the file sector is not loaded yet, do it
|
||||
if(hf->dwSectorOffs != 0) |
||||
{ |
||||
// Is the file compressed?
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) |
||||
{ |
||||
// Allocate space for compressed data
|
||||
pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); |
||||
if(pbCompressed == NULL) |
||||
return ERROR_NOT_ENOUGH_MEMORY; |
||||
pbRawData = pbCompressed; |
||||
} |
||||
|
||||
// Load the raw (compressed, encrypted) data
|
||||
if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) |
||||
{ |
||||
STORM_FREE(pbCompressed); |
||||
return GetLastError(); |
||||
} |
||||
|
||||
// If the file is encrypted, we have to decrypt the data first
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) |
||||
{ |
||||
DecryptMpkTable(pbRawData, pFileEntry->dwCmpSize); |
||||
} |
||||
|
||||
// If the file is compressed, we have to decompress it now
|
||||
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) |
||||
{ |
||||
#ifdef FULL |
||||
int cbOutBuffer = (int)hf->dwDataSize; |
||||
|
||||
hf->dwCompression0 = pbRawData[0]; |
||||
if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, pbRawData, (int)pFileEntry->dwCmpSize)) |
||||
nError = ERROR_FILE_CORRUPT; |
||||
#else |
||||
assert(0); |
||||
#endif |
||||
} |
||||
else |
||||
{ |
||||
if(pbRawData != hf->pbFileSector) |
||||
memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); |
||||
} |
||||
|
||||
// Free the decompression buffer.
|
||||
if(pbCompressed != NULL) |
||||
STORM_FREE(pbCompressed); |
||||
|
||||
// The file sector is now properly loaded
|
||||
hf->dwSectorOffs = 0; |
||||
} |
||||
|
||||
// At this moment, we have the file loaded into the file buffer.
|
||||
// Copy as much as the caller wants
|
||||
if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) |
||||
{ |
||||
// File position is greater or equal to file size ?
|
||||
if(dwFilePos >= hf->dwDataSize) |
||||
{ |
||||
*pdwBytesRead = 0; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
// If not enough bytes remaining in the file, cut them
|
||||
if((hf->dwDataSize - dwFilePos) < dwToRead) |
||||
dwToRead = (hf->dwDataSize - dwFilePos); |
||||
|
||||
// Copy the bytes
|
||||
memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); |
||||
|
||||
// Give the number of bytes read
|
||||
*pdwBytesRead = dwToRead; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
// An error, sorry
|
||||
return ERROR_CAN_NOT_COMPLETE; |
||||
} |
||||
|
||||
|
||||
static int ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead) |
||||
{ |
||||
TMPQArchive * ha = hf->ha; |
||||
LPBYTE pbBuffer = (BYTE *)pvBuffer; |
||||
DWORD dwTotalBytesRead = 0; // Total bytes read in all three parts
|
||||
DWORD dwSectorSizeMask = ha->dwSectorSize - 1; // Mask for block size, usually 0x0FFF
|
||||
DWORD dwFileSectorPos; // File offset of the loaded sector
|
||||
DWORD dwBytesRead; // Number of bytes read (temporary variable)
|
||||
int nError; |
||||
|
||||
// If the file position is at or beyond end of file, do nothing
|
||||
if(dwFilePos >= hf->dwDataSize) |
||||
{ |
||||
*pdwBytesRead = 0; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
// If not enough bytes in the file remaining, cut them
|
||||
if(dwBytesToRead > (hf->dwDataSize - dwFilePos)) |
||||
dwBytesToRead = (hf->dwDataSize - dwFilePos); |
||||
|
||||
// Compute sector position in the file
|
||||
dwFileSectorPos = dwFilePos & ~dwSectorSizeMask; // Position in the block
|
||||
|
||||
// If the file sector buffer is not allocated yet, do it now
|
||||
if(hf->pbFileSector == NULL) |
||||
{ |
||||
nError = AllocateSectorBuffer(hf); |
||||
if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL) |
||||
return nError; |
||||
} |
||||
|
||||
// Load the first (incomplete) file sector
|
||||
if(dwFilePos & dwSectorSizeMask) |
||||
{ |
||||
DWORD dwBytesInSector = ha->dwSectorSize; |
||||
DWORD dwBufferOffs = dwFilePos & dwSectorSizeMask; |
||||
DWORD dwToCopy; |
||||
|
||||
// Is the file sector already loaded ?
|
||||
if(hf->dwSectorOffs != dwFileSectorPos) |
||||
{ |
||||
// Load one MPQ sector into archive buffer
|
||||
nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesInSector); |
||||
if(nError != ERROR_SUCCESS) |
||||
return nError; |
||||
|
||||
// Remember that the data loaded to the sector have new file offset
|
||||
hf->dwSectorOffs = dwFileSectorPos; |
||||
} |
||||
else |
||||
{ |
||||
if((dwFileSectorPos + dwBytesInSector) > hf->dwDataSize) |
||||
dwBytesInSector = hf->dwDataSize - dwFileSectorPos; |
||||
} |
||||
|
||||
// Copy the data from the offset in the loaded sector to the end of the sector
|
||||
dwToCopy = dwBytesInSector - dwBufferOffs; |
||||
if(dwToCopy > dwBytesToRead) |
||||
dwToCopy = dwBytesToRead; |
||||
|
||||
// Copy data from sector buffer into target buffer
|
||||
memcpy(pbBuffer, hf->pbFileSector + dwBufferOffs, dwToCopy); |
||||
|
||||
// Update pointers and byte counts
|
||||
dwTotalBytesRead += dwToCopy; |
||||
dwFileSectorPos += dwBytesInSector; |
||||
pbBuffer += dwToCopy; |
||||
dwBytesToRead -= dwToCopy; |
||||
} |
||||
|
||||
// Load the whole ("middle") sectors only if there is at least one full sector to be read
|
||||
if(dwBytesToRead >= ha->dwSectorSize) |
||||
{ |
||||
DWORD dwBlockBytes = dwBytesToRead & ~dwSectorSizeMask; |
||||
|
||||
// Load all sectors to the output buffer
|
||||
nError = ReadMpqSectors(hf, pbBuffer, dwFileSectorPos, dwBlockBytes, &dwBytesRead); |
||||
if(nError != ERROR_SUCCESS) |
||||
return nError; |
||||
|
||||
// Update pointers
|
||||
dwTotalBytesRead += dwBytesRead; |
||||
dwFileSectorPos += dwBytesRead; |
||||
pbBuffer += dwBytesRead; |
||||
dwBytesToRead -= dwBytesRead; |
||||
} |
||||
|
||||
// Read the terminating sector
|
||||
if(dwBytesToRead > 0) |
||||
{ |
||||
DWORD dwToCopy = ha->dwSectorSize; |
||||
|
||||
// Is the file sector already loaded ?
|
||||
if(hf->dwSectorOffs != dwFileSectorPos) |
||||
{ |
||||
// Load one MPQ sector into archive buffer
|
||||
nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesRead); |
||||
if(nError != ERROR_SUCCESS) |
||||
return nError; |
||||
|
||||
// Remember that the data loaded to the sector have new file offset
|
||||
hf->dwSectorOffs = dwFileSectorPos; |
||||
} |
||||
|
||||
// Check number of bytes read
|
||||
if(dwToCopy > dwBytesToRead) |
||||
dwToCopy = dwBytesToRead; |
||||
|
||||
// Copy the data from the cached last sector to the caller's buffer
|
||||
memcpy(pbBuffer, hf->pbFileSector, dwToCopy); |
||||
|
||||
// Update pointers
|
||||
dwTotalBytesRead += dwToCopy; |
||||
} |
||||
|
||||
// Store total number of bytes read to the caller
|
||||
*pdwBytesRead = dwTotalBytesRead; |
||||
return ERROR_SUCCESS; |
||||
} |
||||
|
||||
#ifdef FULL |
||||
static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) |
||||
{ |
||||
TMPQPatcher Patcher; |
||||
DWORD dwBytesToRead = dwToRead; |
||||
DWORD dwBytesRead = 0; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Make sure that the patch file is loaded completely
|
||||
if(nError == ERROR_SUCCESS && hf->pbFileData == NULL) |
||||
{ |
||||
// Initialize patching process and allocate data
|
||||
nError = Patch_InitPatcher(&Patcher, hf); |
||||
if(nError != ERROR_SUCCESS) |
||||
return nError; |
||||
|
||||
// Set the current data size
|
||||
Patcher.cbFileData = hf->pFileEntry->dwFileSize; |
||||
|
||||
// Initialize the patcher object with initial file data
|
||||
if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) |
||||
nError = ReadMpqFileSingleUnit(hf, Patcher.pbFileData1, 0, Patcher.cbFileData, &dwBytesRead); |
||||
else |
||||
nError = ReadMpqFileSectorFile(hf, Patcher.pbFileData1, 0, Patcher.cbFileData, &dwBytesRead); |
||||
|
||||
// Perform the patching process
|
||||
if(nError == ERROR_SUCCESS) |
||||
nError = Patch_Process(&Patcher, hf); |
||||
|
||||
// Finalize the patcher structure
|
||||
Patch_Finalize(&Patcher); |
||||
dwBytesRead = 0; |
||||
} |
||||
|
||||
// If there is something to read, do it
|
||||
if(nError == ERROR_SUCCESS) |
||||
{ |
||||
if(dwFilePos < hf->cbFileData) |
||||
{ |
||||
// Make sure we don't copy more than file size
|
||||
if((dwFilePos + dwToRead) > hf->cbFileData) |
||||
dwToRead = hf->cbFileData - dwFilePos; |
||||
|
||||
// Copy the appropriate amount of the file data to the caller's buffer
|
||||
memcpy(pvBuffer, hf->pbFileData + dwFilePos, dwToRead); |
||||
dwBytesRead = dwToRead; |
||||
} |
||||
|
||||
// Set the proper error code
|
||||
nError = (dwBytesRead == dwBytesToRead) ? ERROR_SUCCESS : ERROR_HANDLE_EOF; |
||||
} |
||||
|
||||
// Give the result to the caller
|
||||
if(pdwBytesRead != NULL) |
||||
*pdwBytesRead = dwBytesRead; |
||||
return nError; |
||||
} |
||||
#endif |
||||
|
||||
static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) |
||||
{ |
||||
ULONGLONG FilePosition1 = dwFilePos; |
||||
ULONGLONG FilePosition2; |
||||
DWORD dwBytesRead = 0; |
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
assert(hf->pStream != NULL); |
||||
|
||||
// Because stream I/O functions are designed to read
|
||||
// "all or nothing", we compare file position before and after,
|
||||
// and if they differ, we assume that number of bytes read
|
||||
// is the difference between them
|
||||
|
||||
if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead)) |
||||
{ |
||||
// If not all bytes have been read, then return the number of bytes read
|
||||
if((nError = GetLastError()) == ERROR_HANDLE_EOF) |
||||
{ |
||||
FileStream_GetPos(hf->pStream, &FilePosition2); |
||||
dwBytesRead = (DWORD)(FilePosition2 - FilePosition1); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
dwBytesRead = dwToRead; |
||||
} |
||||
|
||||
*pdwBytesRead = dwBytesRead; |
||||
return nError; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileReadFile
|
||||
|
||||
bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped) |
||||
{ |
||||
TMPQFile * hf = (TMPQFile *)hFile; |
||||
DWORD dwBytesRead = 0; // Number of bytes read
|
||||
int nError = ERROR_SUCCESS; |
||||
|
||||
// Always zero the result
|
||||
if(pdwRead != NULL) |
||||
*pdwRead = 0; |
||||
lpOverlapped = lpOverlapped; |
||||
|
||||
// Check valid parameters
|
||||
if(!IsValidFileHandle(hFile)) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return false; |
||||
} |
||||
|
||||
if(pvBuffer == NULL) |
||||
{ |
||||
SetLastError(ERROR_INVALID_PARAMETER); |
||||
return false; |
||||
} |
||||
|
||||
// If we didn't load the patch info yet, do it now
|
||||
if(hf->pFileEntry != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) && hf->pPatchInfo == NULL) |
||||
{ |
||||
nError = AllocatePatchInfo(hf, true); |
||||
if(nError != ERROR_SUCCESS || hf->pPatchInfo == NULL) |
||||
{ |
||||
SetLastError(nError); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// Clear the last used compression
|
||||
hf->dwCompression0 = 0; |
||||
|
||||
// If the file is local file, read the data directly from the stream
|
||||
if(hf->pStream != NULL) |
||||
{ |
||||
nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); |
||||
} |
||||
#ifdef FULL |
||||
// If the file is a patch file, we have to read it special way
|
||||
else if(hf->hfPatch != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) |
||||
{ |
||||
nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); |
||||
} |
||||
#endif |
||||
// If the archive is a MPK archive, we need special way to read the file
|
||||
else if(hf->ha->dwSubType == MPQ_SUBTYPE_MPK) |
||||
{ |
||||
nError = ReadMpkFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); |
||||
} |
||||
|
||||
// If the file is single unit file, redirect it to read file
|
||||
else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) |
||||
{ |
||||
nError = ReadMpqFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); |
||||
} |
||||
|
||||
// Otherwise read it as sector based MPQ file
|
||||
else |
||||
{ |
||||
nError = ReadMpqFileSectorFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); |
||||
} |
||||
|
||||
// Increment the file position
|
||||
hf->dwFilePos += dwBytesRead; |
||||
|
||||
// Give the caller the number of bytes read
|
||||
if(pdwRead != NULL) |
||||
*pdwRead = dwBytesRead; |
||||
|
||||
// If the read operation succeeded, but not full number of bytes was read,
|
||||
// set the last error to ERROR_HANDLE_EOF
|
||||
if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead)) |
||||
nError = ERROR_HANDLE_EOF; |
||||
|
||||
// If something failed, set the last error value
|
||||
if(nError != ERROR_SUCCESS) |
||||
SetLastError(nError); |
||||
return (nError == ERROR_SUCCESS); |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SFileGetFileSize
|
||||
|
||||
DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) |
||||
{ |
||||
ULONGLONG FileSize; |
||||
TMPQFile * hf = (TMPQFile *)hFile; |
||||
|
||||
// Validate the file handle before we go on
|
||||
if(IsValidFileHandle(hFile)) |
||||
{ |
||||
// Make sure that the variable is initialized
|
||||
FileSize = 0; |
||||
|
||||
// If the file is patched file, we have to get the size of the last version
|
||||
if(hf->hfPatch != NULL) |
||||
{ |
||||
// Walk through the entire patch chain, take the last version
|
||||
while(hf != NULL) |
||||
{ |
||||
// Get the size of the currently pointed version
|
||||
FileSize = hf->pFileEntry->dwFileSize; |
||||
|
||||
// Move to the next patch file in the hierarchy
|
||||
hf = hf->hfPatch; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// Is it a local file ?
|
||||
if(hf->pStream != NULL) |
||||
{ |
||||
FileStream_GetSize(hf->pStream, &FileSize); |
||||
} |
||||
else |
||||
{ |
||||
FileSize = hf->dwDataSize; |
||||
} |
||||
} |
||||
|
||||
// If opened from archive, return file size
|
||||
if(pdwFileSizeHigh != NULL) |
||||
*pdwFileSizeHigh = (DWORD)(FileSize >> 32); |
||||
return (DWORD)FileSize; |
||||
} |
||||
|
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return SFILE_INVALID_SIZE; |
||||
} |
||||
|
||||
DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) |
||||
{ |
||||
TMPQFile * hf = (TMPQFile *)hFile; |
||||
ULONGLONG OldPosition; |
||||
ULONGLONG NewPosition; |
||||
ULONGLONG FileSize; |
||||
ULONGLONG DeltaPos; |
||||
|
||||
// If the hFile is not a valid file handle, return an error.
|
||||
if(!IsValidFileHandle(hFile)) |
||||
{ |
||||
SetLastError(ERROR_INVALID_HANDLE); |
||||
return SFILE_INVALID_POS; |
||||
} |
||||
|
||||
// Retrieve the file size for handling the limits
|
||||
if(hf->pStream != NULL) |
||||
{ |
||||
FileStream_GetSize(hf->pStream, &FileSize); |
||||
} |
||||
else |
||||
{ |
||||
FileSize = SFileGetFileSize(hFile, NULL); |
||||
} |
||||
|
||||
// Handle the NULL and non-NULL values of plFilePosHigh
|
||||
// Non-NULL: The DeltaPos is combined from lFilePos and *lpFilePosHigh
|
||||
// NULL: The DeltaPos is sign-extended value of lFilePos
|
||||
DeltaPos = (plFilePosHigh != NULL) ? MAKE_OFFSET64(plFilePosHigh[0], lFilePos) : (ULONGLONG)(LONGLONG)lFilePos; |
||||
|
||||
// Get the relative point where to move from
|
||||
switch(dwMoveMethod) |
||||
{ |
||||
case FILE_BEGIN: |
||||
|
||||
// Move relative to the file begin.
|
||||
OldPosition = 0; |
||||
break; |
||||
|
||||
case FILE_CURRENT: |
||||
|
||||
// Retrieve the current file position
|
||||
if(hf->pStream != NULL) |
||||
{ |
||||
FileStream_GetPos(hf->pStream, &OldPosition); |
||||
} |
||||
else |
||||
{ |
||||
OldPosition = hf->dwFilePos; |
||||
} |
||||
break; |
||||
|
||||
case FILE_END: |
||||
|
||||
// Move relative to the end of the file
|
||||
OldPosition = FileSize; |
||||
break; |
||||
|
||||
default: |
||||
SetLastError(ERROR_INVALID_PARAMETER); |
||||
return SFILE_INVALID_POS; |
||||
} |
||||
|
||||
// Calculate the new position
|
||||
NewPosition = OldPosition + DeltaPos; |
||||
|
||||
// If moving backward, don't allow the new position go negative
|
||||
if((LONGLONG)DeltaPos < 0) |
||||
{ |
||||
if(NewPosition > FileSize) // Position is negative
|
||||
{ |
||||
SetLastError(ERROR_NEGATIVE_SEEK); |
||||
return SFILE_INVALID_POS; |
||||
} |
||||
} |
||||
|
||||
// If moving forward, don't allow the new position go past the end of the file
|
||||
else |
||||
{ |
||||
if(NewPosition > FileSize) |
||||
NewPosition = FileSize; |
||||
} |
||||
|
||||
// Now apply the file pointer to the file
|
||||
if(hf->pStream != NULL) |
||||
{ |
||||
if(!FileStream_Read(hf->pStream, &NewPosition, NULL, 0)) |
||||
return SFILE_INVALID_POS; |
||||
} |
||||
else |
||||
{ |
||||
hf->dwFilePos = (DWORD)NewPosition; |
||||
} |
||||
|
||||
// Return the new file position
|
||||
if(plFilePosHigh != NULL) |
||||
*plFilePosHigh = (LONG)(NewPosition >> 32); |
||||
return (DWORD)NewPosition; |
||||
} |
||||
@ -0,0 +1,387 @@
|
||||
/*****************************************************************************/ |
||||
/* SCommon.h Copyright (c) Ladislav Zezula 2003 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Common functions for encryption/decryption from Storm.dll. Included by */ |
||||
/* SFile*** functions, do not include and do not use this file directly */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* 24.03.03 1.00 Lad The first version of SFileCommon.h */ |
||||
/* 12.06.04 1.00 Lad Renamed to SCommon.h */ |
||||
/* 06.09.10 1.00 Lad Renamed to StormCommon.h */ |
||||
/*****************************************************************************/ |
||||
|
||||
#ifndef __STORMCOMMON_H__ |
||||
#define __STORMCOMMON_H__ |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Compression support
|
||||
|
||||
// Include functions from Pkware Data Compression Library
|
||||
#include "3rdParty/PKWare/pkware.h" |
||||
|
||||
#ifdef FULL |
||||
// Include functions from Huffmann compression
|
||||
#include "huffman/huff.h" |
||||
|
||||
// Include functions from IMA ADPCM compression
|
||||
#include "adpcm/adpcm.h" |
||||
|
||||
// Include functions from SPARSE compression
|
||||
#include "sparse/sparse.h" |
||||
|
||||
// Include functions from LZMA compression
|
||||
#include "lzma/C/LzmaEnc.h" |
||||
#include "lzma/C/LzmaDec.h" |
||||
|
||||
// Include functions from zlib
|
||||
#ifndef __SYS_ZLIB |
||||
#include "zlib/zlib.h" |
||||
#else |
||||
#include <zlib.h> |
||||
#endif |
||||
|
||||
// Include functions from bzlib
|
||||
#ifndef __SYS_BZLIB |
||||
#include "bzip2/bzlib.h" |
||||
#else |
||||
#include <bzlib.h> |
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Cryptography support
|
||||
|
||||
// Headers from LibTomCrypt
|
||||
#include "libtomcrypt/src/headers/tomcrypt.h" |
||||
|
||||
// For HashStringJenkins
|
||||
#include "jenkins/lookup.h" |
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// StormLib private defines
|
||||
|
||||
#define ID_MPQ_FILE 0x46494c45 // Used internally for checking TMPQFile ('FILE')
|
||||
|
||||
// Prevent problems with CRT "min" and "max" functions,
|
||||
// as they are not defined on all platforms
|
||||
#define STORMLIB_MIN(a, b) ((a < b) ? a : b) |
||||
#define STORMLIB_MAX(a, b) ((a > b) ? a : b) |
||||
#define STORMLIB_UNUSED(p) ((void)(p)) |
||||
|
||||
// Macro for building 64-bit file offset from two 32-bit
|
||||
#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo) |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// MPQ signature information
|
||||
|
||||
// Size of each signature type
|
||||
#define MPQ_WEAK_SIGNATURE_SIZE 64 |
||||
#define MPQ_STRONG_SIGNATURE_SIZE 256 |
||||
#define MPQ_STRONG_SIGNATURE_ID 0x5349474E // ID of the strong signature ("NGIS")
|
||||
#define MPQ_SIGNATURE_FILE_SIZE (MPQ_WEAK_SIGNATURE_SIZE + 8) |
||||
|
||||
// MPQ signature info
|
||||
typedef struct _MPQ_SIGNATURE_INFO |
||||
{ |
||||
ULONGLONG BeginMpqData; // File offset where the hashing starts
|
||||
ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file)
|
||||
ULONGLONG EndExclude; // End of the excluded area (used for (signature) file)
|
||||
ULONGLONG EndMpqData; // File offset where the hashing ends
|
||||
ULONGLONG EndOfFile; // Size of the entire file
|
||||
BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; |
||||
DWORD cbSignatureSize; // Length of the signature
|
||||
DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX
|
||||
|
||||
} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory management
|
||||
//
|
||||
// We use our own macros for allocating/freeing memory. If you want
|
||||
// to redefine them, please keep the following rules:
|
||||
//
|
||||
// - The memory allocation must return NULL if not enough memory
|
||||
// (i.e not to throw exception)
|
||||
// - The allocating function does not need to fill the allocated buffer with zeros
|
||||
// - Memory freeing function doesn't have to test the pointer to NULL
|
||||
//
|
||||
|
||||
//#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
//
|
||||
//#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
|
||||
//#define STORM_REALLOC(type, ptr, nitems) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((nitems) * sizeof(type)))
|
||||
//#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
|
||||
//
|
||||
//#else
|
||||
|
||||
#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type)) |
||||
#define STORM_REALLOC(type, ptr, nitems) (type *)realloc(ptr, ((nitems) * sizeof(type))) |
||||
#define STORM_FREE(ptr) free(ptr) |
||||
|
||||
//#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// StormLib internal global variables
|
||||
|
||||
extern LCID lcFileLocale; // Preferred file locale
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Conversion to uppercase/lowercase (and "/" to "\")
|
||||
|
||||
extern unsigned char AsciiToLowerTable[256]; |
||||
extern unsigned char AsciiToUpperTable[256]; |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Safe string functions
|
||||
|
||||
void StringCopy(char * szTarget, size_t cchTarget, const char * szSource); |
||||
void StringCat(char * szTarget, size_t cchTargetMax, const char * szSource); |
||||
|
||||
#ifdef _UNICODE |
||||
void StringCopy(TCHAR * szTarget, size_t cchTarget, const char * szSource); |
||||
void StringCopy(char * szTarget, size_t cchTarget, const TCHAR * szSource); |
||||
void StringCopy(TCHAR * szTarget, size_t cchTarget, const TCHAR * szSource); |
||||
void StringCat(TCHAR * szTarget, size_t cchTargetMax, const TCHAR * szSource); |
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Encryption and decryption functions
|
||||
|
||||
#define MPQ_HASH_TABLE_INDEX 0x000 |
||||
#define MPQ_HASH_NAME_A 0x100 |
||||
#define MPQ_HASH_NAME_B 0x200 |
||||
#define MPQ_HASH_FILE_KEY 0x300 |
||||
#define MPQ_HASH_KEY2_MIX 0x400 |
||||
|
||||
DWORD HashString(const char * szFileName, DWORD dwHashType); |
||||
DWORD HashStringSlash(const char * szFileName, DWORD dwHashType); |
||||
DWORD HashStringLower(const char * szFileName, DWORD dwHashType); |
||||
|
||||
void InitializeMpqCryptography(); |
||||
|
||||
DWORD GetNearestPowerOfTwo(DWORD dwFileCount); |
||||
|
||||
bool IsPseudoFileName(const char * szFileName, LPDWORD pdwFileIndex); |
||||
ULONGLONG HashStringJenkins(const char * szFileName); |
||||
|
||||
DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion); |
||||
|
||||
void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey); |
||||
void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey); |
||||
|
||||
DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwSectorOffsLen); |
||||
DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize); |
||||
DWORD DecryptFileKey(const char * szFileName, ULONGLONG MpqPos, DWORD dwFileSize, DWORD dwFlags); |
||||
|
||||
bool IsValidMD5(LPBYTE pbMd5); |
||||
bool IsValidSignature(LPBYTE pbSignature); |
||||
bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5); |
||||
void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Handle validation functions
|
||||
|
||||
TMPQArchive * IsValidMpqHandle(HANDLE hMpq); |
||||
TMPQFile * IsValidFileHandle(HANDLE hFile); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Support for MPQ file tables
|
||||
|
||||
ULONGLONG FileOffsetFromMpqOffset(TMPQArchive * ha, ULONGLONG MpqOffset); |
||||
ULONGLONG CalculateRawSectorOffset(TMPQFile * hf, DWORD dwSectorOffset); |
||||
|
||||
int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags, bool bIsWarcraft3Map); |
||||
|
||||
bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash); |
||||
|
||||
TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale); |
||||
TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName); |
||||
TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash); |
||||
TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry, LCID lcLocale); |
||||
|
||||
TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey); |
||||
TMPQHetTable * LoadHetTable(TMPQArchive * ha); |
||||
TMPQBetTable * LoadBetTable(TMPQArchive * ha); |
||||
|
||||
TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries = false); |
||||
TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable); |
||||
|
||||
ULONGLONG FindFreeMpqSpace(TMPQArchive * ha); |
||||
|
||||
// Functions that load the HET and BET tables
|
||||
int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize); |
||||
int LoadAnyHashTable(TMPQArchive * ha); |
||||
int BuildFileTable(TMPQArchive * ha); |
||||
int DefragmentFileTable(TMPQArchive * ha); |
||||
|
||||
int CreateFileTable(TMPQArchive * ha, DWORD dwFileTableSize); |
||||
int RebuildHetTable(TMPQArchive * ha); |
||||
int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize); |
||||
int SaveMPQTables(TMPQArchive * ha); |
||||
|
||||
TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwHashBitSize, LPBYTE pbSrcData); |
||||
void FreeHetTable(TMPQHetTable * pHetTable); |
||||
|
||||
TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount); |
||||
void FreeBetTable(TMPQBetTable * pBetTable); |
||||
|
||||
// Functions for finding files in the file table
|
||||
TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex); |
||||
TFileEntry * GetFileEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale); |
||||
TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex); |
||||
|
||||
// Allocates file name in the file entry
|
||||
void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName); |
||||
|
||||
// Allocates new file entry in the MPQ tables. Reuses existing, if possible
|
||||
TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex); |
||||
int RenameFileEntry(TMPQArchive * ha, TMPQFile * hf, const char * szNewFileName); |
||||
int DeleteFileEntry(TMPQArchive * ha, TMPQFile * hf); |
||||
|
||||
// Invalidates entries for (listfile) and (attributes)
|
||||
void InvalidateInternalFiles(TMPQArchive * ha); |
||||
|
||||
// Retrieves information about the strong signature
|
||||
bool QueryMpqSignatureInfo(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSignatureInfo); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Support for alternate file formats (SBaseSubTypes.cpp)
|
||||
|
||||
int ConvertSqpHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); |
||||
TMPQHash * LoadSqpHashTable(TMPQArchive * ha); |
||||
TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha); |
||||
|
||||
int ConvertMpkHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); |
||||
void DecryptMpkTable(void * pvMpkTable, size_t cbSize); |
||||
TMPQHash * LoadMpkHashTable(TMPQArchive * ha); |
||||
TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha); |
||||
int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Common functions - MPQ File
|
||||
|
||||
TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry); |
||||
TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize); |
||||
void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey, bool * pbTableIsCut); |
||||
int AllocateSectorBuffer(TMPQFile * hf); |
||||
int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile); |
||||
int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile); |
||||
int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile); |
||||
int WritePatchInfo(TMPQFile * hf); |
||||
int WriteSectorOffsets(TMPQFile * hf); |
||||
int WriteSectorChecksums(TMPQFile * hf); |
||||
int WriteMemDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, void * pvRawData, DWORD dwRawDataSize, DWORD dwChunkSize, LPDWORD pcbTotalSize); |
||||
int WriteMpqDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, DWORD dwRawDataSize, DWORD dwChunkSize); |
||||
void FreeFileHandle(TMPQFile *& hf); |
||||
void FreeArchiveHandle(TMPQArchive *& ha); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Patch functions
|
||||
|
||||
// Structure used for the patching process
|
||||
typedef struct _TMPQPatcher |
||||
{ |
||||
BYTE this_md5[MD5_DIGEST_SIZE]; // MD5 of the current file state
|
||||
LPBYTE pbFileData1; // Primary working buffer
|
||||
LPBYTE pbFileData2; // Secondary working buffer
|
||||
DWORD cbMaxFileData; // Maximum allowed size of the patch data
|
||||
DWORD cbFileData; // Current size of the result data
|
||||
DWORD nCounter; // Counter of the patch process
|
||||
|
||||
} TMPQPatcher; |
||||
|
||||
bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize); |
||||
int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf); |
||||
int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf); |
||||
void Patch_Finalize(TMPQPatcher * pPatcher); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Utility functions
|
||||
|
||||
bool CheckWildCard(const char * szString, const char * szWildCard); |
||||
bool IsInternalMpqFileName(const char * szFileName); |
||||
|
||||
template <typename XCHAR> |
||||
const XCHAR * GetPlainFileName(const XCHAR * szFileName) |
||||
{ |
||||
const XCHAR * szPlainName = szFileName; |
||||
|
||||
while(*szFileName != 0) |
||||
{ |
||||
if(*szFileName == '\\' || *szFileName == '/') |
||||
szPlainName = szFileName + 1; |
||||
szFileName++; |
||||
} |
||||
|
||||
return szPlainName; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal support for MPQ modifications
|
||||
|
||||
int SFileAddFile_Init( |
||||
TMPQArchive * ha, |
||||
const char * szArchivedName, |
||||
ULONGLONG ft, |
||||
DWORD dwFileSize, |
||||
LCID lcLocale, |
||||
DWORD dwFlags, |
||||
TMPQFile ** phf |
||||
); |
||||
|
||||
int SFileAddFile_Init( |
||||
TMPQArchive * ha, |
||||
TMPQFile * hfSrc, |
||||
TMPQFile ** phf |
||||
); |
||||
|
||||
int SFileAddFile_Write( |
||||
TMPQFile * hf, |
||||
const void * pvData, |
||||
DWORD dwSize, |
||||
DWORD dwCompression |
||||
); |
||||
|
||||
int SFileAddFile_Finish( |
||||
TMPQFile * hf |
||||
); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Attributes support
|
||||
|
||||
int SAttrLoadAttributes(TMPQArchive * ha); |
||||
int SAttrFileSaveToMpq(TMPQArchive * ha); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Listfile functions
|
||||
|
||||
int SListFileSaveToMpq(TMPQArchive * ha); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Weak signature support
|
||||
|
||||
int SSignFileCreate(TMPQArchive * ha); |
||||
int SSignFileFinish(TMPQArchive * ha); |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dump data support
|
||||
|
||||
#ifdef __STORMLIB_DUMP_DATA__ |
||||
|
||||
void DumpMpqHeader(TMPQHeader * pHeader); |
||||
void DumpHashTable(TMPQHash * pHashTable, DWORD dwHashTableSize); |
||||
void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable); |
||||
void DumpFileTable(TFileEntry * pFileTable, DWORD dwFileTableSize); |
||||
|
||||
#else |
||||
|
||||
#define DumpMpqHeader(h) /* */ |
||||
#define DumpHashTable(t, s) /* */ |
||||
#define DumpHetAndBetTable(t, s) /* */ |
||||
#define DumpFileTable(t, s) /* */ |
||||
|
||||
#endif |
||||
|
||||
#endif // __STORMCOMMON_H__
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,309 @@
|
||||
/*****************************************************************************/ |
||||
/* StormPort.h Copyright (c) Marko Friedemann 2001 */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Portability module for the StormLib library. Contains a wrapper symbols */ |
||||
/* to make the compilation under Linux work */ |
||||
/* */ |
||||
/* Author: Marko Friedemann <marko.friedemann@bmx-chemnitz.de> */ |
||||
/* Created at: Mon Jan 29 18:26:01 CEST 2001 */ |
||||
/* Computer: whiplash.flachland-chemnitz.de */ |
||||
/* System: Linux 2.4.0 on i686 */ |
||||
/* */ |
||||
/* Author: Sam Wilkins <swilkins1337@gmail.com> */ |
||||
/* System: Mac OS X and port to big endian processor */ |
||||
/* */ |
||||
/*---------------------------------------------------------------------------*/ |
||||
/* Date Ver Who Comment */ |
||||
/* -------- ---- --- ------- */ |
||||
/* 29.01.01 1.00 Mar Created */ |
||||
/* 24.03.03 1.01 Lad Some cosmetic changes */ |
||||
/* 12.11.03 1.02 Dan Macintosh compatibility */ |
||||
/* 24.07.04 1.03 Sam Mac OS X compatibility */ |
||||
/* 22.11.06 1.04 Sam Mac OS X compatibility (for StormLib 6.0) */ |
||||
/* 31.12.06 1.05 XPinguin Full GNU/Linux compatibility */ |
||||
/* 17.10.12 1.05 Lad Moved error codes so they don't overlap with errno.h */ |
||||
/*****************************************************************************/ |
||||
|
||||
#ifndef __STORMPORT_H__ |
||||
#define __STORMPORT_H__ |
||||
|
||||
#ifndef __cplusplus |
||||
#define bool char |
||||
#define true 1 |
||||
#define false 0 |
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Defines for Windows
|
||||
|
||||
#if !defined(PLATFORM_DEFINED) && defined(_WIN32) |
||||
|
||||
// In MSVC 8.0, there are some functions declared as deprecated.
|
||||
#if _MSC_VER >= 1400 |
||||
#define _CRT_SECURE_NO_DEPRECATE |
||||
#define _CRT_NON_CONFORMING_SWPRINTFS |
||||
#endif |
||||
|
||||
#include <tchar.h> |
||||
#include <assert.h> |
||||
#include <ctype.h> |
||||
#include <stdio.h> |
||||
#include <windows.h> |
||||
#include <wininet.h> |
||||
#define PLATFORM_LITTLE_ENDIAN |
||||
|
||||
#ifdef _WIN64 |
||||
#define PLATFORM_64BIT |
||||
#else |
||||
#define PLATFORM_32BIT |
||||
#endif |
||||
|
||||
#define PLATFORM_WINDOWS |
||||
#define PLATFORM_DEFINED // The platform is known now
|
||||
|
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Defines for Mac
|
||||
|
||||
#if !defined(PLATFORM_DEFINED) && defined(__APPLE__) // Mac BSD API
|
||||
|
||||
// Macintosh
|
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/mman.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <stdlib.h> |
||||
#include <errno.h> |
||||
|
||||
// Support for PowerPC on Max OS X
|
||||
#if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1) |
||||
#include <stdint.h> |
||||
#include <CoreFoundation/CFByteOrder.h> |
||||
#endif |
||||
|
||||
#define PKEXPORT |
||||
#define __SYS_ZLIB |
||||
#define __SYS_BZLIB |
||||
|
||||
#ifndef __BIG_ENDIAN__ |
||||
#define PLATFORM_LITTLE_ENDIAN |
||||
#endif |
||||
|
||||
#define PLATFORM_MAC |
||||
#define PLATFORM_DEFINED // The platform is known now
|
||||
|
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Assumption: we are not on Windows nor Macintosh, so this must be linux *grin*
|
||||
|
||||
#if !defined(PLATFORM_DEFINED) |
||||
|
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/mman.h> |
||||
#include <fcntl.h> |
||||
#include <unistd.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <stdarg.h> |
||||
#include <string.h> |
||||
#include <ctype.h> |
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
|
||||
#define PLATFORM_LITTLE_ENDIAN |
||||
#define PLATFORM_LINUX |
||||
#define PLATFORM_DEFINED |
||||
|
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Definition of Windows-specific types for non-Windows platforms
|
||||
|
||||
#ifndef PLATFORM_WINDOWS |
||||
#if __LP64__ |
||||
#define PLATFORM_64BIT |
||||
#else |
||||
#define PLATFORM_32BIT |
||||
#endif |
||||
|
||||
// Typedefs for ANSI C
|
||||
typedef unsigned char BYTE; |
||||
typedef unsigned short USHORT; |
||||
typedef int LONG; |
||||
typedef unsigned int DWORD; |
||||
typedef unsigned long DWORD_PTR; |
||||
typedef long LONG_PTR; |
||||
typedef long INT_PTR; |
||||
typedef long long LONGLONG; |
||||
typedef unsigned long long ULONGLONG; |
||||
typedef void * HANDLE; |
||||
typedef void * LPOVERLAPPED; // Unsupported on Linux and Mac
|
||||
typedef char TCHAR; |
||||
typedef unsigned int LCID; |
||||
typedef LONG * PLONG; |
||||
typedef DWORD * LPDWORD; |
||||
typedef BYTE * LPBYTE; |
||||
typedef const char * LPCTSTR; |
||||
typedef const char * LPCSTR; |
||||
typedef char * LPTSTR; |
||||
typedef char * LPSTR; |
||||
|
||||
#ifdef PLATFORM_32BIT |
||||
#define _LZMA_UINT32_IS_ULONG |
||||
#endif |
||||
|
||||
// Some Windows-specific defines
|
||||
#ifndef MAX_PATH |
||||
#define MAX_PATH 1024 |
||||
#endif |
||||
|
||||
#ifndef _countof |
||||
#define _countof(x) (sizeof(x) / sizeof(x[0])) |
||||
#endif |
||||
|
||||
// MINIWIN change
|
||||
#define WINAPI __attribute__((stdcall)) |
||||
|
||||
#define FILE_BEGIN SEEK_SET |
||||
#define FILE_CURRENT SEEK_CUR |
||||
#define FILE_END SEEK_END |
||||
|
||||
#define _T(x) x |
||||
#define _tcslen strlen |
||||
#define _tcscpy strcpy |
||||
#define _tcscat strcat |
||||
#define _tcschr strchr |
||||
#define _tcsrchr strrchr |
||||
#define _tcsstr strstr |
||||
#define _tcsnicmp strncasecmp |
||||
#define _tprintf printf |
||||
#define _stprintf sprintf |
||||
#define _tremove remove |
||||
#define _tmain main |
||||
|
||||
#define _stricmp strcasecmp |
||||
#define _strnicmp strncasecmp |
||||
#define _tcsicmp strcasecmp |
||||
#define _tcsnicmp strncasecmp |
||||
|
||||
#endif // !PLATFORM_WINDOWS
|
||||
|
||||
// 64-bit calls are supplied by "normal" calls on Mac
|
||||
#if defined(PLATFORM_MAC) |
||||
#define stat64 stat |
||||
#define fstat64 fstat |
||||
#define lseek64 lseek |
||||
#define ftruncate64 ftruncate |
||||
#define off64_t off_t |
||||
#define O_LARGEFILE 0 |
||||
#endif |
||||
|
||||
// Platform-specific error codes for UNIX-based platforms
|
||||
#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) |
||||
#define ERROR_SUCCESS 0 |
||||
#define ERROR_FILE_NOT_FOUND ENOENT |
||||
#define ERROR_ACCESS_DENIED EPERM |
||||
#define ERROR_INVALID_HANDLE EBADF |
||||
#define ERROR_NOT_ENOUGH_MEMORY ENOMEM |
||||
#define ERROR_NOT_SUPPORTED ENOTSUP |
||||
#define ERROR_INVALID_PARAMETER EINVAL |
||||
#define ERROR_NEGATIVE_SEEK EINVAL |
||||
#define ERROR_DISK_FULL ENOSPC |
||||
#define ERROR_ALREADY_EXISTS EEXIST |
||||
#define ERROR_INSUFFICIENT_BUFFER ENOBUFS |
||||
#define ERROR_BAD_FORMAT 1000 // No such error code under Linux
|
||||
#define ERROR_NO_MORE_FILES 1001 // No such error code under Linux
|
||||
#define ERROR_HANDLE_EOF 1002 // No such error code under Linux
|
||||
#define ERROR_CAN_NOT_COMPLETE 1003 // No such error code under Linux
|
||||
#define ERROR_FILE_CORRUPT 1004 // No such error code under Linux
|
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Swapping functions
|
||||
|
||||
#ifdef PLATFORM_LITTLE_ENDIAN |
||||
#define BSWAP_INT16_UNSIGNED(a) (a) |
||||
#define BSWAP_INT16_SIGNED(a) (a) |
||||
#define BSWAP_INT32_UNSIGNED(a) (a) |
||||
#define BSWAP_INT32_SIGNED(a) (a) |
||||
#define BSWAP_INT64_SIGNED(a) (a) |
||||
#define BSWAP_INT64_UNSIGNED(a) (a) |
||||
#define BSWAP_ARRAY16_UNSIGNED(a,b) {} |
||||
#define BSWAP_ARRAY32_UNSIGNED(a,b) {} |
||||
#define BSWAP_ARRAY64_UNSIGNED(a,b) {} |
||||
#define BSWAP_PART_HEADER(a) {} |
||||
#define BSWAP_TMPQHEADER(a,b) {} |
||||
#define BSWAP_TMPKHEADER(a) {} |
||||
#else |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
int16_t SwapInt16(uint16_t); |
||||
uint16_t SwapUInt16(uint16_t); |
||||
int32_t SwapInt32(uint32_t); |
||||
uint32_t SwapUInt32(uint32_t); |
||||
int64_t SwapInt64(uint64_t); |
||||
uint64_t SwapUInt64(uint64_t); |
||||
void ConvertUInt16Buffer(void * ptr, size_t length); |
||||
void ConvertUInt32Buffer(void * ptr, size_t length); |
||||
void ConvertUInt64Buffer(void * ptr, size_t length); |
||||
void ConvertTMPQUserData(void *userData); |
||||
void ConvertTMPQHeader(void *header, uint16_t wPart); |
||||
void ConvertTMPKHeader(void *header); |
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#define BSWAP_INT16_SIGNED(a) SwapInt16((a)) |
||||
#define BSWAP_INT16_UNSIGNED(a) SwapUInt16((a)) |
||||
#define BSWAP_INT32_SIGNED(a) SwapInt32((a)) |
||||
#define BSWAP_INT32_UNSIGNED(a) SwapUInt32((a)) |
||||
#define BSWAP_INT64_SIGNED(a) SwapInt64((a)) |
||||
#define BSWAP_INT64_UNSIGNED(a) SwapUInt64((a)) |
||||
#define BSWAP_ARRAY16_UNSIGNED(a,b) ConvertUInt16Buffer((a),(b)) |
||||
#define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b)) |
||||
#define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b)) |
||||
#define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b)) |
||||
#define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a)) |
||||
#endif |
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Macro for deprecated symbols
|
||||
|
||||
/*
|
||||
#ifdef _MSC_VER |
||||
#if _MSC_FULL_VER >= 140050320 |
||||
#define STORMLIB_DEPRECATED(_Text) __declspec(deprecated(_Text)) |
||||
#else |
||||
#define STORMLIB_DEPRECATED(_Text) __declspec(deprecated) |
||||
#endif |
||||
#else |
||||
#ifdef __GNUC__ |
||||
#define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated)) |
||||
#else |
||||
#define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated(_Text))) |
||||
#endif |
||||
#endif |
||||
|
||||
// When a flag is deprecated, use this macro
|
||||
#ifndef _STORMLIB_NO_DEPRECATE |
||||
#define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) \ |
||||
const STORMLIB_DEPRECATED(#oldflag " is deprecated. Use " #newflag ". To supress this warning, define _STORMLIB_NO_DEPRECATE") static type oldflag = (type)newflag; |
||||
#else |
||||
#define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) static type oldflag = (type)newflag; |
||||
#endif |
||||
*/ |
||||
|
||||
//
|
||||
// MINIWIN changes
|
||||
//
|
||||
|
||||
#define bool int |
||||
void TranslateFileName(char* dst, int dstLen, const char* src); |
||||
|
||||
#endif // __STORMPORT_H__
|
||||
Loading…
Reference in new issue