From 9feb3139e2af3bc7dfcaa922f9139ee89aeb708e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Mon, 29 Aug 2022 22:01:14 +0900 Subject: [PATCH] Add `XDG_DATA_DIRS` to MPQ search paths Fixes #5304 --- Source/init.cpp | 18 ++++++- Source/utils/str_split.hpp | 98 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 Source/utils/str_split.hpp diff --git a/Source/init.cpp b/Source/init.cpp index 3f3be7c15..ebf2115e8 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -23,6 +23,7 @@ #include "utils/language.h" #include "utils/log.hpp" #include "utils/paths.h" +#include "utils/str_split.hpp" #include "utils/ui_fwd.h" #include "utils/utf8.hpp" @@ -93,8 +94,21 @@ std::vector GetMPQSearchPaths() paths.pop_back(); #if defined(__unix__) && !defined(__ANDROID__) - paths.emplace_back("/usr/share/diasurgical/devilutionx/"); - paths.emplace_back("/usr/local/share/diasurgical/devilutionx/"); + // `XDG_DATA_HOME` is usually the root path of `paths::PrefPath()`, so we only + // add `XDG_DATA_DIRS`. + const char *xdgDataDirs = std::getenv("XDG_DATA_DIRS"); + if (xdgDataDirs != nullptr) { + for (const string_view path : SplitByChar(xdgDataDirs, ':')) { + std::string fullPath(path); + if (!path.empty() && path.back() != '/') + fullPath += '/'; + fullPath.append("diasurgical/devilutionx/"); + paths.push_back(std::move(fullPath)); + } + } else { + paths.emplace_back("/usr/local/share/diasurgical/devilutionx/"); + paths.emplace_back("/usr/share/diasurgical/devilutionx/"); + } #elif defined(NXDK) paths.emplace_back("D:\\"); #elif (defined(_WIN64) || defined(_WIN32)) && !defined(__UWP__) && !defined(NXDK) diff --git a/Source/utils/str_split.hpp b/Source/utils/str_split.hpp new file mode 100644 index 000000000..d56d55090 --- /dev/null +++ b/Source/utils/str_split.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +#include "utils/stdcompat/string_view.hpp" + +namespace devilution { +class SplitByCharIterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = string_view; + using reference = std::add_lvalue_reference::type; + using pointer = std::add_pointer::type; + + static SplitByCharIterator begin(string_view text, char split_by) // NOLINT(readability-identifier-naming) + { + return SplitByCharIterator(split_by, text, text.substr(0, text.find(split_by))); + } + + static SplitByCharIterator end(string_view text, char split_by) // NOLINT(readability-identifier-naming) + { + return SplitByCharIterator(split_by, text, text.substr(text.size())); + } + + [[nodiscard]] string_view operator*() const + { + return slice_; + } + + [[nodiscard]] const string_view *operator->() const + { + return &slice_; + } + + SplitByCharIterator &operator++() + { + slice_ = text_.substr(slice_.data() - text_.data() + slice_.size()); + if (!slice_.empty()) + slice_.remove_prefix(1); // skip the split_by char + slice_ = slice_.substr(0, slice_.find(split_by_)); + return *this; + } + + SplitByCharIterator operator++(int) + { + auto copy = *this; + ++(*this); + return copy; + } + + bool operator==(const SplitByCharIterator &rhs) const + { + return slice_.data() == rhs.slice_.data(); + } + + bool operator!=(const SplitByCharIterator &rhs) const + { + return !(*this == rhs); + } + +private: + SplitByCharIterator(char split_by, string_view text, string_view slice) + : split_by_(split_by) + , text_(text) + , slice_(slice) + { + } + + const char split_by_; + const string_view text_; + string_view slice_; +}; + +class SplitByChar { +public: + explicit SplitByChar(string_view text, char split_by) + : text_(text) + , split_by_(split_by) + { + } + + [[nodiscard]] SplitByCharIterator begin() const // NOLINT(readability-identifier-naming) + { + return SplitByCharIterator::begin(text_, split_by_); + } + + [[nodiscard]] SplitByCharIterator end() const // NOLINT(readability-identifier-naming) + { + return SplitByCharIterator::end(text_, split_by_); + } + +private: + const string_view text_; + const char split_by_; +}; + +} // namespace devilution