Skip to content

Commit 870acd5

Browse files
LadislavLadislav
Ladislav
authored and
Ladislav
committed
+ Adding files to MPQ no longer removes (listfile) and (attributes) when not enough space in the hash table
+ Fixes in deletion of files from MPQs v 4.0
1 parent 1f2305c commit 870acd5

12 files changed

+141
-92
lines changed

src/SBaseCommon.cpp

+4-5
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ TMPQHash * AllocateHashEntry(
843843
// Finds a free space in the MPQ where to store next data
844844
// The free space begins beyond the file that is stored at the fuhrtest
845845
// position in the MPQ.
846-
void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos)
846+
ULONGLONG FindFreeMpqSpace(TMPQArchive * ha)
847847
{
848848
TMPQHeader * pHeader = ha->pHeader;
849849
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
@@ -854,8 +854,8 @@ void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos)
854854
// Parse the entire block table
855855
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
856856
{
857-
// Only take existing files
858-
if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
857+
// Only take existing files with nonzero size
858+
if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0))
859859
{
860860
// If the end of the file is bigger than current MPQ table pos, update it
861861
if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos)
@@ -874,8 +874,7 @@ void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos)
874874
}
875875

876876
// Give the free space position to the caller
877-
if(pFreeSpacePos != NULL)
878-
*pFreeSpacePos = FreeSpacePos;
877+
return FreeSpacePos;
879878
}
880879

881880
//-----------------------------------------------------------------------------

src/SBaseFileTable.cpp

+69-53
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
typedef struct _HET_TABLE_HEADER
2626
{
2727
DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes)
28-
DWORD dwMaxFileCount; // Maximum number of files in the MPQ
28+
DWORD dwFileCount; // Number of occupied entries in the hash table
2929
DWORD dwHashTableSize; // Size of the hash table (in bytes)
3030
DWORD dwHashEntrySize; // Effective size of the hash entry (in bits)
3131
DWORD dwIndexSizeTotal; // Total size of file index (in bits)
@@ -722,10 +722,10 @@ static void CreateHetHeader(
722722
PHET_TABLE_HEADER pHetHeader)
723723
{
724724
// Fill the BET header
725-
pHetHeader->dwMaxFileCount = pHetTable->dwMaxFileCount;
725+
pHetHeader->dwFileCount = pHetTable->dwFileCount;
726726
pHetHeader->dwHashTableSize = pHetTable->dwHashTableSize;
727727
pHetHeader->dwHashEntrySize = pHetTable->dwHashBitSize;
728-
pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwMaxFileCount);
728+
pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwHashTableSize);
729729
pHetHeader->dwIndexSizeExtra = 0;
730730
pHetHeader->dwIndexSize = pHetHeader->dwIndexSizeTotal;
731731
pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwHashTableSize) + 7) / 8;
@@ -736,7 +736,7 @@ static void CreateHetHeader(
736736
pHetHeader->dwIndexTableSize;
737737
}
738738

739-
TMPQHetTable * CreateHetTable(DWORD dwMaxFileCount, DWORD dwHashBitSize, bool bCreateEmpty)
739+
TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty)
740740
{
741741
TMPQHetTable * pHetTable;
742742

@@ -746,12 +746,22 @@ TMPQHetTable * CreateHetTable(DWORD dwMaxFileCount, DWORD dwHashBitSize, bool bC
746746
pHetTable->dwIndexSizeTotal = 0;
747747
pHetTable->dwIndexSizeExtra = 0;
748748
pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
749-
pHetTable->dwMaxFileCount = dwMaxFileCount;
750-
pHetTable->dwHashTableSize = (dwMaxFileCount * 4 / 3);
751749
pHetTable->dwHashBitSize = dwHashBitSize;
752750

753-
// Size of one index is calculated from max file count
754-
pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwMaxFileCount);
751+
// If the hash table size is not entered, calculate an optimal
752+
// hash table size as 4/3 of the current file count.
753+
if(dwHashTableSize == 0)
754+
{
755+
dwHashTableSize = (dwFileCount * 4 / 3);
756+
assert(dwFileCount != 0);
757+
}
758+
759+
// Store the hash table size and file count
760+
pHetTable->dwHashTableSize = dwHashTableSize;
761+
pHetTable->dwFileCount = dwFileCount;
762+
763+
// Size of one index is calculated from hash table size
764+
pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwHashTableSize);
755765
pHetTable->dwIndexSizeExtra = 0;
756766
pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
757767

