1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-22 12:39:42 +00:00
openmw/components/misc/resourcehelpers.cpp
AnyOldName3 69bb65e47b Allow bookart to be in texutres and texutres to be in bookart.
Rebased to account for upstream normalising slashes to turn forward
slashes into backslashes. This simplifies some conditions that
previously needed to check for both kinds.
2023-12-10 18:24:07 +00:00

259 lines
8.3 KiB
C++

#include "resourcehelpers.hpp"
#include <algorithm>
#include <sstream>
#include <string_view>
#include <components/esm/common.hpp>
#include <components/esm/refid.hpp>
#include <components/misc/pathhelpers.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp>
namespace
{
struct MatchPathSeparator
{
bool operator()(char ch) const { return ch == '\\' || ch == '/'; }
};
std::string getBasename(std::string const& pathname)
{
return std::string(
std::find_if(pathname.rbegin(), pathname.rend(), MatchPathSeparator()).base(), pathname.end());
}
}
bool changeExtension(std::string& path, std::string_view ext)
{
std::string::size_type pos = path.rfind('.');
if (pos != std::string::npos && path.compare(pos, path.length() - pos, ext) != 0)
{
path.replace(pos, path.length(), ext);
return true;
}
return false;
}
bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path)
{
return changeExtension(path, ".dds");
}
std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath,
const VFS::Manager* vfs, const std::vector<std::string_view>& alternativeDirectories)
{
/* Bethesda at some point converted all their BSA
* textures from tga to dds for increased load speed, but all
* texture file name references were kept as .tga.
*/
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);
// Handle top level directory
if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size()
|| correctedPath[topLevelDirectory.size()] != '\\')
{
bool needsPrefix = true;
for (std::string_view alternativeDirectory : alternativeDirectories)
{
if (!correctedPath.starts_with(alternativeDirectory) || correctedPath.size() <= alternativeDirectory.size()
|| correctedPath[alternativeDirectory.size()] != '\\')
{
needsPrefix = false;
break;
}
}
if (needsPrefix)
correctedPath = std::string{ topLevelDirectory } + '\\' + correctedPath;
}
std::string origExt = correctedPath;
// since we know all (GOTY edition or less) textures end
// in .dds, we change the extension
bool changedToDds = changeExtensionToDds(correctedPath);
if (vfs->exists(correctedPath))
return correctedPath;
// if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods)
// verify, and revert if false (this call succeeds quickly, but fails slowly)
if (changedToDds && vfs->exists(origExt))
return origExt;
// fall back to a resource in the top level directory if it exists
std::string fallback{ topLevelDirectory };
fallback += '\\';
fallback += getBasename(correctedPath);
if (vfs->exists(fallback))
return fallback;
if (changedToDds)
{
fallback = topLevelDirectory;
fallback += '\\';
fallback += getBasename(origExt);
if (vfs->exists(fallback))
return fallback;
}
return correctedPath;
}
std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs)
{
return correctResourcePath("textures", resPath, vfs, { "bookart" });
}
std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs)
{
return correctResourcePath("icons", resPath, vfs);
}
std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs)
{
return correctResourcePath("bookart", resPath, vfs, { "textures" });
}
std::string Misc::ResourceHelpers::correctBookartPath(
std::string_view resPath, int width, int height, const VFS::Manager* vfs)
{
std::string image = correctBookartPath(resPath, vfs);
// Apparently a bug with some morrowind versions, they reference the image without the size suffix.
// So if the image isn't found, try appending the size.
if (!vfs->exists(image))
{
std::stringstream str;
str << image.substr(0, image.rfind('.')) << "_" << width << "_" << height << image.substr(image.rfind('.'));
image = Misc::ResourceHelpers::correctBookartPath(str.str(), vfs);
}
return image;
}
std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs)
{
std::string mdlname = resPath;
std::string::size_type p = mdlname.find_last_of("/\\");
if (p != std::string::npos)
mdlname.insert(mdlname.begin() + p + 1, 'x');
else
mdlname.insert(mdlname.begin(), 'x');
std::string kfname = mdlname;
if (Misc::StringUtils::ciEndsWith(kfname, ".nif"))
kfname.replace(kfname.size() - 4, 4, ".kf");
if (!vfs->exists(kfname))
{
return resPath;
}
return mdlname;
}
std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath, const VFS::Manager* vfs)
{
std::string res = "meshes\\";
res.append(resPath);
return res;
}
std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath)
{
return "sound\\" + resPath;
}
std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath)
{
return "music\\" + resPath;
}
std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath)
{
constexpr std::string_view prefix = "meshes";
if (resPath.length() < prefix.size() + 1 || !Misc::StringUtils::ciStartsWith(resPath, prefix)
|| (resPath[prefix.size()] != '/' && resPath[prefix.size()] != '\\'))
{
throw std::runtime_error("Path should start with 'meshes\\'");
}
return resPath.substr(prefix.size() + 1);
}
std::string Misc::ResourceHelpers::correctSoundPath(std::string_view resPath, const VFS::Manager* vfs)
{
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
if (!vfs->exists(resPath))
{
std::string sound{ resPath };
changeExtension(sound, ".mp3");
VFS::Path::normalizeFilenameInPlace(sound);
return sound;
}
return VFS::Path::normalizeFilename(resPath);
}
bool Misc::ResourceHelpers::isHiddenMarker(const ESM::RefId& id)
{
return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker";
}
namespace
{
std::string getLODMeshNameImpl(std::string resPath, const VFS::Manager* vfs, std::string_view pattern)
{
if (auto w = Misc::findExtension(resPath); w != std::string::npos)
resPath.insert(w, pattern);
return VFS::Path::normalizeFilename(resPath);
}
std::string getBestLODMeshName(std::string const& resPath, const VFS::Manager* vfs, std::string_view pattern)
{
if (const auto& result = getLODMeshNameImpl(resPath, vfs, pattern); vfs->exists(result))
return result;
return resPath;
}
}
std::string Misc::ResourceHelpers::getLODMeshName(
int esmVersion, std::string resPath, const VFS::Manager* vfs, unsigned char lod)
{
const std::string distantMeshPattern = [&esmVersion] {
switch (esmVersion)
{
case ESM::VER_120:
case ESM::VER_130:
return "_dist";
case ESM::VER_080:
case ESM::VER_100:
return "_far";
case ESM::VER_094:
case ESM::VER_170:
return "_lod";
default:
return "";
}
}();
for (int l = lod; l >= 0; --l)
{
std::stringstream patern;
patern << distantMeshPattern << "_" << l;
std::string const meshName = getBestLODMeshName(resPath, vfs, patern.str());
if (meshName != resPath)
return meshName;
}
return getBestLODMeshName(resPath, vfs, distantMeshPattern);
}