config.cpp
1 // Copyright 2014 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <iomanip> 6 #include <memory> 7 #include <sstream> 8 #include <type_traits> 9 #include <INIReader.h> 10 #include <SDL.h> 11 #include "citra/config.h" 12 #include "citra/default_ini.h" 13 #include "common/file_util.h" 14 #include "common/logging/backend.h" 15 #include "common/logging/log.h" 16 #include "common/settings.h" 17 #include "core/hle/service/service.h" 18 #include "input_common/main.h" 19 #include "input_common/udp/client.h" 20 #include "network/network_settings.h" 21 22 Config::Config() { 23 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 24 sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini"; 25 sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); 26 27 Reload(); 28 } 29 30 Config::~Config() = default; 31 32 bool Config::LoadINI(const std::string& default_contents, bool retry) { 33 const std::string& location = this->sdl2_config_loc; 34 if (sdl2_config->ParseError() < 0) { 35 if (retry) { 36 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); 37 FileUtil::CreateFullPath(location); 38 FileUtil::WriteStringToFile(true, location, default_contents); 39 sdl2_config = std::make_unique<INIReader>(location); // Reopen file 40 41 return LoadINI(default_contents, false); 42 } 43 LOG_ERROR(Config, "Failed."); 44 return false; 45 } 46 LOG_INFO(Config, "Successfully loaded {}", location); 47 return true; 48 } 49 50 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = { 51 SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, SDL_SCANCODE_G, 52 SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_M, SDL_SCANCODE_N, 53 SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B, 54 }; 55 56 static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{ 57 { 58 SDL_SCANCODE_UP, 59 SDL_SCANCODE_DOWN, 60 SDL_SCANCODE_LEFT, 61 SDL_SCANCODE_RIGHT, 62 SDL_SCANCODE_D, 63 }, 64 { 65 SDL_SCANCODE_I, 66 SDL_SCANCODE_K, 67 SDL_SCANCODE_J, 68 SDL_SCANCODE_L, 69 SDL_SCANCODE_D, 70 }, 71 }}; 72 73 template <> 74 void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) { 75 std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault()); 76 if (setting_value.empty()) { 77 setting_value = setting.GetDefault(); 78 } 79 setting = std::move(setting_value); 80 } 81 82 template <> 83 void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) { 84 setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); 85 } 86 87 template <typename Type, bool ranged> 88 void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) { 89 if constexpr (std::is_floating_point_v<Type>) { 90 setting = static_cast<Type>( 91 sdl2_config->GetReal(group, setting.GetLabel(), setting.GetDefault())); 92 } else { 93 setting = static_cast<Type>(sdl2_config->GetInteger( 94 group, setting.GetLabel(), static_cast<long>(setting.GetDefault()))); 95 } 96 } 97 98 void Config::ReadValues() { 99 // Controls 100 // TODO: add multiple input profile support 101 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { 102 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); 103 Settings::values.current_input_profile.buttons[i] = 104 sdl2_config->GetString("Controls", Settings::NativeButton::mapping[i], default_param); 105 if (Settings::values.current_input_profile.buttons[i].empty()) 106 Settings::values.current_input_profile.buttons[i] = default_param; 107 } 108 109 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 110 std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 111 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 112 default_analogs[i][3], default_analogs[i][4], 0.5f); 113 Settings::values.current_input_profile.analogs[i] = 114 sdl2_config->GetString("Controls", Settings::NativeAnalog::mapping[i], default_param); 115 if (Settings::values.current_input_profile.analogs[i].empty()) 116 Settings::values.current_input_profile.analogs[i] = default_param; 117 } 118 119 Settings::values.current_input_profile.motion_device = sdl2_config->GetString( 120 "Controls", "motion_device", 121 "engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0"); 122 Settings::values.current_input_profile.touch_device = 123 sdl2_config->GetString("Controls", "touch_device", "engine:emu_window"); 124 Settings::values.current_input_profile.udp_input_address = sdl2_config->GetString( 125 "Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR); 126 Settings::values.current_input_profile.udp_input_port = 127 static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port", 128 InputCommon::CemuhookUDP::DEFAULT_PORT)); 129 130 // Core 131 ReadSetting("Core", Settings::values.use_cpu_jit); 132 ReadSetting("Core", Settings::values.cpu_clock_percentage); 133 134 // Renderer 135 ReadSetting("Renderer", Settings::values.graphics_api); 136 ReadSetting("Renderer", Settings::values.physical_device); 137 ReadSetting("Renderer", Settings::values.spirv_shader_gen); 138 ReadSetting("Renderer", Settings::values.async_shader_compilation); 139 ReadSetting("Renderer", Settings::values.async_presentation); 140 ReadSetting("Renderer", Settings::values.use_gles); 141 ReadSetting("Renderer", Settings::values.use_hw_shader); 142 ReadSetting("Renderer", Settings::values.shaders_accurate_mul); 143 ReadSetting("Renderer", Settings::values.use_shader_jit); 144 ReadSetting("Renderer", Settings::values.resolution_factor); 145 ReadSetting("Renderer", Settings::values.use_disk_shader_cache); 146 ReadSetting("Renderer", Settings::values.frame_limit); 147 ReadSetting("Renderer", Settings::values.use_vsync_new); 148 ReadSetting("Renderer", Settings::values.texture_filter); 149 ReadSetting("Renderer", Settings::values.texture_sampling); 150 151 ReadSetting("Renderer", Settings::values.mono_render_option); 152 ReadSetting("Renderer", Settings::values.render_3d); 153 ReadSetting("Renderer", Settings::values.factor_3d); 154 ReadSetting("Renderer", Settings::values.pp_shader_name); 155 ReadSetting("Renderer", Settings::values.anaglyph_shader_name); 156 ReadSetting("Renderer", Settings::values.filter_mode); 157 158 ReadSetting("Renderer", Settings::values.bg_red); 159 ReadSetting("Renderer", Settings::values.bg_green); 160 ReadSetting("Renderer", Settings::values.bg_blue); 161 162 // Layout 163 ReadSetting("Layout", Settings::values.layout_option); 164 ReadSetting("Layout", Settings::values.swap_screen); 165 ReadSetting("Layout", Settings::values.upright_screen); 166 ReadSetting("Layout", Settings::values.large_screen_proportion); 167 ReadSetting("Layout", Settings::values.custom_layout); 168 ReadSetting("Layout", Settings::values.custom_top_left); 169 ReadSetting("Layout", Settings::values.custom_top_top); 170 ReadSetting("Layout", Settings::values.custom_top_right); 171 ReadSetting("Layout", Settings::values.custom_top_bottom); 172 ReadSetting("Layout", Settings::values.custom_bottom_left); 173 ReadSetting("Layout", Settings::values.custom_bottom_top); 174 ReadSetting("Layout", Settings::values.custom_bottom_right); 175 ReadSetting("Layout", Settings::values.custom_bottom_bottom); 176 ReadSetting("Layout", Settings::values.custom_second_layer_opacity); 177 178 // Utility 179 ReadSetting("Utility", Settings::values.dump_textures); 180 ReadSetting("Utility", Settings::values.custom_textures); 181 ReadSetting("Utility", Settings::values.preload_textures); 182 ReadSetting("Utility", Settings::values.async_custom_loading); 183 184 // Audio 185 ReadSetting("Audio", Settings::values.audio_emulation); 186 ReadSetting("Audio", Settings::values.enable_audio_stretching); 187 ReadSetting("Audio", Settings::values.volume); 188 ReadSetting("Audio", Settings::values.output_type); 189 ReadSetting("Audio", Settings::values.output_device); 190 ReadSetting("Audio", Settings::values.input_type); 191 ReadSetting("Audio", Settings::values.input_device); 192 193 // Data Storage 194 ReadSetting("Data Storage", Settings::values.use_virtual_sd); 195 ReadSetting("Data Storage", Settings::values.use_custom_storage); 196 197 if (Settings::values.use_custom_storage) { 198 FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, 199 sdl2_config->GetString("Data Storage", "nand_directory", "")); 200 FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, 201 sdl2_config->GetString("Data Storage", "sdmc_directory", "")); 202 } 203 204 // System 205 ReadSetting("System", Settings::values.is_new_3ds); 206 ReadSetting("System", Settings::values.lle_applets); 207 ReadSetting("System", Settings::values.region_value); 208 ReadSetting("System", Settings::values.init_clock); 209 { 210 std::tm t; 211 t.tm_sec = 1; 212 t.tm_min = 0; 213 t.tm_hour = 0; 214 t.tm_mday = 1; 215 t.tm_mon = 0; 216 t.tm_year = 100; 217 t.tm_isdst = 0; 218 std::istringstream string_stream( 219 sdl2_config->GetString("System", "init_time", "2000-01-01 00:00:01")); 220 string_stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S"); 221 if (string_stream.fail()) { 222 LOG_ERROR(Config, "Failed To parse init_time. Using 2000-01-01 00:00:01"); 223 } 224 Settings::values.init_time = 225 std::chrono::duration_cast<std::chrono::seconds>( 226 std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch()) 227 .count(); 228 } 229 ReadSetting("System", Settings::values.init_ticks_type); 230 ReadSetting("System", Settings::values.init_ticks_override); 231 ReadSetting("System", Settings::values.plugin_loader_enabled); 232 ReadSetting("System", Settings::values.allow_plugin_loader); 233 234 { 235 constexpr const char* default_init_time_offset = "0 00:00:00"; 236 237 std::string offset_string = 238 sdl2_config->GetString("System", "init_time_offset", default_init_time_offset); 239 240 std::size_t sep_index = offset_string.find(' '); 241 242 if (sep_index == std::string::npos) { 243 LOG_ERROR(Config, "Failed to parse init_time_offset. Using 0 00:00:00"); 244 offset_string = default_init_time_offset; 245 246 sep_index = offset_string.find(' '); 247 } 248 249 std::string day_string = offset_string.substr(0, sep_index); 250 long long days = 0; 251 252 try { 253 days = std::stoll(day_string); 254 } catch (std::logic_error&) { 255 LOG_ERROR(Config, "Failed to parse days in init_time_offset. Using 0"); 256 days = 0; 257 } 258 259 long long days_in_seconds = days * 86400; 260 261 std::tm t; 262 t.tm_sec = 0; 263 t.tm_min = 0; 264 t.tm_hour = 0; 265 t.tm_mday = 1; 266 t.tm_mon = 0; 267 t.tm_year = 100; 268 t.tm_isdst = 0; 269 270 std::istringstream string_stream(offset_string.substr(sep_index + 1)); 271 string_stream >> std::get_time(&t, "%H:%M:%S"); 272 273 if (string_stream.fail()) { 274 LOG_ERROR(Config, 275 "Failed to parse hours, minutes and seconds in init_time_offset. 00:00:00"); 276 } 277 278 auto time_offset = 279 std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch(); 280 281 auto secs = std::chrono::duration_cast<std::chrono::seconds>(time_offset).count(); 282 283 Settings::values.init_time_offset = static_cast<long long>(secs) + days_in_seconds; 284 } 285 286 // Camera 287 using namespace Service::CAM; 288 Settings::values.camera_name[OuterRightCamera] = 289 sdl2_config->GetString("Camera", "camera_outer_right_name", "blank"); 290 Settings::values.camera_config[OuterRightCamera] = 291 sdl2_config->GetString("Camera", "camera_outer_right_config", ""); 292 Settings::values.camera_flip[OuterRightCamera] = 293 sdl2_config->GetInteger("Camera", "camera_outer_right_flip", 0); 294 Settings::values.camera_name[InnerCamera] = 295 sdl2_config->GetString("Camera", "camera_inner_name", "blank"); 296 Settings::values.camera_config[InnerCamera] = 297 sdl2_config->GetString("Camera", "camera_inner_config", ""); 298 Settings::values.camera_flip[InnerCamera] = 299 sdl2_config->GetInteger("Camera", "camera_inner_flip", 0); 300 Settings::values.camera_name[OuterLeftCamera] = 301 sdl2_config->GetString("Camera", "camera_outer_left_name", "blank"); 302 Settings::values.camera_config[OuterLeftCamera] = 303 sdl2_config->GetString("Camera", "camera_outer_left_config", ""); 304 Settings::values.camera_flip[OuterLeftCamera] = 305 sdl2_config->GetInteger("Camera", "camera_outer_left_flip", 0); 306 307 // Miscellaneous 308 ReadSetting("Miscellaneous", Settings::values.log_filter); 309 310 // Apply the log_filter setting as the logger has already been initialized 311 // and doesn't pick up the filter on its own. 312 Common::Log::Filter filter; 313 filter.ParseFilterString(Settings::values.log_filter.GetValue()); 314 Common::Log::SetGlobalFilter(filter); 315 316 // Debugging 317 Settings::values.record_frame_times = 318 sdl2_config->GetBoolean("Debugging", "record_frame_times", false); 319 ReadSetting("Debugging", Settings::values.renderer_debug); 320 ReadSetting("Debugging", Settings::values.use_gdbstub); 321 ReadSetting("Debugging", Settings::values.gdbstub_port); 322 323 for (const auto& service_module : Service::service_module_map) { 324 bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false); 325 Settings::values.lle_modules.emplace(service_module.name, use_lle); 326 } 327 328 // Web Service 329 NetSettings::values.enable_telemetry = 330 sdl2_config->GetBoolean("WebService", "enable_telemetry", false); 331 NetSettings::values.web_api_url = 332 sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org"); 333 NetSettings::values.citra_username = sdl2_config->GetString("WebService", "citra_username", ""); 334 NetSettings::values.citra_token = sdl2_config->GetString("WebService", "citra_token", ""); 335 336 // Video Dumping 337 Settings::values.output_format = 338 sdl2_config->GetString("Video Dumping", "output_format", "webm"); 339 Settings::values.format_options = sdl2_config->GetString("Video Dumping", "format_options", ""); 340 341 Settings::values.video_encoder = 342 sdl2_config->GetString("Video Dumping", "video_encoder", "libvpx-vp9"); 343 344 // Options for variable bit rate live streaming taken from here: 345 // https://developers.google.com/media/vp9/live-encoding 346 std::string default_video_options; 347 if (Settings::values.video_encoder == "libvpx-vp9") { 348 default_video_options = 349 "quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1"; 350 } 351 Settings::values.video_encoder_options = 352 sdl2_config->GetString("Video Dumping", "video_encoder_options", default_video_options); 353 Settings::values.video_bitrate = 354 sdl2_config->GetInteger("Video Dumping", "video_bitrate", 2500000); 355 356 Settings::values.audio_encoder = 357 sdl2_config->GetString("Video Dumping", "audio_encoder", "libvorbis"); 358 Settings::values.audio_encoder_options = 359 sdl2_config->GetString("Video Dumping", "audio_encoder_options", ""); 360 Settings::values.audio_bitrate = 361 sdl2_config->GetInteger("Video Dumping", "audio_bitrate", 64000); 362 } 363 364 void Config::Reload() { 365 LoadINI(DefaultINI::sdl2_config_file); 366 ReadValues(); 367 }