@@ -805,8 +815,12 @@ static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable)
805815
// Verify the size of the table in the header
806816
if(HetHeader.dwTableSize == pExtTable->dwDataSize)
807817
{
818+
// The size of index table (in entries) is expected
819+
// to be the same like the hash table size (in bytes)
820+
assert(((HetHeader.dwIndexTableSize * 8) / HetHeader.dwIndexSize) == HetHeader.dwHashTableSize);
821+
808822
// Create translated table
809-
pHetTable = CreateHetTable(HetHeader.dwMaxFileCount, HetHeader.dwHashEntrySize, false);
823+
pHetTable = CreateHetTable(HetHeader.dwHashTableSize, HetHeader.dwFileCount, HetHeader.dwHashEntrySize, false);
810824
if(pHetTable != NULL)
811825
{
812826
// Copy the hash table size, index size and extra bits from the HET header
@@ -909,7 +923,7 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
909923
// Go through HET table until we find a terminator
910924
while(pHetTable->pHetHashes[Index] != HET_ENTRY_FREE)
911925
{
912-
// Did we find match ?
926+
// Did we find a match ?
913927
if(pHetTable->pHetHashes[Index] == HetHash)
914928
{
915929
DWORD dwFileIndex = 0;
@@ -919,7 +933,6 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
919933
pHetTable->dwIndexSize,
920934
&dwFileIndex,
921935
4);
922-
923936
//
924937
// TODO: This condition only happens when we are opening a MPQ
925938
// where some files were deleted by StormLib. Perhaps
@@ -952,6 +965,7 @@ DWORD AllocateHetEntry(
952965
ULONGLONG AndMask64;
953966
ULONGLONG OrMask64;
954967
ULONGLONG BetHash;
968+
DWORD FileCountIncrement = 0;
955969
DWORD FreeHetIndex = HASH_ENTRY_FREE;
956970
DWORD dwFileIndex;
957971
DWORD StartIndex;
@@ -978,29 +992,35 @@ DWORD AllocateHetEntry(
978992
// Go through HET table until we find a terminator
979993
for(;;)
980994
{
981-
// Check for entries that might have been deleted
982-
if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED)
995+
// Did we find a match ?
996+
if(pHetTable->pHetHashes[Index] == HetHash)
983997
{
984-
DWORD dwInvalidBetIndex = (1 << pHetTable->dwIndexSizeTotal) - 1;
985-
DWORD dwBetIndex = 0;
998+
DWORD dwFileIndex = 0;
986999

987-
// Verify the BET index. If it's really free, we can use it
988-
dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable);
1000+
// Get the index of the BetHash
9891001
GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
9901002
pHetTable->dwIndexSize,
991-
&dwBetIndex,
1003+
&dwFileIndex,
9921004
4);
993-
994-
if(dwBetIndex == dwInvalidBetIndex)
1005+
//
1006+
// TODO: This condition only happens when we are opening a MPQ
1007+
// where some files were deleted by StormLib. Perhaps
1008+
// we should not allow shrinking of the file table in MPQs v 4.0?
1009+
// assert(dwFileIndex <= ha->dwFileTableSize);
1010+
//
1011+
1012+
// Verify the BetHash against the entry in the table of BET hashes
1013+
if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash)
9951014
{
9961015
FreeHetIndex = Index;
9971016
break;
9981017
}
9991018
}
10001019

1001-
// Is that entry free ?
1002-
if(pHetTable->pHetHashes[Index] == HET_ENTRY_FREE)
1020+
// Check for entries that might have been deleted
1021+
if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED || pHetTable->pHetHashes[Index] == HET_ENTRY_FREE)
10031022
{
1023+
FileCountIncrement++;
10041024
FreeHetIndex = Index;
10051025
break;
10061026
}
@@ -1015,10 +1035,12 @@ DWORD AllocateHetEntry(
10151035
// Fill the HET table entry
10161036
dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable);
10171037
pHetTable->pHetHashes[FreeHetIndex] = HetHash;
1038+
pHetTable->dwFileCount += FileCountIncrement;
10181039
SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * FreeHetIndex,
10191040
pHetTable->dwIndexSize,
10201041
&dwFileIndex,
10211042
4);
1043+
10221044
// Fill the file entry
10231045
pFileEntry->BetHash = BetHash;
10241046
pFileEntry->dwHetIndex = FreeHetIndex;
@@ -1792,22 +1814,28 @@ void InvalidateInternalFiles(TMPQArchive * ha)
17921814
{
17931815
TFileEntry * pFileEntry;
17941816

1817+
//
1818+
// Note: We set the size of both (listfile) and (attributes) to zero.
1819+
// This causes allocating space for newly added files straight over
1820+
// (listfile)/(attributes), if these were the last ones in the MPQ
1821+
//
1822+
17951823
// Invalidate the (listfile), if not done yet
1796-
if(!(ha->dwFlags & MPQ_FLAG_INV_LISTFILE))
1824+
if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID))
17971825
{
17981826
pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL);
17991827
if(pFileEntry != NULL)
1800-
FreeFileEntry(ha, pFileEntry);
1801-
ha->dwFlags |= MPQ_FLAG_INV_LISTFILE;
1828+
pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0;
1829+
ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID;
18021830
}
18031831

