Browse Source

Handle loaded MPQ files in a map instead of bespoke globals (#7887)

pull/7913/head
Anders Jenbo 11 months ago committed by GitHub
parent
commit
96112e675b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Source/DiabloUI/mainmenu.cpp
  2. 2
      Source/DiabloUI/settingsmenu.cpp
  3. 116
      Source/engine/assets.cpp
  4. 46
      Source/engine/assets.hpp
  5. 6
      Source/engine/sound.cpp
  6. 27
      Source/init.cpp
  7. 19
      Source/init.h
  8. 2
      Source/menu.cpp
  9. 4
      Source/player.h
  10. 2
      test/dun_render_benchmark.cpp
  11. 2
      test/inv_test.cpp
  12. 2
      test/pack_test.cpp
  13. 2
      test/player_test.cpp
  14. 2
      test/timedemo_test.cpp
  15. 2
      test/writehero_test.cpp

2
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;
}
}

2
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;

116
Source/engine/assets.cpp

@ -30,17 +30,8 @@ std::optional<std::string> hellfire_data_path;
std::optional<std::string> font_data_path;
std::optional<std::string> lang_data_path;
#else
std::optional<MpqArchive> spawn_mpq;
std::optional<MpqArchive> diabdat_mpq;
std::optional<MpqArchive> hellfire_mpq;
std::optional<MpqArchive> hfmonk_mpq;
std::optional<MpqArchive> hfbard_mpq;
std::optional<MpqArchive> hfbarb_mpq;
std::optional<MpqArchive> hfmusic_mpq;
std::optional<MpqArchive> hfvoice_mpq;
std::optional<MpqArchive> devilutionx_mpq;
std::optional<MpqArchive> lang_mpq;
std::optional<MpqArchive> font_mpq;
std::map<int, std::unique_ptr<MpqArchive>> 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<MpqArchive> &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<std::string> FindUnpackedMpqData(const std::vector<std::string> &p
return std::nullopt;
}
#else
std::optional<MpqArchive> LoadMPQ(const std::vector<std::string> &paths, std::string_view mpqName)
bool FindMPQ(const std::vector<std::string> &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<std::string> &paths, std::string_view mpqName, int priority)
{
std::optional<MpqArchive> archive;
std::string mpqAbsPath;
@ -290,7 +298,8 @@ std::optional<MpqArchive> LoadMPQ(const std::vector<std::string> &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<MpqArchive>(std::move(*archive));
return true;
}
if (error != 0) {
LogError("Error {}: {}", MpqArchive::ErrorMessage(error), mpqAbsPath);
@ -300,7 +309,7 @@ std::optional<MpqArchive> LoadMPQ(const std::vector<std::string> &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

46
Source/engine/assets.hpp

@ -3,6 +3,7 @@
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <map>
#include <optional>
#include <string>
#include <string_view>
@ -284,42 +285,35 @@ extern std::optional<std::string> hellfire_data_path;
extern std::optional<std::string> font_data_path;
extern std::optional<std::string> lang_data_path;
#else
/** A handle to the spawn.mpq archive. */
extern DVL_API_FOR_TEST std::optional<MpqArchive> spawn_mpq;
/** A handle to the diabdat.mpq archive. */
extern DVL_API_FOR_TEST std::optional<MpqArchive> diabdat_mpq;
/** A handle to an hellfire.mpq archive. */
extern std::optional<MpqArchive> hellfire_mpq;
extern std::optional<MpqArchive> hfmonk_mpq;
extern std::optional<MpqArchive> hfbard_mpq;
extern std::optional<MpqArchive> hfbarb_mpq;
extern std::optional<MpqArchive> hfmusic_mpq;
extern std::optional<MpqArchive> hfvoice_mpq;
extern std::optional<MpqArchive> font_mpq;
extern std::optional<MpqArchive> lang_mpq;
extern std::optional<MpqArchive> devilutionx_mpq;
extern DVL_API_FOR_TEST std::map<int, std::unique_ptr<MpqArchive>> 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

6
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;

27
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();

19
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);

2
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();

4
Source/player.h

@ -189,8 +189,8 @@ constexpr std::array<char, 6> CharChar = {
'r', // rogue
's', // sorcerer
'm', // monk
'b',
'c',
'b', // bard
'c', // barbarian
};
/**

2
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);
}

2
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();

2
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();

2
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();

2
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);

2
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());

Loading…
Cancel
Save