diff --git a/CMakeLists.txt b/CMakeLists.txt index 7248aabc7d..7b7e8de656 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 51) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 106) +set(OPENMW_LUA_API_REVISION 107) set(OPENMW_POSTPROCESSING_API_REVISION 4) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/components_tests/misc/testresourcehelpers.cpp b/apps/components_tests/misc/testresourcehelpers.cpp index 95db3ce581..286fb57f7a 100644 --- a/apps/components_tests/misc/testresourcehelpers.cpp +++ b/apps/components_tests/misc/testresourcehelpers.cpp @@ -3,82 +3,172 @@ #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, shouldConvertToForwardSlash) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "SOUND/Foo.MP3", vfs.get(), "mp3"), "sound/foo.mp3"); + } + + TEST(MiscResourceHelpersCorrectResourcePath, shouldHandlePathEqualToDirectory) + { + const std::unique_ptr vfs = TestingOpenMW::createTestVFS({}); + EXPECT_EQ(correctResourcePath({ { "sound" } }, "sound", vfs.get(), "mp3"), "sound/sound"); + } + + 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)); - } } diff --git a/apps/components_tests/vfs/testpathutil.cpp b/apps/components_tests/vfs/testpathutil.cpp index 3819f9905a..36b2ba2347 100644 --- a/apps/components_tests/vfs/testpathutil.cpp +++ b/apps/components_tests/vfs/testpathutil.cpp @@ -10,54 +10,97 @@ namespace VFS::Path { using namespace testing; - TEST(NormalizedTest, shouldSupportDefaultConstructor) + 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; 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 +108,86 @@ 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, 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"); ASSERT_TRUE(value.changeExtension("so")); EXPECT_EQ(value.value(), "foo/bar.so"); } - TEST(NormalizedTest, 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(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); + 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 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 +200,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 +216,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,32 +231,44 @@ 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"); 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/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index e0bed6f8a1..57d27ac021 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -217,7 +217,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/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/lua_ui/resources.cpp b/components/lua_ui/resources.cpp deleted file mode 100644 index 6c98292765..0000000000 --- a/components/lua_ui/resources.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "resources.hpp" - -#include - -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(); - } - - void ResourceManager::clear() - { - mTextures.clear(); - } -} diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp index f2e1753a2c..260e8c473f 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; }; @@ -28,12 +29,18 @@ 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>; - std::unordered_map mTextures; + std::unordered_map mTextures; }; } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index c3164b0dfe..90081e6d81 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -1,6 +1,5 @@ #include "resourcehelpers.hpp" -#include #include #include @@ -26,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) @@ -37,47 +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 = Misc::StringUtils::lowerCase(resPath); - - // Flatten slashes - std::replace(correctedPath.begin(), correctedPath.end(), '/', '\\'); - auto bothSeparators = [](char a, char b) { return a == '\\' && b == '\\'; }; - correctedPath.erase(std::unique(correctedPath.begin(), correctedPath.end(), bothSeparators), correctedPath.end()); - - // Remove leading separator - if (!correctedPath.empty() && correctedPath[0] == '\\') - correctedPath.erase(0, 1); + 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()] == '\\') + 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 } + '\\'; - size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); - if (topLevelPos != std::string::npos) - { - correctedPath.erase(0, topLevelPos + 1); - needsPrefix = false; - break; - } - } } - if (needsPrefix) - correctedPath = std::string{ topLevelDirectories.front() } + '\\' + 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; @@ -87,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 += '\\'; - fallback += Misc::getFileName(origExt); + const VFS::Path::Normalized fallback = withPrefix(origExt.filename(), topLevelDirectories.front()); if (vfs->exists(fallback)) return fallback; } @@ -112,17 +122,17 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span