18041832
// Invalidate the (attributes), if not done yet
1805-
if(!(ha->dwFlags & MPQ_FLAG_INV_ATTRIBUTES))
1833+
if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID))
18061834
{
18071835
pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
18081836
if(pFileEntry != NULL)
1809-
FreeFileEntry(ha, pFileEntry);
1810-
ha->dwFlags |= MPQ_FLAG_INV_ATTRIBUTES;
1837+
pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0;
1838+
ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID;
18111839
}
18121840

18131841
// Remember that the MPQ has been changed and it will be necessary
@@ -2085,11 +2113,11 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize)
20852113
return pBlockTable;
20862114
}
20872115

2088-
int LoadHetTable(TMPQArchive * ha)
2116+
TMPQHetTable * LoadHetTable(TMPQArchive * ha)
20892117
{
2118+
TMPQHetTable * pHetTable = NULL;
20902119
TMPQExtTable * pExtTable;
20912120
TMPQHeader * pHeader = ha->pHeader;
2092-
int nError = ERROR_SUCCESS;
20932121

20942122
// If the HET table position is not NULL, we expect
20952123
// both HET and BET tables to be present.
@@ -2099,22 +2127,13 @@ int LoadHetTable(TMPQArchive * ha)
20992127
pExtTable = LoadExtTable(ha, pHeader->HetTablePos64, (size_t)pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
21002128
if(pExtTable != NULL)
21012129
{
2102-
// If succeeded, we have to limit the maximum file count
2103-
// to the values saved in the HET table
21042130
// If loading HET table fails, we ignore the result.
2105-
ha->pHetTable = TranslateHetTable(pExtTable);
2106-
if(ha->pHetTable != NULL)
2107-
ha->dwMaxFileCount = ha->pHetTable->dwMaxFileCount;
2108-
2131+
pHetTable = TranslateHetTable(pExtTable);
21092132
STORM_FREE(pExtTable);
21102133
}
2111-
2112-
// If the HET hable failed to load, it's corrupt.
2113-
if(ha->pHetTable == NULL)
2114-
nError = ERROR_FILE_CORRUPT;
21152134
}
21162135

2117-
return nError;
2136+
return pHetTable;
21182137
}
21192138

21202139
TMPQBetTable * LoadBetTable(TMPQArchive * ha)
@@ -2149,21 +2168,18 @@ int LoadAnyHashTable(TMPQArchive * ha)
21492168
if(pHeader->dwHashTableSize == 0 && pHeader->HetTableSize64 == 0)
21502169
return CreateHashTable(ha, HASH_TABLE_SIZE_DEFAULT);
21512170

2152-
// Try to load HET and/or classic hash table
2153-
LoadHetTable(ha);
2154-
2155-
// Load the HASH table
2171+
// Try to load HET table (the new hash table) and the classic HASH table
2172+
ha->pHetTable = LoadHetTable(ha);
21562173
ha->pHashTable = LoadHashTable(ha);
2157-
2158-
// Set the maximum file count to the size of the hash table
2159-
// In case there is HET table, we have to keep the file limit
2160-
if(ha->pHetTable == NULL)
2161-
ha->dwMaxFileCount = pHeader->dwHashTableSize;
2162-
2163-
// Did at least one succeed?
21642174
if(ha->pHetTable == NULL && ha->pHashTable == NULL)
21652175
return ERROR_FILE_CORRUPT;
21662176

2177+
// Set the maximum file count
2178+
if(ha->pHetTable != NULL && ha->pHashTable != NULL)
2179+
ha->dwMaxFileCount = STORMLIB_MIN(ha->pHetTable->dwHashTableSize, pHeader->dwHashTableSize);
2180+
else
2181+
ha->dwMaxFileCount = (ha->pHetTable != NULL) ? ha->pHetTable->dwHashTableSize : pHeader->dwHashTableSize;
2182+
21672183
// In theory, a MPQ could have bigger block table than hash table
21682184
if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount)
21692185
{
@@ -2445,7 +2461,7 @@ int SaveMPQTables(TMPQArchive * ha)
24452461
assert(ha->dwFlags & MPQ_FLAG_CHANGED);
24462462

24472463
// Find the space where the MPQ tables will be saved
2448-
FindFreeMpqSpace(ha, &TablePos);
2464+
TablePos = FindFreeMpqSpace(ha);
24492465

24502466
// If the MPQ has HET table, we prepare a ready-to-save version
24512467
if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)

src/SFileAddFile.cpp

+13-9
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ int SFileAddFile_Init(
386386
if(nError == ERROR_SUCCESS)
387387
{
388388
// Find the position where the file will be stored
389-
FindFreeMpqSpace(ha, &hf->MpqFilePos);
389+
hf->MpqFilePos = FindFreeMpqSpace(ha);
390390
hf->RawFilePos = ha->MpqPos + hf->MpqFilePos;
391391
hf->bIsWriteHandle = true;
392392

@@ -418,15 +418,19 @@ int SFileAddFile_Init(
418418
}
419419
else
420420
{
421-
// If the file exists and "replace existing" is not set, fail it
422-
if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0)
423-
nError = ERROR_ALREADY_EXISTS;
424-
425-
// If the file entry already contains a file
426-
// and it is a pseudo-name, replace it
427-
if(nError == ERROR_SUCCESS)
421+
// Only if the file really exists
422+
if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
428423
{
429-
AllocateFileName(pFileEntry, szFileName);
424+
// If the file exists and "replace existing" is not set, fail it
425+
if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0)
426+
nError = ERROR_ALREADY_EXISTS;
427+
428+
// If the file entry already contains a file
429+
// and it is a pseudo-name, replace it
430+
if(nError == ERROR_SUCCESS)
431+
{
432+
AllocateFileName(pFileEntry, szFileName);
433+
}
430434
}
431435
}
432436
}

src/SFileAttributes.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ int SAttrFileSaveToMpq(TMPQArchive * ha)
363363
}
364364

365365
if(nError == ERROR_SUCCESS)
366-
ha->dwFlags &= ~MPQ_FLAG_INV_ATTRIBUTES;
366+
ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID;
367367
return nError;
368368
}
369369

src/SFileCompactArchive.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -625,12 +625,12 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
625625
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
626626
nError = ERROR_ACCESS_DENIED;
627627

628-
// The new limit must not be lower than the index of the last file entry in the table
628+
// The new limit must be greater than the current file table size
629629
if(nError == ERROR_SUCCESS && ha->dwFileTableSize > dwMaxFileCount)
630630
nError = ERROR_DISK_FULL;
631631

632632
// ALL file names must be known in order to be able
633-
// to rebuild hash table size
633+
// to rebuild hash table
634634
if(nError == ERROR_SUCCESS)
635635
{
636636
nError = CheckIfAllFilesKnown(ha, NULL, NULL);
@@ -659,7 +659,7 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
659659
pOldHetTable = ha->pHetTable;
660660

661661
// Create new one
662-
ha->pHetTable = CreateHetTable(dwMaxFileCount, 0x40, true);
662+
ha->pHetTable = CreateHetTable(0, dwMaxFileCount, 0x40, true);
663663
if(ha->pHetTable == NULL)
664664
nError = ERROR_NOT_ENOUGH_MEMORY;
665665
}

src/SFileCreateArchive.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,13 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea
211211
nError = WriteNakedMPQHeader(ha);
212212

213213
// Remember that the (listfile) and (attributes) need to be saved
214-
ha->dwFlags |= MPQ_FLAG_CHANGED | MPQ_FLAG_INV_LISTFILE | MPQ_FLAG_INV_ATTRIBUTES;
214+
ha->dwFlags |= MPQ_FLAG_CHANGED | MPQ_FLAG_LISTFILE_INVALID | MPQ_FLAG_ATTRIBUTES_INVALID;
215215
}
216216

217217
// Create initial HET table, if the caller required an MPQ format 3.0 or newer
218218
if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3)
219219
{
220-
ha->pHetTable = CreateHetTable(ha->dwMaxFileCount, 0x40, true);
220+
ha->pHetTable = CreateHetTable(0, ha->dwFileTableSize, 0x40, true);
221221
if(ha->pHetTable == NULL)
222222
nError = ERROR_NOT_ENOUGH_MEMORY;
223223
}

src/SFileFindFile.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ static DWORD GetSearchTableItems(TMPQArchive * ha)
140140
while(ha != NULL)
141141
{
142142
// Append the number of files
143-
dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwMaxFileCount
143+
dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwFileCount
144144
: ha->pHeader->dwBlockTableSize;
145145
// Move to the patched archive
146146
ha = ha->haPatch;

0 commit comments

Comments
 (0)