diff --git a/CMake/Definitions.cmake b/CMake/Definitions.cmake index 8214193f9..b76fe164f 100644 --- a/CMake/Definitions.cmake +++ b/CMake/Definitions.cmake @@ -19,6 +19,7 @@ foreach( DEVILUTIONX_RESAMPLER_SPEEX DEVILUTIONX_RESAMPLER_SDL DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT + UNPACKED_MPQS ) if(${def_name}) list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}) diff --git a/CMakeLists.txt b/CMakeLists.txt index b86743938..57c4a752b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ RELEASE_OPTION(DEVILUTIONX_STATIC_CXX_STDLIB "Link C++ standard library statical include(MoldLinker) # Memory / performance trade-off options +option(UNPACKED_MPQS "Expect MPQs to be unpacked and the data converted with devilutionx-mpq-tools" OFF) option(DISABLE_STREAMING_MUSIC "Disable streaming music (to work around broken platform implementations)" OFF) mark_as_advanced(DISABLE_STREAMING_MUSIC) option(DISABLE_STREAMING_SOUNDS "Disable streaming sounds (to work around broken platform implementations)" OFF) diff --git a/Source/DiabloUI/button.cpp b/Source/DiabloUI/button.cpp index 1e0d770e4..3a702dbed 100644 --- a/Source/DiabloUI/button.cpp +++ b/Source/DiabloUI/button.cpp @@ -20,7 +20,7 @@ void LoadDialogButtonGraphics() { ButtonSprites = LoadOptionalClx("ui_art\\dvl_but_sml.clx"); if (!ButtonSprites) { - ButtonSprites = LoadPcxSpriteList("ui_art\\but_sml.pcx", 15); + ButtonSprites = LoadPcxSpriteList("ui_art\\but_sml", 15); } } diff --git a/Source/DiabloUI/credits.cpp b/Source/DiabloUI/credits.cpp index 52bc9da43..56aba5028 100644 --- a/Source/DiabloUI/credits.cpp +++ b/Source/DiabloUI/credits.cpp @@ -170,7 +170,7 @@ bool TextDialog(char const *const *text, std::size_t textLines) bool UiCreditsDialog() { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\creditsw.clx"); - LoadBackgroundArt("ui_art\\credits.pcx"); + LoadBackgroundArt("ui_art\\credits"); return TextDialog(CreditLines, CreditLinesSize); } @@ -179,10 +179,10 @@ bool UiSupportDialog() { if (gbIsHellfire) { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\supportw.clx"); - LoadBackgroundArt("ui_art\\support.pcx"); + LoadBackgroundArt("ui_art\\support"); } else { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\creditsw.clx"); - LoadBackgroundArt("ui_art\\credits.pcx"); + LoadBackgroundArt("ui_art\\credits"); } return TextDialog(SupportLines, SupportLinesSize); diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 3c9d165cd..a82faecd1 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -544,7 +544,7 @@ bool IsInsideRect(const SDL_Event &event, const SDL_Rect &rect) void LoadHeros() { constexpr unsigned PortraitHeight = 76; - ArtHero = LoadPcxSpriteList("ui_art\\heros.pcx", -static_cast(PortraitHeight)); + ArtHero = LoadPcxSpriteList("ui_art\\heros", -static_cast(PortraitHeight)); if (!ArtHero) return; const uint16_t numPortraits = ClxSpriteList { *ArtHero }.numSprites(); @@ -561,32 +561,25 @@ void LoadHeros() for (size_t i = 0; i <= enum_size::value; ++i) { char portraitPath[18]; - *BufCopy(portraitPath, "ui_art\\hero", i, ".pcx") = '\0'; - - SDL_RWops *handle = OpenAsset(portraitPath); - if (handle == nullptr) { - // Portrait overrides are optional, ignore the error and continue. - SDL_ClearError(); - continue; - } - ArtHeroOverrides[i] = PcxToClx(handle); + *BufCopy(portraitPath, "ui_art\\hero", i) = '\0'; + ArtHeroOverrides[i] = LoadPcx(portraitPath, /*transparentColor=*/std::nullopt, /*outPalette=*/nullptr, /*logError=*/false); } } void LoadUiGFX() { if (gbIsHellfire) { - ArtLogo = LoadPcxSpriteList("ui_art\\hf_logo2.pcx", /*numFrames=*/16, /*transparentColor=*/0); + ArtLogo = LoadPcxSpriteList("ui_art\\hf_logo2", /*numFrames=*/16, /*transparentColor=*/0); } else { - ArtLogo = LoadPcxSpriteList("ui_art\\smlogo.pcx", /*numFrames=*/15, /*transparentColor=*/250); + ArtLogo = LoadPcxSpriteList("ui_art\\smlogo", /*numFrames=*/15, /*transparentColor=*/250); } - DifficultyIndicator[0] = LoadPcx("ui_art\\radio1.pcx", /*transparentColor=*/0); - DifficultyIndicator[1] = LoadPcx("ui_art\\radio3.pcx", /*transparentColor=*/0); - ArtFocus[FOCUS_SMALL] = LoadPcxSpriteList("ui_art\\focus16.pcx", /*numFrames=*/8, /*transparentColor=*/250); - ArtFocus[FOCUS_MED] = LoadPcxSpriteList("ui_art\\focus.pcx", /*numFrames=*/8, /*transparentColor=*/250); - ArtFocus[FOCUS_BIG] = LoadPcxSpriteList("ui_art\\focus42.pcx", /*numFrames=*/8, /*transparentColor=*/250); + DifficultyIndicator[0] = LoadPcx("ui_art\\radio1", /*transparentColor=*/0); + DifficultyIndicator[1] = LoadPcx("ui_art\\radio3", /*transparentColor=*/0); + ArtFocus[FOCUS_SMALL] = LoadPcxSpriteList("ui_art\\focus16", /*numFrames=*/8, /*transparentColor=*/250); + ArtFocus[FOCUS_MED] = LoadPcxSpriteList("ui_art\\focus", /*numFrames=*/8, /*transparentColor=*/250); + ArtFocus[FOCUS_BIG] = LoadPcxSpriteList("ui_art\\focus42", /*numFrames=*/8, /*transparentColor=*/250); - ArtCursor = LoadPcx("ui_art\\cursor.pcx", /*transparentColor=*/0); + ArtCursor = LoadPcx("ui_art\\cursor", /*transparentColor=*/0); LoadHeros(); } diff --git a/Source/DiabloUI/diabloui.h b/Source/DiabloUI/diabloui.h index 6e49424c6..58e4d8089 100644 --- a/Source/DiabloUI/diabloui.h +++ b/Source/DiabloUI/diabloui.h @@ -7,6 +7,7 @@ #include "DiabloUI/ui_item.h" #include "engine/clx_sprite.hpp" +#include "engine/load_pcx.hpp" // IWYU pragma: export #include "player.h" #include "utils/display.h" diff --git a/Source/DiabloUI/dialogs.cpp b/Source/DiabloUI/dialogs.cpp index 2aaf31bcb..21f35a1b2 100644 --- a/Source/DiabloUI/dialogs.cpp +++ b/Source/DiabloUI/dialogs.cpp @@ -39,14 +39,14 @@ OptionalClxSprite LoadDialogSprite(bool hasCaption, bool isError) { constexpr uint8_t TransparentColor = 255; if (!hasCaption) { - ownedDialogSprite = LoadPcx(isError ? "ui_art\\srpopup.pcx" : "ui_art\\spopup.pcx", TransparentColor); + ownedDialogSprite = LoadPcx(isError ? "ui_art\\srpopup" : "ui_art\\spopup", TransparentColor); } else if (isError) { ownedDialogSprite = LoadOptionalClx("ui_art\\dvl_lrpopup.clx"); if (!ownedDialogSprite) { - ownedDialogSprite = LoadPcx("ui_art\\lrpopup.pcx", TransparentColor); + ownedDialogSprite = LoadPcx("ui_art\\lrpopup", TransparentColor); } } else { - ownedDialogSprite = LoadPcx("ui_art\\lpopup.pcx", TransparentColor); + ownedDialogSprite = LoadPcx("ui_art\\lpopup", TransparentColor); } if (!ownedDialogSprite) return std::nullopt; diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index b46193ba5..806603e7f 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -46,9 +46,9 @@ void MainmenuLoad(const char *name) if (!gbIsSpawn || gbIsHellfire) { if (gbIsHellfire) ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\mainmenuw.clx"); - LoadBackgroundArt("ui_art\\mainmenu.pcx"); + LoadBackgroundArt("ui_art\\mainmenu"); } else { - LoadBackgroundArt("ui_art\\swmmenu.pcx"); + LoadBackgroundArt("ui_art\\swmmenu"); } UiAddBackground(&vecMainMenuDialog); @@ -102,7 +102,7 @@ bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, int att while (MainMenuResult == MAINMENU_NONE) { UiClearScreen(); UiPollAndRender(); - if (SDL_GetTicks() >= dwAttractTicks && (diabdat_mpq || hellfire_mpq)) { + if (SDL_GetTicks() >= dwAttractTicks && (HaveDiabdat() || HaveHellfire())) { MainMenuResult = MAINMENU_ATTRACT_MODE; } } diff --git a/Source/DiabloUI/progress.cpp b/Source/DiabloUI/progress.cpp index 9e1e50b56..55fc19808 100644 --- a/Source/DiabloUI/progress.cpp +++ b/Source/DiabloUI/progress.cpp @@ -30,14 +30,14 @@ void DialogActionCancel() void ProgressLoadBackground() { UiLoadBlackBackground(); - ArtPopupSm = LoadPcx("ui_art\\spopup.pcx"); - ArtProgBG = LoadPcx("ui_art\\prog_bg.pcx"); + ArtPopupSm = LoadPcx("ui_art\\spopup"); + ArtProgBG = LoadPcx("ui_art\\prog_bg"); } void ProgressLoadForeground() { LoadDialogButtonGraphics(); - ProgFil = LoadPcx("ui_art\\prog_fil.pcx"); + ProgFil = LoadPcx("ui_art\\prog_fil"); const Point uiPosition = GetUIRectangle().position; SDL_Rect rect3 = { (Sint16)(uiPosition.x + 265), (Sint16)(uiPosition.y + 267), DialogButtonWidth, DialogButtonHeight }; diff --git a/Source/DiabloUI/scrollbar.cpp b/Source/DiabloUI/scrollbar.cpp index d0da8d689..7ca5d587b 100644 --- a/Source/DiabloUI/scrollbar.cpp +++ b/Source/DiabloUI/scrollbar.cpp @@ -10,9 +10,9 @@ OptionalOwnedClxSpriteList ArtScrollBarArrow; void LoadScrollBar() { - ArtScrollBarBackground = LoadPcx("ui_art\\sb_bg.pcx"); - ArtScrollBarThumb = LoadPcx("ui_art\\sb_thumb.pcx"); - ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\sb_arrow.pcx", 4); + ArtScrollBarBackground = LoadPcx("ui_art\\sb_bg"); + ArtScrollBarThumb = LoadPcx("ui_art\\sb_thumb"); + ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\sb_arrow", 4); } void UnloadScrollBar() diff --git a/Source/DiabloUI/selconn.cpp b/Source/DiabloUI/selconn.cpp index 3c93214d2..f68048f49 100644 --- a/Source/DiabloUI/selconn.cpp +++ b/Source/DiabloUI/selconn.cpp @@ -35,7 +35,7 @@ void SelconnSelect(int value); void SelconnLoad() { - LoadBackgroundArt("ui_art\\selconn.pcx"); + LoadBackgroundArt("ui_art\\selconn"); #ifndef NONET #ifndef DISABLE_ZERO_TIER diff --git a/Source/DiabloUI/selgame.cpp b/Source/DiabloUI/selgame.cpp index e39c96d5e..47d5a0aa7 100644 --- a/Source/DiabloUI/selgame.cpp +++ b/Source/DiabloUI/selgame.cpp @@ -55,7 +55,7 @@ void selgame_FreeVectors() void selgame_Init() { - LoadBackgroundArt("ui_art\\selgame.pcx"); + LoadBackgroundArt("ui_art\\selgame"); LoadScrollBar(); } diff --git a/Source/DiabloUI/selhero.cpp b/Source/DiabloUI/selhero.cpp index 4b21e4fe4..35b7c0304 100644 --- a/Source/DiabloUI/selhero.cpp +++ b/Source/DiabloUI/selhero.cpp @@ -247,7 +247,7 @@ void RemoveSelHeroBackground() void AddSelHeroBackground() { - LoadBackgroundArt("ui_art\\selhero.pcx"); + LoadBackgroundArt("ui_art\\selhero"); vecSelHeroDialog.insert(vecSelHeroDialog.begin(), std::make_unique((*ArtBackground)[0], MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter)); } @@ -255,7 +255,7 @@ void AddSelHeroBackground() void SelheroClassSelectorSelect(int value) { auto hClass = static_cast(vecSelHeroDlgItems[value]->m_value); - if (gbIsSpawn && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !hfbard_mpq))) { + if (gbIsSpawn && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !gbBard))) { RemoveSelHeroBackground(); UiSelOkDialog(nullptr, _("The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase.").data(), false); AddSelHeroBackground(); @@ -350,7 +350,7 @@ void SelheroLoadSelect(int value) selhero_isSavegame = false; SelheroFree(); - LoadBackgroundArt("ui_art\\selgame.pcx"); + LoadBackgroundArt("ui_art\\selgame"); selgame_GameSelection_Select(0); } diff --git a/Source/DiabloUI/selok.cpp b/Source/DiabloUI/selok.cpp index 4d81b681d..131722b8d 100644 --- a/Source/DiabloUI/selok.cpp +++ b/Source/DiabloUI/selok.cpp @@ -46,9 +46,9 @@ void UiSelOkDialog(const char *title, const char *body, bool background) UiLoadBlackBackground(); } else { if (!gbIsSpawn) { - LoadBackgroundArt("ui_art\\mainmenu.pcx"); + LoadBackgroundArt("ui_art\\mainmenu"); } else { - LoadBackgroundArt("ui_art\\swmmenu.pcx"); + LoadBackgroundArt("ui_art\\swmmenu"); } } diff --git a/Source/DiabloUI/selstart.cpp b/Source/DiabloUI/selstart.cpp index e0742fbee..950097e13 100644 --- a/Source/DiabloUI/selstart.cpp +++ b/Source/DiabloUI/selstart.cpp @@ -33,7 +33,7 @@ void EscPressed() void UiSelStartUpGameOption() { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\mainmenuw.clx"); - LoadBackgroundArt("ui_art\\mainmenu.pcx"); + LoadBackgroundArt("ui_art\\mainmenu"); UiAddBackground(&vecDialog); UiAddLogo(&vecDialog); diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index 5f1cfce01..613d25e93 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/Source/DiabloUI/settingsmenu.cpp @@ -46,9 +46,9 @@ enum class SpecialMenuEntry : int8_t { bool IsValidEntry(OptionEntryBase *pOptionEntry) { auto flags = pOptionEntry->GetFlags(); - if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !diabdat_mpq) + if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveDiabdat()) return false; - if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !hellfire_mpq) + if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !HaveHellfire()) return false; return HasNoneOf(flags, OptionEntryFlags::Invisible | (gbIsHellfire ? OptionEntryFlags::OnlyDiablo : OptionEntryFlags::OnlyHellfire)); } diff --git a/Source/DiabloUI/title.cpp b/Source/DiabloUI/title.cpp index 71b01a320..d408e3ccd 100644 --- a/Source/DiabloUI/title.cpp +++ b/Source/DiabloUI/title.cpp @@ -19,11 +19,11 @@ std::vector> vecTitleScreen; void TitleLoad() { if (gbIsHellfire) { - LoadBackgroundArt("ui_art\\hf_logo1.pcx", 16); + LoadBackgroundArt("ui_art\\hf_logo1", 16); ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\hf_titlew.clx"); } else { - LoadBackgroundArt("ui_art\\title.pcx"); - DiabloTitleLogo = LoadPcxSpriteList("ui_art\\logo.pcx", /*numFrames=*/15, /*transparentColor=*/250); + LoadBackgroundArt("ui_art\\title"); + DiabloTitleLogo = LoadPcxSpriteList("ui_art\\logo", /*numFrames=*/15, /*transparentColor=*/250); } } diff --git a/Source/control.cpp b/Source/control.cpp index f2b66280d..8b869de54 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -546,12 +546,12 @@ void InitControlPan() LoadCharPanel(); LoadSpellIcons(); { - const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\panel8.cel", GetMainPanel().size.width); + const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\panel8", GetMainPanel().size.width); ClxDraw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) - 1 }, sprite[0]); } { const Point bulbsPosition { 0, 87 }; - const OwnedClxSpriteList statusPanel = LoadCel("ctrlpan\\p8bulbs.cel", 88); + const OwnedClxSpriteList statusPanel = LoadCel("ctrlpan\\p8bulbs", 88); ClxDraw(*pLifeBuff, bulbsPosition, statusPanel[0]); ClxDraw(*pManaBuff, bulbsPosition, statusPanel[1]); } @@ -560,11 +560,11 @@ void InitControlPan() if (IsChatAvailable()) { if (!HeadlessMode) { { - const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\talkpanl.cel", GetMainPanel().size.width); + const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\talkpanl", GetMainPanel().size.width); ClxDraw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) * 2 - 1 }, sprite[0]); } - multiButtons = LoadCel("ctrlpan\\p8but2.cel", 33); - talkButtons = LoadCel("ctrlpan\\talkbutt.cel", 61); + multiButtons = LoadCel("ctrlpan\\p8but2", 33); + talkButtons = LoadCel("ctrlpan\\talkbutt", 61); } sgbPlrTalkTbl = 0; TalkMessage[0] = '\0'; @@ -577,10 +577,10 @@ void InitControlPan() lvlbtndown = false; if (!HeadlessMode) { LoadMainPanel(); - pPanelButtons = LoadCel("ctrlpan\\panel8bu.cel", 71); + pPanelButtons = LoadCel("ctrlpan\\panel8bu", 71); static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 }; - pChrButtons = LoadCel("data\\charbut.cel", CharButtonsFrameWidths); + pChrButtons = LoadCel("data\\charbut", CharButtonsFrameWidths); } ClearPanBtn(); if (!IsChatAvailable()) @@ -588,7 +588,7 @@ void InitControlPan() else PanelButtonIndex = 8; if (!HeadlessMode) - pDurIcons = LoadCel("items\\duricons.cel", 32); + pDurIcons = LoadCel("items\\duricons", 32); for (bool &buttonEnabled : chrbtn) buttonEnabled = false; chrbtnactive = false; @@ -602,8 +602,8 @@ void InitControlPan() if (!HeadlessMode) { InitSpellBook(); - pQLogCel = LoadCel("data\\quest.cel", static_cast(SidePanelSize.width)); - pGBoxBuff = LoadCel("ctrlpan\\golddrop.cel", 261); + pQLogCel = LoadCel("data\\quest", static_cast(SidePanelSize.width)); + pGBoxBuff = LoadCel("ctrlpan\\golddrop", 261); } CloseGoldDrop(); dropGoldValue = 0; diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 409335768..bf0aeb1be 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -132,9 +132,9 @@ int pcurs; void InitCursor() { assert(!pCursCels); - pCursCels = LoadCel("data\\inv\\objcurs.cel", InvItemWidth1); + pCursCels = LoadCel("data\\inv\\objcurs", InvItemWidth1); if (gbIsHellfire) - pCursCels2 = LoadCel("data\\inv\\objcurs2.cel", InvItemWidth2); + pCursCels2 = LoadCel("data\\inv\\objcurs2", InvItemWidth2); ClearCursor(); } diff --git a/Source/debug.cpp b/Source/debug.cpp index 9d79699d7..c2c1a315b 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -1027,7 +1027,7 @@ std::vector DebugCmdList = { void LoadDebugGFX() { - pSquareCel = LoadCel("data\\square.cel", 64); + pSquareCel = LoadCel("data\\square", 64); } void FreeDebugGFX() diff --git a/Source/diablo.cpp b/Source/diablo.cpp index cf1d45b48..6bb391d4a 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -959,8 +959,13 @@ void SetApplicationVersions() void CheckArchivesUpToDate() { +#ifdef UNPACKED_MPQS + const bool devilutionxMpqOutOfDate = false; + const bool fontsMpqOutOfDate = font_data_path && !FileExists(*font_data_path + "fonts" + DirectorySeparator + "12-4e.clx"); +#else const bool devilutionxMpqOutOfDate = devilutionx_mpq && !devilutionx_mpq->HasFile("data\\charbg.clx"); const bool fontsMpqOutOfDate = font_mpq && !font_mpq->HasFile("fonts\\12-4e.clx"); +#endif if (devilutionxMpqOutOfDate && fontsMpqOutOfDate) { app_fatal(_("Please update devilutionx.mpq and fonts.mpq to the latest version")); @@ -1093,37 +1098,37 @@ void LoadLvlGFX() pDungeonCels = LoadFileInMem("levels\\towndata\\town.cel"); pMegaTiles = LoadFileInMem("levels\\towndata\\town.til"); } - pSpecialCels = LoadCel("levels\\towndata\\towns.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\towndata\\towns", SpecialCelWidth); break; case DTYPE_CATHEDRAL: pDungeonCels = LoadFileInMem("levels\\l1data\\l1.cel"); pMegaTiles = LoadFileInMem("levels\\l1data\\l1.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); break; case DTYPE_CATACOMBS: pDungeonCels = LoadFileInMem("levels\\l2data\\l2.cel"); pMegaTiles = LoadFileInMem("levels\\l2data\\l2.til"); - pSpecialCels = LoadCel("levels\\l2data\\l2s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l2data\\l2s", SpecialCelWidth); break; case DTYPE_CAVES: pDungeonCels = LoadFileInMem("levels\\l3data\\l3.cel"); pMegaTiles = LoadFileInMem("levels\\l3data\\l3.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); break; case DTYPE_HELL: pDungeonCels = LoadFileInMem("levels\\l4data\\l4.cel"); pMegaTiles = LoadFileInMem("levels\\l4data\\l4.til"); - pSpecialCels = LoadCel("levels\\l2data\\l2s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l2data\\l2s", SpecialCelWidth); break; case DTYPE_NEST: pDungeonCels = LoadFileInMem("nlevels\\l6data\\l6.cel"); pMegaTiles = LoadFileInMem("nlevels\\l6data\\l6.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); break; case DTYPE_CRYPT: pDungeonCels = LoadFileInMem("nlevels\\l5data\\l5.cel"); pMegaTiles = LoadFileInMem("nlevels\\l5data\\l5.til"); - pSpecialCels = LoadCel("nlevels\\l5data\\l5s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("nlevels\\l5data\\l5s", SpecialCelWidth); break; default: app_fatal("LoadLvlGFX"); diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 88a9be7d9..a9a3e5c0d 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -110,11 +110,10 @@ std::string GetTooltipString() std::string GetPlayerAssetString() { - constexpr char CaseDistance = 'a' - 'A'; char chars[5] { - static_cast(CharChar[static_cast(MyPlayer->_pClass)] - CaseDistance), - static_cast(ArmourChar[tracked_data.playerGfx >> 4] - CaseDistance), - static_cast(WepChar[tracked_data.playerGfx & 0xF] - CaseDistance), + CharChar[static_cast(MyPlayer->_pClass)], + ArmourChar[tracked_data.playerGfx >> 4], + WepChar[tracked_data.playerGfx & 0xF], 'a', 's' }; diff --git a/Source/doom.cpp b/Source/doom.cpp index 07a311006..6002f1dd2 100644 --- a/Source/doom.cpp +++ b/Source/doom.cpp @@ -21,7 +21,7 @@ bool DoomFlag; void doom_init() { - DoomSprite = LoadCel("items\\map\\mapztown.cel", 640); + DoomSprite = LoadCel("items\\map\\mapztown", 640); DoomFlag = true; } diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index e0e55809c..690ff79a6 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -14,6 +14,39 @@ namespace devilution { namespace { +bool IsDebugLogging() +{ + return SDL_LOG_PRIORITY_DEBUG >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); +} + +SDL_RWops *OpenOptionalRWops(const std::string &path) +{ + // SDL always logs an error in Debug mode. + // We check the file presence in Debug mode to avoid this. + if (IsDebugLogging() && !FileExists(path.c_str())) + return nullptr; + return SDL_RWFromFile(path.c_str(), "rb"); +}; + +#ifdef UNPACKED_MPQS +SDL_RWops *OpenUnpackedMpqFile(const std::string &relativePath) +{ + SDL_RWops *result = nullptr; + std::string tmpPath; + const auto at = [&](const std::optional &unpackedDir) -> bool { + if (!unpackedDir) + return false; + tmpPath.clear(); + tmpPath.reserve(unpackedDir->size() + relativePath.size()); + result = OpenOptionalRWops(tmpPath.append(*unpackedDir).append(relativePath)); + return result != nullptr; + }; + at(font_data_path) || at(lang_data_path) + || (gbIsHellfire && at(hellfire_data_path)) + || at(spawn_data_path) || at(diabdat_data_path); + return result; +} +#else bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumber) { const MpqArchive::FileHash fileHash = MpqArchive::CalculateFileHash(filename); @@ -28,6 +61,7 @@ bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumbe 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); } +#endif } // namespace @@ -43,32 +77,28 @@ SDL_RWops *OpenAsset(const char *filename, bool threadsafe) SDL_RWops *rwops; - // SDL always logs an error in Debug mode. - // We check the file presence in Debug mode to avoid this. - const bool isDebug = SDL_LOG_PRIORITY_DEBUG >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); - - const auto loadFile = [&rwops, isDebug](const std::string &path) { - return (!isDebug || FileExists(path.c_str())) - && (rwops = SDL_RWFromFile(path.c_str(), "rb")) != nullptr; - }; - // Files in the `PrefPath()` directory can override MPQ contents. { const std::string path = paths::PrefPath() + relativePath; - if (loadFile(path)) { + if ((rwops = OpenOptionalRWops(path)) != nullptr) { LogVerbose("Loaded MPQ file override: {}", path); return rwops; } } // Load from all the MPQ archives. +#ifdef UNPACKED_MPQS + if ((rwops = OpenUnpackedMpqFile(relativePath)) != nullptr) + return rwops; +#else MpqArchive *archive; uint32_t fileNumber; if (OpenMpqFile(filename, &archive, &fileNumber)) return SDL_RWops_FromMpqFile(*archive, fileNumber, filename, threadsafe); +#endif // Load from the `/assets` directory next to the devilutionx binary. - if (loadFile(paths::AssetsPath() + relativePath)) + if ((rwops = OpenOptionalRWops(paths::AssetsPath() + relativePath))) return rwops; #if defined(__ANDROID__) || defined(__APPLE__) diff --git a/Source/engine/clx_sprite.hpp b/Source/engine/clx_sprite.hpp index bbfdb7a78..b451c27c5 100644 --- a/Source/engine/clx_sprite.hpp +++ b/Source/engine/clx_sprite.hpp @@ -442,6 +442,18 @@ inline ClxSpriteSheet::ClxSpriteSheet(const OwnedClxSpriteSheet &owned) class OwnedClxSpriteListOrSheet; class OptionalClxSpriteListOrSheet; +inline uint16_t GetNumListsFromClxListOrSheetBuffer(const uint8_t *data, size_t size) +{ + const uint32_t maybeNumFrames = LoadLE32(data); + + // If it is a number of frames, then the last frame offset will be equal to the size of the file. + if (LoadLE32(&data[maybeNumFrames * 4 + 4]) != size) + return maybeNumFrames / 4; + + // Not a sprite sheet. + return 0; +} + /** * @brief A CLX sprite list or a sprite sheet (list of lists). */ @@ -493,7 +505,13 @@ class OptionalOwnedClxSpriteListOrSheet; */ class OwnedClxSpriteListOrSheet { public: - explicit OwnedClxSpriteListOrSheet(std::unique_ptr &&data, uint16_t numLists = 0) + static OwnedClxSpriteListOrSheet FromBuffer(std::unique_ptr &&data, size_t size) + { + const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(data.get(), size); + return OwnedClxSpriteListOrSheet { std::move(data), numLists }; + } + + explicit OwnedClxSpriteListOrSheet(std::unique_ptr &&data, uint16_t numLists) : data_(std::move(data)) , num_lists_(numLists) { diff --git a/Source/engine/load_cel.cpp b/Source/engine/load_cel.cpp index 31800ca86..717a67851 100644 --- a/Source/engine/load_cel.cpp +++ b/Source/engine/load_cel.cpp @@ -4,19 +4,32 @@ #include #endif +#include "mpq/mpq_common.hpp" +#include "utils/str_cat.hpp" + +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#else #include "engine/load_file.hpp" #include "utils/cel_to_clx.hpp" +#endif namespace devilution { OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths) { + char path[MaxMpqPathSize]; + *BufCopy(path, pszName, DEVILUTIONX_CEL_EXT) = '\0'; +#ifdef UNPACKED_MPQS + return LoadClxListOrSheet(path); +#else size_t size; - std::unique_ptr data = LoadFileInMem(pszName, &size); + std::unique_ptr data = LoadFileInMem(path, &size); #ifdef DEBUG_CEL_TO_CL2_SIZE - std::cout << pszName; + std::cout << path; #endif return CelToClx(data.get(), size, widthOrWidths); +#endif } } // namespace devilution diff --git a/Source/engine/load_cel.hpp b/Source/engine/load_cel.hpp index 070e9ab64..289c31524 100644 --- a/Source/engine/load_cel.hpp +++ b/Source/engine/load_cel.hpp @@ -5,6 +5,12 @@ #include "engine/clx_sprite.hpp" #include "utils/pointer_value_union.hpp" +#ifdef UNPACKED_MPQS +#define DEVILUTIONX_CEL_EXT ".clx" +#else +#define DEVILUTIONX_CEL_EXT ".cel" +#endif + namespace devilution { OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths); diff --git a/Source/engine/load_cl2.cpp b/Source/engine/load_cl2.cpp index e366fe351..daf7f8469 100644 --- a/Source/engine/load_cl2.cpp +++ b/Source/engine/load_cl2.cpp @@ -3,16 +3,29 @@ #include #include +#include "mpq/mpq_common.hpp" +#include "utils/str_cat.hpp" + +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#else #include "engine/load_file.hpp" #include "utils/cl2_to_clx.hpp" +#endif namespace devilution { OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths) { + char path[MaxMpqPathSize]; + *BufCopy(path, pszName, DEVILUTIONX_CL2_EXT) = '\0'; +#ifdef UNPACKED_MPQS + return LoadClxListOrSheet(path); +#else size_t size; - std::unique_ptr data = LoadFileInMem(pszName, &size); + std::unique_ptr data = LoadFileInMem(path, &size); return Cl2ToClx(std::move(data), size, widthOrWidths); +#endif } } // namespace devilution diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp index 9caf1d6c1..104bb1832 100644 --- a/Source/engine/load_cl2.hpp +++ b/Source/engine/load_cl2.hpp @@ -12,6 +12,12 @@ #include "utils/pointer_value_union.hpp" #include "utils/static_vector.hpp" +#ifdef UNPACKED_MPQS +#define DEVILUTIONX_CL2_EXT ".clx" +#else +#define DEVILUTIONX_CL2_EXT ".cl2" +#endif + namespace devilution { OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths); @@ -31,15 +37,19 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref totalSize += size; } auto data = std::unique_ptr { new uint8_t[totalSize] }; +#ifndef UNPACKED_MPQS const PointerOrValue frameWidth { width }; +#endif size_t accumulatedSize = sheetHeaderSize; for (size_t i = 0; i < count; ++i) { const size_t size = fileSizes[i]; if (!files[i].Read(&data[accumulatedSize], size)) app_fatal(StrCat("Read failed: ", SDL_GetError())); WriteLE32(&data[i * 4], accumulatedSize); +#ifndef UNPACKED_MPQS [[maybe_unused]] const uint16_t numLists = Cl2ToClx(&data[accumulatedSize], size, frameWidth); assert(numLists == 0); +#endif accumulatedSize += size; } return OwnedClxSpriteSheet { std::move(data), static_cast(count) }; diff --git a/Source/engine/load_clx.cpp b/Source/engine/load_clx.cpp index 082e17db1..93fc2fb43 100644 --- a/Source/engine/load_clx.cpp +++ b/Source/engine/load_clx.cpp @@ -20,12 +20,14 @@ OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path) std::unique_ptr data { new uint8_t[size] }; SDL_RWread(handle, data.get(), size, 1); SDL_RWclose(handle); - return OwnedClxSpriteListOrSheet { std::move(data) }; + return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); } OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path) { - return OwnedClxSpriteListOrSheet { LoadFileInMem(path) }; + size_t size; + std::unique_ptr data = LoadFileInMem(path, &size); + return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); } } // namespace devilution diff --git a/Source/engine/load_pcx.cpp b/Source/engine/load_pcx.cpp index f2de79796..f1782a92a 100644 --- a/Source/engine/load_pcx.cpp +++ b/Source/engine/load_pcx.cpp @@ -1,6 +1,7 @@ #include "engine/load_pcx.hpp" #include +#include #include #include @@ -10,10 +11,18 @@ #include -#include "engine/assets.hpp" +#include "mpq/mpq_common.hpp" #include "utils/log.hpp" +#include "utils/str_cat.hpp" + +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#include "engine/load_file.hpp" +#else +#include "engine/assets.hpp" #include "utils/pcx.hpp" #include "utils/pcx_to_clx.hpp" +#endif #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -21,11 +30,37 @@ namespace devilution { -OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) +OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette, bool logError) { - SDL_RWops *handle = OpenAsset(filename); + char path[MaxMpqPathSize]; + char *pathEnd = BufCopy(path, filename, DEVILUTIONX_PCX_EXT); + *pathEnd = '\0'; +#ifdef UNPACKED_MPQS + OptionalOwnedClxSpriteList result = LoadOptionalClx(path); + if (!result) { + if (logError) + LogError("Missing file: {}", path); + return result; + } + if (outPalette != nullptr) { + std::memcpy(pathEnd - 3, "pal", 3); + std::array palette; + LoadFileInMem(path, &palette[0], palette.size()); + for (unsigned i = 0; i < 256; i++) { + outPalette[i].r = palette[i * 3]; + outPalette[i].g = palette[i * 3 + 1]; + outPalette[i].b = palette[i * 3 + 2]; +#ifndef USE_SDL1 + outPalette[i].a = SDL_ALPHA_OPAQUE; +#endif + } + } + return result; +#else + SDL_RWops *handle = OpenAsset(path); if (handle == nullptr) { - LogError("Missing file: {}", filename); + if (logError) + LogError("Missing file: {}", path); return std::nullopt; } #ifdef DEBUG_PCX_TO_CL2_SIZE @@ -35,6 +70,7 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames if (!result) return std::nullopt; return result; +#endif } } // namespace devilution diff --git a/Source/engine/load_pcx.hpp b/Source/engine/load_pcx.hpp index 89c56189e..21387860f 100644 --- a/Source/engine/load_pcx.hpp +++ b/Source/engine/load_pcx.hpp @@ -7,6 +7,12 @@ #include "engine/clx_sprite.hpp" #include "utils/stdcompat/optional.hpp" +#ifdef UNPACKED_MPQS +#define DEVILUTIONX_PCX_EXT ".clx" +#else +#define DEVILUTIONX_PCX_EXT ".pcx" +#endif + namespace devilution { /** @@ -18,7 +24,7 @@ namespace devilution { * @param outPalette * @return OptionalOwnedClxSpriteList */ -OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); +OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr, bool logError = true); /** * @brief Loads a PCX file as a CLX sprite list with a single sprite. @@ -28,9 +34,9 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames * @param outPalette * @return OptionalOwnedClxSpriteList */ -inline OptionalOwnedClxSpriteList LoadPcx(const char *filename, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr) +inline OptionalOwnedClxSpriteList LoadPcx(const char *filename, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr, bool logError = true) { - return LoadPcxSpriteList(filename, 1, transparentColor, outPalette); + return LoadPcxSpriteList(filename, 1, transparentColor, outPalette, logError); } } // namespace devilution diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index b0ed5e7d0..8dba4abcb 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -414,7 +414,7 @@ int DoDrawString(const Surface &out, string_view text, Rectangle rect, Point &ch void LoadSmallSelectionSpinner() { - pSPentSpn2Cels = LoadCel("data\\pentspn2.cel", 12); + pSPentSpn2Cels = LoadCel("data\\pentspn2", 12); } void UnloadFonts() diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index c6582ec29..7c7542d45 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -259,7 +259,7 @@ void music_start(_music_id nTrack) music_stop(); if (!gbMusicOn) return; - if (spawn_mpq) + if (HaveSpawn()) trackPath = SpawnMusicTracks[nTrack]; else trackPath = MusicTracks[nTrack]; diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index db2b62044..f10508319 100644 --- a/Source/gmenu.cpp +++ b/Source/gmenu.cpp @@ -198,12 +198,12 @@ void gmenu_init_menu() return; if (gbIsHellfire) - sgpLogo = LoadCel("data\\hf_logo3.cel", 430); + sgpLogo = LoadCel("data\\hf_logo3", 430); else - sgpLogo = LoadCel("data\\diabsmal.cel", 296); - PentSpin_cel = LoadCel("data\\pentspin.cel", 48); - option_cel = LoadCel("data\\option.cel", SliderMarkerWidth); - optbar_cel = LoadCel("data\\optbar.cel", SliderValueBoxWidth); + sgpLogo = LoadCel("data\\diabsmal", 296); + PentSpin_cel = LoadCel("data\\pentspin", 48); + option_cel = LoadCel("data\\option", SliderMarkerWidth); + optbar_cel = LoadCel("data\\optbar", SliderValueBoxWidth); } bool gmenu_is_active() diff --git a/Source/init.cpp b/Source/init.cpp index ebf2115e8..3e85bbd80 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -20,6 +20,7 @@ #include "mpq/mpq_reader.hpp" #include "options.h" #include "pfile.h" +#include "utils/file_util.h" #include "utils/language.h" #include "utils/log.hpp" #include "utils/paths.h" @@ -36,14 +37,8 @@ namespace devilution { /** True if the game is the current active window */ bool gbActive; -/** A handle to an hellfire.mpq archive. */ -std::optional hellfire_mpq; /** The current input handler function */ EventHandler CurrentEventHandler; -/** A handle to the spawn.mpq archive. */ -std::optional spawn_mpq; -/** A handle to the diabdat.mpq archive. */ -std::optional diabdat_mpq; /** Indicate if we only have access to demo data */ bool gbIsSpawn; /** Indicate if we have loaded the Hellfire expansion data */ @@ -52,6 +47,17 @@ bool gbIsHellfire; bool gbVanilla; /** Whether the Hellfire mode is required (forced). */ bool forceHellfire; + +#ifdef UNPACKED_MPQS +std::optional spawn_data_path; +std::optional diabdat_data_path; +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; @@ -60,9 +66,26 @@ std::optional hfvoice_mpq; std::optional devilutionx_mpq; std::optional lang_mpq; std::optional font_mpq; +#endif namespace { +#ifdef UNPACKED_MPQS +std::optional FindUnpackedMpqData(const std::vector &paths, string_view mpqName) +{ + std::string targetPath; + for (const std::string &path : paths) { + targetPath.clear(); + targetPath.reserve(path.size() + mpqName.size() + 1); + targetPath.append(path).append(mpqName) += DirectorySeparator; + if (FileExists(targetPath)) { + LogVerbose(" Found unpacked MPQ directory: {}", targetPath); + return targetPath; + } + } + return std::nullopt; +} +#else std::optional LoadMPQ(const std::vector &paths, string_view mpqName) { std::optional archive; @@ -84,6 +107,7 @@ std::optional LoadMPQ(const std::vector &paths, string_ return std::nullopt; } +#endif std::vector GetMPQSearchPaths() { @@ -145,6 +169,13 @@ void init_cleanup() sfile_write_stash(); } +#ifdef UNPACKED_MPQS + lang_data_path = std::nullopt; + font_data_path = std::nullopt; + hellfire_data_path = std::nullopt; + diabdat_data_path = std::nullopt; + spawn_data_path = std::nullopt; +#else spawn_mpq = std::nullopt; diabdat_mpq = std::nullopt; hellfire_mpq = std::nullopt; @@ -156,6 +187,7 @@ void init_cleanup() lang_mpq = std::nullopt; font_mpq = std::nullopt; devilutionx_mpq = std::nullopt; +#endif NetClose(); } @@ -164,31 +196,77 @@ void LoadCoreArchives() { auto paths = GetMPQSearchPaths(); +#ifdef UNPACKED_MPQS + font_data_path = FindUnpackedMpqData(paths, "fonts"); +#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"); #endif font_mpq = LoadMPQ(paths, "fonts.mpq"); // Extra fonts +#endif } void LoadLanguageArchive() { +#ifdef UNPACKED_MPQS + lang_data_path = std::nullopt; +#else lang_mpq = std::nullopt; +#endif string_view code = *sgOptions.Language.code; if (code != "en") { std::string langMpqName { code }; +#ifdef UNPACKED_MPQS + lang_data_path = FindUnpackedMpqData(GetMPQSearchPaths(), langMpqName); +#else langMpqName.append(".mpq"); - - auto paths = GetMPQSearchPaths(); - lang_mpq = LoadMPQ(paths, langMpqName); + lang_mpq = LoadMPQ(GetMPQSearchPaths(), langMpqName); +#endif } } void LoadGameArchives() { auto paths = GetMPQSearchPaths(); +#if UNPACKED_MPQS + diabdat_data_path = FindUnpackedMpqData(paths, "diabdat"); + if (!diabdat_data_path) { + spawn_data_path = FindUnpackedMpqData(paths, "spawn"); + if (spawn_data_path) + gbIsSpawn = true; + } + if (!HeadlessMode) { + SDL_RWops *handle = OpenAsset("ui_art\\title.clx"); + if (handle == nullptr) { + LogError("{}", SDL_GetError()); + InsertCDDlg(_("diabdat.mpq or spawn.mpq")); + } + SDL_RWclose(handle); + } + hellfire_data_path = FindUnpackedMpqData(paths, "hellfire"); + if (hellfire_data_path) + 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"); + // Bard and barbarian are not currently supported in unpacked mode + // because they use the same paths as rogue and warrior. + gbBard = false; + gbBarbarian = false; + + if (gbIsHellfire && (!hasMonk || !hasMusic || !hasVoice)) { + UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); + diablo_quit(1); + } +#else // !UNPACKED_MPQS diabdat_mpq = LoadMPQ(paths, "DIABDAT.MPQ"); if (!diabdat_mpq) { // DIABDAT.MPQ is uppercase on the original CD and the GOG version. @@ -229,6 +307,7 @@ void LoadGameArchives() UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); diablo_quit(1); } +#endif } void init_create_window() diff --git a/Source/init.h b/Source/init.h index 75cf48190..2dd98e7c1 100644 --- a/Source/init.h +++ b/Source/init.h @@ -12,14 +12,25 @@ namespace devilution { extern bool gbActive; -extern std::optional hellfire_mpq; extern EventHandler CurrentEventHandler; -extern DVL_API_FOR_TEST std::optional spawn_mpq; -extern DVL_API_FOR_TEST std::optional diabdat_mpq; extern DVL_API_FOR_TEST bool gbIsSpawn; extern DVL_API_FOR_TEST bool gbIsHellfire; extern DVL_API_FOR_TEST bool gbVanilla; extern bool forceHellfire; + +#ifdef UNPACKED_MPQS +extern DVL_API_FOR_TEST std::optional spawn_data_path; +extern DVL_API_FOR_TEST std::optional diabdat_data_path; +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; @@ -28,6 +39,43 @@ extern std::optional hfvoice_mpq; extern std::optional font_mpq; extern std::optional lang_mpq; extern std::optional devilutionx_mpq; +#endif + +inline bool HaveSpawn() +{ +#ifdef UNPACKED_MPQS + return bool(spawn_data_path); +#else + return bool(spawn_mpq); +#endif +} + +inline bool HaveDiabdat() +{ +#ifdef UNPACKED_MPQS + return bool(diabdat_data_path); +#else + return bool(diabdat_mpq); +#endif +} + +inline bool HaveHellfire() +{ +#ifdef UNPACKED_MPQS + return bool(hellfire_data_path); +#else + return bool(hellfire_mpq); +#endif +} + +inline bool HaveExtraFonts() +{ +#ifdef UNPACKED_MPQS + return bool(font_data_path); +#else + return bool(font_mpq); +#endif +} void init_cleanup(); void LoadCoreArchives(); diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 9cfcad54e..0c4c1c701 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -14,6 +14,7 @@ #include "engine/dx.h" #include "engine/load_cel.hpp" #include "engine/load_clx.hpp" +#include "engine/load_pcx.hpp" #include "engine/palette.h" #include "engine/render/clx_render.hpp" #include "hwcursor.hpp" @@ -105,60 +106,60 @@ void LoadCutsceneBackground(interface_mode uMsg) switch (PickCutscene(uMsg)) { case CutStart: - ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutstartw.pcx"); - celPath = "gendata\\cutstart.cel"; + ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutstartw.clx"); + celPath = "gendata\\cutstart"; palPath = "gendata\\cutstart.pal"; progress_id = 1; break; case CutTown: - celPath = "gendata\\cuttt.cel"; + celPath = "gendata\\cuttt"; palPath = "gendata\\cuttt.pal"; progress_id = 1; break; case CutLevel1: - celPath = "gendata\\cutl1d.cel"; + celPath = "gendata\\cutl1d"; palPath = "gendata\\cutl1d.pal"; progress_id = 0; break; case CutLevel2: - celPath = "gendata\\cut2.cel"; + celPath = "gendata\\cut2"; palPath = "gendata\\cut2.pal"; progress_id = 2; break; case CutLevel3: - celPath = "gendata\\cut3.cel"; + celPath = "gendata\\cut3"; palPath = "gendata\\cut3.pal"; progress_id = 1; break; case CutLevel4: - celPath = "gendata\\cut4.cel"; + celPath = "gendata\\cut4"; palPath = "gendata\\cut4.pal"; progress_id = 1; break; case CutLevel5: - celPath = "nlevels\\cutl5.cel"; + celPath = "nlevels\\cutl5"; palPath = "nlevels\\cutl5.pal"; progress_id = 1; break; case CutLevel6: - celPath = "nlevels\\cutl6.cel"; + celPath = "nlevels\\cutl6"; palPath = "nlevels\\cutl6.pal"; progress_id = 1; break; case CutPortal: - ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportlw.pcx"); - celPath = "gendata\\cutportl.cel"; + ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportlw.clx"); + celPath = "gendata\\cutportl"; palPath = "gendata\\cutportl.pal"; progress_id = 1; break; case CutPortalRed: - ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportrw.pcx"); - celPath = "gendata\\cutportr.cel"; + ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportrw.clx"); + celPath = "gendata\\cutportr"; palPath = "gendata\\cutportr.pal"; progress_id = 1; break; case CutGate: - celPath = "gendata\\cutgate.cel"; + celPath = "gendata\\cutgate"; palPath = "gendata\\cutgate.pal"; progress_id = 1; break; diff --git a/Source/inv.cpp b/Source/inv.cpp index fbbdb9939..d5479c72e 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -1077,17 +1077,17 @@ void InitInv() switch (MyPlayer->_pClass) { case HeroClass::Warrior: case HeroClass::Barbarian: - pInvCels = LoadCel("data\\inv\\inv.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("data\\inv\\inv", static_cast(SidePanelSize.width)); break; case HeroClass::Rogue: case HeroClass::Bard: - pInvCels = LoadCel("data\\inv\\inv_rog.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("data\\inv\\inv_rog", static_cast(SidePanelSize.width)); break; case HeroClass::Sorcerer: - pInvCels = LoadCel("data\\inv\\inv_sor.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("data\\inv\\inv_sor", static_cast(SidePanelSize.width)); break; case HeroClass::Monk: - pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor.cel" : "data\\inv\\inv.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor" : "data\\inv\\inv", static_cast(SidePanelSize.width)); break; } } diff --git a/Source/items.cpp b/Source/items.cpp index c9f6810fd..402c6bcda 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -2315,7 +2315,7 @@ void InitItemGFX() int itemTypes = gbIsHellfire ? ITEMTYPES : 35; for (int i = 0; i < itemTypes; i++) { - *BufCopy(arglist, "items\\", ItemDropNames[i], ".cel") = '\0'; + *BufCopy(arglist, "items\\", ItemDropNames[i]) = '\0'; itemanims[i] = LoadCel(arglist, ItemAnimWidth); } memset(UniqueItemFlags, 0, sizeof(UniqueItemFlags)); diff --git a/Source/levels/gendung.h b/Source/levels/gendung.h index 52fb818fb..f3bdac79f 100644 --- a/Source/levels/gendung.h +++ b/Source/levels/gendung.h @@ -198,8 +198,8 @@ extern DVL_API_FOR_TEST int8_t dCorpse[MAXDUNX][MAXDUNY]; extern DVL_API_FOR_TEST int8_t dObject[MAXDUNX][MAXDUNY]; /** * Contains the arch frame numbers of the map from the special tileset - * (e.g. "levels/l1data/l1s.cel"). Note, the special tileset of Tristram (i.e. - * "levels/towndata/towns.cel") contains trees rather than arches. + * (e.g. "levels/l1data/l1s"). Note, the special tileset of Tristram (i.e. + * "levels/towndata/towns") contains trees rather than arches. */ extern char dSpecial[MAXDUNX][MAXDUNY]; extern int themeCount; diff --git a/Source/menu.cpp b/Source/menu.cpp index 8cc7873e9..54c741202 100644 --- a/Source/menu.cpp +++ b/Source/menu.cpp @@ -165,7 +165,7 @@ void mainmenu_loop() done = true; break; case MAINMENU_ATTRACT_MODE: - if (gbIsSpawn && !diabdat_mpq) + if (gbIsSpawn && !HaveDiabdat()) done = false; else if (gbActive) PlayIntro(); diff --git a/Source/minitext.cpp b/Source/minitext.cpp index a6324b32d..868b3afb0 100644 --- a/Source/minitext.cpp +++ b/Source/minitext.cpp @@ -126,7 +126,7 @@ void FreeQuestText() void InitQuestText() { - pTextBoxCels = LoadCel("data\\textbox.cel", 591); + pTextBoxCels = LoadCel("data\\textbox", 591); } void InitQTextMsg(_speech_id m) diff --git a/Source/misdat.cpp b/Source/misdat.cpp index ca19a0b07..d11154799 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -7,7 +7,9 @@ #include "engine/load_cl2.hpp" #include "missiles.h" +#include "mpq/mpq_common.hpp" #include "utils/file_name_generator.hpp" +#include "utils/str_cat.hpp" namespace devilution { @@ -236,10 +238,12 @@ void MissileFileData::LoadGFX() if (name.empty()) return; - FileNameGenerator pathGenerator({ "missiles\\", name }, ".cl2"); if (animFAmt == 1) { - sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(pathGenerator(), animWidth) }); + char path[MaxMpqPathSize]; + *BufCopy(path, "missiles\\", name) = '\0'; + sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(path, animWidth) }); } else { + FileNameGenerator pathGenerator({ "missiles\\", name }, DEVILUTIONX_CL2_EXT); sprites.emplace(OwnedClxSpriteListOrSheet { LoadMultipleCl2Sheet<16>(pathGenerator, animFAmt, animWidth) }); } } diff --git a/Source/monster.cpp b/Source/monster.cpp index 4c32f2293..608d99050 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -3346,7 +3346,7 @@ void InitMonsterGFX(CMonster &monsterType) if (!HeadlessMode) { monsterType.animData = MultiFileLoader {}( numAnims, - FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, ".cl2", Animletter), + FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, DEVILUTIONX_CL2_EXT, Animletter), animOffsets.data(), hasAnim); } diff --git a/Source/objects.cpp b/Source/objects.cpp index b4567d1a7..875869b9a 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -3649,7 +3649,7 @@ void LoadLevelObjects(uint16_t filesWidths[65]) ObjFileList[numobjfiles] = static_cast(i); char filestr[32]; - *BufCopy(filestr, "objects\\", ObjMasterLoadList[i], ".cel") = '\0'; + *BufCopy(filestr, "objects\\", ObjMasterLoadList[i]) = '\0'; pObjCels[numobjfiles] = LoadCel(filestr, filesWidths[i]); numobjfiles++; } diff --git a/Source/options.cpp b/Source/options.cpp index 95d4e951a..7960b13cb 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -1155,7 +1155,7 @@ void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const languages.emplace_back("hr", "Hrvatski"); languages.emplace_back("it", "Italiano"); - if (font_mpq) { + if (HaveExtraFonts()) { languages.emplace_back("ja", "日本語"); languages.emplace_back("ko", "한국어"); } @@ -1167,7 +1167,7 @@ void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const languages.emplace_back("sv", "Svenska"); languages.emplace_back("uk", "Українська"); - if (font_mpq) { + if (HaveExtraFonts()) { languages.emplace_back("zh_CN", "汉语"); languages.emplace_back("zh_TW", "漢語"); } diff --git a/Source/panels/info_box.cpp b/Source/panels/info_box.cpp index 90af6a714..de535fd4c 100644 --- a/Source/panels/info_box.cpp +++ b/Source/panels/info_box.cpp @@ -9,8 +9,8 @@ OptionalOwnedClxSpriteList pSTextSlidCels; void InitInfoBoxGfx() { - pSTextSlidCels = LoadCel("data\\textslid.cel", 12); - pSTextBoxCels = LoadCel("data\\textbox2.cel", 271); + pSTextSlidCels = LoadCel("data\\textslid", 12); + pSTextBoxCels = LoadCel("data\\textbox2", 271); } void FreeInfoBoxGfx() diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 794ead508..21510309b 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -81,9 +81,9 @@ spell_type GetSBookTrans(spell_id ii, bool townok) void InitSpellBook() { - pSpellBkCel = LoadCel("data\\spellbk.cel", static_cast(SidePanelSize.width)); - pSBkBtnCel = LoadCel("data\\spellbkb.cel", gbIsHellfire ? 61 : 76); - pSBkIconCels = LoadCel("data\\spelli2.cel", 37); + pSpellBkCel = LoadCel("data\\spellbk", static_cast(SidePanelSize.width)); + pSBkBtnCel = LoadCel("data\\spellbkb", gbIsHellfire ? 61 : 76); + pSBkIconCels = LoadCel("data\\spelli2", 37); Player &player = *MyPlayer; if (player._pClass == HeroClass::Warrior) { diff --git a/Source/panels/spell_icons.cpp b/Source/panels/spell_icons.cpp index 24e6f87d6..93bf92308 100644 --- a/Source/panels/spell_icons.cpp +++ b/Source/panels/spell_icons.cpp @@ -71,9 +71,9 @@ const char SpellITbl[] = { void LoadSpellIcons() { if (!gbIsHellfire) - pSpellCels = LoadCel("ctrlpan\\spelicon.cel", SPLICONLENGTH); + pSpellCels = LoadCel("ctrlpan\\spelicon", SPLICONLENGTH); else - pSpellCels = LoadCel("data\\spelicon.cel", SPLICONLENGTH); + pSpellCels = LoadCel("data\\spelicon", SPLICONLENGTH); SetSpellTrans(RSPLTYPE_SKILL); } diff --git a/Source/player.cpp b/Source/player.cpp index e43bdfae0..fd37c7dbc 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -185,12 +185,12 @@ constexpr int8_t PlrGFXAnimLens[enum_size::value][11] = { }; const char *const ClassPathTbl[] = { - "Warrior", - "Rogue", - "Sorceror", - "Monk", - "Rogue", - "Warrior", + "warrior", + "rogue", + "sorceror", + "monk", + "rogue", + "warrior", }; void PmChangeLightOff(Player &player) @@ -1701,9 +1701,9 @@ void CheckCheatStats(Player &player) HeroClass GetPlayerSpriteClass(HeroClass cls) { - if (cls == HeroClass::Bard && !hfbard_mpq) + if (cls == HeroClass::Bard && !gbBard) return HeroClass::Rogue; - if (cls == HeroClass::Barbarian && !hfbarb_mpq) + if (cls == HeroClass::Barbarian && !gbBarbarian) return HeroClass::Warrior; return cls; } @@ -2227,45 +2227,45 @@ void LoadPlrGFX(Player &player, player_graphic graphic) const char *szCel; switch (graphic) { case player_graphic::Stand: - szCel = "AS"; + szCel = "as"; if (leveltype == DTYPE_TOWN) - szCel = "ST"; + szCel = "st"; break; case player_graphic::Walk: - szCel = "AW"; + szCel = "aw"; if (leveltype == DTYPE_TOWN) - szCel = "WL"; + szCel = "wl"; break; case player_graphic::Attack: if (leveltype == DTYPE_TOWN) return; - szCel = "AT"; + szCel = "at"; break; case player_graphic::Hit: if (leveltype == DTYPE_TOWN) return; - szCel = "HT"; + szCel = "ht"; break; case player_graphic::Lightning: - szCel = "LM"; + szCel = "lm"; break; case player_graphic::Fire: - szCel = "FM"; + szCel = "fm"; break; case player_graphic::Magic: - szCel = "QM"; + szCel = "qm"; break; case player_graphic::Death: if (animWeaponId != PlayerWeaponGraphic::Unarmed) return; - szCel = "DT"; + szCel = "dt"; break; case player_graphic::Block: if (leveltype == DTYPE_TOWN) return; if (!player._pBlockFlag) return; - szCel = "BL"; + szCel = "bl"; break; default: app_fatal("PLR:2"); @@ -2276,7 +2276,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic) char prefix[3] = { CharChar[static_cast(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; char pszName[256]; - *fmt::format_to(pszName, FMT_COMPILE(R"(plrgfx\{0}\{1}\{1}{2}.cl2)"), path, string_view(prefix, 3), szCel) = 0; + *fmt::format_to(pszName, FMT_COMPILE(R"(plrgfx\{0}\{1}\{1}{2})"), path, string_view(prefix, 3), szCel) = 0; const uint16_t animationWidth = GetPlayerSpriteWidth(cls, graphic, animWeaponId); animationData.sprites = LoadCl2Sheet(pszName, animationWidth); std::optional> trn = GetClassTRN(player); diff --git a/Source/player.h b/Source/player.h index a1bb2cbc4..b68cfdab6 100644 --- a/Source/player.h +++ b/Source/player.h @@ -164,31 +164,31 @@ use_enum_as_flags(SpellFlag); /** Maps from armor animation to letter used in graphic files. */ constexpr std::array ArmourChar = { - 'L', // light - 'M', // medium - 'H', // heavy + 'l', // light + 'm', // medium + 'h', // heavy }; /** Maps from weapon animation to letter used in graphic files. */ constexpr std::array WepChar = { - 'N', // unarmed - 'U', // no weapon + shield - 'S', // sword + no shield - 'D', // sword + shield - 'B', // bow - 'A', // axe - 'M', // blunt + no shield - 'H', // blunt + shield - 'T', // staff + 'n', // unarmed + 'u', // no weapon + shield + 's', // sword + no shield + 'd', // sword + shield + 'b', // bow + 'a', // axe + 'm', // blunt + no shield + 'h', // blunt + shield + 't', // staff }; /** Maps from player class to letter used in graphic files. */ constexpr std::array CharChar = { - 'W', // warrior - 'R', // rogue - 'S', // sorcerer - 'M', // monk - 'B', - 'C', + 'w', // warrior + 'r', // rogue + 's', // sorcerer + 'm', // monk + 'b', + 'c', }; /** diff --git a/Source/towners.cpp b/Source/towners.cpp index e152e2e59..9e0eee629 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -77,7 +77,7 @@ void InitSmith(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\smith\\smithn.cel", 16, 3); + LoadTownerAnimations(towner, "towners\\smith\\smithn", 16, 3); towner.name = _("Griswold the Blacksmith"); } @@ -99,7 +99,7 @@ void InitBarOwner(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\twnf\\twnfn.cel", 16, 3); + LoadTownerAnimations(towner, "towners\\twnf\\twnfn", 16, 3); towner.name = _("Ogden the Tavern owner"); } @@ -108,7 +108,7 @@ void InitTownDead(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\butch\\deadguy.cel", 8, 6); + LoadTownerAnimations(towner, "towners\\butch\\deadguy", 8, 6); towner.name = _("Wounded Townsman"); } @@ -130,7 +130,7 @@ void InitWitch(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\townwmn1\\witch.cel", 19, 6); + LoadTownerAnimations(towner, "towners\\townwmn1\\witch", 19, 6); towner.name = _("Adria the Witch"); } @@ -139,7 +139,7 @@ void InitBarmaid(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\townwmn1\\wmnn.cel", 18, 6); + LoadTownerAnimations(towner, "towners\\townwmn1\\wmnn", 18, 6); towner.name = _("Gillian the Barmaid"); } @@ -148,7 +148,7 @@ void InitBoy(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\townboy\\pegkid1.cel", 20, 6); + LoadTownerAnimations(towner, "towners\\townboy\\pegkid1", 20, 6); towner.name = _("Wirt the Peg-legged boy"); } @@ -170,7 +170,7 @@ void InitHealer(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\healer\\healer.cel", 20, 6); + LoadTownerAnimations(towner, "towners\\healer\\healer", 20, 6); towner.name = _("Pepin the Healer"); } @@ -187,7 +187,7 @@ void InitTeller(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\strytell\\strytell.cel", 25, 3); + LoadTownerAnimations(towner, "towners\\strytell\\strytell", 25, 3); towner.name = _("Cain the Elder"); } @@ -203,7 +203,7 @@ void InitDrunk(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\drunk\\twndrunk.cel", 18, 3); + LoadTownerAnimations(towner, "towners\\drunk\\twndrunk", 18, 3); towner.name = _("Farnham the Drunk"); } @@ -238,15 +238,15 @@ void InitFarmer(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\farmer\\farmrn2.cel", 15, 3); + LoadTownerAnimations(towner, "towners\\farmer\\farmrn2", 15, 3); towner.name = _("Lester the farmer"); } void InitCowFarmer(Towner &towner, const TownerData &townerData) { - const char *celPath = "towners\\farmer\\cfrmrn2.cel"; + const char *celPath = "towners\\farmer\\cfrmrn2"; if (Quests[Q_JERSEY]._qactive == QUEST_DONE) { - celPath = "towners\\farmer\\mfrmrn2.cel"; + celPath = "towners\\farmer\\mfrmrn2"; } towner._tAnimWidth = 96; towner.animOrder = nullptr; @@ -260,7 +260,7 @@ void InitGirl(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\girl\\girlw1.cel", 20, 6); + LoadTownerAnimations(towner, "towners\\girl\\girlw1", 20, 6); towner.name = _("Celia"); } @@ -649,7 +649,7 @@ void TalkToCowFarmer(Player &player, Towner &cowFarmer) InitQTextMsg(TEXT_JERSEY8); quest._qactive = QUEST_DONE; auto curFrame = cowFarmer._tAnimFrame; - LoadTownerAnimations(cowFarmer, "towners\\farmer\\mfrmrn2.cel", 15, 3); + LoadTownerAnimations(cowFarmer, "towners\\farmer\\mfrmrn2", 15, 3); cowFarmer._tAnimFrame = std::min(curFrame, cowFarmer._tAnimLen - 1); return; } @@ -729,7 +729,7 @@ void TalkToGirl(Player &player, Towner &girl) quest._qlog = false; quest._qactive = QUEST_DONE; auto curFrame = girl._tAnimFrame; - LoadTownerAnimations(girl, "towners\\girl\\girls1.cel", 20, 6); + LoadTownerAnimations(girl, "towners\\girl\\girls1", 20, 6); girl._tAnimFrame = std::min(curFrame, girl._tAnimLen - 1); if (gbIsMultiplayer) NetSendCmdQuest(true, quest); @@ -820,7 +820,7 @@ void InitTowners() { assert(!CowSprites); - CowSprites.emplace(LoadCelSheet("towners\\animals\\cow.cel", 128)); + CowSprites.emplace(LoadCelSheet("towners\\animals\\cow", 128)); int i = 0; for (const auto &townerData : TownersData) { diff --git a/Source/utils/file_util.h b/Source/utils/file_util.h index b385a622b..cec0a7fde 100644 --- a/Source/utils/file_util.h +++ b/Source/utils/file_util.h @@ -12,6 +12,12 @@ namespace devilution { bool FileExists(const char *path); + +inline bool FileExists(const std::string &str) +{ + return FileExists(str.c_str()); +} + bool FileExistsAndIsWriteable(const char *path); bool GetFileSize(const char *path, std::uintmax_t *size); bool ResizeFile(const char *path, std::uintmax_t size); diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index adea252dd..b164f4421 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -326,7 +326,7 @@ void LanguageInitialize() translationKeys = nullptr; translationValues = nullptr; - if (IsSmallFontTall() && !font_mpq) { + if (IsSmallFontTall() && !HaveExtraFonts()) { UiErrorOkDialog( "Missing fonts.mpq", StrCat("fonts.mpq is required for locale \"", diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp index 8186f147c..4f813bc10 100644 --- a/Source/utils/paths.cpp +++ b/Source/utils/paths.cpp @@ -21,6 +21,12 @@ #include "utils/sdl2_to_1_2_backports.h" #endif +#ifdef _WIN32 +#define DIRECTORY_SEPARATOR_STR "\\" +#else +#define DIRECTORY_SEPARATOR_STR "/" +#endif + namespace devilution { namespace paths { @@ -32,14 +38,6 @@ std::optional prefPath; std::optional configPath; std::optional assetsPath; -#ifdef _WIN32 -constexpr char DirectorySeparator = '\\'; -#define DIRECTORY_SEPARATOR_STR "\\" -#else -constexpr char DirectorySeparator = '/'; -#define DIRECTORY_SEPARATOR_STR "/" -#endif - void AddTrailingSlash(std::string &path) { if (!path.empty() && path.back() != DirectorySeparator) diff --git a/Source/utils/paths.h b/Source/utils/paths.h index 639059a74..98739f0ab 100644 --- a/Source/utils/paths.h +++ b/Source/utils/paths.h @@ -6,6 +6,12 @@ namespace devilution { +#ifdef _WIN32 +constexpr char DirectorySeparator = '\\'; +#else +constexpr char DirectorySeparator = '/'; +#endif + namespace paths { const std::string &BasePath(); diff --git a/test/timedemo_test.cpp b/test/timedemo_test.cpp index a625f5ed6..992e6df32 100644 --- a/test/timedemo_test.cpp +++ b/test/timedemo_test.cpp @@ -28,7 +28,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(spawn_mpq || diabdat_mpq); + ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); InitKeymapActions(); LoadOptions();