/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see .
*/
#pragma once
#include
#include
#include
#include
#include "cfg.h"
#include "hw/maple/maple_cfg.h"
#ifdef LIBRETRO
#include
#endif
namespace config {
class BaseOption {
public:
virtual ~BaseOption() = default;
virtual void save() const = 0;
virtual void load() = 0;
virtual void reset() = 0;
};
#ifdef LIBRETRO
#include "option_lr.h"
#else
class Settings {
public:
void reset() {
for (const auto& o : options)
o->reset();
gameId.clear();
perGameConfig = false;
}
void load(bool gameSpecific) {
if (gameSpecific)
{
if (gameId.empty())
return;
if (!cfgHasSection(gameId))
return;
perGameConfig = true;
}
for (const auto& o : options)
o->load();
}
void save() {
cfgSetAutoSave(false);
for (const auto& o : options)
o->save();
cfgSetAutoSave(true);
}
void setGameId(const std::string& gameId) {
this->gameId = gameId;
}
bool hasPerGameConfig() const {
return perGameConfig;
}
void setPerGameConfig(bool perGameConfig) {
this->perGameConfig = perGameConfig;
if (!perGameConfig) {
if (!gameId.empty())
cfgDeleteSection(gameId);
reset();
}
}
static Settings& instance() {
static Settings *_instance = new Settings();
return *_instance;
}
private:
std::vector options;
std::string gameId;
bool perGameConfig = false;
template
friend class Option;
};
// Missing in C++11
template
using enable_if_t = typename std::enable_if::type;
template
class Option : public BaseOption {
public:
Option(const std::string& name, T defaultValue = T(), const std::string& section = "config")
: section(section), name(name), value(defaultValue), defaultValue(defaultValue),
settings(Settings::instance())
{
settings.options.push_back(this);
}
void reset() override {
set(defaultValue);
overridden = false;
}
void load() override {
if (PerGameOption && settings.hasPerGameConfig())
set(doLoad(settings.gameId, section + "." + name));
else
{
set(doLoad(section, name));
if (cfgIsVirtual(section, name))
override(value);
}
}
void save() const override
{
if (overridden) {
if (value == overriddenDefault)
return;
if (!settings.hasPerGameConfig())
// overridden options can only be saved in per-game settings
return;
}
else if (PerGameOption && settings.hasPerGameConfig())
{
if (value == doLoad(section, name))
{
// delete existing per-game option if any
cfgDeleteEntry(settings.gameId, section + "." + name);
return;
}
}
if (PerGameOption && settings.hasPerGameConfig())
doSave(settings.gameId, section + "." + name);
else
doSave(section, name);
}
T& get() { return value; }
void set(T v) { value = v; }
void override(T v) {
verify(PerGameOption);
overriddenDefault = v;
overridden = true;
value = v;
}
bool isReadOnly() const {
return overridden && !settings.hasPerGameConfig();
}
explicit operator T() const { return value; }
operator T&() { return value; }
T& operator=(const T& v) { set(v); return value; }
protected:
template
enable_if_t::value, T>
doLoad(const std::string& section, const std::string& name) const
{
return cfgLoadBool(section, name, value);
}
template
enable_if_t::value, T>
doLoad(const std::string& section, const std::string& name) const
{
return cfgLoadInt64(section, name, value);
}
template
enable_if_t<(std::is_integral::value || std::is_enum::value)
&& !std::is_same::value && !std::is_same::value, T>
doLoad(const std::string& section, const std::string& name) const
{
return (T)cfgLoadInt(section, name, (int)value);
}
template
enable_if_t::value, T>
doLoad(const std::string& section, const std::string& name) const
{
return cfgLoadStr(section, name, value);
}
template
enable_if_t::value, T>
doLoad(const std::string& section, const std::string& name) const
{
std::string strValue = cfgLoadStr(section, name, "");
if (strValue.empty())
return value;
else
return (float)atof(strValue.c_str());
}
template
enable_if_t, U>::value, T>
doLoad(const std::string& section, const std::string& name) const
{
std::string paths = cfgLoadStr(section, name, "");
if (paths.empty())
return value;
std::string::size_type start = 0;
std::vector newValue;
while (true)
{
if (paths[start] == '"')
{
std::string v;
start++;
while (true)
{
if (paths[start] == '"')
{
if (start + 1 >= paths.size())
{
newValue.push_back(v);
return newValue;
}
if (paths[start + 1] == '"')
{
v += paths[start++];
start++;
}
else if (paths[start + 1] == ';')
{
newValue.push_back(v);
start += 2;
break;
}
else
{
v += paths[start++];
}
}
else
v += paths[start++];
}
}
else
{
std::string::size_type end = paths.find(';', start);
if (end == std::string::npos)
end = paths.size();
if (start != end)
newValue.push_back(paths.substr(start, end - start));
if (end == paths.size())
break;
start = end + 1;
}
}
return newValue;
}
template
enable_if_t::value>
doSave(const std::string& section, const std::string& name) const
{
cfgSaveBool(section, name, value);
}
template
enable_if_t::value>
doSave(const std::string& section, const std::string& name) const
{
cfgSaveInt64(section, name, value);
}
template
enable_if_t<(std::is_integral::value || std::is_enum::value)
&& !std::is_same::value && !std::is_same::value>
doSave(const std::string& section, const std::string& name) const
{
cfgSaveInt(section, name, (int)value);
}
template
enable_if_t::value>
doSave(const std::string& section, const std::string& name) const
{
cfgSaveStr(section, name, value);
}
template
enable_if_t::value>
doSave(const std::string& section, const std::string& name) const
{
char buf[64];
snprintf(buf, sizeof(buf), "%f", value);
cfgSaveStr(section, name, buf);
}
template
enable_if_t, U>::value>
doSave(const std::string& section, const std::string& name) const
{
std::string s;
for (const auto& v : value)
{
if (!s.empty())
s += ';';
if (v.find(';') != std::string::npos || (!v.empty() && v[0] == '"'))
{
s += '"';
std::string v2 = v;
while (true)
{
auto pos = v2.find('"');
if (pos != std::string::npos)
{
s += v2.substr(0, pos + 1) + '"';
v2 = v2.substr(pos + 1);
}
else
{
s += v2;
break;
}
}
s += '"';
}
else
s += v;
}
cfgSaveStr(section, name, s);
}
std::string section;
std::string name;
T value;
T defaultValue;
T overriddenDefault = T();
bool overridden = false;
Settings& settings;
};
#endif
using OptionString = Option;
// Dynarec
extern Option DynarecEnabled;
extern Option DynarecIdleSkip;
constexpr bool DynarecSafeMode = false;
// General
extern Option Cable; // 0 -> VGA, 1 -> VGA, 2 -> RGB, 3 -> TV Composite
extern Option Region; // 0 -> JP, 1 -> USA, 2 -> EU, 3 -> default
extern Option Broadcast; // 0 -> NTSC, 1 -> PAL, 2 -> PAL/M, 3 -> PAL/N, 4 -> default
extern Option Language; // 0 -> JP, 1 -> EN, 2 -> DE, 3 -> FR, 4 -> SP, 5 -> IT, 6 -> default
extern Option FullMMU;
extern Option ForceWindowsCE;
extern Option AutoLoadState;
extern Option AutoSaveState;
extern Option SavestateSlot;
extern Option ForceFreePlay;
extern Option FetchBoxart;
extern Option BoxartDisplayMode;
// Sound
constexpr bool LimitFPS = true;
extern Option DSPEnabled;
extern Option AudioBufferSize; //In samples ,*4 for bytes
extern Option AutoLatency;
extern OptionString AudioBackend;
class AudioVolumeOption : public Option {
public:
AudioVolumeOption() : Option("aica.Volume", 100) {};
float logarithmic_volume_scale = 1.0;
void load() override {
Option::load();
calcDbPower();
}
float dbPower()
{
return logarithmic_volume_scale;
}
void calcDbPower()
{
// dB scaling calculation: https://www.dr-lex.be/info-stuff/volumecontrols.html
logarithmic_volume_scale = std::min(std::exp(4.605f * float(value) / 100.f) / 100.f, 1.f);
if (value < 10)
logarithmic_volume_scale *= value / 10.f;
}
};
extern AudioVolumeOption AudioVolume;
// Rendering
class RendererOption : public Option {
public:
RendererOption()
#ifdef USE_DX9
: Option("pvr.rend", RenderType::DirectX9) {}
#elif defined(USE_DX11)
: Option("pvr.rend", RenderType::DirectX11) {}
#else
: Option("pvr.rend", RenderType::OpenGL) {}
#endif
RenderType& operator=(const RenderType& v) { set(v); return value; }
void reset() override {
// don't reset the value to avoid quick switching when starting a game
overridden = false;
}
};
extern RendererOption RendererType;
extern Option UseMipmaps;
extern Option Widescreen;
extern Option SuperWidescreen;
extern Option ShowFPS;
extern Option RenderToTextureBuffer;
extern Option TranslucentPolygonDepthMask;
extern Option ModifierVolumes;
constexpr bool Clipping = true;
#ifndef LIBRETRO
extern Option TextureUpscale;
extern Option MaxFilteredTextureSize;
extern Option PerPixelLayers;
#endif
extern Option ExtraDepthScale;
extern Option CustomTextures;
extern Option DumpTextures;
extern Option ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
extern Option Fog;
extern Option FloatVMUs;
extern Option Rotate90;
extern Option PerStripSorting;
extern Option DelayFrameSwapping; // Delay swapping frame until FB_R_SOF matches FB_W_SOF
extern Option WidescreenGameHacks;
extern std::array