Skip to content

Commit

Permalink
Merge pull request #18610 from hrydgard/texture-replacer-no-ini-fix
Browse files Browse the repository at this point in the history
Texture replacer: Fix for texture directories missing an ini file
  • Loading branch information
hrydgard committed Dec 25, 2023
2 parents fe96d15 + faef4aa commit a9a670f
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 52 deletions.
118 changes: 66 additions & 52 deletions GPU/Common/TextureReplacer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,16 @@ bool TextureReplacer::LoadIni() {
return false;
} else {
WARN_LOG(G3D, "Texture pack lacking ini file: %s", basePath_.c_str());
// Do what we can do anyway: Scan for textures and build the map.
std::map<ReplacementCacheKey, std::map<int, std::string>> filenameMap;
ScanForHashNamedFiles(dir, filenameMap);
ComputeAliasMap(filenameMap);
}
}

auto gr = GetI18NCategory(I18NCat::GRAPHICS);
g_OSD.Show(OSDType::MESSAGE_SUCCESS, gr->T("Texture replacement pack activated"), 2.0f);

vfs_ = dir;

// If we have stuff loaded from before, need to update the vfs pointers to avoid
Expand All @@ -198,6 +205,60 @@ bool TextureReplacer::LoadIni() {
return true;
}

void TextureReplacer::ScanForHashNamedFiles(VFSBackend *dir, std::map<ReplacementCacheKey, std::map<int, std::string>> &filenameMap) {
// Scan the root of the texture folder/zip and preinitialize the hash map.
std::vector<File::FileInfo> filesInRoot;
dir->GetFileListing("", &filesInRoot, nullptr);
for (auto file : filesInRoot) {
if (file.isDirectory)
continue;
if (file.name.empty() || file.name[0] == '.')
continue;
Path path(file.name);
std::string ext = path.GetFileExtension();

std::string hash = file.name.substr(0, file.name.size() - ext.size());
if (!((hash.size() >= 26 && hash.size() <= 27 && hash[24] == '_') || hash.size() == 24)) {
continue;
}
// OK, it's hash-like enough to try to parse it into the map.
if (equalsNoCase(ext, ".ktx2") || equalsNoCase(ext, ".png") || equalsNoCase(ext, ".dds") || equalsNoCase(ext, ".zim")) {
ReplacementCacheKey key(0, 0);
int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. sscanf doesn't write to non-matched outputs.
if (sscanf(hash.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) {
// INFO_LOG(G3D, "hash-like file in root, adding: %s", file.name.c_str());
filenameMap[key][level] = file.name;
}
}
}
}

void TextureReplacer::ComputeAliasMap(const std::map<ReplacementCacheKey, std::map<int, std::string>> &filenameMap) {
for (auto &pair : filenameMap) {
std::string alias;
int mipIndex = 0;
for (auto &level : pair.second) {
if (level.first == mipIndex) {
alias += level.second + "|";
mipIndex++;
} else {
WARN_LOG(G3D, "Non-sequential mip index %d, breaking. filenames=%s", level.first, level.second.c_str());
break;
}
}
if (alias == "|") {
alias = ""; // marker for no replacement
}
// Replace any '\' with '/', to be safe and consistent. Since these are from the ini file, we do this on all platforms.
for (auto &c : alias) {
if (c == '\\') {
c = '/';
}
}
aliases_[pair.first] = alias;
}
}

bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverride) {
auto options = ini.GetOrCreateSection("options");
std::string hash;
Expand Down Expand Up @@ -236,36 +297,13 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri
int badFileNameCount = 0;

std::map<ReplacementCacheKey, std::map<int, std::string>> filenameMap;
std::string badFilenames;

// Scan the root of the texture folder/zip and preinitialize the hash map.
std::vector<File::FileInfo> filesInRoot;
if (dir) {
dir->GetFileListing("", &filesInRoot, nullptr);
for (auto file : filesInRoot) {
if (file.isDirectory)
continue;
if (file.name.empty() || file.name[0] == '.')
continue;
Path path(file.name);
std::string ext = path.GetFileExtension();

std::string hash = file.name.substr(0, file.name.size() - ext.size());
if (!((hash.size() >= 26 && hash.size() <= 27 && hash[24] == '_') || hash.size() == 24)) {
continue;
}
// OK, it's hash-like enough to try to parse it into the map.
if (equalsNoCase(ext, ".ktx2") || equalsNoCase(ext, ".png") || equalsNoCase(ext, ".dds") || equalsNoCase(ext, ".zim")) {
ReplacementCacheKey key(0, 0);
int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. sscanf doesn't write to non-matched outputs.
if (sscanf(hash.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) {
// INFO_LOG(G3D, "hash-like file in root, adding: %s", file.name.c_str());
filenameMap[key][level] = file.name;
}
}
}
ScanForHashNamedFiles(dir, filenameMap);
}

std::string badFilenames;

if (ini.HasSection("hashes")) {
auto hashes = ini.GetOrCreateSection("hashes")->ToMap();
// Format: hashname = filename.png
Expand Down Expand Up @@ -307,29 +345,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri
}

// Now, translate the filenameMap to the final aliasMap.
for (auto &pair : filenameMap) {
std::string alias;
int mipIndex = 0;
for (auto &level : pair.second) {
if (level.first == mipIndex) {
alias += level.second + "|";
mipIndex++;
} else {
WARN_LOG(G3D, "Non-sequential mip index %d, breaking. filenames=%s", level.first, level.second.c_str());
break;
}
}
if (alias == "|") {
alias = ""; // marker for no replacement
}
// Replace any '\' with '/', to be safe and consistent. Since these are from the ini file, we do this on all platforms.
for (auto &c : alias) {
if (c == '\\') {
c = '/';
}
}
aliases_[pair.first] = alias;
}
ComputeAliasMap(filenameMap);

if (badFileNameCount > 0) {
auto err = GetI18NCategory(I18NCat::ERRORS);
Expand Down Expand Up @@ -361,9 +377,6 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri
}
}

auto gr = GetI18NCategory(I18NCat::GRAPHICS);

g_OSD.Show(OSDType::MESSAGE_SUCCESS, gr->T("Texture replacement pack activated"), 2.0f);
return true;
}

Expand Down Expand Up @@ -580,6 +593,7 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w,
}
desc.logId = desc.filenames[0];
desc.hashfiles = desc.filenames[0]; // The generated filename of the top level is used as the key in the data cache.
// TODO: here `hashfiles` is set to an empty string, breaking stuff below.
} else {
desc.logId = hashfiles;
SplitString(hashfiles, '|', desc.filenames);
Expand Down
5 changes: 5 additions & 0 deletions GPU/Common/TextureReplacer.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
#pragma once

#include "ppsspp_config.h"

#include <mutex>
#include <string>
#include <unordered_map>
#include <map>
#include <vector>

#include "Common/CommonFuncs.h"
Expand Down Expand Up @@ -135,6 +137,9 @@ class TextureReplacer {
float LookupReduceHashRange(int w, int h);
std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundAlias, bool *ignored);

void ScanForHashNamedFiles(VFSBackend *dir, std::map<ReplacementCacheKey, std::map<int, std::string>> &filenameMap);
void ComputeAliasMap(const std::map<ReplacementCacheKey, std::map<int, std::string>> &filenameMap);

bool enabled_ = false;
bool allowVideo_ = false;
bool ignoreAddress_ = false;
Expand Down

0 comments on commit a9a670f

Please sign in to comment.