From 9c6407a1e335a68b38b764b2cf51f6ed582c6834 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 8 Sep 2025 22:46:50 +0200 Subject: [PATCH 1/7] Cleanup resource helpers tests --- .../misc/testresourcehelpers.cpp | 229 ++++++++++++------ 1 file changed, 157 insertions(+), 72 deletions(-) diff --git a/apps/components_tests/misc/testresourcehelpers.cpp b/apps/components_tests/misc/testresourcehelpers.cpp index 95db3ce581..2e02590624 100644 --- a/apps/components_tests/misc/testresourcehelpers.cpp +++ b/apps/components_tests/misc/testresourcehelpers.cpp @@ -3,82 +3,167 @@ #include -namespace +namespace Misc::ResourceHelpers { - using namespace Misc::ResourceHelpers; - TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) - { - constexpr VFS::Path::NormalizedView path("sound/bar.wav"); - std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { path, nullptr } }); - EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/bar.wav"); - } - - TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) - { - constexpr VFS::Path::NormalizedView mp3("sound/foo.mp3"); - std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { mp3, nullptr } }); - constexpr VFS::Path::NormalizedView wav("sound/foo.wav"); - EXPECT_EQ(correctSoundPath(wav, *mVFS), "sound/foo.mp3"); - } - - TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) - { - std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - - { - constexpr VFS::Path::NormalizedView path("sound/foo.wav"); - EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); - } - - auto correctESM4SoundPath = [](auto path, auto* vfs) { - return Misc::ResourceHelpers::correctResourcePath({ { "sound" } }, path, vfs, ".mp3"); - }; - - EXPECT_EQ(correctESM4SoundPath("foo.WAV", mVFS.get()), "sound\\foo.mp3"); - EXPECT_EQ(correctESM4SoundPath("SOUND/foo.WAV", mVFS.get()), "sound\\foo.mp3"); - EXPECT_EQ(correctESM4SoundPath("DATA\\SOUND\\foo.WAV", mVFS.get()), "sound\\foo.mp3"); - EXPECT_EQ(correctESM4SoundPath("\\Data/Sound\\foo.WAV", mVFS.get()), "sound\\foo.mp3"); - } - namespace { - std::string checkChangeExtensionToDds(std::string path) + using namespace ::testing; + + TEST(MiscResourceHelpersCorrectSoundPath, shouldKeepWavExtensionIfExistsInVfs) { - changeExtensionToDds(path); - return path; + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ { path, nullptr } }); + EXPECT_EQ(correctSoundPath(path, *vfs), "sound/foo.wav"); + } + + TEST(MiscResourceHelpersCorrectSoundPath, shouldFallbackToMp3IfWavDoesNotExistInVfs) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *vfs), "sound/foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectSoundPath, shouldKeepWavExtensionIfBothExistsInVfs) + { + constexpr VFS::Path::NormalizedView wav("sound/foo.wav"); + constexpr VFS::Path::NormalizedView mp3("sound/foo.mp3"); + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ + { wav, nullptr }, + { mp3, nullptr }, + }); + EXPECT_EQ(correctSoundPath(wav, *vfs), "sound/foo.wav"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfDoesNotExistInVfs) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound/foo.wav", vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfBothExistInVfs) + { + constexpr VFS::Path::NormalizedView wav("sound/foo.wav"); + constexpr VFS::Path::NormalizedView mp3("sound/foo.mp3"); + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ + { wav, nullptr }, + { mp3, nullptr }, + }); + EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldKeepExtentionIfExistInVfs) + { + constexpr VFS::Path::NormalizedView wav("sound/foo.wav"); + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ + { wav, nullptr }, + }); + EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound\\foo.wav"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldPrefixWithGivenTopDirectory) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "foo.mp3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndKeepExtensionIfOriginalExistInVfs) + { + constexpr VFS::Path::NormalizedView a("textures/foo.a"); + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ + { a, nullptr }, + }); + EXPECT_EQ(correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), + "textures\\foo.a"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndChangeExtensionIfFallbackExistInVfs) + { + constexpr VFS::Path::NormalizedView b("textures/foo.b"); + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ + { b, nullptr }, + }); + EXPECT_EQ(correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), + "textures\\foo.b"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldLowerCase) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveLeadingSlash) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveDuplicateSlashes) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ( + correctResourcePath({ { "sound" } }, "\\\\SOUND\\\\Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldConvertToBackSlash) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + struct MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix : TestWithParam + { + }; + + TEST_P(MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix, shouldMatchExpected) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, GetParam(), vfs.get(), ".mp3"), "sound\\foo.mp3"); + } + + const std::vector pathsWithPrefix = { + "data/sound/foo.mp3", + "data/notsound/sound/foo.mp3", + "data/soundnot/sound/foo.mp3", + "data/notsoundnot/sound/foo.mp3", + }; + + INSTANTIATE_TEST_SUITE_P( + PathsWithPrefix, MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix, ValuesIn(pathsWithPrefix)); + + TEST(MiscResourceHelpersChangeExtensionToDds, original_extension_with_same_size_as_dds) + { + std::string path = "texture/bar.tga"; + ASSERT_TRUE(changeExtensionToDds(path)); + EXPECT_EQ(path, "texture/bar.dds"); + } + + TEST(MiscResourceHelpersChangeExtensionToDds, original_extension_greater_than_dds) + { + std::string path = "texture/bar.jpeg"; + ASSERT_TRUE(changeExtensionToDds(path)); + EXPECT_EQ(path, "texture/bar.dds"); + } + + TEST(MiscResourceHelpersChangeExtensionToDds, original_extension_smaller_than_dds) + { + std::string path = "texture/bar.xx"; + ASSERT_TRUE(changeExtensionToDds(path)); + EXPECT_EQ(path, "texture/bar.dds"); + } + + TEST(MiscResourceHelpersChangeExtensionToDds, does_not_change_dds_extension) + { + std::string path = "texture/bar.dds"; + EXPECT_FALSE(changeExtensionToDds(path)); + EXPECT_EQ(path, "texture/bar.dds"); + } + + TEST(MiscResourceHelpersChangeExtensionToDds, does_not_change_when_no_extension) + { + std::string path = "texture/bar"; + EXPECT_FALSE(changeExtensionToDds(path)); + EXPECT_EQ(path, "texture/bar"); } } - - TEST(ChangeExtensionToDds, original_extension_with_same_size_as_dds) - { - EXPECT_EQ(checkChangeExtensionToDds("texture/bar.tga"), "texture/bar.dds"); - } - - TEST(ChangeExtensionToDds, original_extension_greater_than_dds) - { - EXPECT_EQ(checkChangeExtensionToDds("texture/bar.jpeg"), "texture/bar.dds"); - } - - TEST(ChangeExtensionToDds, original_extension_smaller_than_dds) - { - EXPECT_EQ(checkChangeExtensionToDds("texture/bar.xx"), "texture/bar.dds"); - } - - TEST(ChangeExtensionToDds, does_not_change_dds_extension) - { - std::string path = "texture/bar.dds"; - EXPECT_FALSE(changeExtensionToDds(path)); - } - - TEST(ChangeExtensionToDds, does_not_change_when_no_extension) - { - std::string path = "texture/bar"; - EXPECT_FALSE(changeExtensionToDds(path)); - } - - TEST(ChangeExtensionToDds, change_when_there_is_an_extension) - { - std::string path = "texture/bar.jpeg"; - EXPECT_TRUE(changeExtensionToDds(path)); - } } From a79e121df6ba517f694a4e7ca2677872c216e4ed Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Sep 2025 15:48:17 +0200 Subject: [PATCH 2/7] Use normalizeFilename in correctResourcePath --- CMakeLists.txt | 2 +- .../misc/testresourcehelpers.cpp | 29 +++++++++---------- components/misc/resourcehelpers.cpp | 19 ++++++------ 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 834668e92a..63ed9edfe1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 95) +set(OPENMW_LUA_API_REVISION 96) set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/components_tests/misc/testresourcehelpers.cpp b/apps/components_tests/misc/testresourcehelpers.cpp index 2e02590624..4b6c544038 100644 --- a/apps/components_tests/misc/testresourcehelpers.cpp +++ b/apps/components_tests/misc/testresourcehelpers.cpp @@ -37,7 +37,7 @@ namespace Misc::ResourceHelpers TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfDoesNotExistInVfs) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound/foo.wav", vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound/foo.wav", vfs.get(), ".mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfBothExistInVfs) @@ -48,7 +48,7 @@ namespace Misc::ResourceHelpers { wav, nullptr }, { mp3, nullptr }, }); - EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldKeepExtentionIfExistInVfs) @@ -57,13 +57,13 @@ namespace Misc::ResourceHelpers const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ { wav, nullptr }, }); - EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound\\foo.wav"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound/foo.wav"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldPrefixWithGivenTopDirectory) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "foo.mp3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "foo.mp3", vfs.get(), ".mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndKeepExtensionIfOriginalExistInVfs) @@ -72,8 +72,8 @@ namespace Misc::ResourceHelpers const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ { a, nullptr }, }); - EXPECT_EQ(correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), - "textures\\foo.a"); + EXPECT_EQ( + correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), "textures/foo.a"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndChangeExtensionIfFallbackExistInVfs) @@ -82,33 +82,32 @@ namespace Misc::ResourceHelpers const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ { b, nullptr }, }); - EXPECT_EQ(correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), - "textures\\foo.b"); + EXPECT_EQ( + correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), "textures/foo.b"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldLowerCase) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveLeadingSlash) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveDuplicateSlashes) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ( - correctResourcePath({ { "sound" } }, "\\\\SOUND\\\\Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\\\SOUND\\\\Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); } - TEST(MiscResourceHelpersCorrectResourcePath, shouldConvertToBackSlash) + TEST(MiscResourceHelpersCorrectResourcePath, shouldConvertToForwardSlash) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); } struct MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix : TestWithParam @@ -118,7 +117,7 @@ namespace Misc::ResourceHelpers TEST_P(MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix, shouldMatchExpected) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, GetParam(), vfs.get(), ".mp3"), "sound\\foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, GetParam(), vfs.get(), ".mp3"), "sound/foo.mp3"); } const std::vector pathsWithPrefix = { diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index c3164b0dfe..9b0e81dc59 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -37,15 +37,14 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) std::string Misc::ResourceHelpers::correctResourcePath(std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs, std::string_view ext) { - std::string correctedPath = Misc::StringUtils::lowerCase(resPath); + std::string correctedPath = VFS::Path::normalizeFilename(resPath); // Flatten slashes - std::replace(correctedPath.begin(), correctedPath.end(), '/', '\\'); - auto bothSeparators = [](char a, char b) { return a == '\\' && b == '\\'; }; + auto bothSeparators = [](char a, char b) { return a == VFS::Path::separator && b == VFS::Path::separator; }; correctedPath.erase(std::unique(correctedPath.begin(), correctedPath.end(), bothSeparators), correctedPath.end()); // Remove leading separator - if (!correctedPath.empty() && correctedPath[0] == '\\') + if (!correctedPath.empty() && correctedPath[0] == VFS::Path::separator) correctedPath.erase(0, 1); // Handle top level directory @@ -54,15 +53,15 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span potentialTopLevelDirectory.size() - && correctedPath[potentialTopLevelDirectory.size()] == '\\') + && correctedPath[potentialTopLevelDirectory.size()] == VFS::Path::separator) { needsPrefix = false; break; } else { - std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + '\\'; - size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); + std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + VFS::Path::separator; + size_t topLevelPos = correctedPath.find(VFS::Path::separator + topLevelPrefix); if (topLevelPos != std::string::npos) { correctedPath.erase(0, topLevelPos + 1); @@ -72,7 +71,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::spanexists(fallback)) @@ -97,7 +96,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::spanexists(fallback)) return fallback; From bc219b6a1ebf78319f8fdf6d179bf83f88b4467e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Sep 2025 15:54:57 +0200 Subject: [PATCH 3/7] Use normalized path in LuaUi::TextureData --- apps/openmw/mwlua/uibindings.cpp | 2 +- components/lua_ui/resources.cpp | 2 -- components/lua_ui/resources.hpp | 7 ++++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 7c708fefa9..fe44a107f0 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -231,7 +231,7 @@ namespace MWLua LuaUi::TextureData data; sol::object path = LuaUtil::getFieldOrNil(options, "path"); if (path.is()) - data.mPath = path.as(); + data.mPath = VFS::Path::Normalized(path.as()); if (data.mPath.empty()) throw std::logic_error("Invalid texture path"); sol::object offset = LuaUtil::getFieldOrNil(options, "offset"); diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp index 6c98292765..2bd26a4f63 100644 --- a/components/lua_ui/resources.cpp +++ b/components/lua_ui/resources.cpp @@ -6,8 +6,6 @@ namespace LuaUi { std::shared_ptr ResourceManager::registerTexture(TextureData data) { - VFS::Path::normalizeFilenameInPlace(data.mPath); - TextureResources& list = mTextures[data.mPath]; list.push_back(std::make_shared(data)); return list.back(); diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp index f2e1753a2c..a472f954db 100644 --- a/components/lua_ui/resources.hpp +++ b/components/lua_ui/resources.hpp @@ -2,12 +2,13 @@ #define OPENMW_LUAUI_RESOURCES #include -#include #include #include #include +#include + namespace VFS { class Manager; @@ -17,7 +18,7 @@ namespace LuaUi { struct TextureData { - std::string mPath; + VFS::Path::Normalized mPath; osg::Vec2f mOffset; osg::Vec2f mSize; }; @@ -33,7 +34,7 @@ namespace LuaUi private: using TextureResources = std::vector>; - std::unordered_map mTextures; + std::unordered_map mTextures; }; } From 7982751cbb92b69ddd5745a6171b1b0565c1865d Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Sep 2025 15:56:34 +0200 Subject: [PATCH 4/7] Move code from components/lua_ui/resources.cpp to header --- components/lua_ui/resources.cpp | 18 ------------------ components/lua_ui/resources.hpp | 10 ++++++++-- 2 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 components/lua_ui/resources.cpp diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp deleted file mode 100644 index 2bd26a4f63..0000000000 --- a/components/lua_ui/resources.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "resources.hpp" - -#include - -namespace LuaUi -{ - std::shared_ptr ResourceManager::registerTexture(TextureData data) - { - TextureResources& list = mTextures[data.mPath]; - list.push_back(std::make_shared(data)); - return list.back(); - } - - void ResourceManager::clear() - { - mTextures.clear(); - } -} diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp index a472f954db..260e8c473f 100644 --- a/components/lua_ui/resources.hpp +++ b/components/lua_ui/resources.hpp @@ -29,8 +29,14 @@ namespace LuaUi class ResourceManager { public: - std::shared_ptr registerTexture(TextureData data); - void clear(); + std::shared_ptr registerTexture(TextureData data) + { + TextureResources& list = mTextures[data.mPath]; + list.push_back(std::make_shared(std::move(data))); + return list.back(); + } + + void clear() { mTextures.clear(); } private: using TextureResources = std::vector>; From e9997f8d35bb425d50a0fcd3879f952faf526180 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Sep 2025 17:54:06 +0200 Subject: [PATCH 5/7] Add namespace prefix to all VFS::Path related tests --- apps/components_tests/vfs/testpathutil.cpp | 52 +++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/components_tests/vfs/testpathutil.cpp b/apps/components_tests/vfs/testpathutil.cpp index 3819f9905a..771036d60c 100644 --- a/apps/components_tests/vfs/testpathutil.cpp +++ b/apps/components_tests/vfs/testpathutil.cpp @@ -10,54 +10,54 @@ namespace VFS::Path { using namespace testing; - TEST(NormalizedTest, shouldSupportDefaultConstructor) + TEST(VFSPathNormalizedTest, shouldSupportDefaultConstructor) { const Normalized value; EXPECT_EQ(value.value(), ""); } - TEST(NormalizedTest, shouldSupportConstructorFromString) + TEST(VFSPathNormalizedTest, shouldSupportConstructorFromString) { const std::string string("Foo\\Bar/baz"); const Normalized value(string); EXPECT_EQ(value.value(), "foo/bar/baz"); } - TEST(NormalizedTest, shouldSupportConstructorFromConstCharPtr) + TEST(VFSPathNormalizedTest, shouldSupportConstructorFromConstCharPtr) { const char* const ptr = "Foo\\Bar/baz"; const Normalized value(ptr); EXPECT_EQ(value.value(), "foo/bar/baz"); } - TEST(NormalizedTest, shouldSupportConstructorFromStringView) + TEST(VFSPathNormalizedTest, shouldSupportConstructorFromStringView) { const std::string_view view = "Foo\\Bar/baz"; const Normalized value(view); EXPECT_EQ(value.view(), "foo/bar/baz"); } - TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) + TEST(VFSPathNormalizedTest, shouldSupportConstructorFromNormalizedView) { const NormalizedView view("foo/bar/baz"); const Normalized value(view); EXPECT_EQ(value.view(), "foo/bar/baz"); } - TEST(NormalizedTest, supportMovingValueOut) + TEST(VFSPathNormalizedTest, supportMovingValueOut) { Normalized value("Foo\\Bar/baz"); EXPECT_EQ(std::move(value).value(), "foo/bar/baz"); EXPECT_EQ(value.value(), ""); } - TEST(NormalizedTest, isNotEqualToNotNormalized) + TEST(VFSPathNormalizedTest, isNotEqualToNotNormalized) { const Normalized value("Foo\\Bar/baz"); EXPECT_NE(value.value(), "Foo\\Bar/baz"); } - TEST(NormalizedTest, shouldSupportOperatorLeftShiftToOStream) + TEST(VFSPathNormalizedTest, shouldSupportOperatorLeftShiftToOStream) { const Normalized value("Foo\\Bar/baz"); std::stringstream stream; @@ -65,68 +65,68 @@ namespace VFS::Path EXPECT_EQ(stream.str(), "foo/bar/baz"); } - TEST(NormalizedTest, shouldSupportOperatorDivEqual) + TEST(VFSPathNormalizedTest, shouldSupportOperatorDivEqual) { Normalized value("foo/bar"); value /= NormalizedView("baz"); EXPECT_EQ(value.value(), "foo/bar/baz"); } - TEST(NormalizedTest, shouldSupportOperatorDivEqualWithStringView) + TEST(VFSPathNormalizedTest, shouldSupportOperatorDivEqualWithStringView) { Normalized value("foo/bar"); value /= std::string_view("BAZ"); EXPECT_EQ(value.value(), "foo/bar/baz"); } - TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot) + TEST(VFSPathNormalizedTest, changeExtensionShouldReplaceAfterLastDot) { Normalized value("foo/bar.a"); ASSERT_TRUE(value.changeExtension("so")); EXPECT_EQ(value.value(), "foo/bar.so"); } - TEST(NormalizedTest, changeExtensionShouldNormalizeExtension) + TEST(VFSPathNormalizedTest, changeExtensionShouldNormalizeExtension) { Normalized value("foo/bar.a"); ASSERT_TRUE(value.changeExtension("SO")); EXPECT_EQ(value.value(), "foo/bar.so"); } - TEST(NormalizedTest, changeExtensionShouldIgnorePathWithoutADot) + TEST(VFSPathNormalizedTest, changeExtensionShouldIgnorePathWithoutADot) { Normalized value("foo/bar"); ASSERT_FALSE(value.changeExtension("so")); EXPECT_EQ(value.value(), "foo/bar"); } - TEST(NormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator) + TEST(VFSPathNormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator) { Normalized value("foo.bar/baz"); ASSERT_FALSE(value.changeExtension("so")); EXPECT_EQ(value.value(), "foo.bar/baz"); } - TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot) + TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot) { Normalized value("foo.a"); EXPECT_THROW(value.changeExtension(".so"), std::invalid_argument); } - TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator) + TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator) { Normalized value("foo.a"); EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument); } template - struct NormalizedOperatorsTest : Test + struct VFSPathNormalizedOperatorsTest : Test { }; - TYPED_TEST_SUITE_P(NormalizedOperatorsTest); + TYPED_TEST_SUITE_P(VFSPathNormalizedOperatorsTest); - TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) + TYPED_TEST_P(VFSPathNormalizedOperatorsTest, supportsEqual) { using Type0 = typename TypeParam::Type0; using Type1 = typename TypeParam::Type1; @@ -139,7 +139,7 @@ namespace VFS::Path EXPECT_NE(otherNotEqual, normalized); } - TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) + TYPED_TEST_P(VFSPathNormalizedOperatorsTest, supportsLess) { using Type0 = typename TypeParam::Type0; using Type1 = typename TypeParam::Type1; @@ -155,7 +155,7 @@ namespace VFS::Path EXPECT_FALSE(otherGreater < normalized); } - REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); + REGISTER_TYPED_TEST_SUITE_P(VFSPathNormalizedOperatorsTest, supportsEqual, supportsLess); template struct TypePair @@ -170,27 +170,27 @@ namespace VFS::Path TypePair, TypePair, TypePair, TypePair>; - INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, TypePairs); + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, VFSPathNormalizedOperatorsTest, TypePairs); - TEST(NormalizedViewTest, shouldSupportConstructorFromNormalized) + TEST(VFSPathNormalizedViewTest, shouldSupportConstructorFromNormalized) { const Normalized value("Foo\\Bar/baz"); const NormalizedView view(value); EXPECT_EQ(view.value(), "foo/bar/baz"); } - TEST(NormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral) + TEST(VFSPathNormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral) { constexpr NormalizedView view("foo/bar/baz"); EXPECT_EQ(view.value(), "foo/bar/baz"); } - TEST(NormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized) + TEST(VFSPathNormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized) { EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); } - TEST(NormalizedView, shouldSupportOperatorDiv) + TEST(VFSPathNormalizedViewTest, shouldSupportOperatorDiv) { const NormalizedView a("foo/bar"); const NormalizedView b("baz"); From 01f5791668480e5959b3f404c9f75a9a27222358 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Sep 2025 17:42:48 +0200 Subject: [PATCH 6/7] Remove duplicated and leading slashes in normalizeFilenameInPlace To be consistent with correctResourcePath. --- apps/components_tests/vfs/testpathutil.cpp | 57 ++++++++++++++++++++-- components/misc/resourcehelpers.cpp | 9 ---- components/vfs/pathutil.hpp | 53 +++++++++++++++++--- 3 files changed, 99 insertions(+), 20 deletions(-) diff --git a/apps/components_tests/vfs/testpathutil.cpp b/apps/components_tests/vfs/testpathutil.cpp index 771036d60c..c30f563a4d 100644 --- a/apps/components_tests/vfs/testpathutil.cpp +++ b/apps/components_tests/vfs/testpathutil.cpp @@ -10,6 +10,49 @@ namespace VFS::Path { using namespace testing; + struct VFSPathIsNormalizedTest : TestWithParam> + { + }; + + TEST_P(VFSPathIsNormalizedTest, shouldReturnExpectedResult) + { + EXPECT_EQ(isNormalized(GetParam().first), GetParam().second); + } + + const std::pair isNormalizedTestParams[] = { + { std::string_view(), true }, + { "foo", true }, + { "foo/bar", true }, + { "foo/bar/baz", true }, + { "/foo", false }, + { "foo//", false }, + { "foo\\", false }, + { "Foo", false }, + }; + + INSTANTIATE_TEST_SUITE_P(IsNormalizedTestParams, VFSPathIsNormalizedTest, ValuesIn(isNormalizedTestParams)); + + TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveLeadingSeparators) + { + std::string value("//foo"); + normalizeFilenameInPlace(value); + EXPECT_EQ(value, "foo"); + } + + TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveDuplicatedSeparators) + { + std::string value("foo//bar///baz"); + normalizeFilenameInPlace(value); + EXPECT_EQ(value, "foo/bar/baz"); + } + + TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveDuplicatedLeadingSeparator) + { + std::string value("//foo"); + normalizeFilenameInPlace(value); + EXPECT_EQ(value, "foo"); + } + TEST(VFSPathNormalizedTest, shouldSupportDefaultConstructor) { const Normalized value; @@ -79,6 +122,13 @@ namespace VFS::Path EXPECT_EQ(value.value(), "foo/bar/baz"); } + TEST(VFSPathNormalizedTest, operatorDivShouldNormalizeSuffix) + { + Normalized value("foo/bar"); + value /= std::string_view("\\A\\\\B"); + EXPECT_EQ(value.value(), "foo/bar/a/b"); + } + TEST(VFSPathNormalizedTest, changeExtensionShouldReplaceAfterLastDot) { Normalized value("foo/bar.a"); @@ -86,11 +136,10 @@ namespace VFS::Path EXPECT_EQ(value.value(), "foo/bar.so"); } - TEST(VFSPathNormalizedTest, changeExtensionShouldNormalizeExtension) + TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnNotNormalizedExtension) { Normalized value("foo/bar.a"); - ASSERT_TRUE(value.changeExtension("SO")); - EXPECT_EQ(value.value(), "foo/bar.so"); + EXPECT_THROW(value.changeExtension("\\SO"), std::invalid_argument); } TEST(VFSPathNormalizedTest, changeExtensionShouldIgnorePathWithoutADot) @@ -116,7 +165,7 @@ namespace VFS::Path TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator) { Normalized value("foo.a"); - EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument); + EXPECT_THROW(value.changeExtension("so/"), std::invalid_argument); } template diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 9b0e81dc59..0a9ac84168 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -1,6 +1,5 @@ #include "resourcehelpers.hpp" -#include #include #include @@ -39,14 +38,6 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span Date: Mon, 8 Sep 2025 23:52:18 +0200 Subject: [PATCH 7/7] Use normalized path to implement correctResourcePath --- .../misc/testresourcehelpers.cpp | 22 ++--- apps/components_tests/vfs/testpathutil.cpp | 24 +++++ apps/openmw/mwsound/soundbuffer.cpp | 4 +- components/misc/resourcehelpers.cpp | 88 ++++++++++++------- components/vfs/pathutil.hpp | 13 +++ 5 files changed, 104 insertions(+), 47 deletions(-) diff --git a/apps/components_tests/misc/testresourcehelpers.cpp b/apps/components_tests/misc/testresourcehelpers.cpp index 4b6c544038..52286354a3 100644 --- a/apps/components_tests/misc/testresourcehelpers.cpp +++ b/apps/components_tests/misc/testresourcehelpers.cpp @@ -37,7 +37,7 @@ namespace Misc::ResourceHelpers TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfDoesNotExistInVfs) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound/foo.wav", vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound/foo.wav", vfs.get(), "mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldFallbackToGivenExtentionIfBothExistInVfs) @@ -48,7 +48,7 @@ namespace Misc::ResourceHelpers { wav, nullptr }, { mp3, nullptr }, }); - EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), "mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldKeepExtentionIfExistInVfs) @@ -57,13 +57,13 @@ namespace Misc::ResourceHelpers const std::unique_ptr vfs = TestingOpenMW::createTestVFS({ { wav, nullptr }, }); - EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), ".mp3"), "sound/foo.wav"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, wav.value(), vfs.get(), "mp3"), "sound/foo.wav"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldPrefixWithGivenTopDirectory) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "foo.mp3", vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "foo.mp3", vfs.get(), "mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndKeepExtensionIfOriginalExistInVfs) @@ -73,7 +73,7 @@ namespace Misc::ResourceHelpers { a, nullptr }, }); EXPECT_EQ( - correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), "textures/foo.a"); + correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), "b"), "textures/foo.a"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldChangeTopDirectoryAndChangeExtensionIfFallbackExistInVfs) @@ -83,31 +83,31 @@ namespace Misc::ResourceHelpers { b, nullptr }, }); EXPECT_EQ( - correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), ".b"), "textures/foo.b"); + correctResourcePath({ { "textures", "bookart" } }, "bookart/foo.a", vfs.get(), "b"), "textures/foo.b"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldLowerCase) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND\\Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveLeadingSlash) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\SOUND\\Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\SOUND\\Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldRemoveDuplicateSlashes) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\\\SOUND\\\\Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "\\\\SOUND\\\\Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3"); } TEST(MiscResourceHelpersCorrectResourcePath, shouldConvertToForwardSlash) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3"); } struct MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix : TestWithParam @@ -117,7 +117,7 @@ namespace Misc::ResourceHelpers TEST_P(MiscResourceHelpersCorrectResourcePathShouldRemoveExtraPrefix, shouldMatchExpected) { const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctResourcePath({ { "sound" } }, GetParam(), vfs.get(), ".mp3"), "sound/foo.mp3"); + EXPECT_EQ(correctResourcePath({ { "sound" } }, GetParam(), vfs.get(), "mp3"), "sound/foo.mp3"); } const std::vector pathsWithPrefix = { diff --git a/apps/components_tests/vfs/testpathutil.cpp b/apps/components_tests/vfs/testpathutil.cpp index c30f563a4d..36b2ba2347 100644 --- a/apps/components_tests/vfs/testpathutil.cpp +++ b/apps/components_tests/vfs/testpathutil.cpp @@ -168,6 +168,18 @@ namespace VFS::Path EXPECT_THROW(value.changeExtension("so/"), std::invalid_argument); } + TEST(VFSPathNormalizedTest, filenameShouldReturnLastComponentOfThePath) + { + const Normalized value("foo/bar"); + EXPECT_EQ(value.filename(), "bar"); + } + + TEST(VFSPathNormalizedTest, filenameShouldReturnSameValueForPathWithSingleComponent) + { + const Normalized value("foo"); + EXPECT_EQ(value.filename(), "foo"); + } + template struct VFSPathNormalizedOperatorsTest : Test { @@ -246,5 +258,17 @@ namespace VFS::Path const Normalized result = a / b; EXPECT_EQ(result.value(), "foo/bar/baz"); } + + TEST(VFSPathNormalizedViewTest, filenameShouldReturnLastComponentOfThePath) + { + const NormalizedView value("foo/bar"); + EXPECT_EQ(value.filename(), "bar"); + } + + TEST(VFSPathNormalizedViewTest, filenameShouldReturnSameValueForPathWithSingleComponent) + { + const NormalizedView value("foo"); + EXPECT_EQ(value.filename(), "foo"); + } } } diff --git a/apps/openmw/mwsound/soundbuffer.cpp b/apps/openmw/mwsound/soundbuffer.cpp index 0c10ba5552..7a1aaba66b 100644 --- a/apps/openmw/mwsound/soundbuffer.cpp +++ b/apps/openmw/mwsound/soundbuffer.cpp @@ -201,7 +201,7 @@ namespace MWSound SoundBuffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM4::Sound& sound) { std::string path = Misc::ResourceHelpers::correctResourcePath( - { { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), ".mp3"); + { { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), "mp3"); float volume = 1, min = 1, max = 255; // TODO: needs research SoundBuffer& sfx = mSoundBuffers.emplace_back(VFS::Path::Normalized(std::move(path)), volume, min, max); mBufferNameMap.emplace(soundId, &sfx); @@ -211,7 +211,7 @@ namespace MWSound SoundBuffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM4::SoundReference& sound) { std::string path = Misc::ResourceHelpers::correctResourcePath( - { { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), ".mp3"); + { { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), "mp3"); float volume = 1, min = 1, max = 255; // TODO: needs research // TODO: sound.mSoundId can link to another SoundReference, probably we will need to add additional lookups to // ESMStore. diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 0a9ac84168..d2d2e05d9d 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -25,6 +25,38 @@ namespace } return false; } + + std::size_t findDirectory(VFS::Path::NormalizedView path, std::string_view directory) + { + const std::string_view pathValue = path.value(); + const std::size_t directorySize = directory.size(); + + for (std::size_t offset = 0, pathSize = pathValue.size(); offset < pathSize;) + { + const std::size_t position = pathValue.find(directory, offset); + + if (position == std::string_view::npos) + return std::string_view::npos; + + if (position + directorySize > pathSize) + return std::string_view::npos; + + if ((position == 0 || pathValue[position - 1] == VFS::Path::separator) + && pathValue[position + directorySize] == VFS::Path::separator) + return position; + + offset = position + directorySize; + } + + return std::string_view::npos; + } + + VFS::Path::Normalized withPrefix(VFS::Path::NormalizedView path, std::string_view prefix) + { + VFS::Path::Normalized prefixed(prefix); + prefixed /= path; + return prefixed; + } } bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) @@ -36,38 +68,29 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) std::string Misc::ResourceHelpers::correctResourcePath(std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs, std::string_view ext) { - std::string correctedPath = VFS::Path::normalizeFilename(resPath); + VFS::Path::Normalized correctedPath(resPath); // Handle top level directory bool needsPrefix = true; - for (std::string_view potentialTopLevelDirectory : topLevelDirectories) + + for (const std::string_view potentialTopLevelDirectory : topLevelDirectories) { - if (correctedPath.starts_with(potentialTopLevelDirectory) - && correctedPath.size() > potentialTopLevelDirectory.size() - && correctedPath[potentialTopLevelDirectory.size()] == VFS::Path::separator) + if (const std::size_t topLevelPos = findDirectory(correctedPath, potentialTopLevelDirectory); + topLevelPos != std::string::npos) { + correctedPath = VFS::Path::Normalized(correctedPath.value().substr(topLevelPos)); needsPrefix = false; break; } - else - { - std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + VFS::Path::separator; - size_t topLevelPos = correctedPath.find(VFS::Path::separator + topLevelPrefix); - if (topLevelPos != std::string::npos) - { - correctedPath.erase(0, topLevelPos + 1); - needsPrefix = false; - break; - } - } } - if (needsPrefix) - correctedPath = std::string{ topLevelDirectories.front() } + VFS::Path::separator + correctedPath; - std::string origExt = correctedPath; + if (needsPrefix) + correctedPath = withPrefix(correctedPath, topLevelDirectories.front()); + + const VFS::Path::Normalized origExt = correctedPath; // replace extension if `ext` is specified (used for .tga -> .dds, .wav -> .mp3) - bool isExtChanged = !ext.empty() && changeExtension(correctedPath, ext); + const bool isExtChanged = !ext.empty() && correctedPath.changeExtension(ext); if (vfs->exists(correctedPath)) return correctedPath; @@ -77,18 +100,15 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::spanexists(fallback)) - return fallback; + { + const VFS::Path::Normalized fallback = withPrefix(correctedPath.filename(), topLevelDirectories.front()); + if (vfs->exists(fallback)) + return fallback; + } if (isExtChanged) { - fallback = topLevelDirectories.front(); - fallback += VFS::Path::separator; - fallback += Misc::getFileName(origExt); + const VFS::Path::Normalized fallback = withPrefix(origExt.filename(), topLevelDirectories.front()); if (vfs->exists(fallback)) return fallback; } @@ -102,17 +122,17 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span