mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-02 13:01:18 +12:00
1271 lines
37 KiB
C++
1271 lines
37 KiB
C++
#include "Cafe/GraphicPack/GraphicPack2.h"
|
|
#include "config/CemuConfig.h"
|
|
#include "config/ActiveSettings.h"
|
|
#include "openssl/sha.h"
|
|
#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h"
|
|
#include "Cafe/Filesystem/fsc.h"
|
|
#include "boost/algorithm/string.hpp"
|
|
#include "util/helpers/MapAdaptor.h"
|
|
#include "util/helpers/StringParser.h"
|
|
#include "Cafe/HW/Latte/Core/LatteTiming.h"
|
|
#include "util/IniParser/IniParser.h"
|
|
#include "util/helpers/StringHelpers.h"
|
|
#include "Cafe/CafeSystem.h"
|
|
#include <cinttypes>
|
|
|
|
std::vector<GraphicPackPtr> GraphicPack2::s_graphic_packs;
|
|
std::vector<GraphicPackPtr> GraphicPack2::s_active_graphic_packs;
|
|
std::atomic_bool GraphicPack2::s_isReady;
|
|
|
|
#define GP_LEGACY_VERSION (2)
|
|
|
|
void GraphicPack2::LoadGraphicPack(fs::path graphicPackPath)
|
|
{
|
|
fs::path rulesPath = graphicPackPath;
|
|
rulesPath.append("rules.txt");
|
|
std::unique_ptr<FileStream> fs_rules(FileStream::openFile2(rulesPath));
|
|
if (!fs_rules)
|
|
return;
|
|
std::vector<uint8> rulesData;
|
|
fs_rules->extract(rulesData);
|
|
IniParser iniParser(rulesData, _pathToUtf8(rulesPath));
|
|
|
|
if (!iniParser.NextSection())
|
|
{
|
|
cemuLog_log(LogType::Force, "{}: Does not contain any sections", _pathToUtf8(rulesPath));
|
|
return;
|
|
}
|
|
if (!boost::iequals(iniParser.GetCurrentSectionName(), "Definition"))
|
|
{
|
|
cemuLog_log(LogType::Force, "{}: [Definition] must be the first section", _pathToUtf8(rulesPath));
|
|
return;
|
|
}
|
|
|
|
auto option_version = iniParser.FindOption("version");
|
|
if (option_version)
|
|
{
|
|
sint32 versionNum = -1;
|
|
auto [ptr, ec] = std::from_chars(option_version->data(), option_version->data() + option_version->size(), versionNum);
|
|
if (ec != std::errc{})
|
|
{
|
|
cemuLog_log(LogType::Force, "{}: Unable to parse version", _pathToUtf8(rulesPath));
|
|
return;
|
|
}
|
|
if (versionNum > GP_LEGACY_VERSION)
|
|
{
|
|
GraphicPack2::LoadGraphicPack(rulesPath, iniParser);
|
|
return;
|
|
}
|
|
}
|
|
cemuLog_log(LogType::Force, "{}: Outdated graphic pack", _pathToUtf8(rulesPath));
|
|
}
|
|
|
|
void GraphicPack2::LoadAll()
|
|
{
|
|
std::error_code ec;
|
|
fs::path basePath = ActiveSettings::GetUserDataPath("graphicPacks");
|
|
for (fs::recursive_directory_iterator it(basePath, ec); it != end(it); ++it)
|
|
{
|
|
if (!it->is_directory(ec))
|
|
continue;
|
|
fs::path gfxPackPath = it->path();
|
|
if (fs::exists(gfxPackPath / "rules.txt", ec))
|
|
{
|
|
LoadGraphicPack(gfxPackPath);
|
|
it.disable_recursion_pending(); // dont recurse deeper in a gfx pack directory
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GraphicPack2::LoadGraphicPack(const fs::path& rulesPath, IniParser& rules)
|
|
{
|
|
try
|
|
{
|
|
auto gp = std::make_shared<GraphicPack2>(rulesPath, rules);
|
|
|
|
// check if enabled and preset set
|
|
const auto& config_entries = g_config.data().graphic_pack_entries;
|
|
|
|
// legacy absolute path checking for not breaking compatibility
|
|
auto file = gp->GetRulesPath();
|
|
auto it = config_entries.find(file.lexically_normal());
|
|
if (it == config_entries.cend())
|
|
{
|
|
// check for relative path
|
|
it = config_entries.find(_utf8ToPath(gp->GetNormalizedPathString()));
|
|
}
|
|
|
|
if (it != config_entries.cend())
|
|
{
|
|
bool enabled = true;
|
|
for (auto& kv : it->second)
|
|
{
|
|
if (boost::iequals(kv.first, "_disabled"))
|
|
{
|
|
enabled = false;
|
|
continue;
|
|
}
|
|
|
|
gp->SetActivePreset(kv.first, kv.second, false);
|
|
}
|
|
|
|
gp->SetEnabled(enabled);
|
|
}
|
|
|
|
gp->UpdatePresetVisibility();
|
|
gp->ValidatePresetSelections();
|
|
|
|
s_graphic_packs.emplace_back(gp);
|
|
return true;
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GraphicPack2::ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack)
|
|
{
|
|
if (graphic_pack->Activate())
|
|
{
|
|
s_active_graphic_packs.push_back(graphic_pack);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GraphicPack2::DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack)
|
|
{
|
|
if (!graphic_pack->IsActivated())
|
|
return false;
|
|
|
|
const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(),
|
|
[graphic_pack](const GraphicPackPtr& gp)
|
|
{
|
|
return gp->GetNormalizedPathString() == graphic_pack->GetNormalizedPathString();
|
|
}
|
|
);
|
|
|
|
if (it == s_active_graphic_packs.end())
|
|
return false;
|
|
|
|
graphic_pack->Deactivate();
|
|
s_active_graphic_packs.erase(it);
|
|
return true;
|
|
}
|
|
|
|
void GraphicPack2::ActivateForCurrentTitle()
|
|
{
|
|
uint64 titleId = CafeSystem::GetForegroundTitleId();
|
|
// activate graphic packs
|
|
for (const auto& gp : GraphicPack2::GetGraphicPacks())
|
|
{
|
|
if (!gp->IsEnabled())
|
|
continue;
|
|
|
|
if (!gp->ContainsTitleId(titleId))
|
|
continue;
|
|
|
|
if (GraphicPack2::ActivateGraphicPack(gp))
|
|
{
|
|
if (gp->GetPresets().empty())
|
|
{
|
|
cemuLog_log(LogType::Force, "Activate graphic pack: {}", gp->GetVirtualPath());
|
|
}
|
|
else
|
|
{
|
|
std::string logLine;
|
|
logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetVirtualPath()));
|
|
bool isFirst = true;
|
|
for (auto& itr : gp->GetPresets())
|
|
{
|
|
if (!itr->active)
|
|
continue;
|
|
if (isFirst)
|
|
isFirst = false;
|
|
else
|
|
logLine.append(",");
|
|
logLine.append(itr->name);
|
|
}
|
|
logLine.append("]");
|
|
cemuLog_log(LogType::Force, logLine);
|
|
}
|
|
}
|
|
}
|
|
s_isReady = true;
|
|
}
|
|
|
|
void GraphicPack2::Reset()
|
|
{
|
|
s_active_graphic_packs.clear();
|
|
s_isReady = false;
|
|
}
|
|
|
|
void GraphicPack2::ClearGraphicPacks()
|
|
{
|
|
s_graphic_packs.clear();
|
|
s_active_graphic_packs.clear();
|
|
}
|
|
|
|
void GraphicPack2::WaitUntilReady()
|
|
{
|
|
while (!s_isReady)
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
|
|
std::unordered_map<std::string, GraphicPack2::PresetVar> GraphicPack2::ParsePresetVars(IniParser& rules) const
|
|
{
|
|
ExpressionParser parser;
|
|
std::unordered_map<std::string, PresetVar> vars;
|
|
for(auto& itr : rules.GetAllOptions())
|
|
{
|
|
auto option_name = itr.first;
|
|
auto option_value = itr.second;
|
|
if (option_name.empty() || option_name[0] != '$')
|
|
continue;
|
|
VarType type = kDouble;
|
|
std::string name(option_name);
|
|
const auto index = name.find(':');
|
|
if(index != std::string::npos)
|
|
{
|
|
auto type_name = name.substr(index + 1);
|
|
name = name.substr(0, index);
|
|
|
|
trim(name);
|
|
trim(type_name);
|
|
|
|
if (type_name == "double")
|
|
type = kDouble;
|
|
else if (type_name == "int")
|
|
type = kInt;
|
|
}
|
|
const double value = parser.Evaluate(option_value);
|
|
vars.try_emplace(name, std::make_pair(type, value));
|
|
parser.AddConstant(name, value);
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
|
: m_rulesPath(std::move(rulesPath))
|
|
{
|
|
// we're already in [Definition]
|
|
auto option_version = rules.FindOption("version");
|
|
if (!option_version)
|
|
throw std::exception();
|
|
m_version = StringHelpers::ToInt(*option_version, -1);
|
|
if (m_version < 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "{}: Invalid version", _pathToUtf8(m_rulesPath));
|
|
throw std::exception();
|
|
}
|
|
|
|
auto option_rendererFilter = rules.FindOption("rendererFilter");
|
|
if (option_rendererFilter)
|
|
{
|
|
if (boost::iequals(*option_rendererFilter, "vulkan"))
|
|
m_renderer_api = RendererAPI::Vulkan;
|
|
else if (boost::iequals(*option_rendererFilter, "opengl"))
|
|
m_renderer_api = RendererAPI::OpenGL;
|
|
else
|
|
cemuLog_log(LogType::Force, "Unknown value '{}' for rendererFilter option", *option_rendererFilter);
|
|
}
|
|
|
|
auto option_defaultEnabled = rules.FindOption("default");
|
|
if(option_defaultEnabled)
|
|
{
|
|
m_default_enabled = boost::iequals(*option_defaultEnabled, "true") || boost::iequals(*option_defaultEnabled, "1");
|
|
m_enabled = m_default_enabled;
|
|
}
|
|
|
|
auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware");
|
|
if (option_allowRendertargetSizeOptimization)
|
|
m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1");
|
|
|
|
auto option_vendorFilter = rules.FindOption("vendorFilter");
|
|
if (option_vendorFilter)
|
|
{
|
|
if (boost::iequals(*option_vendorFilter, "amd"))
|
|
m_gfx_vendor = GfxVendor::AMD;
|
|
else if (boost::iequals(*option_vendorFilter, "intel"))
|
|
m_gfx_vendor = GfxVendor::Intel;
|
|
else if (boost::iequals(*option_vendorFilter, "mesa"))
|
|
m_gfx_vendor = GfxVendor::Mesa;
|
|
else if (boost::iequals(*option_vendorFilter, "nvidia"))
|
|
m_gfx_vendor = GfxVendor::Nvidia;
|
|
else if (boost::iequals(*option_vendorFilter, "apple"))
|
|
m_gfx_vendor = GfxVendor::Apple;
|
|
else
|
|
cemuLog_log(LogType::Force, "Unknown value '{}' for vendorFilter", *option_vendorFilter);
|
|
}
|
|
|
|
auto option_path = rules.FindOption("path");
|
|
if (!option_path)
|
|
{
|
|
auto gp_name_log = rules.FindOption("name");
|
|
cemuLog_log(LogType::Force, "[Definition] section from '{}' graphic pack must contain option: path", gp_name_log.has_value() ? *gp_name_log : "Unknown");
|
|
throw std::exception();
|
|
}
|
|
m_virtualPath = *option_path;
|
|
|
|
auto option_gp_name = rules.FindOption("name");
|
|
if (option_gp_name)
|
|
m_name = *option_gp_name;
|
|
|
|
auto option_description = rules.FindOption("description");
|
|
if (option_description)
|
|
{
|
|
m_description = *option_description;
|
|
std::replace(m_description.begin(), m_description.end(), '|', '\n');
|
|
}
|
|
|
|
m_title_ids = ParseTitleIds(rules, "titleIds");
|
|
if(m_title_ids.empty())
|
|
throw std::exception();
|
|
|
|
auto option_fsPriority = rules.FindOption("fsPriority");
|
|
if (option_fsPriority)
|
|
{
|
|
std::string tmp(*option_fsPriority);
|
|
m_fs_priority = std::stoi(tmp);
|
|
}
|
|
|
|
// load presets
|
|
while (rules.NextSection())
|
|
{
|
|
auto currentSectionName = rules.GetCurrentSectionName();
|
|
if (boost::iequals(currentSectionName, "Default"))
|
|
{
|
|
m_preset_vars = ParsePresetVars(rules);
|
|
}
|
|
else if (boost::iequals(currentSectionName, "Preset"))
|
|
{
|
|
const auto preset_name = rules.FindOption("name");
|
|
if (!preset_name)
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber());
|
|
continue;
|
|
}
|
|
|
|
const auto category = rules.FindOption("category");
|
|
const auto condition = rules.FindOption("condition");
|
|
const auto default_selected = rules.FindOption("default");
|
|
|
|
try
|
|
{
|
|
const auto vars = ParsePresetVars(rules);
|
|
PresetPtr preset;
|
|
if (category && condition)
|
|
preset = std::make_shared<Preset>(*category, *preset_name, *condition, vars);
|
|
else if (category)
|
|
preset = std::make_shared<Preset>(*category, *preset_name, vars);
|
|
else
|
|
preset = std::make_shared<Preset>(*preset_name, vars);
|
|
if (default_selected)
|
|
preset->is_default = StringHelpers::ToInt(*default_selected) != 0;
|
|
m_presets.emplace_back(preset);
|
|
}
|
|
catch (const std::exception & ex)
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what());
|
|
}
|
|
}
|
|
else if (boost::iequals(currentSectionName, "RAM"))
|
|
{
|
|
for (uint32 i = 0; i < 32; i++)
|
|
{
|
|
char optionNameBuf[64];
|
|
*fmt::format_to(optionNameBuf, "mapping{}", i) = '\0';
|
|
const auto mappingOption = rules.FindOption(optionNameBuf);
|
|
if (mappingOption)
|
|
{
|
|
if (m_version <= 5)
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf);
|
|
throw std::exception();
|
|
}
|
|
|
|
StringTokenParser parser(*mappingOption);
|
|
uint32 addrStart = 0, addrEnd = 0;
|
|
if (parser.parseU32(addrStart) && parser.matchWordI("-") && parser.parseU32(addrEnd) && parser.isEndOfString())
|
|
{
|
|
if (addrEnd <= addrStart)
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf);
|
|
throw std::exception();
|
|
}
|
|
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf);
|
|
throw std::exception();
|
|
}
|
|
else
|
|
{
|
|
m_ramMappings.emplace_back(addrStart, addrEnd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf);
|
|
throw std::exception();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_version >= 5)
|
|
{
|
|
// store by category
|
|
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
|
|
|
|
// all vars must be defined in the default preset vars before
|
|
std::vector<std::pair<std::string, std::string>> mismatchingPresetVars;
|
|
for (const auto& presetEntry : m_presets)
|
|
{
|
|
tmp_map[presetEntry->category].emplace_back(presetEntry);
|
|
|
|
for (auto& presetVar : presetEntry->variables)
|
|
{
|
|
const auto it = m_preset_vars.find(presetVar.first);
|
|
if (it == m_preset_vars.cend())
|
|
{
|
|
mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first);
|
|
continue;
|
|
}
|
|
// overwrite var type with default var type
|
|
presetVar.second.first = it->second.first;
|
|
}
|
|
}
|
|
|
|
if(!mismatchingPresetVars.empty())
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString());
|
|
for (const auto& [presetName, varName] : mismatchingPresetVars)
|
|
cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName);
|
|
throw std::exception();
|
|
}
|
|
|
|
// have first entry be default active for every category if no default= is set
|
|
for(auto entry : get_values(tmp_map))
|
|
{
|
|
if (!entry.empty())
|
|
{
|
|
const auto it = std::find_if(entry.cbegin(), entry.cend(), [](const PresetPtr& preset) { return preset->is_default; });
|
|
if (it != entry.cend())
|
|
(*it)->active = true;
|
|
else
|
|
(*entry.begin())->active = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// verify preset data to contain the same keys
|
|
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
|
|
for (const auto& entry : m_presets)
|
|
tmp_map[entry->category].emplace_back(entry);
|
|
|
|
for (const auto& kv : tmp_map)
|
|
{
|
|
bool has_default = false;
|
|
for (size_t i = 0; i + 1 < kv.second.size(); ++i)
|
|
{
|
|
auto& p1 = kv.second[i];
|
|
auto& p2 = kv.second[i + 1];
|
|
if (p1->variables.size() != p2->variables.size())
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
|
|
throw std::exception();
|
|
}
|
|
|
|
std::set<std::string> keys1(get_keys(p1->variables).begin(), get_keys(p1->variables).end());
|
|
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
|
|
if (keys1 != keys2)
|
|
{
|
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
|
|
throw std::exception();
|
|
}
|
|
|
|
if(p1->is_default)
|
|
{
|
|
if(has_default)
|
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name);
|
|
p1->active = true;
|
|
has_default = true;
|
|
}
|
|
}
|
|
|
|
// have first entry by default active if no default is set
|
|
if (!has_default)
|
|
kv.second[0]->active = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns true if enabling, disabling (changeEnableState) or changing presets (changePreset) for the graphic pack requires restarting if the game is already running
|
|
bool GraphicPack2::RequiresRestart(bool changeEnableState, bool changePreset)
|
|
{
|
|
if (!GetTextureRules().empty())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool GraphicPack2::Reload()
|
|
{
|
|
Deactivate();
|
|
return Activate();
|
|
}
|
|
|
|
std::string GraphicPack2::GetNormalizedPathString() const
|
|
{
|
|
return _pathToUtf8(MakeRelativePath(ActiveSettings::GetUserDataPath(), GetRulesPath()).lexically_normal());
|
|
}
|
|
|
|
bool GraphicPack2::ContainsTitleId(uint64_t title_id) const
|
|
{
|
|
const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; });
|
|
return it != m_title_ids.end();
|
|
}
|
|
|
|
bool GraphicPack2::HasActivePreset() const
|
|
{
|
|
return std::any_of(m_presets.cbegin(), m_presets.cend(), [](const PresetPtr& preset)
|
|
{
|
|
return preset->active;
|
|
});
|
|
}
|
|
|
|
std::string GraphicPack2::GetActivePreset(std::string_view category) const
|
|
{
|
|
const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category](const PresetPtr& preset)
|
|
{
|
|
return preset->active && preset->category == category;
|
|
});
|
|
return it != m_presets.cend() ? (*it)->name : std::string{ "" };
|
|
}
|
|
|
|
void GraphicPack2::UpdatePresetVisibility()
|
|
{
|
|
// update visiblity of each preset
|
|
std::for_each(m_presets.begin(), m_presets.end(), [this](PresetPtr& p)
|
|
{
|
|
p->visible = m_version >= 5 ? IsPresetVisible(p) : true;
|
|
});
|
|
}
|
|
|
|
void GraphicPack2::ValidatePresetSelections()
|
|
{
|
|
if (m_version < 5)
|
|
return; // only applies to new categorized presets
|
|
|
|
// if any preset is changed then other categories might be affected indirectly
|
|
//
|
|
// example: selecting the aspect ratio in a resolution graphic pack would change the available presets in the resolution category
|
|
// how to handle: select the first available resolution (or the one marked as default)
|
|
//
|
|
// example: a preset category might be hidden entirely (e.g. due to a separate advanced options dropdown)
|
|
// how to handle: leave the previously selected preset
|
|
//
|
|
// the logic is therefore as follows:
|
|
// if there is a preset category with at least 1 visible preset entry then make sure one of those is actually selected
|
|
// for completely hidden preset categories we leave the selection as-is
|
|
|
|
std::vector<std::string> order;
|
|
std::unordered_map<std::string, std::vector<GraphicPack2::PresetPtr>> categorizedPresets = GraphicPack2::GetCategorizedPresets(order);
|
|
|
|
bool changedPresets = false;
|
|
for (auto& categoryItr : categorizedPresets)
|
|
{
|
|
// get selection of this category
|
|
size_t numVisiblePresets = 0;
|
|
GraphicPack2::PresetPtr defaultSelection = nullptr;
|
|
GraphicPack2::PresetPtr selectedPreset = nullptr;
|
|
for (auto& presetItr : categoryItr.second)
|
|
{
|
|
if (presetItr->visible)
|
|
{
|
|
numVisiblePresets++;
|
|
if (!defaultSelection || presetItr->is_default) // the preset marked as default becomes the default selection, otherwise pick first visible one
|
|
defaultSelection = presetItr;
|
|
}
|
|
if (presetItr->active)
|
|
{
|
|
if (selectedPreset)
|
|
{
|
|
// multiple selections inside the same group are invalid
|
|
presetItr->active = false;
|
|
changedPresets = true;
|
|
}
|
|
else
|
|
selectedPreset = presetItr;
|
|
}
|
|
}
|
|
if (numVisiblePresets == 0)
|
|
continue; // do not touch selection
|
|
if (!selectedPreset)
|
|
{
|
|
// no selection at all
|
|
if (defaultSelection)
|
|
{
|
|
selectedPreset = defaultSelection;
|
|
selectedPreset->active = true;
|
|
}
|
|
continue;
|
|
}
|
|
// if the currently selected preset is invisible, update it to the preferred visible selection
|
|
if (!selectedPreset->visible)
|
|
{
|
|
selectedPreset->active = false;
|
|
defaultSelection->active = true;
|
|
changedPresets = true;
|
|
}
|
|
}
|
|
if (changedPresets)
|
|
UpdatePresetVisibility();
|
|
}
|
|
|
|
bool GraphicPack2::SetActivePreset(std::string_view category, std::string_view name, bool update_visibility)
|
|
{
|
|
// disable currently active preset
|
|
std::for_each(m_presets.begin(), m_presets.end(), [category](PresetPtr& p)
|
|
{
|
|
if(p->category == category)
|
|
p->active = false;
|
|
});
|
|
|
|
if (name.empty())
|
|
return true;
|
|
|
|
// enable new preset
|
|
const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category, name](const PresetPtr& preset)
|
|
{
|
|
return preset->category == category && preset->name == name;
|
|
});
|
|
|
|
bool result;
|
|
if (it != m_presets.cend())
|
|
{
|
|
(*it)->active = true;
|
|
cemu_assert_debug(std::count_if(m_presets.cbegin(), m_presets.cend(), [category](const PresetPtr& p) { return p->category == category && p->active; }) == 1);
|
|
result = true;
|
|
}
|
|
else
|
|
result = false;
|
|
|
|
if (update_visibility)
|
|
{
|
|
UpdatePresetVisibility();
|
|
ValidatePresetSelections();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void GraphicPack2::LoadShaders()
|
|
{
|
|
fs::path path = GetRulesPath();
|
|
for (auto& it : fs::directory_iterator(path.remove_filename()))
|
|
{
|
|
if (!is_regular_file(it))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
const auto& p = it.path();
|
|
auto filename = p.filename().wstring();
|
|
uint64 shader_base_hash = 0;
|
|
uint64 shader_aux_hash = 0;
|
|
wchar_t shader_type[256]{};
|
|
if (filename.size() < 256 && swscanf(filename.c_str(), L"%" SCNx64 "_%" SCNx64 "_%ls", &shader_base_hash, &shader_aux_hash, shader_type) == 3)
|
|
{
|
|
if (shader_type[0] == 'p' && shader_type[1] == 's')
|
|
m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::PIXEL));
|
|
else if (shader_type[0] == 'v' && shader_type[1] == 's')
|
|
m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::VERTEX));
|
|
else if (shader_type[0] == 'g' && shader_type[1] == 's')
|
|
m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::GEOMETRY));
|
|
}
|
|
else if (filename == L"output.glsl")
|
|
{
|
|
std::ifstream file(p);
|
|
if (!file.is_open())
|
|
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename())));
|
|
|
|
file.seekg(0, std::ios::end);
|
|
m_output_shader_source.reserve(file.tellg());
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
m_output_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
|
ApplyShaderPresets(m_output_shader_source);
|
|
}
|
|
else if (filename == L"upscaling.glsl")
|
|
{
|
|
std::ifstream file(p);
|
|
if (!file.is_open())
|
|
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename())));
|
|
|
|
file.seekg(0, std::ios::end);
|
|
m_upscaling_shader_source.reserve(file.tellg());
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
m_upscaling_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
|
ApplyShaderPresets(m_upscaling_shader_source);
|
|
}
|
|
else if (filename == L"downscaling.glsl")
|
|
{
|
|
std::ifstream file(p);
|
|
if (!file.is_open())
|
|
throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename())));
|
|
|
|
file.seekg(0, std::ios::end);
|
|
m_downscaling_shader_source.reserve(file.tellg());
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
m_downscaling_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
|
ApplyShaderPresets(m_downscaling_shader_source);
|
|
}
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
cemuLog_log(LogType::Force, "graphicPack: error while loading custom shader: {}", ex.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GraphicPack2::SetActivePreset(std::string_view name)
|
|
{
|
|
return SetActivePreset("", name);
|
|
}
|
|
|
|
bool GraphicPack2::IsPresetVisible(const PresetPtr& preset) const
|
|
{
|
|
if (preset->condition.empty())
|
|
return true;
|
|
|
|
try
|
|
{
|
|
TExpressionParser<int> p;
|
|
FillPresetConstants(p);
|
|
return p.Evaluate(preset->condition) != 0;
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
cemuLog_log(LogType::Force, "error when trying to check visiblity of preset: {}", ex.what());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::optional<GraphicPack2::PresetVar> GraphicPack2::GetPresetVariable(const std::vector<PresetPtr>& presets, std::string_view var_name) const
|
|
{
|
|
// no priority and visibility filter
|
|
if(m_version < 5)
|
|
{
|
|
for (const auto& preset : presets)
|
|
{
|
|
const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; });
|
|
if (it != preset->variables.cend())
|
|
return it->second;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
// visible > none visible > default
|
|
for (const auto& preset : presets)
|
|
{
|
|
if (preset->visible)
|
|
{
|
|
const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; });
|
|
if (it != preset->variables.cend())
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
for (const auto& preset : presets)
|
|
{
|
|
if (!preset->visible)
|
|
{
|
|
const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; });
|
|
if (it != preset->variables.cend())
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
const auto it = std::find_if(m_preset_vars.cbegin(), m_preset_vars.cend(), [&var_name](auto p) { return p.first == var_name; });
|
|
if (it != m_preset_vars.cend())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep)
|
|
{
|
|
if (m_version < 5)
|
|
{
|
|
for (const auto& preset : GetActivePresets())
|
|
{
|
|
for (auto& v : preset->variables)
|
|
{
|
|
ep.AddConstant(v.first, v.second.second);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FillPresetConstants(ep);
|
|
}
|
|
}
|
|
|
|
void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase)
|
|
{
|
|
uint64 currentTitleId = CafeSystem::GetForegroundTitleId();
|
|
uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull;
|
|
for (auto& it : fs::recursive_directory_iterator(currentPath))
|
|
{
|
|
if (fs::is_regular_file(it))
|
|
{
|
|
fs::path virtualMountPath = fs::relative(it.path(), currentPath);
|
|
if (isAOC)
|
|
{
|
|
virtualMountPath = fs::path(fmt::format("/vol/aoc{:016x}/", aocTitleId)) / virtualMountPath;
|
|
}
|
|
else
|
|
{
|
|
virtualMountPath = fs::path(virtualMountBase) / virtualMountPath;
|
|
}
|
|
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GraphicPack2::LoadReplacedFiles()
|
|
{
|
|
if (m_patchedFilesLoaded)
|
|
return;
|
|
m_patchedFilesLoaded = true;
|
|
|
|
fs::path gfxPackPath = GetRulesPath();
|
|
gfxPackPath = gfxPackPath.remove_filename();
|
|
|
|
// /content/
|
|
fs::path contentPath(gfxPackPath);
|
|
contentPath.append("content");
|
|
|
|
std::error_code ec;
|
|
if (fs::exists(contentPath, ec))
|
|
{
|
|
// setup redirections
|
|
fscDeviceRedirect_map();
|
|
_iterateReplacedFiles(contentPath, false, "vol/content/");
|
|
}
|
|
// /aoc/
|
|
fs::path aocPath(gfxPackPath);
|
|
aocPath.append("aoc");
|
|
|
|
if (fs::exists(aocPath, ec))
|
|
{
|
|
uint64 aocTitleId = CafeSystem::GetForegroundTitleId();
|
|
aocTitleId = aocTitleId & 0xFFFFFFFFULL;
|
|
aocTitleId |= 0x0005000c00000000ULL;
|
|
// setup redirections
|
|
fscDeviceRedirect_map();
|
|
_iterateReplacedFiles(aocPath, true, nullptr);
|
|
}
|
|
|
|
// /code/
|
|
fs::path codePath(gfxPackPath);
|
|
codePath.append("code");
|
|
|
|
if (fs::exists(codePath, ec))
|
|
{
|
|
// setup redirections
|
|
fscDeviceRedirect_map();
|
|
_iterateReplacedFiles(codePath, false, CafeSystem::GetInternalVirtualCodeFolder().c_str());
|
|
}
|
|
}
|
|
|
|
bool GraphicPack2::Activate()
|
|
{
|
|
if (m_activated)
|
|
return true;
|
|
|
|
// check if gp should be loaded
|
|
if (m_renderer_api.has_value() && m_renderer_api.value() != g_renderer->GetType())
|
|
return false;
|
|
|
|
if (m_gfx_vendor.has_value())
|
|
{
|
|
auto vendor = g_renderer->GetVendor();
|
|
if (m_gfx_vendor.value() != vendor)
|
|
return false;
|
|
}
|
|
|
|
FileStream* fs_rules = FileStream::openFile2(m_rulesPath);
|
|
if (!fs_rules)
|
|
return false;
|
|
std::vector<uint8> rulesData;
|
|
fs_rules->extract(rulesData);
|
|
delete fs_rules;
|
|
|
|
IniParser rules({ (char*)rulesData.data(), rulesData.size()}, GetNormalizedPathString());
|
|
|
|
// load rules
|
|
try
|
|
{
|
|
ExpressionParser parser;
|
|
AddConstantsForCurrentPreset(parser);
|
|
|
|
while (rules.NextSection())
|
|
{
|
|
//const char* category_name = sPref_currentCategoryName(rules);
|
|
std::string_view category_name = rules.GetCurrentSectionName();
|
|
if (boost::iequals(category_name, "TextureRedefine"))
|
|
{
|
|
TextureRule rule{};
|
|
ParseRule(parser, rules, "width", &rule.filter_settings.width);
|
|
ParseRule(parser, rules, "height", &rule.filter_settings.height);
|
|
ParseRule(parser, rules, "depth", &rule.filter_settings.depth);
|
|
|
|
bool inMem1 = false;
|
|
if (ParseRule(parser, rules, "inMEM1", &inMem1))
|
|
rule.filter_settings.inMEM1 = inMem1 ? TextureRule::FILTER_SETTINGS::MEM1_FILTER::INSIDE : TextureRule::FILTER_SETTINGS::MEM1_FILTER::OUTSIDE;
|
|
|
|
rule.filter_settings.format_whitelist = ParseList<sint32>(parser, rules, "formats");
|
|
rule.filter_settings.format_blacklist = ParseList<sint32>(parser, rules, "formatsExcluded");
|
|
rule.filter_settings.tilemode_whitelist = ParseList<sint32>(parser, rules, "tilemodes");
|
|
rule.filter_settings.tilemode_blacklist = ParseList<sint32>(parser, rules, "tilemodesExcluded");
|
|
|
|
ParseRule(parser, rules, "overwriteWidth", &rule.overwrite_settings.width);
|
|
ParseRule(parser, rules, "overwriteHeight", &rule.overwrite_settings.height);
|
|
ParseRule(parser, rules, "overwriteDepth", &rule.overwrite_settings.depth);
|
|
ParseRule(parser, rules, "overwriteFormat", &rule.overwrite_settings.format);
|
|
|
|
float lod_bias;
|
|
if(ParseRule(parser, rules, "overwriteLodBias", &lod_bias))
|
|
rule.overwrite_settings.lod_bias = (sint32)(lod_bias * 64.0f);
|
|
|
|
if(ParseRule(parser, rules, "overwriteRelativeLodBias", &lod_bias))
|
|
rule.overwrite_settings.relative_lod_bias = (sint32)(lod_bias * 64.0f);
|
|
|
|
sint32 anisotropyValue;
|
|
if (ParseRule(parser, rules, "overwriteAnisotropy", &anisotropyValue))
|
|
{
|
|
if (anisotropyValue == 1)
|
|
rule.overwrite_settings.anistropic_value = 0;
|
|
else if (anisotropyValue == 2)
|
|
rule.overwrite_settings.anistropic_value = 1;
|
|
else if (anisotropyValue == 4)
|
|
rule.overwrite_settings.anistropic_value = 2;
|
|
else if (anisotropyValue == 8)
|
|
rule.overwrite_settings.anistropic_value = 3;
|
|
else if (anisotropyValue == 16)
|
|
rule.overwrite_settings.anistropic_value = 4;
|
|
else
|
|
cemuLog_log(LogType::Force, "Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, GetNormalizedPathString());
|
|
}
|
|
m_texture_rules.emplace_back(rule);
|
|
}
|
|
else if (boost::iequals(category_name, "Control"))
|
|
{
|
|
ParseRule(parser, rules, "vsyncFrequency", &m_vsync_frequency);
|
|
}
|
|
else if (boost::iequals(category_name, "OutputShader"))
|
|
{
|
|
auto option_upscale = rules.FindOption("upscaleMagFilter");
|
|
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
|
|
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
|
auto option_downscale = rules.FindOption("downscaleMinFilter");
|
|
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
|
|
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
|
}
|
|
}
|
|
}
|
|
catch(const std::exception& ex)
|
|
{
|
|
cemuLog_log(LogType::Force, ex.what());
|
|
return false;
|
|
}
|
|
|
|
// load shaders
|
|
LoadShaders();
|
|
|
|
// load patches
|
|
LoadPatchFiles();
|
|
|
|
// enable patch groups
|
|
EnablePatches();
|
|
|
|
// load replaced files
|
|
LoadReplacedFiles();
|
|
|
|
// set custom vsync
|
|
if (HasCustomVSyncFrequency())
|
|
{
|
|
sint32 customVsyncFreq = GetCustomVSyncFrequency();
|
|
sint32 globalCustomVsyncFreq = 0;
|
|
if (LatteTiming_getCustomVsyncFrequency(globalCustomVsyncFreq))
|
|
{
|
|
if (customVsyncFreq != globalCustomVsyncFreq)
|
|
cemuLog_log(LogType::Force, "rules.txt error: Mismatching vsync frequency {} in graphic pack \'{}\'", customVsyncFreq, GetVirtualPath());
|
|
}
|
|
else
|
|
{
|
|
cemuLog_log(LogType::Force, "Set vsync frequency to {} (graphic pack {})", customVsyncFreq, GetVirtualPath());
|
|
LatteTiming_setCustomVsyncFrequency(customVsyncFreq);
|
|
}
|
|
}
|
|
m_activated = true;
|
|
return true;
|
|
}
|
|
|
|
bool GraphicPack2::Deactivate()
|
|
{
|
|
if (!m_activated)
|
|
return false;
|
|
|
|
UnloadPatches();
|
|
|
|
m_activated = false;
|
|
m_custom_shaders.clear();
|
|
m_texture_rules.clear();
|
|
|
|
m_output_shader.reset();
|
|
m_upscaling_shader.reset();
|
|
m_downscaling_shader.reset();
|
|
|
|
m_output_shader_ud.reset();
|
|
m_upscaling_shader_ud.reset();
|
|
m_downscaling_shader_ud.reset();
|
|
|
|
m_output_shader_source.clear();
|
|
m_upscaling_shader_source.clear();
|
|
m_downscaling_shader_source.clear();
|
|
|
|
if (HasCustomVSyncFrequency())
|
|
{
|
|
m_vsync_frequency = -1;
|
|
|
|
LatteTiming_disableCustomVsyncFrequency();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const std::string* GraphicPack2::FindCustomShaderSource(uint64 shaderBaseHash, uint64 shaderAuxHash, GP_SHADER_TYPE type, bool isVulkanRenderer)
|
|
{
|
|
for (const auto& gp : GraphicPack2::GetActiveGraphicPacks())
|
|
{
|
|
const auto it = std::find_if(gp->m_custom_shaders.begin(), gp->m_custom_shaders.end(),
|
|
[shaderBaseHash, shaderAuxHash, type](const auto& s) { return s.shader_base_hash == shaderBaseHash && s.shader_aux_hash == shaderAuxHash && s.type == type; });
|
|
|
|
if (it == gp->m_custom_shaders.end())
|
|
continue;
|
|
|
|
if(isVulkanRenderer && (*it).isPreVulkanShader)
|
|
continue;
|
|
|
|
return &it->source;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unordered_map<std::string, std::vector<GraphicPack2::PresetPtr>> GraphicPack2::GetCategorizedPresets(std::vector<std::string>& order) const
|
|
{
|
|
order.clear();
|
|
|
|
std::unordered_map<std::string, std::vector<PresetPtr>> result;
|
|
for(const auto& entry : m_presets)
|
|
{
|
|
result[entry->category].emplace_back(entry);
|
|
const auto it = std::find(order.cbegin(), order.cend(), entry->category);
|
|
if (it == order.cend())
|
|
order.emplace_back(entry->category);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool GraphicPack2::HasShaders() const
|
|
{
|
|
return !GetCustomShaders().empty()
|
|
|| !m_output_shader_source.empty() || !m_upscaling_shader_source.empty() || !m_downscaling_shader_source.empty();
|
|
}
|
|
|
|
RendererOutputShader* GraphicPack2::GetOuputShader(bool render_upside_down)
|
|
{
|
|
if(render_upside_down)
|
|
{
|
|
if (m_output_shader_ud)
|
|
return m_output_shader_ud.get();
|
|
|
|
if (!m_output_shader_source.empty())
|
|
m_output_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_output_shader_source);
|
|
|
|
return m_output_shader_ud.get();
|
|
}
|
|
else
|
|
{
|
|
if (m_output_shader)
|
|
return m_output_shader.get();
|
|
|
|
if (!m_output_shader_source.empty())
|
|
m_output_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_output_shader_source);
|
|
|
|
return m_output_shader.get();
|
|
}
|
|
}
|
|
|
|
RendererOutputShader* GraphicPack2::GetUpscalingShader(bool render_upside_down)
|
|
{
|
|
if (render_upside_down)
|
|
{
|
|
if (m_upscaling_shader_ud)
|
|
return m_upscaling_shader_ud.get();
|
|
|
|
if (!m_upscaling_shader_source.empty())
|
|
m_upscaling_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_upscaling_shader_source);
|
|
|
|
return m_upscaling_shader_ud.get();
|
|
}
|
|
else
|
|
{
|
|
if (m_upscaling_shader)
|
|
return m_upscaling_shader.get();
|
|
|
|
if (!m_upscaling_shader_source.empty())
|
|
m_upscaling_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_upscaling_shader_source);
|
|
|
|
return m_upscaling_shader.get();
|
|
}
|
|
}
|
|
|
|
RendererOutputShader* GraphicPack2::GetDownscalingShader(bool render_upside_down)
|
|
{
|
|
if (render_upside_down)
|
|
{
|
|
if (m_downscaling_shader_ud)
|
|
return m_downscaling_shader_ud.get();
|
|
|
|
if (!m_downscaling_shader_source.empty())
|
|
m_downscaling_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_downscaling_shader_source);
|
|
|
|
return m_downscaling_shader_ud.get();
|
|
}
|
|
else
|
|
{
|
|
if (m_downscaling_shader)
|
|
return m_downscaling_shader.get();
|
|
|
|
if (!m_downscaling_shader_source.empty())
|
|
m_downscaling_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_downscaling_shader_source);
|
|
|
|
return m_downscaling_shader.get();
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<GraphicPack2::PresetPtr> GraphicPack2::GetActivePresets() const
|
|
{
|
|
std::vector<PresetPtr> result;
|
|
result.reserve(m_presets.size());
|
|
std::copy_if(m_presets.cbegin(), m_presets.cend(), std::back_inserter(result), [](const PresetPtr& p) { return p->active; });
|
|
return result;
|
|
}
|
|
|
|
std::vector<uint64> GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) const
|
|
{
|
|
std::vector<uint64> result;
|
|
|
|
auto option_text = rules.FindOption(option_name);
|
|
if (!option_text)
|
|
return result;
|
|
|
|
for (auto& token : TokenizeView(*option_text, ','))
|
|
{
|
|
try
|
|
{
|
|
result.emplace_back(ConvertString<uint64>(token, 16));
|
|
}
|
|
catch (const std::invalid_argument&) {}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void GraphicPack2::ApplyShaderPresets(std::string& shader_source) const
|
|
{
|
|
const auto active_presets = GetActivePresets();
|
|
const std::regex regex(R"(\$[a-zA-Z_0-9]+)");
|
|
|
|
std::smatch match;
|
|
size_t offset = 0;
|
|
while (std::regex_search(shader_source.cbegin() + offset, shader_source.cend(), match, regex))
|
|
{
|
|
if (active_presets.empty())
|
|
throw std::runtime_error("found variable in shader but no preset is active");
|
|
|
|
const auto str = match.str();
|
|
|
|
std::optional<PresetVar> var = GetPresetVariable(active_presets, str);
|
|
if(!var)
|
|
throw std::runtime_error("using an unknown preset variable in shader");
|
|
|
|
std::string new_value;
|
|
if (var->first == kInt)
|
|
new_value = fmt::format("{}", (int)var->second);
|
|
else
|
|
new_value = fmt::format("{:f}", var->second);
|
|
|
|
shader_source.replace(match.position() + offset, match.length(), new_value);
|
|
offset += match.position() + new_value.length();
|
|
}
|
|
}
|
|
|
|
GraphicPack2::CustomShader GraphicPack2::LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const
|
|
{
|
|
CustomShader shader;
|
|
|
|
std::ifstream file(path);
|
|
if (!file.is_open())
|
|
throw std::runtime_error("can't open shader file");
|
|
|
|
file.seekg(0, std::ios::end);
|
|
shader.source.reserve(file.tellg());
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
shader.source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
|
ApplyShaderPresets(shader.source);
|
|
|
|
shader.shader_base_hash = shader_base_hash;
|
|
shader.shader_aux_hash = shader_aux_hash;
|
|
shader.type = shader_type;
|
|
shader.isPreVulkanShader = this->m_version <= 3;
|
|
|
|
return shader;
|
|
}
|
|
|
|
std::vector<std::pair<MPTR, MPTR>> GraphicPack2::GetActiveRAMMappings()
|
|
{
|
|
uint64 currentTitleId = CafeSystem::GetForegroundTitleId();
|
|
std::vector<std::pair<MPTR, MPTR>> v;
|
|
for (const auto& gp : GraphicPack2::GetGraphicPacks())
|
|
{
|
|
if (!gp->IsEnabled())
|
|
continue;
|
|
if (!gp->ContainsTitleId(currentTitleId))
|
|
continue;
|
|
if (!gp->m_ramMappings.empty())
|
|
v.insert(v.end(), gp->m_ramMappings.begin(), gp->m_ramMappings.end());
|
|
}
|
|
std::sort(v.begin(), v.end(),
|
|
[](const std::pair<MPTR, MPTR>& a, const std::pair<MPTR, MPTR>& b) -> bool
|
|
{
|
|
return a.first < b.first;
|
|
});
|
|
return v;
|
|
}
|