/* 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, 4> CrosshairColor; extern Option SkipFrame; extern Option MaxThreads; extern Option AutoSkipFrame; // 0: none, 1: some, 2: more extern Option RenderResolution; extern Option VSync; extern Option PixelBufferSize; extern Option AnisotropicFiltering; extern Option TextureFiltering; // 0: default, 1: force nearest, 2: force linear extern Option ThreadedRendering; extern Option DupeFrames; extern Option NativeDepthInterpolation; extern Option EmulateFramebuffer; // Misc extern Option SerialConsole; extern Option SerialPTY; extern Option GDB; extern Option GDBPort; extern Option GDBWaitForConnection; extern Option UseReios; extern Option FastGDRomLoad; extern Option OpenGlChecks; extern Option, false> ContentPath; extern Option HideLegacyNaomiRoms; extern Option UploadCrashLogs; // Profiling extern Option ProfilerEnabled; extern Option ProfilerDrawToGUI; extern Option ProfilerOutputTTY; extern Option ProfilerFrameWarningTime; // Network extern Option NetworkEnable; extern Option ActAsServer; extern OptionString DNS; extern OptionString NetworkServer; extern Option LocalPort; extern Option EmulateBBA; extern Option EnableUPnP; extern Option GGPOEnable; extern Option GGPODelay; extern Option NetworkStats; extern Option GGPOAnalogAxes; extern Option GGPOChat; extern Option GGPOChatTimeoutToggle; extern Option GGPOChatTimeout; extern Option NetworkOutput; #ifdef SUPPORT_DISPMANX extern Option DispmanxMaintainAspect; #endif #ifdef USE_OMX extern Option OmxAudioLatency; extern Option OmxAudioHdmi; #endif // Maple extern Option MouseSensitivity; extern Option VirtualGamepadVibration; extern std::array, 4> MapleMainDevices; extern std::array, 2>, 4> MapleExpansionDevices; #ifdef _WIN32 extern Option UseRawInput; #else constexpr bool UseRawInput = false; #endif #ifdef USE_LUA extern OptionString LuaFileName; #endif } // namespace config