mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-04 14:01:17 +12:00
369 lines
No EOL
10 KiB
C++
369 lines
No EOL
10 KiB
C++
#include "Cafe/GameProfile/GameProfile.h"
|
|
#include "util/helpers/helpers.h"
|
|
|
|
#include "boost/nowide/convert.hpp"
|
|
|
|
#include "config/ActiveSettings.h"
|
|
#include "Common/FileStream.h"
|
|
#include "util/IniParser/IniParser.h"
|
|
#include "util/helpers/StringHelpers.h"
|
|
#include "Cafe/CafeSystem.h"
|
|
|
|
std::unique_ptr<GameProfile> g_current_game_profile = std::make_unique<GameProfile>();
|
|
|
|
struct gameProfileBooleanOption_t
|
|
{
|
|
bool isPresent = false;
|
|
bool value;
|
|
};
|
|
|
|
/*
|
|
* Attempts to load a boolean option
|
|
* If the option exists, true is returned.
|
|
* The boolean is stored in *optionValue
|
|
*/
|
|
bool gameProfile_loadBooleanOption(IniParser* iniParser, char* optionName, gameProfileBooleanOption_t* option)
|
|
{
|
|
auto option_value = iniParser->FindOption(optionName);
|
|
option->isPresent = false;
|
|
option->value = false;
|
|
if (!option_value)
|
|
return false;
|
|
// parse option
|
|
if (boost::iequals(*option_value, "false") || boost::iequals(*option_value, "0"))
|
|
{
|
|
option->isPresent = true;
|
|
option->value = false;
|
|
return true;
|
|
}
|
|
else if (boost::iequals(*option_value, "true") || boost::iequals(*option_value, "1"))
|
|
{
|
|
option->isPresent = true;
|
|
option->value = true;
|
|
return true;
|
|
}
|
|
else
|
|
cemuLog_log(LogType::Force, "Unknown value '{}' for option '{}' in game profile", *option_value, optionName);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool gameProfile_loadBooleanOption2(IniParser& iniParser, const char* optionName, bool& option)
|
|
{
|
|
auto option_value = iniParser.FindOption(optionName);
|
|
if (!option_value)
|
|
return false;
|
|
if (boost::iequals(*option_value, "1") || boost::iequals(*option_value, "true"))
|
|
{
|
|
option = true;
|
|
return true;
|
|
}
|
|
else if (boost::iequals(*option_value, "0") || boost::iequals(*option_value, "false"))
|
|
{
|
|
option = false;
|
|
return true;
|
|
}
|
|
else
|
|
cemuLog_log(LogType::Force, "Unknown value '{}' for option '{}' in game profile", *option_value, optionName);
|
|
return false;
|
|
}
|
|
|
|
bool gameProfile_loadBooleanOption2(IniParser& iniParser, const char* optionName, std::optional<bool>& option)
|
|
{
|
|
bool tmp;
|
|
const auto result = gameProfile_loadBooleanOption2(iniParser, optionName, tmp);
|
|
if(result)
|
|
option = tmp;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Attempts to load a integer option
|
|
* Allows to specify min and max value (error is logged if out of range and default value is picked)
|
|
*/
|
|
bool gameProfile_loadIntegerOption(IniParser* iniParser, const char* optionName, gameProfileIntegerOption_t* option, sint32 defaultValue, sint32 minVal, sint32 maxVal)
|
|
{
|
|
auto option_value = iniParser->FindOption(optionName);
|
|
option->isPresent = false;
|
|
if (!option_value)
|
|
{
|
|
option->value = defaultValue;
|
|
return false;
|
|
}
|
|
// parse option
|
|
sint32 val = StringHelpers::ToInt(*option_value, defaultValue);
|
|
if (val < minVal || val > maxVal)
|
|
{
|
|
cemuLog_log(LogType::Force, "Value '{}' is out of range for option '{}' in game profile", *option_value, optionName);
|
|
option->value = defaultValue;
|
|
return false;
|
|
}
|
|
option->isPresent = true;
|
|
option->value = val;
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool gameProfile_loadIntegerOption(IniParser& iniParser, const char* optionName, T& option, T minVal, T maxVal)
|
|
{
|
|
static_assert(std::is_integral<T>::value);
|
|
auto option_value = iniParser.FindOption(optionName);
|
|
if (!option_value)
|
|
return false;
|
|
// parse option
|
|
try
|
|
{
|
|
T val = ConvertString<T>(*option_value);
|
|
if (val < minVal || val > maxVal)
|
|
{
|
|
cemuLog_log(LogType::Force, "Value '{}' is out of range for option '{}' in game profile", *option_value, optionName);
|
|
return false;
|
|
}
|
|
|
|
option = val;
|
|
return true;
|
|
}
|
|
catch(std::exception&)
|
|
{
|
|
cemuLog_log(LogType::Force, "Value '{}' is out of range for option '{}' in game profile", *option_value, optionName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& option)
|
|
{
|
|
static_assert(std::is_enum<T>::value);
|
|
auto option_value = iniParser.FindOption(optionName);
|
|
if (!option_value)
|
|
return false;
|
|
for(const T& v : T())
|
|
{
|
|
// test integer option
|
|
if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value))
|
|
{
|
|
option = v;
|
|
return true;
|
|
}
|
|
|
|
// test enum name
|
|
if(boost::iequals(fmt::format("{}", v), *option_value))
|
|
{
|
|
option = v;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<typename T>
|
|
bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, std::optional<T>& option)
|
|
{
|
|
T tmp;
|
|
const auto result = gameProfile_loadEnumOption(iniParser, optionName, tmp);
|
|
if(result)
|
|
option = tmp;
|
|
return result;
|
|
}
|
|
|
|
void gameProfile_load()
|
|
{
|
|
g_current_game_profile->ResetOptional(); // reset with global values as optional
|
|
g_current_game_profile->Load(CafeSystem::GetForegroundTitleId());
|
|
|
|
// apply some settings immediately
|
|
ppcThreadQuantum = g_current_game_profile->GetThreadQuantum();
|
|
|
|
if (ppcThreadQuantum != GameProfile::kThreadQuantumDefault)
|
|
cemuLog_log(LogType::Force, "Thread quantum set to {}", ppcThreadQuantum);
|
|
}
|
|
|
|
bool GameProfile::Load(uint64_t title_id)
|
|
{
|
|
auto gameProfilePath = ActiveSettings::GetConfigPath("gameProfiles/{:016x}.ini", title_id);
|
|
|
|
std::optional<std::vector<uint8>> profileContents = FileStream::LoadIntoMemory(gameProfilePath);
|
|
if (!profileContents)
|
|
{
|
|
gameProfilePath = ActiveSettings::GetDataPath("gameProfiles/default/{:016x}.ini", title_id);
|
|
profileContents = FileStream::LoadIntoMemory(gameProfilePath);
|
|
if (!profileContents)
|
|
return false;
|
|
m_is_default = true;
|
|
}
|
|
else
|
|
m_is_default = false;
|
|
|
|
m_is_loaded = true;
|
|
// most official gameprofiles start with "# gamename"
|
|
std::vector<char> game_name;
|
|
if (profileContents->size() > 0 && profileContents->data()[0] == '#')
|
|
{
|
|
char c;
|
|
size_t idx = 1;
|
|
while (idx < profileContents->size() && (c = profileContents->data()[idx]) != '\n' && idx < 128)
|
|
{
|
|
game_name.emplace_back(c);
|
|
idx++;
|
|
}
|
|
m_gameName = std::string(game_name.begin(), game_name.end());
|
|
trim(m_gameName.value());
|
|
}
|
|
IniParser iniParser(*profileContents, _pathToUtf8(gameProfilePath));
|
|
// parse ini
|
|
while (iniParser.NextSection())
|
|
{
|
|
if (boost::iequals(iniParser.GetCurrentSectionName(), "General"))
|
|
{
|
|
gameProfile_loadBooleanOption2(iniParser, "loadSharedLibraries", m_loadSharedLibraries);
|
|
gameProfile_loadBooleanOption2(iniParser, "startWithPadView", m_startWithPadView);
|
|
}
|
|
else if (boost::iequals(iniParser.GetCurrentSectionName(), "Graphics"))
|
|
{
|
|
gameProfileIntegerOption_t graphicsApi;
|
|
gameProfile_loadIntegerOption(&iniParser, "graphics_api", &graphicsApi, -1, 0, 1);
|
|
if (graphicsApi.value != -1)
|
|
m_graphics_api = (GraphicAPI)graphicsApi.value;
|
|
|
|
gameProfile_loadEnumOption(iniParser, "accurateShaderMul", m_accurateShaderMul);
|
|
|
|
// legacy support
|
|
auto option_precompiledShaders = iniParser.FindOption("precompiledShaders");
|
|
if (option_precompiledShaders)
|
|
{
|
|
if (boost::iequals(*option_precompiledShaders, "1") || boost::iequals(*option_precompiledShaders, "true"))
|
|
m_precompiledShaders = PrecompiledShaderOption::Enable;
|
|
else if (boost::iequals(*option_precompiledShaders, "0") || boost::iequals(*option_precompiledShaders, "false"))
|
|
m_precompiledShaders = PrecompiledShaderOption::Disable;
|
|
else
|
|
m_precompiledShaders = PrecompiledShaderOption::Auto;
|
|
}
|
|
else
|
|
m_precompiledShaders = PrecompiledShaderOption::Auto;
|
|
}
|
|
else if (boost::iequals(iniParser.GetCurrentSectionName(), "Audio"))
|
|
{
|
|
gameProfile_loadBooleanOption2(iniParser, "disableAudio", m_disableAudio);
|
|
}
|
|
else if (boost::iequals(iniParser.GetCurrentSectionName(), "CPU"))
|
|
{
|
|
gameProfile_loadIntegerOption(iniParser, "threadQuantum", m_threadQuantum, 1000U, 536870912U);
|
|
if (!gameProfile_loadEnumOption(iniParser, "cpuMode", m_cpuMode))
|
|
{
|
|
// try to load the old enum value strings
|
|
std::optional<CPUModeLegacy> cpu_mode_legacy;
|
|
if (gameProfile_loadEnumOption(iniParser, "cpuMode", cpu_mode_legacy) && cpu_mode_legacy.has_value())
|
|
{
|
|
m_cpuMode = (CPUMode)cpu_mode_legacy.value();
|
|
if (m_cpuMode == CPUMode::DualcoreRecompiler)
|
|
m_cpuMode = CPUMode::MulticoreRecompiler;
|
|
}
|
|
}
|
|
}
|
|
else if (boost::iequals(iniParser.GetCurrentSectionName(), "Controller"))
|
|
{
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
auto option_value = iniParser.FindOption(fmt::format("controller{}", (i + 1)));
|
|
if (option_value)
|
|
m_controllerProfile[i] = std::string(*option_value);
|
|
}
|
|
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GameProfile::Save(uint64_t title_id)
|
|
{
|
|
auto gameProfileDir = ActiveSettings::GetConfigPath("gameProfiles");
|
|
if (std::error_code ex_ec; !fs::exists(gameProfileDir, ex_ec))
|
|
fs::create_directories(gameProfileDir, ex_ec);
|
|
auto gameProfilePath = gameProfileDir / fmt::format("{:016x}.ini", title_id);
|
|
FileStream* fs = FileStream::createFile2(gameProfilePath);
|
|
if (!fs)
|
|
{
|
|
cemuLog_log(LogType::Force, "Failed to write game profile");
|
|
return;
|
|
}
|
|
|
|
if (m_gameName)
|
|
fs->writeLine(fmt::format("# {}\n", m_gameName.value()).c_str());
|
|
|
|
#define WRITE_OPTIONAL_ENTRY(__NAME) if (m_##__NAME) fs->writeLine(fmt::format("{} = {}", #__NAME, m_##__NAME.value()).c_str());
|
|
#define WRITE_ENTRY(__NAME) fs->writeLine(fmt::format("{} = {}", #__NAME, m_##__NAME).c_str());
|
|
|
|
fs->writeLine("[General]");
|
|
WRITE_OPTIONAL_ENTRY(loadSharedLibraries);
|
|
WRITE_ENTRY(startWithPadView);
|
|
|
|
fs->writeLine("");
|
|
|
|
|
|
fs->writeLine("[CPU]");
|
|
WRITE_OPTIONAL_ENTRY(cpuMode);
|
|
WRITE_ENTRY(threadQuantum);
|
|
|
|
fs->writeLine("");
|
|
|
|
fs->writeLine("[Graphics]");
|
|
WRITE_ENTRY(accurateShaderMul);
|
|
WRITE_OPTIONAL_ENTRY(precompiledShaders);
|
|
WRITE_OPTIONAL_ENTRY(graphics_api);
|
|
fs->writeLine("");
|
|
|
|
fs->writeLine("[Controller]");
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
if (m_controllerProfile[i])
|
|
fs->writeLine(fmt::format("controller{} = {}", (i + 1), m_controllerProfile[i].value()).c_str());
|
|
}
|
|
|
|
fs->writeLine("");
|
|
|
|
#undef WRITE_OPTIONAL_ENTRY
|
|
#undef WRITE_ENTRY
|
|
|
|
delete fs;
|
|
}
|
|
|
|
void GameProfile::ResetOptional()
|
|
{
|
|
m_gameName.reset();
|
|
|
|
// general settings
|
|
m_loadSharedLibraries.reset(); // true;
|
|
m_startWithPadView = false;
|
|
|
|
// graphic settings
|
|
m_accurateShaderMul = AccurateShaderMulOption::True;
|
|
// cpu settings
|
|
m_threadQuantum = kThreadQuantumDefault;
|
|
m_cpuMode.reset(); // CPUModeOption::kSingleCoreRecompiler;
|
|
// audio
|
|
m_disableAudio = false;
|
|
// controller settings
|
|
for (auto& profile : m_controllerProfile)
|
|
profile.reset();
|
|
}
|
|
|
|
void GameProfile::Reset()
|
|
{
|
|
m_gameName.reset();
|
|
|
|
// general settings
|
|
m_loadSharedLibraries = true;
|
|
m_startWithPadView = false;
|
|
|
|
// graphic settings
|
|
m_accurateShaderMul = AccurateShaderMulOption::True;
|
|
m_precompiledShaders = PrecompiledShaderOption::Auto;
|
|
// cpu settings
|
|
m_threadQuantum = kThreadQuantumDefault;
|
|
m_cpuMode = CPUMode::Auto;
|
|
// audio
|
|
m_disableAudio = false;
|
|
// controller settings
|
|
for (auto& profile : m_controllerProfile)
|
|
profile.reset();
|
|
} |