From 96112e675b579038ff4b01871c560b7010277caa Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 7 Apr 2025 17:17:05 +0200 Subject: [PATCH] Handle loaded MPQ files in a map instead of bespoke globals (#7887) --- Source/DiabloUI/mainmenu.cpp | 2 +- Source/DiabloUI/settingsmenu.cpp | 2 +- Source/engine/assets.cpp | 116 ++++++++++++++++--------------- Source/engine/assets.hpp | 46 ++++++------ Source/engine/sound.cpp | 6 +- Source/init.cpp | 27 ++----- Source/init.h | 19 ++--- Source/menu.cpp | 2 +- Source/player.h | 4 +- test/dun_render_benchmark.cpp | 2 +- test/inv_test.cpp | 2 +- test/pack_test.cpp | 2 +- test/player_test.cpp | 2 +- test/timedemo_test.cpp | 2 +- test/writehero_test.cpp | 2 +- 15 files changed, 106 insertions(+), 130 deletions(-) diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index 0e8e6a13a..bf47dc3ad 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -105,7 +105,7 @@ bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, int att while (MainMenuResult == MAINMENU_NONE) { UiClearScreen(); UiPollAndRender(); - if (SDL_GetTicks() >= dwAttractTicks && (HaveDiabdat() || HaveHellfire())) { + if (SDL_GetTicks() >= dwAttractTicks && (HaveIntro() || gbIsHellfire)) { MainMenuResult = MAINMENU_ATTRACT_MODE; } } diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index 8d73c7feb..a21d8900d 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/Source/DiabloUI/settingsmenu.cpp @@ -66,7 +66,7 @@ std::string padEntryTimerText; bool IsValidEntry(OptionEntryBase *pOptionEntry) { auto flags = pOptionEntry->GetFlags(); - if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveDiabdat()) + if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveIntro()) return false; if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !HaveHellfire()) return false; diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index 560d15ef0..f3c5b5238 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -30,17 +30,8 @@ std::optional hellfire_data_path; std::optional font_data_path; std::optional lang_data_path; #else -std::optional spawn_mpq; -std::optional diabdat_mpq; -std::optional hellfire_mpq; -std::optional hfmonk_mpq; -std::optional hfbard_mpq; -std::optional hfbarb_mpq; -std::optional hfmusic_mpq; -std::optional hfvoice_mpq; -std::optional devilutionx_mpq; -std::optional lang_mpq; -std::optional font_mpq; +std::map> MpqArchives; +bool HasHellfireMpq; #endif namespace { @@ -82,17 +73,21 @@ SDL_RWops *OpenOptionalRWops(const std::string &path) bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *fileNumber) { const MpqFileHash fileHash = CalculateMpqFileHash(filename); - const auto at = [=](std::optional &src) -> bool { - if (src && src->GetFileNumber(fileHash, *fileNumber)) { - *archive = &(*src); + + // Iterate over archives in reverse order to prefer files from high priority archives + for (auto rit = MpqArchives.rbegin(); rit != MpqArchives.rend(); ++rit) { + int priority = rit->first; + if (!gbIsHellfire && priority >= 8000 && priority < 9000) + continue; + if (rit->second->GetFileNumber(fileHash, *fileNumber)) { + *archive = rit->second.get(); return true; } - return false; - }; + } - return at(font_mpq) || at(lang_mpq) || at(devilutionx_mpq) - || (gbIsHellfire && (at(hfvoice_mpq) || at(hfmusic_mpq) || at(hfbarb_mpq) || at(hfbard_mpq) || at(hfmonk_mpq) || at(hellfire_mpq))) || at(spawn_mpq) || at(diabdat_mpq); + return false; } + #endif } // namespace @@ -281,7 +276,20 @@ std::optional FindUnpackedMpqData(const std::vector &p return std::nullopt; } #else -std::optional LoadMPQ(const std::vector &paths, std::string_view mpqName) +bool FindMPQ(const std::vector &paths, std::string_view mpqName) +{ + std::string mpqAbsPath; + for (const auto &path : paths) { + mpqAbsPath = path + mpqName.data(); + if (FileExists(mpqAbsPath)) { + LogVerbose(" Found: {} in {}", mpqName, path); + return true; + } + } + + return false; +} +bool LoadMPQ(const std::vector &paths, std::string_view mpqName, int priority) { std::optional archive; std::string mpqAbsPath; @@ -290,7 +298,8 @@ std::optional LoadMPQ(const std::vector &paths, std::st mpqAbsPath = path + mpqName.data(); if ((archive = MpqArchive::Open(mpqAbsPath.c_str(), error))) { LogVerbose(" Found: {} in {}", mpqName, path); - return archive; + MpqArchives[priority] = std::make_unique(std::move(*archive)); + return true; } if (error != 0) { LogError("Error {}: {}", MpqArchive::ErrorMessage(error), mpqAbsPath); @@ -300,7 +309,7 @@ std::optional LoadMPQ(const std::vector &paths, std::st LogVerbose("Missing: {}", mpqName); } - return std::nullopt; + return false; } #endif @@ -371,9 +380,10 @@ void LoadCoreArchives() #else // !UNPACKED_MPQS #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__3DS__) && !defined(__SWITCH__) // Load devilutionx.mpq first to get the font file for error messages - devilutionx_mpq = LoadMPQ(paths, "devilutionx.mpq"); + LoadMPQ(paths, "devilutionx.mpq", DevilutionXMpqPriority); #endif - font_mpq = LoadMPQ(paths, "fonts.mpq"); // Extra fonts + LoadMPQ(paths, "fonts.mpq", FontMpqPriority); // Extra fonts + HasHellfireMpq = FindMPQ(paths, "hellfire.mpq"); #endif } @@ -381,8 +391,6 @@ void LoadLanguageArchive() { #ifdef UNPACKED_MPQS lang_data_path = std::nullopt; -#else - lang_mpq = std::nullopt; #endif std::string_view code = GetLanguageCode(); @@ -392,7 +400,7 @@ void LoadLanguageArchive() lang_data_path = FindUnpackedMpqData(GetMPQSearchPaths(), langMpqName); #else langMpqName.append(".mpq"); - lang_mpq = LoadMPQ(GetMPQSearchPaths(), langMpqName); + LoadMPQ(GetMPQSearchPaths(), langMpqName, LangMpqPriority); #endif } } @@ -419,28 +427,13 @@ void LoadGameArchives() gbIsHellfire = true; if (forceHellfire && !hellfire_data_path) InsertCDDlg("hellfire"); - - const bool hasMonk = FileExists(*hellfire_data_path + "plrgfx/monk/mha/mhaas.clx"); - const bool hasMusic = FileExists(*hellfire_data_path + "music/dlvlf.wav") - || FileExists(*hellfire_data_path + "music/dlvlf.mp3"); - const bool hasVoice = FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.wav") - || FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.mp3"); - - if (gbIsHellfire && (!hasMonk || !hasMusic || !hasVoice)) { - DisplayFatalErrorAndExit(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); - } #else // !UNPACKED_MPQS - diabdat_mpq = LoadMPQ(paths, "DIABDAT.MPQ"); - if (!diabdat_mpq) { + if (!LoadMPQ(paths, "DIABDAT.MPQ", MainMpqPriority)) { // DIABDAT.MPQ is uppercase on the original CD and the GOG version. - diabdat_mpq = LoadMPQ(paths, "diabdat.mpq"); + if (!LoadMPQ(paths, "diabdat.mpq", MainMpqPriority)) + gbIsSpawn = LoadMPQ(paths, "spawn.mpq", MainMpqPriority); } - if (!diabdat_mpq) { - spawn_mpq = LoadMPQ(paths, "spawn.mpq"); - if (spawn_mpq) - gbIsSpawn = true; - } if (!HeadlessMode) { AssetRef ref = FindAsset("ui_art\\title.pcx"); if (!ref.ok()) { @@ -449,22 +442,35 @@ void LoadGameArchives() } } - hellfire_mpq = LoadMPQ(paths, "hellfire.mpq"); - if (hellfire_mpq) + if (HasHellfireMpq) gbIsHellfire = true; - if (forceHellfire && !hellfire_mpq) + if (forceHellfire && !HasHellfireMpq) InsertCDDlg("hellfire.mpq"); + LoadMPQ(paths, "hfbard.mpq", 8110); + LoadMPQ(paths, "hfbarb.mpq", 8120); +#endif + if (gbIsHellfire) + LoadHellfireArchives(); +} - hfmonk_mpq = LoadMPQ(paths, "hfmonk.mpq"); - hfbard_mpq = LoadMPQ(paths, "hfbard.mpq"); - hfbarb_mpq = LoadMPQ(paths, "hfbarb.mpq"); - hfmusic_mpq = LoadMPQ(paths, "hfmusic.mpq"); - hfvoice_mpq = LoadMPQ(paths, "hfvoice.mpq"); +void LoadHellfireArchives() +{ + auto paths = GetMPQSearchPaths(); +#ifdef UNPACKED_MPQS + const bool hasMonk = FileExists(*hellfire_data_path + "plrgfx/monk/mha/mhaas.clx"); + const bool hasMusic = FileExists(*hellfire_data_path + "music/dlvlf.wav") + || FileExists(*hellfire_data_path + "music/dlvlf.mp3"); + const bool hasVoice = FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.wav") + || FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.mp3"); +#else // !UNPACKED_MPQS + LoadMPQ(paths, "hellfire.mpq", 8000); + const bool hasMonk = LoadMPQ(paths, "hfmonk.mpq", 8100); + const bool hasMusic = LoadMPQ(paths, "hfmusic.mpq", 8200); + const bool hasVoice = LoadMPQ(paths, "hfvoice.mpq", 8500); +#endif - if (gbIsHellfire && (!hfmonk_mpq || !hfmusic_mpq || !hfvoice_mpq)) { + if (!hasMonk || !hasMusic || !hasVoice) DisplayFatalErrorAndExit(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); - } -#endif } } // namespace devilution diff --git a/Source/engine/assets.hpp b/Source/engine/assets.hpp index aae897ead..bba39b86e 100644 --- a/Source/engine/assets.hpp +++ b/Source/engine/assets.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -284,42 +285,35 @@ extern std::optional hellfire_data_path; extern std::optional font_data_path; extern std::optional lang_data_path; #else -/** A handle to the spawn.mpq archive. */ -extern DVL_API_FOR_TEST std::optional spawn_mpq; -/** A handle to the diabdat.mpq archive. */ -extern DVL_API_FOR_TEST std::optional diabdat_mpq; -/** A handle to an hellfire.mpq archive. */ -extern std::optional hellfire_mpq; -extern std::optional hfmonk_mpq; -extern std::optional hfbard_mpq; -extern std::optional hfbarb_mpq; -extern std::optional hfmusic_mpq; -extern std::optional hfvoice_mpq; -extern std::optional font_mpq; -extern std::optional lang_mpq; -extern std::optional devilutionx_mpq; +extern DVL_API_FOR_TEST std::map> MpqArchives; +constexpr int MainMpqPriority = 1000; +constexpr int DevilutionXMpqPriority = 9000; +constexpr int LangMpqPriority = 9100; +constexpr int FontMpqPriority = 9200; +extern bool HasHellfireMpq; #endif void LoadCoreArchives(); void LoadLanguageArchive(); void LoadGameArchives(); +void LoadHellfireArchives(); #ifdef UNPACKED_MPQS -[[nodiscard]] inline bool HaveSpawn() { return spawn_data_path.has_value(); } -[[nodiscard]] inline bool HaveDiabdat() { return diabdat_data_path.has_value(); } +#ifdef BUILD_TESTING +[[nodiscard]] inline bool HaveMainData() { return diabdat_data_path.has_value() || spawn_data_path.has_value(); } +#endif [[nodiscard]] inline bool HaveHellfire() { return hellfire_data_path.has_value(); } [[nodiscard]] inline bool HaveExtraFonts() { return font_data_path.has_value(); } - -// Bard and barbarian are not currently supported in unpacked mode. -[[nodiscard]] inline bool HaveBardAssets() { return false; } -[[nodiscard]] inline bool HaveBarbarianAssets() { return false; } #else -[[nodiscard]] inline bool HaveSpawn() { return spawn_mpq.has_value(); } -[[nodiscard]] inline bool HaveDiabdat() { return diabdat_mpq.has_value(); } -[[nodiscard]] inline bool HaveHellfire() { return hellfire_mpq.has_value(); } -[[nodiscard]] inline bool HaveExtraFonts() { return font_mpq.has_value(); } -[[nodiscard]] inline bool HaveBardAssets() { return hfbard_mpq.has_value(); } -[[nodiscard]] inline bool HaveBarbarianAssets() { return hfbarb_mpq.has_value(); } +#ifdef BUILD_TESTING +[[nodiscard]] inline bool HaveMainData() { return MpqArchives.find(MainMpqPriority) != MpqArchives.end(); } +#endif +[[nodiscard]] inline bool HaveHellfire() { return HasHellfireMpq; } +[[nodiscard]] inline bool HaveExtraFonts() { return MpqArchives.find(FontMpqPriority) != MpqArchives.end(); } #endif +[[nodiscard]] inline bool HaveIntro() { return FindAsset("gendata\\diablo1.smk").ok(); } +[[nodiscard]] inline bool HaveFullMusic() { return FindAsset("music\\dintro.wav").ok() || FindAsset("music\\dintro.mp3").ok(); } +[[nodiscard]] inline bool HaveBardAssets() { return FindAsset("plrgfx\\bard\\bha\\bhaas.clx").ok(); } +[[nodiscard]] inline bool HaveBarbarianAssets() { return FindAsset("plrgfx\\barbarian\\cha\\chaas.clx").ok(); } } // namespace devilution diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index a1ad69748..1c1093578 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -293,10 +293,10 @@ void music_start(_music_id nTrack) music_stop(); if (!gbMusicOn) return; - if (HaveSpawn()) - trackPath = SpawnMusicTracks[nTrack]; - else + if (HaveFullMusic()) trackPath = MusicTracks[nTrack]; + else + trackPath = SpawnMusicTracks[nTrack]; #ifdef DISABLE_STREAMING_MUSIC const bool stream = false; diff --git a/Source/init.cpp b/Source/init.cpp index 25917360f..2df9a883d 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -71,21 +71,13 @@ bool CheckExtraFontsVersion(AssetRef &&ref) } // namespace -#ifndef UNPACKED_MPQS -bool IsDevilutionXMpqOutOfDate(MpqArchive &archive) +bool IsDevilutionXMpqOutOfDate() { - const char filename[] = "ASSETS_VERSION"; - const MpqFileHash fileHash = CalculateMpqFileHash(filename); - uint32_t fileNumber; - if (!archive.GetFileNumber(fileHash, fileNumber)) + AssetRef ref = FindAsset("ASSETS_VERSION"); + if (!ref.ok()) return true; - AssetRef ref; - ref.archive = &archive; - ref.fileNumber = fileNumber; - ref.filename = filename; return CheckDevilutionXMpqVersion(std::move(ref)); } -#endif #ifdef UNPACKED_MPQS bool AreExtraFontsOutOfDate(const std::string &path) @@ -127,17 +119,8 @@ void init_cleanup() diabdat_data_path = std::nullopt; spawn_data_path = std::nullopt; #else - spawn_mpq = std::nullopt; - diabdat_mpq = std::nullopt; - hellfire_mpq = std::nullopt; - hfmonk_mpq = std::nullopt; - hfbard_mpq = std::nullopt; - hfbarb_mpq = std::nullopt; - hfmusic_mpq = std::nullopt; - hfvoice_mpq = std::nullopt; - lang_mpq = std::nullopt; - font_mpq = std::nullopt; - devilutionx_mpq = std::nullopt; + MpqArchives.clear(); + HasHellfireMpq = false; #endif NetClose(); diff --git a/Source/init.h b/Source/init.h index eab3ae3d6..aff0ac924 100644 --- a/Source/init.h +++ b/Source/init.h @@ -32,23 +32,16 @@ inline bool AreExtraFontsOutOfDate() #ifdef UNPACKED_MPQS return font_data_path && AreExtraFontsOutOfDate(*font_data_path); #else - return font_mpq && AreExtraFontsOutOfDate(*font_mpq); -#endif -} - -#ifndef UNPACKED_MPQS -bool IsDevilutionXMpqOutOfDate(MpqArchive &archive); -#endif + auto it = MpqArchives.find(FontMpqPriority); + if (it == MpqArchives.end()) { + return false; + } -inline bool IsDevilutionXMpqOutOfDate() -{ -#ifdef UNPACKED_MPQS - return false; -#else - return devilutionx_mpq.has_value() && IsDevilutionXMpqOutOfDate(*devilutionx_mpq); + return AreExtraFontsOutOfDate(*it->second); #endif } +bool IsDevilutionXMpqOutOfDate(); void init_cleanup(); void init_create_window(); void MainWndProc(const SDL_Event &event); diff --git a/Source/menu.cpp b/Source/menu.cpp index ed6614b93..651ad92c9 100644 --- a/Source/menu.cpp +++ b/Source/menu.cpp @@ -167,7 +167,7 @@ void mainmenu_loop() done = true; break; case MAINMENU_ATTRACT_MODE: - if (gbIsSpawn && !HaveDiabdat()) + if (gbIsSpawn && !HaveIntro()) done = false; else if (gbActive) PlayIntro(); diff --git a/Source/player.h b/Source/player.h index 28ee469bc..e24a2d7db 100644 --- a/Source/player.h +++ b/Source/player.h @@ -189,8 +189,8 @@ constexpr std::array CharChar = { 'r', // rogue 's', // sorcerer 'm', // monk - 'b', - 'c', + 'b', // bard + 'c', // barbarian }; /** diff --git a/test/dun_render_benchmark.cpp b/test/dun_render_benchmark.cpp index 6d22e3a51..51367c112 100644 --- a/test/dun_render_benchmark.cpp +++ b/test/dun_render_benchmark.cpp @@ -29,7 +29,7 @@ void InitOnce() [[maybe_unused]] static const bool GlobalInitDone = []() { LoadCoreArchives(); LoadGameArchives(); - if (!HaveSpawn() && !HaveDiabdat()) { + if (!HaveMainData()) { LogError("This benchmark needs spawn.mpq or diabdat.mpq"); exit(1); } diff --git a/test/inv_test.cpp b/test/inv_test.cpp index 143749608..ca9ef71ae 100644 --- a/test/inv_test.cpp +++ b/test/inv_test.cpp @@ -24,7 +24,7 @@ public: // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully - ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + ASSERT_TRUE(HaveMainData()); InitCursor(); LoadSpellData(); diff --git a/test/pack_test.cpp b/test/pack_test.cpp index ce4f4f393..a13cdd047 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -966,7 +966,7 @@ public: // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully - ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + ASSERT_TRUE(HaveMainData()); gbIsHellfire = false; InitCursor(); diff --git a/test/player_test.cpp b/test/player_test.cpp index 9730a6a8b..bb8b64ba5 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -186,7 +186,7 @@ TEST(Player, CreatePlayer) // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully - ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + ASSERT_TRUE(HaveMainData()); LoadPlayerDataFiles(); LoadMonsterData(); diff --git a/test/timedemo_test.cpp b/test/timedemo_test.cpp index 6ebb3f4ff..cf897ce64 100644 --- a/test/timedemo_test.cpp +++ b/test/timedemo_test.cpp @@ -42,7 +42,7 @@ void RunTimedemo(std::string timedemoFolderName) // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully - ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + ASSERT_TRUE(HaveMainData()); std::string unitTestFolderCompletePath = paths::BasePath() + "test/fixtures/timedemo/" + timedemoFolderName; paths::SetPrefPath(unitTestFolderCompletePath); diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 6d6306674..5c942d3de 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -368,7 +368,7 @@ TEST(Writehero, pfile_write_hero) // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully - ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); + ASSERT_TRUE(HaveMainData()); const std::string savePath = paths::BasePath() + "multi_0.sv"; paths::SetPrefPath(paths::BasePath());