aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SBaseCommon.cpp41
-rw-r--r--src/SBaseFileTable.cpp90
-rw-r--r--src/SFileOpenArchive.cpp46
-rw-r--r--src/StormCommon.h3
4 files changed, 112 insertions, 68 deletions
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index 28dc701..9003960 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -907,6 +907,7 @@ TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize)
void * LoadMpqTable(
TMPQArchive * ha,
ULONGLONG ByteOffset,
+ LPBYTE pbTableHash,
DWORD dwCompressedSize,
DWORD dwTableSize,
DWORD dwKey,
@@ -965,6 +966,19 @@ void * LoadMpqTable(
// If everything succeeded, read the raw table from the MPQ
if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
{
+ // Verify the MD5 of the table, if present
+ if(!VerifyDataBlockHash(pbToRead, dwBytesToRead, pbTableHash))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ if(nError == ERROR_SUCCESS)
+ {
// First of all, decrypt the table
if(dwKey != 0)
{
@@ -986,10 +1000,6 @@ void * LoadMpqTable(
// Make sure that the table is properly byte-swapped
BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
}
- else
- {
- nError = GetLastError();
- }
// If read failed, free the table and return
if(nError != ERROR_SUCCESS)
@@ -1341,7 +1351,7 @@ int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
// Now read the table from the MPQ
- hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0, NULL);
+ hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, NULL, dwCompressedSize, dwCrcSize, 0, NULL);
if(hf->SectorChksums == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
}
@@ -1692,7 +1702,7 @@ bool IsValidMD5(LPBYTE pbMd5)
{
LPDWORD Md5 = (LPDWORD)pbMd5;
- return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
+ return ((Md5 != NULL) && (Md5[0] | Md5[1] | Md5[2] | Md5[3])) ? true : false;
}
bool IsValidSignature(LPBYTE pbSignature)
@@ -1711,18 +1721,21 @@ bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_
{
hash_state md5_state;
BYTE md5_digest[MD5_DIGEST_SIZE];
+ bool bResult = true;
// Don't verify the block if the MD5 is not valid.
- if(!IsValidMD5(expected_md5))
- return true;
+ if(IsValidMD5(expected_md5))
+ {
+ // Calculate the MD5 of the data block
+ md5_init(&md5_state);
+ md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
+ md5_done(&md5_state, md5_digest);
- // Calculate the MD5 of the data block
- md5_init(&md5_state);
- md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
- md5_done(&md5_state, md5_digest);
+ // Does the MD5's match?
+ bResult = (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
+ }
- // Does the MD5's match?
- return (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
+ return bResult;
}
void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index 98d8e44..f9a86a0 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -258,6 +258,18 @@ static bool VerifyTablePosition64(
return true;
}
+static bool VerifyTableTandemPositions(
+ ULONGLONG MpqOffset, // Position of the MPQ header
+ ULONGLONG TableOffset1, // 1st table: Position, relative to MPQ header
+ ULONGLONG TableSize1, // 1st table: Size in bytes
+ ULONGLONG TableOffset2, // 2nd table: Position, relative to MPQ header
+ ULONGLONG TableSize2, // 2nd table: Size in bytes
+ ULONGLONG FileSize) // Size of the entire file, in bytes
+{
+ return VerifyTablePosition64(MpqOffset, TableOffset1, TableSize1, FileSize) &&
+ VerifyTablePosition64(MpqOffset, TableOffset2, TableSize2, FileSize);
+}
+
static ULONGLONG DetermineArchiveSize_V1(
TMPQArchive * ha,
TMPQHeader * pHeader,
@@ -417,7 +429,7 @@ ULONGLONG CalculateRawSectorOffset(
// This function converts the MPQ header so it always looks like version 4
int ConvertMpqHeaderToFormat4(
TMPQArchive * ha,
- ULONGLONG MpqOffset,
+ ULONGLONG ByteOffset,
ULONGLONG FileSize,
DWORD dwFlags,
MTYPE MapType)
@@ -426,8 +438,10 @@ int ConvertMpqHeaderToFormat4(
ULONGLONG BlockTablePos64 = 0;
ULONGLONG HashTablePos64 = 0;
ULONGLONG BlockTableMask = (ULONGLONG)-1;
- ULONGLONG ByteOffset;
+ ULONGLONG MaxOffset;
USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion);
+ bool bHashBlockOffsetOK = false;
+ bool bHetBetOffsetOK = false;
int nError = ERROR_SUCCESS;
// If version 1.0 is forced, then the format version is forced to be 1.0
@@ -479,14 +493,14 @@ int ConvertMpqHeaderToFormat4(
// Block table position must be calculated as 32-bit value
// Note: BOBA protector puts block table before the MPQ header, so it is negative
- BlockTablePos64 = (ULONGLONG)((DWORD)MpqOffset + pHeader->dwBlockTablePos);
+ BlockTablePos64 = (ULONGLONG)((DWORD)ByteOffset + pHeader->dwBlockTablePos);
BlockTableMask = 0xFFFFFFF0;
// Determine the archive size on malformed MPQs
if(ha->dwFlags & MPQ_FLAG_MALFORMED)
{
// Calculate the archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V1(ha, pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V1(ha, pHeader, ByteOffset, FileSize);
pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
}
@@ -538,7 +552,7 @@ int ConvertMpqHeaderToFormat4(
assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
// Determine real archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, ByteOffset, FileSize);
// Calculate the size of the hi-block table
pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64;
@@ -547,7 +561,7 @@ int ConvertMpqHeaderToFormat4(
else
{
// Determine real archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, ByteOffset, FileSize);
// Calculate size of the block table
pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - BlockTablePos64;
@@ -561,7 +575,7 @@ int ConvertMpqHeaderToFormat4(
}
// Add the MPQ Offset
- BlockTablePos64 += MpqOffset;
+ BlockTablePos64 += ByteOffset;
break;
case MPQ_FORMAT_VERSION_3:
@@ -590,45 +604,45 @@ int ConvertMpqHeaderToFormat4(
memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3);
BlockTablePos64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
HashTablePos64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
- ByteOffset = pHeader->ArchiveSize64;
+ MaxOffset = pHeader->ArchiveSize64;
// Size of the hi-block table
if(pHeader->HiBlockTablePos64)
{
- pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64;
- ByteOffset = pHeader->HiBlockTablePos64;
+ pHeader->HiBlockTableSize64 = MaxOffset - pHeader->HiBlockTablePos64;
+ MaxOffset = pHeader->HiBlockTablePos64;
}
// Size of the block table
if(BlockTablePos64)
{
- pHeader->BlockTableSize64 = ByteOffset - BlockTablePos64;
- ByteOffset = BlockTablePos64;
+ pHeader->BlockTableSize64 = MaxOffset - BlockTablePos64;
+ MaxOffset = BlockTablePos64;
}
// Size of the hash table
if(HashTablePos64)
{
- pHeader->HashTableSize64 = ByteOffset - HashTablePos64;
- ByteOffset = HashTablePos64;
+ pHeader->HashTableSize64 = MaxOffset - HashTablePos64;
+ MaxOffset = HashTablePos64;
}
// Size of the BET table
if(pHeader->BetTablePos64)
{
- pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64;
- ByteOffset = pHeader->BetTablePos64;
+ pHeader->BetTableSize64 = MaxOffset - pHeader->BetTablePos64;
+ MaxOffset = pHeader->BetTablePos64;
}
// Size of the HET table
if(pHeader->HetTablePos64)
{
- pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64;
-// ByteOffset = pHeader->HetTablePos64;
+ pHeader->HetTableSize64 = MaxOffset - pHeader->HetTablePos64;
+// MaxOffset = pHeader->HetTablePos64;
}
// Add the MPQ Offset
- BlockTablePos64 += MpqOffset;
+ BlockTablePos64 += ByteOffset;
break;
case MPQ_FORMAT_VERSION_4:
@@ -638,16 +652,34 @@ int ConvertMpqHeaderToFormat4(
BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4);
// Apparently, Starcraft II only accepts MPQ headers where the MPQ header hash matches
- // If MD5 doesn't match, we ignore this offset
+ // If MD5 doesn't match, we ignore this offset. We also ignore it if there's no MD5 at all
+ if(!IsValidMD5(pHeader->MD5_MpqHeader))
+ return ERROR_FAKE_MPQ_HEADER;
if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader))
return ERROR_FAKE_MPQ_HEADER;
- if(!VerifyTablePosition64(MpqOffset, pHeader->HiBlockTablePos64, pHeader->HiBlockTableSize64, FileSize))
+
+ // HiBlockTable must be 0 for archives under 4GB
+ if((pHeader->ArchiveSize64 >> 0x20) == 0 && pHeader->HiBlockTablePos64 != 0)
return ERROR_FAKE_MPQ_HEADER;
- if(!VerifyTablePosition64(MpqOffset, pHeader->HetTablePos64, pHeader->HetTableSize64, FileSize))
+
+ // Is the "HET&BET" table tandem OK?
+ bHetBetOffsetOK = VerifyTableTandemPositions(ByteOffset,
+ pHeader->HetTablePos64, pHeader->HetTableSize64,
+ pHeader->BetTablePos64, pHeader->BetTableSize64,
+ FileSize);
+
+ // Is the "Hash&Block" table tandem OK?
+ bHashBlockOffsetOK = VerifyTableTandemPositions(ByteOffset,
+ pHeader->dwHashTablePos, pHeader->HashTableSize64,
+ pHeader->dwBlockTablePos, pHeader->BlockTableSize64,
+ FileSize);
+
+ // At least one pair must be OK
+ if(bHetBetOffsetOK == false && bHashBlockOffsetOK == false)
return ERROR_FAKE_MPQ_HEADER;
// Check for malformed MPQs
- if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_4 || (MpqOffset + pHeader->ArchiveSize64) != FileSize || (MpqOffset + pHeader->HiBlockTablePos64) >= FileSize)
+ if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_4 || (ByteOffset + pHeader->ArchiveSize64) != FileSize || (ByteOffset + pHeader->HiBlockTablePos64) >= FileSize)
{
pHeader->wFormatVersion = MPQ_FORMAT_VERSION_4;
pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V4;
@@ -658,12 +690,12 @@ int ConvertMpqHeaderToFormat4(
if(ha->dwFlags & MPQ_FLAG_MALFORMED)
{
// Calculate the archive size
- pHeader->ArchiveSize64 = DetermineArchiveSize_V4(pHeader, MpqOffset, FileSize);
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V4(pHeader, ByteOffset, FileSize);
pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
}
// Calculate the block table position
- BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ BlockTablePos64 = ByteOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
break;
default:
@@ -679,13 +711,13 @@ int ConvertMpqHeaderToFormat4(
}
// Calculate the block table position
- BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ BlockTablePos64 = ByteOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
break;
}
// Handle case when block table is placed before the MPQ header
// Used by BOBA protector
- if(BlockTablePos64 < MpqOffset)
+ if(BlockTablePos64 < ByteOffset)
ha->dwFlags |= MPQ_FLAG_MALFORMED;
return nError;
}
@@ -2304,7 +2336,7 @@ static TMPQHash * LoadHashTable(TMPQArchive * ha)
dwCmpSize = (DWORD)pHeader->HashTableSize64;
// Read, decrypt and uncompress the hash table
- pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE, &bHashTableIsCut);
+ pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, pHeader->MD5_HashTable, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE, &bHashTableIsCut);
// DumpHashTable(pHashTable, pHeader->dwHashTableSize);
// If the hash table was cut, we can/have to defragment it
@@ -2365,7 +2397,7 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool /* bDontFixEntries */)
dwCmpSize = (DWORD)pHeader->BlockTableSize64;
// Read, decrypt and uncompress the block table
- pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE, &bBlockTableIsCut);
+ pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, NULL, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE, &bBlockTableIsCut);
// If the block table was cut, we need to remember it
if(pBlockTable != NULL && bBlockTableIsCut)
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index 7dfb851..500e041 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -135,12 +135,12 @@ static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize)
}
// Check the begin of hi-block table
- if(pHeader->HiBlockTablePos64 != 0)
- {
- ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64;
- if(ByteOffset > FileSize)
- return ERROR_BAD_FORMAT;
- }
+ //if(pHeader->HiBlockTablePos64 != 0)
+ //{
+ // ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64;
+ // if(ByteOffset > FileSize)
+ // return ERROR_BAD_FORMAT;
+ //}
// All OK.
return ERROR_SUCCESS;
@@ -186,7 +186,7 @@ bool WINAPI SFileOpenArchive(
ULONGLONG FileSize = 0; // Size of the file
LPBYTE pbHeaderBuffer = NULL; // Buffer for searching MPQ header
DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK);
- MTYPE MapType = MapTypeNotRecognized;
+ MTYPE MapType = MapTypeNotChecked;
int nError = ERROR_SUCCESS;
// Verify the parameters
@@ -234,7 +234,7 @@ bool WINAPI SFileOpenArchive(
// Find the position of MPQ header
if(nError == ERROR_SUCCESS)
{
- ULONGLONG SearchOffset = 0;
+ ULONGLONG ByteOffset = 0;
ULONGLONG EndOfSearch = FileSize;
DWORD dwStrmFlags = 0;
DWORD dwHeaderSize;
@@ -261,26 +261,25 @@ bool WINAPI SFileOpenArchive(
EndOfSearch = 0x08000000;
// Find the offset of MPQ header within the file
- while(bSearchComplete == false && SearchOffset < EndOfSearch)
+ while(bSearchComplete == false && ByteOffset < 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);
+ if((FileSize - ByteOffset) < HEADER_SEARCH_BUFFER_SIZE)
+ dwBytesAvailable = (DWORD)(FileSize - ByteOffset);
// Read the eventual MPQ header
- if(!FileStream_Read(ha->pStream, &SearchOffset, pbHeaderBuffer, dwBytesAvailable))
+ if(!FileStream_Read(ha->pStream, &ByteOffset, pbHeaderBuffer, dwBytesAvailable))
{
nError = GetLastError();
break;
}
// Check whether the file is AVI file or a Warcraft III/Starcraft II map
- if(SearchOffset == 0)
+ if(MapType == MapTypeNotChecked)
{
// Do nothing if the file is an AVI file
if((MapType = CheckMapType(szMpqName, pbHeaderBuffer, dwBytesAvailable)) == MapTypeAviFile)
@@ -291,7 +290,7 @@ bool WINAPI SFileOpenArchive(
}
// Search the header buffer
- while(dwInBufferOffset < dwBytesAvailable)
+ for(DWORD dwInBufferOffset = 0; dwInBufferOffset < dwBytesAvailable; dwInBufferOffset += 0x200)
{
// Copy the data from the potential header buffer to the MPQ header
memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData));
@@ -304,16 +303,16 @@ bool WINAPI SFileOpenArchive(
if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA)
{
// Verify if this looks like a valid user data
- pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData);
+ pUserData = IsValidMpqUserData(ByteOffset, FileSize, ha->HeaderData);
if(pUserData != NULL)
{
// Fill the user data header
- ha->UserDataPos = SearchOffset;
+ ha->UserDataPos = ByteOffset;
ha->pUserData = &ha->UserData;
memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData));
// Continue searching from that position
- SearchOffset += ha->pUserData->dwHeaderOffs;
+ ByteOffset += ha->pUserData->dwHeaderOffs;
break;
}
}
@@ -327,7 +326,7 @@ bool WINAPI SFileOpenArchive(
if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1)
{
// Now convert the header to version 4
- nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags, MapType);
+ nError = ConvertMpqHeaderToFormat4(ha, ByteOffset, FileSize, dwFlags, MapType);
if(nError != ERROR_FAKE_MPQ_HEADER)
{
bSearchComplete = true;
@@ -353,8 +352,7 @@ bool WINAPI SFileOpenArchive(
}
// Move the pointers
- SearchOffset += 0x200;
- dwInBufferOffset += 0x200;
+ ByteOffset += 0x200;
}
}
@@ -363,15 +361,15 @@ bool WINAPI SFileOpenArchive(
{
// Set the user data position to the MPQ header, if none
if(ha->pUserData == NULL)
- ha->UserDataPos = SearchOffset;
+ ha->UserDataPos = ByteOffset;
// Set the position of the MPQ header
ha->pHeader = (TMPQHeader *)ha->HeaderData;
- ha->MpqPos = SearchOffset;
+ ha->MpqPos = ByteOffset;
ha->FileSize = FileSize;
// Sector size must be nonzero.
- if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0)
+ if(ByteOffset >= FileSize || ha->pHeader->wSectorSize == 0)
nError = ERROR_BAD_FORMAT;
}
}
diff --git a/src/StormCommon.h b/src/StormCommon.h
index 1167d29..9f9715f 100644
--- a/src/StormCommon.h
+++ b/src/StormCommon.h
@@ -75,6 +75,7 @@
typedef enum _MTYPE
{
+ MapTypeNotChecked, // The map type was not checked yet
MapTypeNotRecognized, // The file does not seems to be a map
MapTypeAviFile, // The file is actually an AVI file (Warcraft III cinematics)
MapTypeWarcraft3, // The file is a Warcraft III map
@@ -310,7 +311,7 @@ int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer
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);
+void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, LPBYTE pbTableHash, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey, bool * pbTableIsCut);
int AllocateSectorBuffer(TMPQFile * hf);
int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile);
int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile);