diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 7787b23cf..06597d305 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -796,7 +796,7 @@ void RunGameLoop(interface_mode uMsg) demo::NotifyGameLoopEnd(); if (gbIsMultiplayer) { - pfile_write_hero(/*writeGameData=*/false, /*clearTables=*/true); + pfile_write_hero(/*writeGameData=*/false); sfile_write_stash(); } diff --git a/Source/init.cpp b/Source/init.cpp index 11f8ff9f8..df5f57fb4 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -129,7 +129,7 @@ std::vector GetMPQSearchPaths() void init_cleanup() { if (gbIsMultiplayer && gbRunGame) { - pfile_write_hero(/*writeGameData=*/false, /*clearTables=*/true); + pfile_write_hero(/*writeGameData=*/false); sfile_write_stash(); } diff --git a/Source/interfac.cpp b/Source/interfac.cpp index d084057ff..6117f8439 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -284,7 +284,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABNEXTLVL: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -300,7 +300,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABPREVLVL: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -317,7 +317,7 @@ void ShowProgress(interface_mode uMsg) SetReturnLvlPos(); IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -332,7 +332,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABRTNLVL: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -347,7 +347,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABWARPLVL: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -361,7 +361,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABTOWNWARP: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -377,7 +377,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABTWARPUP: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } @@ -392,7 +392,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABRETOWN: IncProgress(); if (!gbIsMultiplayer) { - SaveLevel(); + pfile_save_level(); } else { DeltaSaveLevel(); } diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 508a37a87..2bc43dd5f 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -877,54 +877,32 @@ void LoadPortal(LoadHelper *file, int i) pPortal->setlvl = file->NextBool32(); } -void ConvertLevels() +void GetTempLevelNames(char *szTemp) { - // Backup current level state - bool tmpSetlevel = setlevel; - _setlevels tmpSetlvlnum = setlvlnum; - int tmpCurrlevel = currlevel; - dungeon_type tmpLeveltype = leveltype; - - gbSkipSync = true; - - setlevel = false; // Convert regular levels - for (int i = 0; i < giNumberOfLevels; i++) { - currlevel = i; - if (!LevelFileExists()) - continue; - - leveltype = GetLevelType(currlevel); - - LoadLevel(); - SaveLevel(); - } - - setlevel = true; // Convert quest levels - for (auto &quest : Quests) { - if (quest._qactive == QUEST_NOTAVAIL) { - continue; - } - - leveltype = quest._qlvltype; - if (leveltype == DTYPE_NONE) { - continue; - } + if (setlevel) + sprintf(szTemp, "temps%02d", setlvlnum); + else + sprintf(szTemp, "templ%02d", currlevel); +} - setlvlnum = quest._qslvl; - if (!LevelFileExists()) - continue; +void GetPermLevelNames(char *szPerm) +{ + if (setlevel) + sprintf(szPerm, "perms%02d", setlvlnum); + else + sprintf(szPerm, "perml%02d", currlevel); +} - LoadLevel(); - SaveLevel(); - } +bool LevelFileExists(MpqWriter &archive) +{ + char szName[MAX_PATH]; - gbSkipSync = false; + GetTempLevelNames(szName); + if (archive.HasFile(szName)) + return true; - // Restor current level state - setlevel = tmpSetlevel; - setlvlnum = tmpSetlvlnum; - currlevel = tmpCurrlevel; - leveltype = tmpLeveltype; + GetPermLevelNames(szName); + return archive.HasFile(szName); } void LoadMatchingItems(LoadHelper &file, const int n, Item *pItem) @@ -1627,11 +1605,11 @@ void SaveDroppedItemLocations(SaveHelper &file, const std::unordered_map MaxMissilesForSaveGame) ? static_cast(Missiles.size() - MaxMissilesForSaveGame) : 0; - SaveHelper file(CurrentSaveArchive(), "additionalMissiles", sizeof(uint32_t) + sizeof(uint32_t) + (missileCountAdditional * BytesWrittenBySaveMissile)); + SaveHelper file(saveWriter, "additionalMissiles", sizeof(uint32_t) + sizeof(uint32_t) + (missileCountAdditional * BytesWrittenBySaveMissile)); file.WriteLE(VersionAdditionalMissiles); file.WriteLE(missileCountAdditional); @@ -1671,6 +1649,56 @@ const int HellfireItemSaveSize = 372; } // namespace +void ConvertLevels(MpqWriter &saveWriter) +{ + // Backup current level state + bool tmpSetlevel = setlevel; + _setlevels tmpSetlvlnum = setlvlnum; + int tmpCurrlevel = currlevel; + dungeon_type tmpLeveltype = leveltype; + + gbSkipSync = true; + + setlevel = false; // Convert regular levels + for (int i = 0; i < giNumberOfLevels; i++) { + currlevel = i; + if (!LevelFileExists(saveWriter)) + continue; + + leveltype = GetLevelType(currlevel); + + LoadLevel(); + SaveLevel(saveWriter); + } + + setlevel = true; // Convert quest levels + for (auto &quest : Quests) { + if (quest._qactive == QUEST_NOTAVAIL) { + continue; + } + + leveltype = quest._qlvltype; + if (leveltype == DTYPE_NONE) { + continue; + } + + setlvlnum = quest._qslvl; + if (!LevelFileExists(saveWriter)) + continue; + + LoadLevel(); + SaveLevel(saveWriter); + } + + gbSkipSync = false; + + // Restor current level state + setlevel = tmpSetlevel; + setlvlnum = tmpSetlvlnum; + currlevel = tmpCurrlevel; + leveltype = tmpLeveltype; +} + void RemoveInvalidItem(Item &item) { bool isInvalid = !IsItemAvailable(item.IDidx) || !IsUniqueAvailable(item._iUid); @@ -1864,11 +1892,11 @@ void LoadHotkeys() myPlayer._pRSplType = static_cast(file.NextLE()); } -void SaveHotkeys() +void SaveHotkeys(MpqWriter &saveWriter) { Player &myPlayer = *MyPlayer; - SaveHelper file(CurrentSaveArchive(), "hotkeys", HotkeysSize()); + SaveHelper file(saveWriter, "hotkeys", HotkeysSize()); // Write the number of spell hotkeys file.WriteLE(static_cast(NumHotkeys)); @@ -2013,7 +2041,7 @@ void LoadGame(bool firstflag) LoadPortal(&file, i); if (gbIsHellfireSaveGame != gbIsHellfire) { - ConvertLevels(); + pfile_convert_levels(); RemoveEmptyInventory(myPlayer); } @@ -2153,10 +2181,10 @@ void LoadGame(bool firstflag) gbIsHellfireSaveGame = gbIsHellfire; } -void SaveHeroItems(Player &player) +void SaveHeroItems(MpqWriter &saveWriter, Player &player) { size_t itemCount = NUM_INVLOC + NUM_INV_GRID_ELEM + MAXBELTITEMS; - SaveHelper file(CurrentSaveArchive(), "heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); + SaveHelper file(saveWriter, "heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); file.WriteLE(gbIsHellfire ? 1 : 0); @@ -2168,7 +2196,7 @@ void SaveHeroItems(Player &player) SaveItem(file, item); } -void SaveStash() +void SaveStash(MpqWriter &stashWriter) { const char *filename; if (!gbIsMultiplayer) @@ -2179,7 +2207,7 @@ void SaveStash() const int itemSize = (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize); SaveHelper file( - StashArchive(), + stashWriter, filename, sizeof(uint8_t) + sizeof(uint32_t) @@ -2225,9 +2253,9 @@ void SaveStash() file.WriteLE(static_cast(Stash.GetPage())); } -void SaveGameData() +void SaveGameData(MpqWriter &saveWriter) { - SaveHelper file(CurrentSaveArchive(), "game", 320 * 1024); + SaveHelper file(saveWriter, "game", 320 * 1024); if (gbIsSpawn && !gbIsHellfire) file.WriteLE(LoadLE32("SHAR")); @@ -2386,7 +2414,7 @@ void SaveGameData() file.WriteLE(AutomapActive ? 1 : 0); file.WriteBE(AutoMapScale); - SaveAdditionalMissiles(); + SaveAdditionalMissiles(saveWriter); } void SaveGame() @@ -2396,10 +2424,8 @@ void SaveGame() sfile_write_stash(); } -void SaveLevel() +void SaveLevel(MpqWriter &saveWriter) { - PFileScopedArchiveWriter scopedWriter; - Player &myPlayer = *MyPlayer; DoUnVision(myPlayer.position.tile, myPlayer._pLightRad); // fix for vision staying on the level @@ -2409,7 +2435,7 @@ void SaveLevel() char szName[MAX_PATH]; GetTempLevelNames(szName); - SaveHelper file(CurrentSaveArchive(), szName, 256 * 1024); + SaveHelper file(saveWriter, szName, 256 * 1024); if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { @@ -2475,8 +2501,11 @@ void SaveLevel() void LoadLevel() { char szName[MAX_PATH]; - GetPermLevelNames(szName); - LoadHelper file(OpenSaveArchive(gSaveNumber), szName); + std::optional archive = OpenSaveArchive(gSaveNumber); + GetTempLevelNames(szName); + if (!archive || !archive->HasFile(szName)) + GetPermLevelNames(szName); + LoadHelper file(std::move(archive), szName); if (!file.IsValid()) app_fatal(_("Unable to open save file archive")); diff --git a/Source/loadsave.h b/Source/loadsave.h index edc2b0031..5f1b93ca3 100644 --- a/Source/loadsave.h +++ b/Source/loadsave.h @@ -5,6 +5,7 @@ */ #pragma once +#include "mpq/mpq_writer.hpp" #include "player.h" #include "utils/attributes.h" @@ -32,13 +33,14 @@ void RemoveEmptyInventory(Player &player); * @param firstflag Can be set to false if we are simply reloading the current game */ void LoadGame(bool firstflag); -void SaveHotkeys(); -void SaveHeroItems(Player &player); -void SaveGameData(); +void SaveHotkeys(MpqWriter &saveWriter); +void SaveHeroItems(MpqWriter &saveWriter, Player &player); +void SaveGameData(MpqWriter &saveWriter); void SaveGame(); -void SaveLevel(); +void SaveLevel(MpqWriter &saveWriter); void LoadLevel(); +void ConvertLevels(MpqWriter &saveWriter); void LoadStash(); -void SaveStash(); +void SaveStash(MpqWriter &stashWriter); } // namespace devilution diff --git a/Source/mpq/mpq_reader.cpp b/Source/mpq/mpq_reader.cpp index a0bcdd99c..a3ca475c4 100644 --- a/Source/mpq/mpq_reader.cpp +++ b/Source/mpq/mpq_reader.cpp @@ -142,4 +142,11 @@ std::size_t MpqArchive::GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, return blockSize; } +bool MpqArchive::HasFile(const char *filename) const +{ + std::uint32_t fileNumber; + int32_t error = libmpq__file_number(archive_, filename, &fileNumber); + return error == 0; +} + } // namespace devilution diff --git a/Source/mpq/mpq_reader.hpp b/Source/mpq/mpq_reader.hpp index 1fa3e9ec4..995eef113 100644 --- a/Source/mpq/mpq_reader.hpp +++ b/Source/mpq/mpq_reader.hpp @@ -59,6 +59,8 @@ public: // Requires the block offset table to be open std::size_t GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, int32_t &error); + bool HasFile(const char *filename) const; + private: MpqArchive(std::string path, mpq_archive_s *archive) : path_(std::move(path)) diff --git a/Source/mpq/mpq_writer.cpp b/Source/mpq/mpq_writer.cpp index 36089134c..16fd7c028 100644 --- a/Source/mpq/mpq_writer.cpp +++ b/Source/mpq/mpq_writer.cpp @@ -11,6 +11,7 @@ #include "engine.h" #include "utils/endian.hpp" #include "utils/file_util.h" +#include "utils/language.h" #include "utils/log.hpp" namespace devilution { @@ -83,16 +84,15 @@ bool IsUnallocatedBlock(const MpqBlockEntry *block) } // namespace -bool MpqWriter::Open(const char *path) +MpqWriter::MpqWriter(const char *path) { - Close(/*clearTables=*/false); LogVerbose("Opening {}", path); - exists_ = FileExists(path); + bool exists = FileExists(path); std::ios::openmode mode = std::ios::in | std::ios::out | std::ios::binary; - if (exists_) { + if (exists) { if (!GetFileSize(path, &size_)) { Log(R"(GetFileSize("{}") failed with "{}")", path, std::strerror(errno)); - return false; + goto on_error; } LogVerbose("GetFileSize(\"{}\") = {}", path, size_); } else { @@ -100,37 +100,36 @@ bool MpqWriter::Open(const char *path) } if (!stream_.Open(path, mode)) { stream_.Close(); - return false; + goto on_error; } - modified_ = !exists_; name_ = path; if (blockTable_ == nullptr || hashTable_ == nullptr) { MpqFileHeader fhdr; - if (!exists_) { + if (!exists) { InitDefaultMpqHeader(&fhdr); } else if (!ReadMPQHeader(&fhdr)) { goto on_error; } - blockTable_ = new MpqBlockEntry[BlockEntriesCount]; - std::memset(blockTable_, 0, BlockEntriesCount * sizeof(MpqBlockEntry)); + blockTable_ = std::make_unique(BlockEntriesCount); + std::memset(blockTable_.get(), 0, BlockEntriesCount * sizeof(MpqBlockEntry)); if (fhdr.blockEntriesCount > 0) { - if (!stream_.Read(reinterpret_cast(blockTable_), static_cast(fhdr.blockEntriesCount * sizeof(MpqBlockEntry)))) + if (!stream_.Read(reinterpret_cast(blockTable_.get()), static_cast(fhdr.blockEntriesCount * sizeof(MpqBlockEntry)))) goto on_error; uint32_t key = Hash("(block table)", 3); - Decrypt(reinterpret_cast(blockTable_), fhdr.blockEntriesCount * sizeof(MpqBlockEntry), key); + Decrypt(reinterpret_cast(blockTable_.get()), fhdr.blockEntriesCount * sizeof(MpqBlockEntry), key); } - hashTable_ = new MpqHashEntry[HashEntriesCount]; + hashTable_ = std::make_unique(HashEntriesCount); // We fill with 0xFF so that the `block` field defaults to -1 (a null block pointer). - std::memset(hashTable_, 0xFF, HashEntriesCount * sizeof(MpqHashEntry)); + std::memset(hashTable_.get(), 0xFF, HashEntriesCount * sizeof(MpqHashEntry)); if (fhdr.hashEntriesCount > 0) { - if (!stream_.Read(reinterpret_cast(hashTable_), static_cast(fhdr.hashEntriesCount * sizeof(MpqHashEntry)))) + if (!stream_.Read(reinterpret_cast(hashTable_.get()), static_cast(fhdr.hashEntriesCount * sizeof(MpqHashEntry)))) goto on_error; uint32_t key = Hash("(hash table)", 3); - Decrypt(reinterpret_cast(hashTable_), fhdr.hashEntriesCount * sizeof(MpqHashEntry), key); + Decrypt(reinterpret_cast(hashTable_.get()), fhdr.hashEntriesCount * sizeof(MpqHashEntry), key); } #ifndef CAN_SEEKP_BEYOND_EOF @@ -143,38 +142,31 @@ bool MpqWriter::Open(const char *path) // Write garbage header and tables because some platforms cannot `Seekp` beyond EOF. // The data is incorrect at this point, it will be overwritten on Close. - if (!exists_) + if (!exists) WriteHeaderAndTables(); #endif } - return true; + return; on_error: - Close(/*clearTables=*/true); - return false; + app_fatal(_("Failed to open archive for writing.")); } -bool MpqWriter::Close(bool clearTables) +MpqWriter::~MpqWriter() { if (!stream_.IsOpen()) - return true; - LogVerbose("Closing {} with clearTables={}", name_, clearTables); + return; + LogVerbose("Closing {}", name_); bool result = true; - if (modified_ && !(stream_.Seekp(0, std::ios::beg) && WriteHeaderAndTables())) + if (!(stream_.Seekp(0, std::ios::beg) && WriteHeaderAndTables())) result = false; stream_.Close(); - if (modified_ && result && size_ != 0) { + if (result && size_ != 0) { LogVerbose("ResizeFile(\"{}\", {})", name_, size_); result = ResizeFile(name_.c_str(), size_); } - name_.clear(); - if (clearTables) { - delete[] hashTable_; - hashTable_ = nullptr; - delete[] blockTable_; - blockTable_ = nullptr; - } - return result; + if (!result) + LogVerbose("Closing failed {}", name_); } uint32_t MpqWriter::FetchHandle(const char *filename) const @@ -190,7 +182,6 @@ void MpqWriter::InitDefaultMpqHeader(MpqFileHeader *hdr) hdr->blockSizeFactor = BlockSizeFactor; hdr->version = 0; size_ = MpqHashEntryOffset + HashEntrySize; - modified_ = true; } bool MpqWriter::IsValidMpqHeader(MpqFileHeader *hdr) const @@ -222,7 +213,7 @@ bool MpqWriter::ReadMPQHeader(MpqFileHeader *hdr) MpqBlockEntry *MpqWriter::NewBlock(uint32_t *blockIndex) { - MpqBlockEntry *blockEntry = blockTable_; + MpqBlockEntry *blockEntry = blockTable_.get(); for (unsigned i = 0; i < BlockEntriesCount; ++i, ++blockEntry) { if (!IsUnallocatedBlock(blockEntry)) @@ -242,7 +233,7 @@ void MpqWriter::AllocBlock(uint32_t blockOffset, uint32_t blockSize) MpqBlockEntry *block; bool expand; do { - block = blockTable_; + block = blockTable_.get(); expand = false; for (unsigned i = BlockEntriesCount; i-- != 0; ++block) { // Expand to adjacent blocks. @@ -282,7 +273,7 @@ uint32_t MpqWriter::FindFreeBlock(uint32_t size) { uint32_t result; - MpqBlockEntry *block = blockTable_; + MpqBlockEntry *block = blockTable_.get(); for (unsigned i = 0; i < BlockEntriesCount; ++i, ++block) { // Find a block entry to use space from. if (!IsAllocatedUnusedBlock(block) || block->packedSize < size) @@ -464,17 +455,17 @@ bool MpqWriter::WriteHeader() bool MpqWriter::WriteBlockTable() { - Encrypt(reinterpret_cast(blockTable_), BlockEntrySize, Hash("(block table)", 3)); - const bool success = stream_.Write(reinterpret_cast(blockTable_), BlockEntrySize); - Decrypt(reinterpret_cast(blockTable_), BlockEntrySize, Hash("(block table)", 3)); + Encrypt(reinterpret_cast(blockTable_.get()), BlockEntrySize, Hash("(block table)", 3)); + const bool success = stream_.Write(reinterpret_cast(blockTable_.get()), BlockEntrySize); + Decrypt(reinterpret_cast(blockTable_.get()), BlockEntrySize, Hash("(block table)", 3)); return success; } bool MpqWriter::WriteHashTable() { - Encrypt(reinterpret_cast(hashTable_), HashEntrySize, Hash("(hash table)", 3)); - const bool success = stream_.Write(reinterpret_cast(hashTable_), HashEntrySize); - Decrypt(reinterpret_cast(hashTable_), HashEntrySize, Hash("(hash table)", 3)); + Encrypt(reinterpret_cast(hashTable_.get()), HashEntrySize, Hash("(hash table)", 3)); + const bool success = stream_.Write(reinterpret_cast(hashTable_.get()), HashEntrySize); + Decrypt(reinterpret_cast(hashTable_.get()), HashEntrySize, Hash("(hash table)", 3)); return success; } @@ -492,7 +483,6 @@ void MpqWriter::RemoveHashEntry(const char *filename) const uint32_t blockSize = block->packedSize; memset(block, 0, sizeof(*block)); AllocBlock(blockOffset, blockSize); - modified_ = true; } void MpqWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) @@ -508,7 +498,6 @@ bool MpqWriter::WriteFile(const char *filename, const byte *data, size_t size) { MpqBlockEntry *blockEntry; - modified_ = true; RemoveHashEntry(filename); blockEntry = AddFile(filename, nullptr, 0); if (!WriteFileContents(filename, data, size, blockEntry)) { @@ -530,7 +519,6 @@ void MpqWriter::RenameFile(const char *name, const char *newName) // NOLINT(bugp MpqBlockEntry *blockEntry = &blockTable_[block]; hashEntry->block = MpqHashEntry::DeletedBlock; AddFile(newName, blockEntry, block); - modified_ = true; } bool MpqWriter::HasFile(const char *name) const diff --git a/Source/mpq/mpq_writer.hpp b/Source/mpq/mpq_writer.hpp index 6de2c58ab..0d6f74234 100644 --- a/Source/mpq/mpq_writer.hpp +++ b/Source/mpq/mpq_writer.hpp @@ -14,14 +14,10 @@ namespace devilution { class MpqWriter { public: - bool Open(const char *path); - - bool Close(bool clearTables = true); - - ~MpqWriter() - { - Close(); - } + explicit MpqWriter(const char *path); + MpqWriter(MpqWriter &&other) = default; + MpqWriter &operator=(MpqWriter &&other) = default; + ~MpqWriter(); bool HasFile(const char *name) const; @@ -56,11 +52,9 @@ private: LoggedFStream stream_; std::string name_; - std::uintmax_t size_; - bool modified_; - bool exists_; - MpqHashEntry *hashTable_; - MpqBlockEntry *blockTable_; + std::uintmax_t size_ {}; + std::unique_ptr hashTable_; + std::unique_ptr blockTable_; // Amiga cannot Seekp beyond EOF. // See https://github.com/bebbo/libnix/issues/30 diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 446cf0836..124be67ef 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -32,9 +32,6 @@ bool gbValidSaveFile; namespace { -MpqWriter SaveWriter; -MpqWriter StashWriter; - /** List of character names for the character selection screen. */ char hero_names[MAX_CHARACTERS][PLR_NAME_LEN]; @@ -118,7 +115,7 @@ bool GetTempSaveNames(uint8_t dwIndex, char *szTemp) return true; } -void RenameTempToPerm() +void RenameTempToPerm(MpqWriter &saveWriter) { char szTemp[MAX_PATH]; char szPerm[MAX_PATH]; @@ -128,10 +125,10 @@ void RenameTempToPerm() [[maybe_unused]] bool result = GetPermSaveNames(dwIndex, szPerm); // DO NOT PUT DIRECTLY INTO ASSERT! assert(result); dwIndex++; - if (SaveWriter.HasFile(szTemp)) { - if (SaveWriter.HasFile(szPerm)) - SaveWriter.RemoveHashEntry(szPerm); - SaveWriter.RenameFile(szTemp, szPerm); + if (saveWriter.HasFile(szTemp)) { + if (saveWriter.HasFile(szPerm)) + saveWriter.RemoveHashEntry(szPerm); + saveWriter.RenameFile(szTemp, szPerm); } } assert(!GetPermSaveNames(dwIndex, szPerm)); @@ -154,19 +151,24 @@ bool ReadHero(MpqArchive &archive, PlayerPack *pPack) return ret; } -void EncodeHero(const PlayerPack *pack) +void EncodeHero(MpqWriter &saveWriter, const PlayerPack *pack) { size_t packedLen = codec_get_encoded_len(sizeof(*pack)); std::unique_ptr packed { new byte[packedLen] }; memcpy(packed.get(), pack, sizeof(*pack)); codec_encode(packed.get(), sizeof(*pack), packedLen, pfile_get_password()); - SaveWriter.WriteFile("hero", packed.get(), packedLen); + saveWriter.WriteFile("hero", packed.get(), packedLen); +} + +MpqWriter GetSaveWriter(uint32_t saveNum) +{ + return MpqWriter(GetSavePath(saveNum).c_str()); } -bool OpenArchive(uint32_t saveNum) +MpqWriter GetStashWriter() { - return SaveWriter.Open(GetSavePath(saveNum).c_str()); + return MpqWriter(GetStashSavePath().c_str()); } void Game2UiPlayer(const Player &player, _uiheroinfo *heroinfo, bool bHasSaveFile) @@ -298,51 +300,28 @@ const char *pfile_get_password() return gbIsMultiplayer ? PASSWORD_MULTI : PASSWORD_SINGLE; } -PFileScopedArchiveWriter::PFileScopedArchiveWriter(bool clearTables) - : save_num_(gSaveNumber) - , clear_tables_(clearTables) -{ - if (!OpenArchive(save_num_)) - app_fatal(_("Failed to open player archive for writing.")); -} - -PFileScopedArchiveWriter::~PFileScopedArchiveWriter() -{ - SaveWriter.Close(clear_tables_); -} - -MpqWriter &CurrentSaveArchive() -{ - return SaveWriter; -} - -MpqWriter &StashArchive() +void pfile_write_hero(bool writeGameData) { - return StashWriter; -} - -void pfile_write_hero(bool writeGameData, bool clearTables) -{ - PFileScopedArchiveWriter scopedWriter(clearTables); + MpqWriter saveWriter = GetSaveWriter(gSaveNumber); if (writeGameData) { - SaveGameData(); - RenameTempToPerm(); + SaveGameData(saveWriter); + RenameTempToPerm(saveWriter); } PlayerPack pkplr; Player &myPlayer = *MyPlayer; PackPlayer(&pkplr, myPlayer, !gbIsMultiplayer, false); - EncodeHero(&pkplr); + EncodeHero(saveWriter, &pkplr); if (!gbVanilla) { - SaveHotkeys(); - SaveHeroItems(myPlayer); + SaveHotkeys(saveWriter); + SaveHeroItems(saveWriter, myPlayer); } } void pfile_write_hero_demo(int demo) { savePrefix = fmt::format("demo_{}_reference_", demo); - pfile_write_hero(true, true); + pfile_write_hero(true); savePrefix.clear(); } @@ -356,7 +335,7 @@ HeroCompareResult pfile_compare_hero_demo(int demo) return HeroCompareResult::ReferenceNotFound; savePrefix = fmt::format("demo_{}_actual_", demo); - pfile_write_hero(true, true); + pfile_write_hero(true); std::string actualSavePath = GetSavePath(gSaveNumber); savePrefix.clear(); @@ -369,12 +348,9 @@ void sfile_write_stash() if (!Stash.dirty) return; - if (!StashWriter.Open(GetStashSavePath().c_str())) - app_fatal(_("Failed to open stash archive for writing.")); - - SaveStash(); + MpqWriter stashWriter = GetStashWriter(); - StashWriter.Close(); + SaveStash(stashWriter); Stash.dirty = false; } @@ -439,27 +415,25 @@ bool pfile_ui_save_create(_uiheroinfo *heroinfo) uint32_t saveNum = heroinfo->saveNumber; if (saveNum >= MAX_CHARACTERS) return false; - if (!OpenArchive(saveNum)) - return false; heroinfo->saveNumber = saveNum; giNumberOfLevels = gbIsHellfire ? 25 : 17; - SaveWriter.RemoveHashEntries(GetFileName); + MpqWriter saveWriter = GetSaveWriter(saveNum); + saveWriter.RemoveHashEntries(GetFileName); CopyUtf8(hero_names[saveNum], heroinfo->name, sizeof(hero_names[saveNum])); Player &player = Players[0]; CreatePlayer(0, heroinfo->heroclass); CopyUtf8(player._pName, heroinfo->name, PLR_NAME_LEN); PackPlayer(&pkplr, player, true, false); - EncodeHero(&pkplr); + EncodeHero(saveWriter, &pkplr); Game2UiPlayer(player, heroinfo, false); if (!gbVanilla) { - SaveHotkeys(); - SaveHeroItems(player); + SaveHotkeys(saveWriter); + SaveHeroItems(saveWriter, player); } - SaveWriter.Close(); return true; } @@ -499,44 +473,16 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) CalcPlrInv(player, false); } -bool LevelFileExists() +void pfile_save_level() { - char szName[MAX_PATH]; - - GetPermLevelNames(szName); - - uint32_t saveNum = gSaveNumber; - if (!OpenArchive(saveNum)) - app_fatal(_("Unable to read to save file archive")); - - bool hasFile = SaveWriter.HasFile(szName); - SaveWriter.Close(); - return hasFile; -} - -void GetTempLevelNames(char *szTemp) -{ - if (setlevel) - sprintf(szTemp, "temps%02d", setlvlnum); - else - sprintf(szTemp, "templ%02d", currlevel); + MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + SaveLevel(saveWriter); } -void GetPermLevelNames(char *szPerm) +void pfile_convert_levels() { - uint32_t saveNum = gSaveNumber; - GetTempLevelNames(szPerm); - if (!OpenArchive(saveNum)) - app_fatal(_("Unable to read to save file archive")); - - bool hasFile = SaveWriter.HasFile(szPerm); - SaveWriter.Close(); - if (!hasFile) { - if (setlevel) - sprintf(szPerm, "perms%02d", setlvlnum); - else - sprintf(szPerm, "perml%02d", currlevel); - } + MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + ConvertLevels(saveWriter); } void pfile_remove_temp_files() @@ -544,11 +490,8 @@ void pfile_remove_temp_files() if (gbIsMultiplayer) return; - uint32_t saveNum = gSaveNumber; - if (!OpenArchive(saveNum)) - app_fatal(_("Unable to write to save file archive")); - SaveWriter.RemoveHashEntries(GetTempSaveNames); - SaveWriter.Close(); + MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + saveWriter.RemoveHashEntries(GetTempSaveNames); } void pfile_update(bool forceSave) diff --git a/Source/pfile.h b/Source/pfile.h index 1e4a984e6..3455f4593 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -6,7 +6,6 @@ #pragma once #include "DiabloUI/diabloui.h" -#include "mpq/mpq_writer.hpp" #include "player.h" namespace devilution { @@ -15,19 +14,6 @@ namespace devilution { extern bool gbValidSaveFile; -class PFileScopedArchiveWriter { -public: - // Opens the player save file for writing - PFileScopedArchiveWriter(bool clearTables = !gbIsMultiplayer); - - // Finishes writing and closes the player save file. - ~PFileScopedArchiveWriter(); - -private: - int save_num_; - bool clear_tables_; -}; - /** * @brief Comparsion result of pfile_compare_hero_demo */ @@ -37,13 +23,11 @@ enum class HeroCompareResult { Difference, }; -MpqWriter &CurrentSaveArchive(); -MpqWriter &StashArchive(); std::optional OpenSaveArchive(uint32_t saveNum); std::optional OpenStashArchive(); const char *pfile_get_password(); std::unique_ptr ReadArchive(MpqArchive &archive, const char *pszName, size_t *pdwLen = nullptr); -void pfile_write_hero(bool writeGameData = false, bool clearTables = !gbIsMultiplayer); +void pfile_write_hero(bool writeGameData = false); /** * @brief Save a reference game-state (save game) for the demo recording * @param demo that is recorded @@ -62,9 +46,8 @@ uint32_t pfile_ui_get_first_unused_save_num(); bool pfile_ui_save_create(_uiheroinfo *heroinfo); bool pfile_delete_save(_uiheroinfo *heroInfo); void pfile_read_player_from_save(uint32_t saveNum, Player &player); -bool LevelFileExists(); -void GetTempLevelNames(char *szTemp); -void GetPermLevelNames(char *szPerm); +void pfile_save_level(); +void pfile_convert_levels(); void pfile_remove_temp_files(); std::unique_ptr pfile_read(const char *pszName, size_t *pdwLen); void pfile_update(bool forceSave);