#include "whiteout/storages/mpq/storage.h"
#include "whiteout/utils/mpq_file_system.h"
#include <algorithm>
#include <cctype>
#include <set>
#include <string>
namespace whiteout::utils {
namespace {
std::string normalizePath(const std::string& path) {
std::string result = path;
for (char& c : result) {
if (c == '/')
c = '\\';
else
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
return result;
}
std::string stripTrailingSep(std::string path) {
if (!path.empty() && path.back() == '\\')
path.pop_back();
return path;
}
}
struct MpqFileSystem::Impl {
storages::mpq::Storage& storage;
explicit Impl(storages::mpq::Storage& s) : storage(s) {}
std::string resolve(const std::string& path) const {
return stripTrailingSep(normalizePath(path));
}
};
MpqFileSystem::MpqFileSystem(storages::mpq::Storage& storage)
: m_impl(std::make_unique<Impl>(storage)) {}
MpqFileSystem::~MpqFileSystem() = default;
MpqFileSystem::MpqFileSystem(MpqFileSystem&&) noexcept = default;
MpqFileSystem& MpqFileSystem::operator=(MpqFileSystem&&) noexcept = default;
std::vector<u8> MpqFileSystem::readFile(const std::string& path) const {
auto result = m_impl->storage.readFile(m_impl->resolve(path));
if (!result)
return {};
return std::move(*result);
}
bool MpqFileSystem::writeFile(const std::string& path, const std::vector<u8>& data) {
return m_impl->storage.writeFile(m_impl->resolve(path), std::span<const u8>(data));
}
bool MpqFileSystem::fileExists(const std::string& path) const {
return m_impl->storage.fileExists(m_impl->resolve(path));
}
std::vector<interfaces::DirectoryEntry> MpqFileSystem::listDirectory(
const std::string& path) const {
const std::string normPrefix = stripTrailingSep(normalizePath(path));
const std::string matchPrefix = normPrefix.empty() ? "" : normPrefix + "\\";
std::set<std::string> seen; std::vector<interfaces::DirectoryEntry> entries;
m_impl->storage.enumerate([&](const std::string& name) -> bool {
const std::string normName = normalizePath(name);
if (!matchPrefix.empty()) {
if (normName.size() <= matchPrefix.size())
return true;
if (normName.compare(0, matchPrefix.size(), matchPrefix) != 0)
return true;
}
const std::string rest = normName.substr(matchPrefix.size());
if (rest.empty())
return true;
const size_t sep = rest.find('\\');
const std::string normComponent = (sep == std::string::npos) ? rest : rest.substr(0, sep);
const bool isDir = (sep != std::string::npos);
if (seen.insert(normComponent).second) {
const std::string original = name.substr(matchPrefix.size());
const size_t origSep = original.find_first_of("/\\");
const std::string origComponent =
(origSep == std::string::npos) ? original : original.substr(0, origSep);
entries.push_back({origComponent, isDir});
}
return true; });
return entries;
}
}