diff --git a/CMake/Definitions.cmake b/CMake/Definitions.cmake index b76fe164f..ea2618476 100644 --- a/CMake/Definitions.cmake +++ b/CMake/Definitions.cmake @@ -20,6 +20,7 @@ foreach( DEVILUTIONX_RESAMPLER_SDL DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT UNPACKED_MPQS + UNPACKED_SAVES ) if(${def_name}) list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}) diff --git a/CMake/platforms/rg99.cmake b/CMake/platforms/rg99.cmake index 6a31b5607..2dbdf61f6 100644 --- a/CMake/platforms/rg99.cmake +++ b/CMake/platforms/rg99.cmake @@ -1,6 +1,7 @@ # RG99 has the same layout as RG300 but only 32 MiB RAM set(BUILD_ASSETS_MPQ OFF) set(UNPACKED_MPQS ON) +set(UNPACKED_SAVES ON) set(NONET ON) set(USE_SDL1 ON) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a14607fe..8cf04b7d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ 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(UNPACKED_SAVES "Uses unpacked save files instead of MPQ .sv/.hsv files" 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/CMakeLists.txt b/Source/CMakeLists.txt index f36a33212..34af4ced3 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -129,10 +129,6 @@ set(libdevilutionx_SRCS miniwin/misc_msg.cpp - mpq/mpq_reader.cpp - mpq/mpq_sdl_rwops.cpp - mpq/mpq_writer.cpp - panels/charpanel.cpp panels/info_box.cpp panels/mainpanel.cpp @@ -168,6 +164,14 @@ set(libdevilutionx_SRCS utils/surface_to_clx.cpp utils/utf8.cpp) +if(NOT (UNPACKED_MPQS AND UNPACKED_SAVES)) + list(APPEND libdevilutionx_DEPS libmpq) + list(APPEND libdevilutionx_SRCS + mpq/mpq_reader.cpp + mpq/mpq_sdl_rwops.cpp + mpq/mpq_writer.cpp) +endif() + if(IOS) list(APPEND libdevilutionx_SRCS platform/ios/ios_paths.m) endif() @@ -251,11 +255,11 @@ target_link_libraries(libdevilutionx PUBLIC DevilutionX::SDL fmt::fmt PKWare - libmpq libsmackerdec simpleini::simpleini tl hoehrmann_utf8 + ${libdevilutionx_DEPS} ) if(NOT USE_SDL1) diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index 6ea3b250f..15b5278b2 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -5,12 +5,15 @@ #include #include "init.h" -#include "mpq/mpq_sdl_rwops.hpp" #include "utils/file_util.h" #include "utils/log.hpp" #include "utils/paths.h" #include "utils/str_cat.hpp" +#ifndef UNPACKED_MPQS +#include "mpq/mpq_sdl_rwops.hpp" +#endif + namespace devilution { namespace { diff --git a/Source/init.cpp b/Source/init.cpp index 32e79c188..e33817035 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -18,7 +18,6 @@ #include "engine/dx.h" #include "hwcursor.hpp" #include "miniwin/misc_msg.h" -#include "mpq/mpq_reader.hpp" #include "options.h" #include "pfile.h" #include "utils/file_util.h" @@ -29,6 +28,10 @@ #include "utils/ui_fwd.h" #include "utils/utf8.hpp" +#ifndef UNPACKED_MPQS +#include "mpq/mpq_reader.hpp" +#endif + #ifdef __vita__ // increase default allowed heap size on Vita int _newlib_heap_size_user = 100 * 1024 * 1024; diff --git a/Source/init.h b/Source/init.h index 2dd98e7c1..10f9cfa02 100644 --- a/Source/init.h +++ b/Source/init.h @@ -6,8 +6,12 @@ #pragma once #include "miniwin/misc_msg.h" -#include "mpq/mpq_reader.hpp" #include "utils/attributes.h" +#include "utils/stdcompat/optional.hpp" + +#ifndef UNPACKED_MPQS +#include "mpq/mpq_reader.hpp" +#endif namespace devilution { diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 2a6fe95ec..1dd22829a 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -28,7 +28,7 @@ #include "menu.h" #include "missiles.h" #include "monster.h" -#include "mpq/mpq_writer.hpp" +#include "mpq/mpq_common.hpp" #include "pfile.h" #include "qol/stash.h" #include "stores.h" @@ -97,7 +97,7 @@ class LoadHelper { } public: - LoadHelper(std::optional archive, const char *szFileName) + LoadHelper(std::optional archive, const char *szFileName) { if (archive) m_buffer_ = ReadArchive(*archive, szFileName, &m_size_); @@ -163,14 +163,14 @@ public: }; class SaveHelper { - MpqWriter &m_mpqWriter; + SaveWriter &m_mpqWriter; const char *m_szFileName_; std::unique_ptr m_buffer_; size_t m_cur_ = 0; size_t m_capacity_; public: - SaveHelper(MpqWriter &mpqWriter, const char *szFileName, size_t bufferLen) + SaveHelper(SaveWriter &mpqWriter, const char *szFileName, size_t bufferLen) : m_mpqWriter(mpqWriter) , m_szFileName_(szFileName) , m_buffer_(new byte[codec_get_encoded_len(bufferLen)]) @@ -913,7 +913,7 @@ void GetPermLevelNames(char *szPerm) return GetLevelNames("perm", szPerm); } -bool LevelFileExists(MpqWriter &archive) +bool LevelFileExists(SaveWriter &archive) { char szName[MaxMpqPathSize]; @@ -1647,7 +1647,7 @@ void SaveDroppedItemLocations(SaveHelper &file, const std::unordered_map MaxMissilesForSaveGame) ? static_cast(Missiles.size() - MaxMissilesForSaveGame) : 0; @@ -1691,7 +1691,7 @@ const int HellfireItemSaveSize = 372; } // namespace -void ConvertLevels(MpqWriter &saveWriter) +void ConvertLevels(SaveWriter &saveWriter) { // Backup current level state bool tmpSetlevel = setlevel; @@ -1934,7 +1934,7 @@ void LoadHotkeys() myPlayer._pRSplType = static_cast(file.NextLE()); } -void SaveHotkeys(MpqWriter &saveWriter, const Player &player) +void SaveHotkeys(SaveWriter &saveWriter, const Player &player) { SaveHelper file(saveWriter, "hotkeys", HotkeysSize()); @@ -2221,7 +2221,7 @@ void LoadGame(bool firstflag) gbIsHellfireSaveGame = gbIsHellfire; } -void SaveHeroItems(MpqWriter &saveWriter, Player &player) +void SaveHeroItems(SaveWriter &saveWriter, Player &player) { size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; SaveHelper file(saveWriter, "heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); @@ -2236,7 +2236,7 @@ void SaveHeroItems(MpqWriter &saveWriter, Player &player) SaveItem(file, item); } -void SaveStash(MpqWriter &stashWriter) +void SaveStash(SaveWriter &stashWriter) { const char *filename; if (!gbIsMultiplayer) @@ -2293,7 +2293,7 @@ void SaveStash(MpqWriter &stashWriter) file.WriteLE(static_cast(Stash.GetPage())); } -void SaveGameData(MpqWriter &saveWriter) +void SaveGameData(SaveWriter &saveWriter) { SaveHelper file(saveWriter, "game", 320 * 1024); @@ -2462,7 +2462,7 @@ void SaveGame() sfile_write_stash(); } -void SaveLevel(MpqWriter &saveWriter) +void SaveLevel(SaveWriter &saveWriter) { Player &myPlayer = *MyPlayer; @@ -2539,7 +2539,7 @@ void SaveLevel(MpqWriter &saveWriter) void LoadLevel() { char szName[MaxMpqPathSize]; - std::optional archive = OpenSaveArchive(gSaveNumber); + std::optional archive = OpenSaveArchive(gSaveNumber); GetTempLevelNames(szName); if (!archive || !archive->HasFile(szName)) GetPermLevelNames(szName); diff --git a/Source/loadsave.h b/Source/loadsave.h index 2d826cc1d..ad8a42e7e 100644 --- a/Source/loadsave.h +++ b/Source/loadsave.h @@ -5,7 +5,7 @@ */ #pragma once -#include "mpq/mpq_writer.hpp" +#include "pfile.h" #include "player.h" #include "utils/attributes.h" @@ -33,14 +33,14 @@ void RemoveEmptyInventory(Player &player); * @param firstflag Can be set to false if we are simply reloading the current game */ void LoadGame(bool firstflag); -void SaveHotkeys(MpqWriter &saveWriter, const Player &player); -void SaveHeroItems(MpqWriter &saveWriter, Player &player); -void SaveGameData(MpqWriter &saveWriter); +void SaveHotkeys(SaveWriter &saveWriter, const Player &player); +void SaveHeroItems(SaveWriter &saveWriter, Player &player); +void SaveGameData(SaveWriter &saveWriter); void SaveGame(); -void SaveLevel(MpqWriter &saveWriter); +void SaveLevel(SaveWriter &saveWriter); void LoadLevel(); -void ConvertLevels(MpqWriter &saveWriter); +void ConvertLevels(SaveWriter &saveWriter); void LoadStash(); -void SaveStash(MpqWriter &stashWriter); +void SaveStash(SaveWriter &stashWriter); } // namespace devilution diff --git a/Source/mpq/mpq_writer.hpp b/Source/mpq/mpq_writer.hpp index 0d6f74234..7f3a41580 100644 --- a/Source/mpq/mpq_writer.hpp +++ b/Source/mpq/mpq_writer.hpp @@ -15,6 +15,10 @@ namespace devilution { class MpqWriter { public: explicit MpqWriter(const char *path); + explicit MpqWriter(const std::string &path) + : MpqWriter(path.c_str()) + { + } MpqWriter(MpqWriter &&other) = default; MpqWriter &operator=(MpqWriter &&other) = default; ~MpqWriter(); diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 0567c668c..86b7665e8 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -17,7 +17,7 @@ #include "init.h" #include "loadsave.h" #include "menu.h" -#include "mpq/mpq_reader.hpp" +#include "mpq/mpq_common.hpp" #include "pack.h" #include "qol/stash.h" #include "utils/endian.hpp" @@ -29,6 +29,12 @@ #include "utils/str_cat.hpp" #include "utils/utf8.hpp" +#ifdef UNPACKED_SAVES +#include "utils/file_util.h" +#else +#include "mpq/mpq_reader.hpp" +#endif + namespace devilution { #define PASSWORD_SPAWN_SINGLE "adslhfb1" @@ -49,14 +55,25 @@ std::string GetSavePath(uint32_t saveNum, string_view savePrefix = {}) gbIsSpawn ? (gbIsMultiplayer ? "share_" : "spawn_") : (gbIsMultiplayer ? "multi_" : "single_"), - saveNum, gbIsHellfire ? ".hsv" : ".sv"); + saveNum, +#ifdef UNPACKED_SAVES + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR +#else + gbIsHellfire ? ".hsv" : ".sv" +#endif + ); } std::string GetStashSavePath() { return StrCat(paths::PrefPath(), gbIsSpawn ? "stash_spawn" : "stash", - gbIsHellfire ? ".hsv" : ".sv"); +#ifdef UNPACKED_SAVES + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR +#else + gbIsHellfire ? ".hsv" : ".sv" +#endif + ); } bool GetSaveNames(uint8_t index, string_view prefix, char *out) @@ -85,7 +102,7 @@ bool GetTempSaveNames(uint8_t dwIndex, char *szTemp) return GetSaveNames(dwIndex, "temp", szTemp); } -void RenameTempToPerm(MpqWriter &saveWriter) +void RenameTempToPerm(SaveWriter &saveWriter) { char szTemp[MaxMpqPathSize]; char szPerm[MaxMpqPathSize]; @@ -104,7 +121,7 @@ void RenameTempToPerm(MpqWriter &saveWriter) assert(!GetPermSaveNames(dwIndex, szPerm)); } -bool ReadHero(MpqArchive &archive, PlayerPack *pPack) +bool ReadHero(SaveReader &archive, PlayerPack *pPack) { size_t read; @@ -121,7 +138,7 @@ bool ReadHero(MpqArchive &archive, PlayerPack *pPack) return ret; } -void EncodeHero(MpqWriter &saveWriter, const PlayerPack *pack) +void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack) { size_t packedLen = codec_get_encoded_len(sizeof(*pack)); std::unique_ptr packed { new byte[packedLen] }; @@ -131,14 +148,14 @@ void EncodeHero(MpqWriter &saveWriter, const PlayerPack *pack) saveWriter.WriteFile("hero", packed.get(), packedLen); } -MpqWriter GetSaveWriter(uint32_t saveNum) +SaveWriter GetSaveWriter(uint32_t saveNum) { - return MpqWriter(GetSavePath(saveNum).c_str()); + return SaveWriter(GetSavePath(saveNum)); } -MpqWriter GetStashWriter() +SaveWriter GetStashWriter() { - return MpqWriter(GetStashSavePath().c_str()); + return SaveWriter(GetStashSavePath()); } #ifndef DISABLE_DEMOMODE @@ -191,7 +208,7 @@ bool GetFileName(uint8_t lvl, char *dst) return false; } -bool ArchiveContainsGame(MpqArchive &hsArchive) +bool ArchiveContainsGame(SaveReader &hsArchive) { if (gbIsMultiplayer) return false; @@ -205,6 +222,18 @@ bool ArchiveContainsGame(MpqArchive &hsArchive) return IsHeaderValid(hdr); } +std::optional CreateSaveReader(std::string &&path) +{ +#ifdef UNPACKED_SAVES + if (!FileExists(path)) + return std::nullopt; + return SaveReader(std::move(path)); +#else + std::int32_t error; + return MpqArchive::Open(path.c_str(), error); +#endif +} + #ifndef DISABLE_DEMOMODE class MemoryBuffer : public std::basic_streambuf { public: @@ -425,9 +454,8 @@ HeroCompareResult CompareSaves(const std::string &actualSavePath, const std::str possibleFileToCheck.push_back({ std::string(szPerm), "level", i == 0 }); } - std::int32_t error; - auto actualArchive = *MpqArchive::Open(actualSavePath.c_str(), error); - auto referenceArchive = *MpqArchive::Open(referenceSavePath.c_str(), error); + SaveReader actualArchive = *CreateSaveReader(std::string(actualSavePath)); + SaveReader referenceArchive = *CreateSaveReader(std::string(referenceSavePath)); bool compareResult = true; std::string message; @@ -466,7 +494,7 @@ HeroCompareResult CompareSaves(const std::string &actualSavePath, const std::str } #endif // !DISABLE_DEMOMODE -void pfile_write_hero(MpqWriter &saveWriter, bool writeGameData) +void pfile_write_hero(SaveWriter &saveWriter, bool writeGameData) { if (writeGameData) { SaveGameData(saveWriter); @@ -485,19 +513,69 @@ void pfile_write_hero(MpqWriter &saveWriter, bool writeGameData) } // namespace -std::optional OpenSaveArchive(uint32_t saveNum) +#ifdef UNPACKED_SAVES +std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) { - std::int32_t error; - return MpqArchive::Open(GetSavePath(saveNum).c_str(), error); + std::unique_ptr result; + error = 0; + const std::string path = dir_ + filename; + uintmax_t size; + if (!GetFileSize(path.c_str(), &size)) { + error = 1; + return nullptr; + } + fileSize = size; + FILE *file = OpenFile(path.c_str(), "rb"); + if (file == nullptr) { + error = 1; + return nullptr; + } + result.reset(new byte[size]); + if (std::fread(result.get(), size, 1, file) != 1) { + std::fclose(file); + error = 1; + return nullptr; + } + std::fclose(file); + return result; } -std::optional OpenStashArchive() +bool SaveWriter::WriteFile(const char *filename, const byte *data, size_t size) { - std::int32_t error; - return MpqArchive::Open(GetStashSavePath().c_str(), error); + const std::string path = dir_ + filename; + FILE *file = OpenFile(path.c_str(), "wb"); + if (file == nullptr) { + return false; + } + if (std::fwrite(data, size, 1, file) != 1) { + std::fclose(file); + return false; + } + std::fclose(file); + return true; +} + +void SaveWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) +{ + char pszFileName[MaxMpqPathSize]; + + for (uint8_t i = 0; fnGetName(i, pszFileName); i++) { + RemoveHashEntry(pszFileName); + } +} +#endif + +std::optional OpenSaveArchive(uint32_t saveNum) +{ + return CreateSaveReader(GetSavePath(saveNum)); +} + +std::optional OpenStashArchive() +{ + return CreateSaveReader(GetStashSavePath()); } -std::unique_ptr ReadArchive(MpqArchive &archive, const char *pszName, size_t *pdwLen) +std::unique_ptr ReadArchive(SaveReader &archive, const char *pszName, size_t *pdwLen) { int32_t error; std::size_t length; @@ -525,7 +603,7 @@ const char *pfile_get_password() void pfile_write_hero(bool writeGameData) { - MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + SaveWriter saveWriter = GetSaveWriter(gSaveNumber); pfile_write_hero(saveWriter, writeGameData); } @@ -534,7 +612,7 @@ void pfile_write_hero_demo(int demo) { std::string savePath = GetSavePath(gSaveNumber, StrCat("demo_", demo, "_reference_")); CopySaveFile(gSaveNumber, savePath); - auto saveWriter = MpqWriter(savePath.c_str()); + auto saveWriter = SaveWriter(savePath.c_str()); pfile_write_hero(saveWriter, true); } @@ -548,7 +626,7 @@ HeroCompareResult pfile_compare_hero_demo(int demo, bool logDetails) std::string actualSavePath = GetSavePath(gSaveNumber, StrCat("demo_", demo, "_actual_")); { CopySaveFile(gSaveNumber, actualSavePath); - MpqWriter saveWriter(actualSavePath.c_str()); + SaveWriter saveWriter(actualSavePath.c_str()); pfile_write_hero(saveWriter, true); } @@ -561,7 +639,7 @@ void sfile_write_stash() if (!Stash.dirty) return; - MpqWriter stashWriter = GetStashWriter(); + SaveWriter stashWriter = GetStashWriter(); SaveStash(stashWriter); @@ -573,7 +651,7 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) memset(hero_names, 0, sizeof(hero_names)); for (uint32_t i = 0; i < MAX_CHARACTERS; i++) { - std::optional archive = OpenSaveArchive(i); + std::optional archive = OpenSaveArchive(i); if (archive) { PlayerPack pkplr; if (ReadHero(*archive, &pkplr)) { @@ -632,7 +710,7 @@ bool pfile_ui_save_create(_uiheroinfo *heroinfo) giNumberOfLevels = gbIsHellfire ? 25 : 17; - MpqWriter saveWriter = GetSaveWriter(saveNum); + SaveWriter saveWriter = GetSaveWriter(saveNum); saveWriter.RemoveHashEntries(GetFileName); CopyUtf8(hero_names[saveNum], heroinfo->name, sizeof(hero_names[saveNum])); @@ -666,7 +744,7 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) PlayerPack pkplr; { - std::optional archive = OpenSaveArchive(saveNum); + std::optional archive = OpenSaveArchive(saveNum); if (!archive) app_fatal(_("Unable to open archive")); if (!ReadHero(*archive, &pkplr)) @@ -688,13 +766,13 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) void pfile_save_level() { - MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + SaveWriter saveWriter = GetSaveWriter(gSaveNumber); SaveLevel(saveWriter); } void pfile_convert_levels() { - MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + SaveWriter saveWriter = GetSaveWriter(gSaveNumber); ConvertLevels(saveWriter); } @@ -703,7 +781,7 @@ void pfile_remove_temp_files() if (gbIsMultiplayer) return; - MpqWriter saveWriter = GetSaveWriter(gSaveNumber); + SaveWriter saveWriter = GetSaveWriter(gSaveNumber); saveWriter.RemoveHashEntries(GetTempSaveNames); } diff --git a/Source/pfile.h b/Source/pfile.h index d02af3f7c..82c28ec2b 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -8,12 +8,76 @@ #include "DiabloUI/diabloui.h" #include "player.h" +#ifdef UNPACKED_SAVES +#include "utils/file_util.h" +#else +#include "mpq/mpq_reader.hpp" +#include "mpq/mpq_writer.hpp" +#endif + namespace devilution { #define MAX_CHARACTERS 99 extern bool gbValidSaveFile; +#ifdef UNPACKED_SAVES +struct SaveReader { + explicit SaveReader(std::string &&dir) + : dir_(std::move(dir)) + { + } + + const std::string &dir() const + { + return dir_; + } + + std::unique_ptr ReadFile(const char *filename, std::size_t &fileSize, int32_t &error); + + bool HasFile(const char *path) + { + return ::devilution::FileExists((dir_ + path).c_str()); + } + +private: + std::string dir_; +}; + +struct SaveWriter { + explicit SaveWriter(std::string &&dir) + : dir_(std::move(dir)) + { + } + + bool WriteFile(const char *filename, const byte *data, size_t size); + + bool HasFile(const char *path) + { + return ::devilution::FileExists((dir_ + path).c_str()); + } + + void RenameFile(const char *from, const char *to) + { + ::devilution::RenameFile((dir_ + from).c_str(), (dir_ + to).c_str()); + } + + void RemoveHashEntry(const char *path) + { + RemoveFile((dir_ + path).c_str()); + } + + void RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)); + +private: + std::string dir_; +}; + +#else +using SaveReader = MpqArchive; +using SaveWriter = MpqWriter; +#endif + /** * @brief Comparsion result of pfile_compare_hero_demo */ @@ -27,10 +91,10 @@ struct HeroCompareResult { std::string message; }; -std::optional OpenSaveArchive(uint32_t saveNum); -std::optional OpenStashArchive(); +std::optional OpenSaveArchive(uint32_t saveNum); +std::optional OpenStashArchive(); const char *pfile_get_password(); -std::unique_ptr ReadArchive(MpqArchive &archive, const char *pszName, size_t *pdwLen = nullptr); +std::unique_ptr ReadArchive(SaveReader &archive, const char *pszName, size_t *pdwLen = nullptr); void pfile_write_hero(bool writeGameData = false); #ifndef DISABLE_DEMOMODE diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index 6565e6667..72a4c531f 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -6,6 +6,7 @@ #include #include "utils/log.hpp" +#include "utils/stdcompat/filesystem.hpp" #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -74,6 +75,9 @@ bool FileExists(const char *path) return true; #elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) return ::access(path, F_OK) == 0; +#elif defined(DVL_HAS_FILESYSTEM) + std::error_code ec; + return std::filesystem::exists(path, ec); #else SDL_RWops *file = SDL_RWFromFile(path, "r+b"); if (file == nullptr) @@ -185,6 +189,26 @@ bool ResizeFile(const char *path, std::uintmax_t size) #endif } +void RenameFile(const char *from, const char *to) +{ +#if defined(NXDK) + ::MoveFile(from, to); +#elif defined(_WIN64) || defined(_WIN32) + const auto fromUtf16 = ToWideChar(from); + const auto toUtf16 = ToWideChar(to); + if (fromUtf16 == nullptr || toUtf16 == nullptr) { + LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError()); + return; + } + ::MoveFileW(&fromUtf16[0], &toUtf16[0]); +#elif defined(DVL_HAS_FILESYSTEM) + std::error_code ec; + return std::filesystem::rename(from, to); +#else + ::rename(from, to); +#endif +} + void RemoveFile(const char *path) { #if defined(NXDK) @@ -204,9 +228,9 @@ void RemoveFile(const char *path) fclose(f); remove(name.c_str()); f = nullptr; - Log("Removed file: {}", name); + LogVerbose("Removed file: {}", name); } else { - Log("Failed to remove file: {}", name); + LogVerbose("Failed to remove file: {}", name); } #endif } diff --git a/Source/utils/file_util.h b/Source/utils/file_util.h index 0fe7e15cb..d25e14e51 100644 --- a/Source/utils/file_util.h +++ b/Source/utils/file_util.h @@ -21,6 +21,7 @@ inline bool FileExists(const std::string &str) bool FileExistsAndIsWriteable(const char *path); bool GetFileSize(const char *path, std::uintmax_t *size); bool ResizeFile(const char *path, std::uintmax_t size); +void RenameFile(const char *from, const char *to); void RemoveFile(const char *path); std::optional CreateFileStream(const char *path, std::ios::openmode mode); FILE *OpenFile(const char *path, const char *mode); diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp index 823b4d867..e80343635 100644 --- a/Source/utils/paths.cpp +++ b/Source/utils/paths.cpp @@ -21,12 +21,6 @@ #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 { diff --git a/Source/utils/paths.h b/Source/utils/paths.h index 98739f0ab..406104f5e 100644 --- a/Source/utils/paths.h +++ b/Source/utils/paths.h @@ -8,8 +8,10 @@ namespace devilution { #ifdef _WIN32 constexpr char DirectorySeparator = '\\'; +#define DIRECTORY_SEPARATOR_STR "\\" #else constexpr char DirectorySeparator = '/'; +#define DIRECTORY_SEPARATOR_STR "/" #endif namespace paths {