/***************************************************************************** * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "Config.h" #include "../Context.h" #include "../OpenRCT2.h" #include "../PlatformEnvironment.h" #include "../core/Console.hpp" #include "../core/File.h" #include "../core/FileStream.h" #include "../core/Memory.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../drawing/IDrawingEngine.h" #include "../interface/Window.h" #include "../localisation/Currency.h" #include "../localisation/Date.h" #include "../localisation/Language.h" #include "../localisation/Localisation.h" #include "../localisation/StringIds.h" #include "../network/network.h" #include "../paint/VirtualFloor.h" #include "../platform/Platform2.h" #include "../platform/platform.h" #include "../rct1/RCT1.h" #include "../scenario/Scenario.h" #include "../ui/UiContext.h" #include "../util/Util.h" #include "ConfigEnum.hpp" #include "IniReader.hpp" #include "IniWriter.hpp" #include using namespace OpenRCT2; using namespace OpenRCT2::Ui; namespace Config { #pragma region Enums static const auto Enum_MeasurementFormat = ConfigEnum({ ConfigEnumEntry("IMPERIAL", MeasurementFormat::Imperial), ConfigEnumEntry("METRIC", MeasurementFormat::Metric), ConfigEnumEntry("SI", MeasurementFormat::SI), }); static const auto Enum_Currency = ConfigEnum({ ConfigEnumEntry("GBP", CurrencyType::Pounds), ConfigEnumEntry("USD", CurrencyType::Dollars), ConfigEnumEntry("FRF", CurrencyType::Franc), ConfigEnumEntry("DEM", CurrencyType::DeutscheMark), ConfigEnumEntry("JPY", CurrencyType::Yen), ConfigEnumEntry("ESP", CurrencyType::Peseta), ConfigEnumEntry("ITL", CurrencyType::Lira), ConfigEnumEntry("NLG", CurrencyType::Guilders), ConfigEnumEntry("SEK", CurrencyType::Krona), ConfigEnumEntry("EUR", CurrencyType::Euros), ConfigEnumEntry("KRW", CurrencyType::Won), ConfigEnumEntry("RUB", CurrencyType::Rouble), ConfigEnumEntry("CZK", CurrencyType::CzechKoruna), ConfigEnumEntry("HKD", CurrencyType::HKD), ConfigEnumEntry("TWD", CurrencyType::TWD), ConfigEnumEntry("CNY", CurrencyType::Yuan), ConfigEnumEntry("HUF", CurrencyType::Forint), ConfigEnumEntry("CUSTOM", CurrencyType::Custom), }); static const auto Enum_CurrencySymbolAffix = ConfigEnum({ ConfigEnumEntry("PREFIX", CurrencyAffix::Prefix), ConfigEnumEntry("SUFFIX", CurrencyAffix::Suffix), }); static const auto Enum_DateFormat = ConfigEnum({ ConfigEnumEntry("DD/MM/YY", DATE_FORMAT_DAY_MONTH_YEAR), ConfigEnumEntry("MM/DD/YY", DATE_FORMAT_MONTH_DAY_YEAR), ConfigEnumEntry("YY/MM/DD", DATE_FORMAT_YEAR_MONTH_DAY), ConfigEnumEntry("YY/DD/MM", DATE_FORMAT_YEAR_DAY_MONTH), }); static const auto Enum_DrawingEngine = ConfigEnum({ ConfigEnumEntry("SOFTWARE", DrawingEngine::Software), ConfigEnumEntry("SOFTWARE_HWD", DrawingEngine::SoftwareWithHardwareDisplay), ConfigEnumEntry("OPENGL", DrawingEngine::OpenGL), }); static const auto Enum_Temperature = ConfigEnum({ ConfigEnumEntry("CELSIUS", TemperatureUnit::Celsius), ConfigEnumEntry("FAHRENHEIT", TemperatureUnit::Fahrenheit), }); static const auto Enum_ScaleQuality = ConfigEnum({ ConfigEnumEntry("NEAREST_NEIGHBOUR", ScaleQuality::NearestNeighbour), ConfigEnumEntry("LINEAR", ScaleQuality::Linear), ConfigEnumEntry("SMOOTH_NEAREST_NEIGHBOUR", ScaleQuality::SmoothNearestNeighbour), }); static const auto Enum_Sort = ConfigEnum({ ConfigEnumEntry("NAME_ASCENDING", Sort::NameAscending), ConfigEnumEntry("NAME_DESCENDING", Sort::NameDescending), ConfigEnumEntry("DATE_ASCENDING", Sort::DateAscending), ConfigEnumEntry("DATE_DESCENDING", Sort::DateDescending), }); static const auto Enum_VirtualFloorStyle = ConfigEnum({ ConfigEnumEntry("OFF", VirtualFloorStyles::Off), ConfigEnumEntry("CLEAR", VirtualFloorStyles::Clear), ConfigEnumEntry("GLASSY", VirtualFloorStyles::Glassy), }); /** * Config enum wrapping LanguagesDescriptors. */ static class LanguageConfigEnum final : public IConfigEnum { public: std::string GetName(int32_t value) const override { return LanguagesDescriptors[value].locale; } int32_t GetValue(const std::string& key, int32_t defaultValue) const override { int32_t i = 0; for (const auto& langDesc : LanguagesDescriptors) { if (String::Equals(key.c_str(), langDesc.locale)) { return i; } i++; } return defaultValue; } } Enum_LanguageEnum; #pragma endregion static void ReadGeneral(IIniReader* reader) { if (reader->ReadSection("general")) { auto model = &gConfigGeneral; model->always_show_gridlines = reader->GetBoolean("always_show_gridlines", false); model->autosave_frequency = reader->GetInt32("autosave", AUTOSAVE_EVERY_5MINUTES); model->autosave_amount = reader->GetInt32("autosave_amount", DEFAULT_NUM_AUTOSAVES_TO_KEEP); model->confirmation_prompt = reader->GetBoolean("confirmation_prompt", false); model->currency_format = reader->GetEnum( "currency_format", platform_get_locale_currency(), Enum_Currency); model->custom_currency_rate = reader->GetInt32("custom_currency_rate", 10); model->custom_currency_affix = reader->GetEnum( "custom_currency_affix", CurrencyAffix::Suffix, Enum_CurrencySymbolAffix); model->custom_currency_symbol = reader->GetCString("custom_currency_symbol", "Ctm"); model->edge_scrolling = reader->GetBoolean("edge_scrolling", true); model->edge_scrolling_speed = reader->GetInt32("edge_scrolling_speed", 12); model->fullscreen_mode = reader->GetInt32("fullscreen_mode", 0); model->fullscreen_height = reader->GetInt32("fullscreen_height", -1); model->fullscreen_width = reader->GetInt32("fullscreen_width", -1); model->rct1_path = reader->GetCString("rct1_path", nullptr); model->rct2_path = reader->GetCString("game_path", nullptr); model->landscape_smoothing = reader->GetBoolean("landscape_smoothing", true); model->language = reader->GetEnum("language", platform_get_locale_language(), Enum_LanguageEnum); model->measurement_format = reader->GetEnum( "measurement_format", platform_get_locale_measurement_format(), Enum_MeasurementFormat); model->play_intro = reader->GetBoolean("play_intro", false); model->save_plugin_data = reader->GetBoolean("save_plugin_data", true); model->debugging_tools = reader->GetBoolean("debugging_tools", false); model->show_height_as_units = reader->GetBoolean("show_height_as_units", false); model->temperature_format = reader->GetEnum( "temperature_format", platform_get_locale_temperature_format(), Enum_Temperature); model->window_height = reader->GetInt32("window_height", -1); model->window_snap_proximity = reader->GetInt32("window_snap_proximity", 5); model->window_width = reader->GetInt32("window_width", -1); model->default_display = reader->GetInt32("default_display", 0); model->drawing_engine = reader->GetEnum( "drawing_engine", DrawingEngine::Software, Enum_DrawingEngine); model->uncap_fps = reader->GetBoolean("uncap_fps", false); model->use_vsync = reader->GetBoolean("use_vsync", true); model->virtual_floor_style = reader->GetEnum( "virtual_floor_style", VirtualFloorStyles::Glassy, Enum_VirtualFloorStyle); model->date_format = reader->GetEnum("date_format", platform_get_locale_date_format(), Enum_DateFormat); model->auto_staff_placement = reader->GetBoolean("auto_staff", true); model->handymen_mow_default = reader->GetBoolean("handymen_mow_default", false); model->default_inspection_interval = reader->GetInt32("default_inspection_interval", 2); model->last_run_version = reader->GetCString("last_run_version", nullptr); model->invert_viewport_drag = reader->GetBoolean("invert_viewport_drag", false); model->load_save_sort = reader->GetEnum("load_save_sort", Sort::NameAscending, Enum_Sort); model->minimize_fullscreen_focus_loss = reader->GetBoolean("minimize_fullscreen_focus_loss", true); model->disable_screensaver = reader->GetBoolean("disable_screensaver", true); // Default config setting is false until the games canvas can be separated from the effect model->day_night_cycle = reader->GetBoolean("day_night_cycle", false); const bool isHardware = model->drawing_engine != DrawingEngine::Software; model->enable_light_fx = isHardware && reader->GetBoolean("enable_light_fx", false); model->enable_light_fx_for_vehicles = isHardware && reader->GetBoolean("enable_light_fx_for_vehicles", false); model->upper_case_banners = reader->GetBoolean("upper_case_banners", false); model->disable_lightning_effect = reader->GetBoolean("disable_lightning_effect", false); model->allow_loading_with_incorrect_checksum = reader->GetBoolean("allow_loading_with_incorrect_checksum", true); model->steam_overlay_pause = reader->GetBoolean("steam_overlay_pause", true); model->window_scale = reader->GetFloat("window_scale", platform_get_default_scale()); model->scale_quality = reader->GetEnum( "scale_quality", ScaleQuality::SmoothNearestNeighbour, Enum_ScaleQuality); model->show_fps = reader->GetBoolean("show_fps", false); model->multithreading = reader->GetBoolean("multi_threading", false); model->trap_cursor = reader->GetBoolean("trap_cursor", false); model->auto_open_shops = reader->GetBoolean("auto_open_shops", false); model->scenario_select_mode = reader->GetInt32("scenario_select_mode", SCENARIO_SELECT_MODE_ORIGIN); model->scenario_unlocking_enabled = reader->GetBoolean("scenario_unlocking_enabled", true); model->scenario_hide_mega_park = reader->GetBoolean("scenario_hide_mega_park", true); model->last_save_game_directory = reader->GetCString("last_game_directory", nullptr); model->last_save_landscape_directory = reader->GetCString("last_landscape_directory", nullptr); model->last_save_scenario_directory = reader->GetCString("last_scenario_directory", nullptr); model->last_save_track_directory = reader->GetCString("last_track_directory", nullptr); model->use_native_browse_dialog = reader->GetBoolean("use_native_browse_dialog", false); model->window_limit = reader->GetInt32("window_limit", WINDOW_LIMIT_MAX); model->zoom_to_cursor = reader->GetBoolean("zoom_to_cursor", true); model->render_weather_effects = reader->GetBoolean("render_weather_effects", true); model->render_weather_gloom = reader->GetBoolean("render_weather_gloom", true); model->show_guest_purchases = reader->GetBoolean("show_guest_purchases", false); model->show_real_names_of_guests = reader->GetBoolean("show_real_names_of_guests", true); model->allow_early_completion = reader->GetBoolean("allow_early_completion", false); model->transparent_screenshot = reader->GetBoolean("transparent_screenshot", true); model->last_version_check_time = reader->GetInt64("last_version_check_time", 0); } } static void WriteGeneral(IIniWriter* writer) { auto model = &gConfigGeneral; writer->WriteSection("general"); writer->WriteBoolean("always_show_gridlines", model->always_show_gridlines); writer->WriteInt32("autosave", model->autosave_frequency); writer->WriteInt32("autosave_amount", model->autosave_amount); writer->WriteBoolean("confirmation_prompt", model->confirmation_prompt); writer->WriteEnum("currency_format", model->currency_format, Enum_Currency); writer->WriteInt32("custom_currency_rate", model->custom_currency_rate); writer->WriteEnum("custom_currency_affix", model->custom_currency_affix, Enum_CurrencySymbolAffix); writer->WriteString("custom_currency_symbol", model->custom_currency_symbol); writer->WriteBoolean("edge_scrolling", model->edge_scrolling); writer->WriteInt32("edge_scrolling_speed", model->edge_scrolling_speed); writer->WriteInt32("fullscreen_mode", model->fullscreen_mode); writer->WriteInt32("fullscreen_height", model->fullscreen_height); writer->WriteInt32("fullscreen_width", model->fullscreen_width); writer->WriteString("rct1_path", model->rct1_path); writer->WriteString("game_path", model->rct2_path); writer->WriteBoolean("landscape_smoothing", model->landscape_smoothing); writer->WriteEnum("language", model->language, Enum_LanguageEnum); writer->WriteEnum("measurement_format", model->measurement_format, Enum_MeasurementFormat); writer->WriteBoolean("play_intro", model->play_intro); writer->WriteBoolean("save_plugin_data", model->save_plugin_data); writer->WriteBoolean("debugging_tools", model->debugging_tools); writer->WriteBoolean("show_height_as_units", model->show_height_as_units); writer->WriteEnum("temperature_format", model->temperature_format, Enum_Temperature); writer->WriteInt32("window_height", model->window_height); writer->WriteInt32("window_snap_proximity", model->window_snap_proximity); writer->WriteInt32("window_width", model->window_width); writer->WriteInt32("default_display", model->default_display); writer->WriteEnum("drawing_engine", model->drawing_engine, Enum_DrawingEngine); writer->WriteBoolean("uncap_fps", model->uncap_fps); writer->WriteBoolean("use_vsync", model->use_vsync); writer->WriteEnum("date_format", model->date_format, Enum_DateFormat); writer->WriteBoolean("auto_staff", model->auto_staff_placement); writer->WriteBoolean("handymen_mow_default", model->handymen_mow_default); writer->WriteInt32("default_inspection_interval", model->default_inspection_interval); writer->WriteString("last_run_version", model->last_run_version); writer->WriteBoolean("invert_viewport_drag", model->invert_viewport_drag); writer->WriteEnum("load_save_sort", model->load_save_sort, Enum_Sort); writer->WriteBoolean("minimize_fullscreen_focus_loss", model->minimize_fullscreen_focus_loss); writer->WriteBoolean("disable_screensaver", model->disable_screensaver); writer->WriteBoolean("day_night_cycle", model->day_night_cycle); writer->WriteBoolean("enable_light_fx", model->enable_light_fx); writer->WriteBoolean("enable_light_fx_for_vehicles", model->enable_light_fx_for_vehicles); writer->WriteBoolean("upper_case_banners", model->upper_case_banners); writer->WriteBoolean("disable_lightning_effect", model->disable_lightning_effect); writer->WriteBoolean("allow_loading_with_incorrect_checksum", model->allow_loading_with_incorrect_checksum); writer->WriteBoolean("steam_overlay_pause", model->steam_overlay_pause); writer->WriteFloat("window_scale", model->window_scale); writer->WriteEnum("scale_quality", model->scale_quality, Enum_ScaleQuality); writer->WriteBoolean("show_fps", model->show_fps); writer->WriteBoolean("multi_threading", model->multithreading); writer->WriteBoolean("trap_cursor", model->trap_cursor); writer->WriteBoolean("auto_open_shops", model->auto_open_shops); writer->WriteInt32("scenario_select_mode", model->scenario_select_mode); writer->WriteBoolean("scenario_unlocking_enabled", model->scenario_unlocking_enabled); writer->WriteBoolean("scenario_hide_mega_park", model->scenario_hide_mega_park); writer->WriteString("last_game_directory", model->last_save_game_directory); writer->WriteString("last_landscape_directory", model->last_save_landscape_directory); writer->WriteString("last_scenario_directory", model->last_save_scenario_directory); writer->WriteString("last_track_directory", model->last_save_track_directory); writer->WriteBoolean("use_native_browse_dialog", model->use_native_browse_dialog); writer->WriteInt32("window_limit", model->window_limit); writer->WriteBoolean("zoom_to_cursor", model->zoom_to_cursor); writer->WriteBoolean("render_weather_effects", model->render_weather_effects); writer->WriteBoolean("render_weather_gloom", model->render_weather_gloom); writer->WriteBoolean("show_guest_purchases", model->show_guest_purchases); writer->WriteBoolean("show_real_names_of_guests", model->show_real_names_of_guests); writer->WriteBoolean("allow_early_completion", model->allow_early_completion); writer->WriteEnum("virtual_floor_style", model->virtual_floor_style, Enum_VirtualFloorStyle); writer->WriteBoolean("transparent_screenshot", model->transparent_screenshot); writer->WriteInt64("last_version_check_time", model->last_version_check_time); } static void ReadInterface(IIniReader* reader) { if (reader->ReadSection("interface")) { auto model = &gConfigInterface; model->toolbar_show_finances = reader->GetBoolean("toolbar_show_finances", true); model->toolbar_show_research = reader->GetBoolean("toolbar_show_research", true); model->toolbar_show_cheats = reader->GetBoolean("toolbar_show_cheats", false); model->toolbar_show_news = reader->GetBoolean("toolbar_show_news", false); model->toolbar_show_mute = reader->GetBoolean("toolbar_show_mute", false); model->toolbar_show_chat = reader->GetBoolean("toolbar_show_chat", false); model->toolbar_show_zoom = reader->GetBoolean("toolbar_show_zoom", true); model->console_small_font = reader->GetBoolean("console_small_font", false); model->current_theme_preset = reader->GetCString("current_theme", "*RCT2"); model->current_title_sequence_preset = reader->GetCString("current_title_sequence", "*OPENRCT2"); model->random_title_sequence = reader->GetBoolean("random_title_sequence", false); model->object_selection_filter_flags = reader->GetInt32("object_selection_filter_flags", 0x3FFF); model->scenarioselect_last_tab = reader->GetInt32("scenarioselect_last_tab", 0); } } static void WriteInterface(IIniWriter* writer) { auto model = &gConfigInterface; writer->WriteSection("interface"); writer->WriteBoolean("toolbar_show_finances", model->toolbar_show_finances); writer->WriteBoolean("toolbar_show_research", model->toolbar_show_research); writer->WriteBoolean("toolbar_show_cheats", model->toolbar_show_cheats); writer->WriteBoolean("toolbar_show_news", model->toolbar_show_news); writer->WriteBoolean("toolbar_show_mute", model->toolbar_show_mute); writer->WriteBoolean("toolbar_show_chat", model->toolbar_show_chat); writer->WriteBoolean("toolbar_show_zoom", model->toolbar_show_zoom); writer->WriteBoolean("console_small_font", model->console_small_font); writer->WriteString("current_theme", model->current_theme_preset); writer->WriteString("current_title_sequence", model->current_title_sequence_preset); writer->WriteBoolean("random_title_sequence", model->random_title_sequence); writer->WriteInt32("object_selection_filter_flags", model->object_selection_filter_flags); writer->WriteInt32("scenarioselect_last_tab", model->scenarioselect_last_tab); } static void ReadSound(IIniReader* reader) { if (reader->ReadSection("sound")) { auto model = &gConfigSound; model->device = reader->GetCString("audio_device", nullptr); model->master_sound_enabled = reader->GetBoolean("master_sound", true); model->master_volume = reader->GetInt32("master_volume", 100); model->title_music = reader->GetInt32("title_music", 2); model->sound_enabled = reader->GetBoolean("sound", true); model->sound_volume = reader->GetInt32("sound_volume", 100); model->ride_music_enabled = reader->GetBoolean("ride_music", true); model->ride_music_volume = reader->GetInt32("ride_music_volume", 100); model->audio_focus = reader->GetBoolean("audio_focus", false); } } static void WriteSound(IIniWriter* writer) { auto model = &gConfigSound; writer->WriteSection("sound"); writer->WriteString("audio_device", model->device); writer->WriteBoolean("master_sound", model->master_sound_enabled); writer->WriteInt32("master_volume", model->master_volume); writer->WriteInt32("title_music", model->title_music); writer->WriteBoolean("sound", model->sound_enabled); writer->WriteInt32("sound_volume", model->sound_volume); writer->WriteBoolean("ride_music", model->ride_music_enabled); writer->WriteInt32("ride_music_volume", model->ride_music_volume); writer->WriteBoolean("audio_focus", model->audio_focus); } static void ReadNetwork(IIniReader* reader) { if (reader->ReadSection("network")) { // If the `player_name` setting is missing or equal to the empty string // use the logged-in user's username instead auto playerName = reader->GetString("player_name", ""); if (playerName.empty()) { playerName = platform_get_username(); if (playerName.empty()) { playerName = "Player"; } } // Trim any whitespace before or after the player's name, // to avoid people pretending to be someone else playerName = String::Trim(playerName); auto model = &gConfigNetwork; model->player_name = playerName; model->default_port = reader->GetInt32("default_port", NETWORK_DEFAULT_PORT); model->listen_address = reader->GetString("listen_address", ""); model->default_password = reader->GetString("default_password", ""); model->stay_connected = reader->GetBoolean("stay_connected", true); model->advertise = reader->GetBoolean("advertise", true); model->advertise_address = reader->GetString("advertise_address", ""); model->maxplayers = reader->GetInt32("maxplayers", 16); model->server_name = reader->GetString("server_name", "Server"); model->server_description = reader->GetString("server_description", ""); model->server_greeting = reader->GetString("server_greeting", ""); model->master_server_url = reader->GetString("master_server_url", ""); model->provider_name = reader->GetString("provider_name", ""); model->provider_email = reader->GetString("provider_email", ""); model->provider_website = reader->GetString("provider_website", ""); model->known_keys_only = reader->GetBoolean("known_keys_only", false); model->log_chat = reader->GetBoolean("log_chat", false); model->log_server_actions = reader->GetBoolean("log_server_actions", false); model->pause_server_if_no_clients = reader->GetBoolean("pause_server_if_no_clients", false); model->desync_debugging = reader->GetBoolean("desync_debugging", false); } } static void WriteNetwork(IIniWriter* writer) { auto model = &gConfigNetwork; writer->WriteSection("network"); writer->WriteString("player_name", model->player_name); writer->WriteInt32("default_port", model->default_port); writer->WriteString("listen_address", model->listen_address); writer->WriteString("default_password", model->default_password); writer->WriteBoolean("stay_connected", model->stay_connected); writer->WriteBoolean("advertise", model->advertise); writer->WriteString("advertise_address", model->advertise_address); writer->WriteInt32("maxplayers", model->maxplayers); writer->WriteString("server_name", model->server_name); writer->WriteString("server_description", model->server_description); writer->WriteString("server_greeting", model->server_greeting); writer->WriteString("master_server_url", model->master_server_url); writer->WriteString("provider_name", model->provider_name); writer->WriteString("provider_email", model->provider_email); writer->WriteString("provider_website", model->provider_website); writer->WriteBoolean("known_keys_only", model->known_keys_only); writer->WriteBoolean("log_chat", model->log_chat); writer->WriteBoolean("log_server_actions", model->log_server_actions); writer->WriteBoolean("pause_server_if_no_clients", model->pause_server_if_no_clients); writer->WriteBoolean("desync_debugging", model->desync_debugging); } static void ReadNotifications(IIniReader* reader) { if (reader->ReadSection("notifications")) { auto model = &gConfigNotifications; model->park_award = reader->GetBoolean("park_award", true); model->park_marketing_campaign_finished = reader->GetBoolean("park_marketing_campaign_finished", true); model->park_warnings = reader->GetBoolean("park_warnings", true); model->park_rating_warnings = reader->GetBoolean("park_rating_warnings", true); model->ride_broken_down = reader->GetBoolean("ride_broken_down", true); model->ride_crashed = reader->GetBoolean("ride_crashed", true); model->ride_casualties = reader->GetBoolean("ride_casualties", true); model->ride_warnings = reader->GetBoolean("ride_warnings", true); model->ride_researched = reader->GetBoolean("ride_researched", true); model->ride_stalled_vehicles = reader->GetBoolean("ride_stalled_vehicles", true); model->guest_warnings = reader->GetBoolean("guest_warnings", true); model->guest_left_park = reader->GetBoolean("guest_left_park", true); model->guest_queuing_for_ride = reader->GetBoolean("guest_queuing_for_ride", true); model->guest_on_ride = reader->GetBoolean("guest_on_ride", true); model->guest_left_ride = reader->GetBoolean("guest_left_ride", true); model->guest_bought_item = reader->GetBoolean("guest_bought_item", true); model->guest_used_facility = reader->GetBoolean("guest_used_facility", true); model->guest_died = reader->GetBoolean("guest_died", true); } } static void WriteNotifications(IIniWriter* writer) { auto model = &gConfigNotifications; writer->WriteSection("notifications"); writer->WriteBoolean("park_award", model->park_award); writer->WriteBoolean("park_marketing_campaign_finished", model->park_marketing_campaign_finished); writer->WriteBoolean("park_warnings", model->park_warnings); writer->WriteBoolean("park_rating_warnings", model->park_rating_warnings); writer->WriteBoolean("ride_broken_down", model->ride_broken_down); writer->WriteBoolean("ride_crashed", model->ride_crashed); writer->WriteBoolean("ride_casualties", model->ride_casualties); writer->WriteBoolean("ride_warnings", model->ride_warnings); writer->WriteBoolean("ride_researched", model->ride_researched); writer->WriteBoolean("ride_stalled_vehicles", model->ride_stalled_vehicles); writer->WriteBoolean("guest_warnings", model->guest_warnings); writer->WriteBoolean("guest_left_park", model->guest_left_park); writer->WriteBoolean("guest_queuing_for_ride", model->guest_queuing_for_ride); writer->WriteBoolean("guest_on_ride", model->guest_on_ride); writer->WriteBoolean("guest_left_ride", model->guest_left_ride); writer->WriteBoolean("guest_bought_item", model->guest_bought_item); writer->WriteBoolean("guest_used_facility", model->guest_used_facility); writer->WriteBoolean("guest_died", model->guest_died); } static void ReadFont(IIniReader* reader) { if (reader->ReadSection("font")) { auto model = &gConfigFonts; model->file_name = reader->GetCString("file_name", nullptr); model->font_name = reader->GetCString("font_name", nullptr); model->x_offset = reader->GetInt32("x_offset", false); model->y_offset = reader->GetInt32("y_offset", true); model->size_tiny = reader->GetInt32("size_tiny", true); model->size_small = reader->GetInt32("size_small", false); model->size_medium = reader->GetInt32("size_medium", false); model->size_big = reader->GetInt32("size_big", false); model->height_tiny = reader->GetInt32("height_tiny", false); model->height_small = reader->GetInt32("height_small", false); model->height_medium = reader->GetInt32("height_medium", false); model->height_big = reader->GetInt32("height_big", false); model->enable_hinting = reader->GetBoolean("enable_hinting", true); model->hinting_threshold = reader->GetInt32("hinting_threshold", false); } } static void WriteFont(IIniWriter* writer) { auto model = &gConfigFonts; writer->WriteSection("font"); writer->WriteString("file_name", model->file_name); writer->WriteString("font_name", model->font_name); writer->WriteInt32("x_offset", model->x_offset); writer->WriteInt32("y_offset", model->y_offset); writer->WriteInt32("size_tiny", model->size_tiny); writer->WriteInt32("size_small", model->size_small); writer->WriteInt32("size_medium", model->size_medium); writer->WriteInt32("size_big", model->size_big); writer->WriteInt32("height_tiny", model->height_tiny); writer->WriteInt32("height_small", model->height_small); writer->WriteInt32("height_medium", model->height_medium); writer->WriteInt32("height_big", model->height_big); writer->WriteBoolean("enable_hinting", model->enable_hinting); writer->WriteInt32("hinting_threshold", model->hinting_threshold); } static void ReadPlugin(IIniReader* reader) { if (reader->ReadSection("plugin")) { auto model = &gConfigPlugin; model->enable_hot_reloading = reader->GetBoolean("enable_hot_reloading", false); model->allowed_hosts = reader->GetString("allowed_hosts", ""); } } static void WritePlugin(IIniWriter* writer) { auto model = &gConfigPlugin; writer->WriteSection("plugin"); writer->WriteBoolean("enable_hot_reloading", model->enable_hot_reloading); writer->WriteString("allowed_hosts", model->allowed_hosts); } static bool SetDefaults() { try { auto reader = std::unique_ptr(CreateDefaultIniReader()); ReadGeneral(reader.get()); ReadInterface(reader.get()); ReadSound(reader.get()); ReadNetwork(reader.get()); ReadNotifications(reader.get()); ReadFont(reader.get()); ReadPlugin(reader.get()); return true; } catch (const std::exception&) { return false; } } static bool ReadFile(const std::string& path) { try { auto fs = FileStream(path, FILE_MODE_OPEN); auto reader = std::unique_ptr(CreateIniReader(&fs)); ReadGeneral(reader.get()); ReadInterface(reader.get()); ReadSound(reader.get()); ReadNetwork(reader.get()); ReadNotifications(reader.get()); ReadFont(reader.get()); ReadPlugin(reader.get()); return true; } catch (const std::exception&) { return false; } } static bool WriteFile(const std::string& path) { try { auto directory = Path::GetDirectory(path); Path::CreateDirectory(directory); auto fs = FileStream(path, FILE_MODE_WRITE); auto writer = std::unique_ptr(CreateIniWriter(&fs)); WriteGeneral(writer.get()); WriteInterface(writer.get()); WriteSound(writer.get()); WriteNetwork(writer.get()); WriteNotifications(writer.get()); WriteFont(writer.get()); WritePlugin(writer.get()); return true; } catch (const std::exception& ex) { Console::WriteLine("Error saving to '%s'", path.c_str()); Console::WriteLine(ex.what()); return false; } } /** * Attempts to find the RCT1 installation directory. * @returns Path to RCT1, if found. Empty string otherwise. */ static std::string FindRCT1Path() { log_verbose("config_find_rct1_path(...)"); static constexpr const utf8* searchLocations[] = { R"(C:\Program Files\Steam\steamapps\common\Rollercoaster Tycoon Deluxe)", R"(C:\Program Files (x86)\Steam\steamapps\common\Rollercoaster Tycoon Deluxe)", R"(C:\GOG Games\RollerCoaster Tycoon Deluxe)", R"(C:\Program Files\GalaxyClient\Games\RollerCoaster Tycoon Deluxe)", R"(C:\Program Files (x86)\GalaxyClient\Games\RollerCoaster Tycoon Deluxe)", R"(C:\Program Files\Hasbro Interactive\RollerCoaster Tycoon)", R"(C:\Program Files (x86)\Hasbro Interactive\RollerCoaster Tycoon)", }; for (const utf8* location : searchLocations) { if (RCT1DataPresentAtLocation(location)) { return location; } } utf8 steamPath[2048] = { 0 }; if (platform_get_steam_path(steamPath, sizeof(steamPath))) { std::string location = Path::Combine(steamPath, platform_get_rct1_steam_dir()); if (RCT1DataPresentAtLocation(location.c_str())) { return location; } } auto exePath = Path::GetDirectory(Platform::GetCurrentExecutablePath()); if (RCT1DataPresentAtLocation(exePath.c_str())) { return exePath; } return std::string(); } /** * Attempts to find the RCT2 installation directory. * This should be created from some other resource when OpenRCT2 grows. * @returns Path to RCT2, if found. Empty string otherwise. */ static std::string FindRCT2Path() { log_verbose("config_find_rct2_path(...)"); static constexpr const utf8* searchLocations[] = { R"(C:\Program Files\Steam\steamapps\common\Rollercoaster Tycoon 2)", R"(C:\Program Files (x86)\Steam\steamapps\common\Rollercoaster Tycoon 2)", R"(C:\GOG Games\RollerCoaster Tycoon 2 Triple Thrill Pack)", R"(C:\Program Files\GalaxyClient\Games\RollerCoaster Tycoon 2 Triple Thrill Pack)", R"(C:\Program Files (x86)\GalaxyClient\Games\RollerCoaster Tycoon 2 Triple Thrill Pack)", R"(C:\Program Files\Atari\RollerCoaster Tycoon 2)", R"(C:\Program Files (x86)\Atari\RollerCoaster Tycoon 2)", R"(C:\Program Files\Infogrames\RollerCoaster Tycoon 2)", R"(C:\Program Files (x86)\Infogrames\RollerCoaster Tycoon 2)", R"(C:\Program Files\Infogrames Interactive\RollerCoaster Tycoon 2)", R"(C:\Program Files (x86)\Infogrames Interactive\RollerCoaster Tycoon 2)", }; for (const utf8* location : searchLocations) { if (platform_original_game_data_exists(location)) { return location; } } utf8 steamPath[2048] = { 0 }; if (platform_get_steam_path(steamPath, sizeof(steamPath))) { std::string location = Path::Combine(steamPath, platform_get_rct2_steam_dir()); if (platform_original_game_data_exists(location.c_str())) { return location; } } auto discordPath = Platform::GetFolderPath(SPECIAL_FOLDER::RCT2_DISCORD); if (!discordPath.empty() && platform_original_game_data_exists(discordPath.c_str())) { return discordPath; } auto exePath = Path::GetDirectory(Platform::GetCurrentExecutablePath()); if (platform_original_game_data_exists(exePath.c_str())) { return exePath; } return std::string(); } static bool SelectGogInstaller(utf8* installerPath) { file_dialog_desc desc; memset(&desc, 0, sizeof(desc)); desc.type = FileDialogType::Open; desc.title = language_get_string(STR_SELECT_GOG_INSTALLER); desc.filters[0].name = language_get_string(STR_GOG_INSTALLER); desc.filters[0].pattern = "*.exe"; desc.filters[1].name = language_get_string(STR_ALL_FILES); desc.filters[1].pattern = "*"; desc.filters[2].name = nullptr; desc.initial_directory = Platform::GetFolderPath(SPECIAL_FOLDER::USER_HOME).c_str(); return platform_open_common_file_dialog(installerPath, &desc, 4096); } static bool ExtractGogInstaller(const utf8* installerPath, const utf8* targetPath) { std::string path; std::string output; if (!Platform::FindApp("innoextract", &path)) { log_error("Please install innoextract to extract files from GOG."); return false; } int32_t exit_status = Platform::Execute( String::Format("%s '%s' --exclude-temp --output-dir '%s'", path.c_str(), installerPath, targetPath), &output); log_info("Exit status %d", exit_status); return exit_status == 0; } } // namespace Config GeneralConfiguration gConfigGeneral; InterfaceConfiguration gConfigInterface; SoundConfiguration gConfigSound; NetworkConfiguration gConfigNetwork; NotificationConfiguration gConfigNotifications; FontConfiguration gConfigFonts; PluginConfiguration gConfigPlugin; void config_set_defaults() { config_release(); Config::SetDefaults(); } bool config_open(const utf8* path) { if (!File::Exists(path)) { return false; } config_release(); auto result = Config::ReadFile(path); if (result) { currency_load_custom_currency_config(); } return result; } bool config_save(const utf8* path) { return Config::WriteFile(path); } void config_release() { SafeFree(gConfigGeneral.rct1_path); SafeFree(gConfigGeneral.rct2_path); SafeFree(gConfigGeneral.custom_currency_symbol); SafeFree(gConfigGeneral.last_save_game_directory); SafeFree(gConfigGeneral.last_save_landscape_directory); SafeFree(gConfigGeneral.last_save_scenario_directory); SafeFree(gConfigGeneral.last_save_track_directory); SafeFree(gConfigGeneral.last_run_version); SafeFree(gConfigInterface.current_theme_preset); SafeFree(gConfigInterface.current_title_sequence_preset); SafeFree(gConfigSound.device); SafeFree(gConfigFonts.file_name); SafeFree(gConfigFonts.font_name); } void config_get_default_path(utf8* outPath, size_t size) { platform_get_user_directory(outPath, nullptr, size); Path::Append(outPath, size, "config.ini"); } bool config_save_default() { utf8 path[MAX_PATH]; config_get_default_path(path, sizeof(path)); return config_save(path); } bool config_find_or_browse_install_directory() { std::string path = Config::FindRCT2Path(); if (!path.empty()) { Memory::Free(gConfigGeneral.rct2_path); gConfigGeneral.rct2_path = String::Duplicate(path.c_str()); } else { if (gOpenRCT2Headless) { return false; } auto uiContext = GetContext()->GetUiContext(); if (!uiContext->HasFilePicker()) { uiContext->ShowMessageBox(format_string(STR_NEEDS_RCT2_FILES_MANUAL, nullptr)); return false; } try { const char* g1DatPath = PATH_SEPARATOR "Data" PATH_SEPARATOR "g1.dat"; while (true) { uiContext->ShowMessageBox(format_string(STR_NEEDS_RCT2_FILES, nullptr)); std::string gog = language_get_string(STR_OWN_ON_GOG); std::string hdd = language_get_string(STR_INSTALLED_ON_HDD); std::vector options; std::string chosenOption; if (uiContext->HasMenuSupport()) { options.push_back(hdd); options.push_back(gog); int optionIndex = uiContext->ShowMenuDialog( options, language_get_string(STR_OPENRCT2_SETUP), language_get_string(STR_WHICH_APPLIES_BEST)); if (optionIndex < 0 || static_cast(optionIndex) >= options.size()) { // graceful fallback if app errors or user exits out of window chosenOption = hdd; } else { chosenOption = options[optionIndex]; } } else { chosenOption = hdd; } std::string installPath; if (chosenOption == hdd) { installPath = uiContext->ShowDirectoryDialog(language_get_string(STR_PICK_RCT2_DIR)); } else if (chosenOption == gog) { // Check if innoextract is installed. If not, prompt the user to install it. std::string dummy; if (!Platform::FindApp("innoextract", &dummy)) { uiContext->ShowMessageBox(format_string(STR_INSTALL_INNOEXTRACT, nullptr)); return false; } const std::string dest = Path::Combine( GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::CONFIG), "rct2"); while (true) { uiContext->ShowMessageBox(language_get_string(STR_PLEASE_SELECT_GOG_INSTALLER)); utf8 gogPath[4096]; if (!Config::SelectGogInstaller(gogPath)) { // The user clicked "Cancel", so stop trying. return false; } uiContext->ShowMessageBox(language_get_string(STR_THIS_WILL_TAKE_A_FEW_MINUTES)); if (Config::ExtractGogInstaller(gogPath, dest.c_str())) break; uiContext->ShowMessageBox(language_get_string(STR_NOT_THE_GOG_INSTALLER)); } installPath = Path::Combine(dest, "app"); } if (installPath.empty()) { return false; } Memory::Free(gConfigGeneral.rct2_path); gConfigGeneral.rct2_path = String::Duplicate(installPath.c_str()); if (platform_original_game_data_exists(installPath.c_str())) { return true; } uiContext->ShowMessageBox(format_string(STR_COULD_NOT_FIND_AT_PATH, &g1DatPath)); } } catch (const std::exception& ex) { Console::Error::WriteLine(ex.what()); return false; } } // While we're at it, also check if the player has RCT1 std::string rct1Path = Config::FindRCT1Path(); if (!rct1Path.empty()) { free(gConfigGeneral.rct1_path); gConfigGeneral.rct1_path = String::Duplicate(rct1Path.c_str()); } return true; } std::string FindCsg1datAtLocation(const utf8* path) { char buffer[MAX_PATH], checkPath1[MAX_PATH], checkPath2[MAX_PATH]; safe_strcpy(buffer, path, MAX_PATH); safe_strcat_path(buffer, "Data", MAX_PATH); safe_strcpy(checkPath1, buffer, MAX_PATH); safe_strcpy(checkPath2, buffer, MAX_PATH); safe_strcat_path(checkPath1, "CSG1.DAT", MAX_PATH); safe_strcat_path(checkPath2, "CSG1.1", MAX_PATH); // Since Linux is case sensitive (and macOS sometimes too), make sure we handle case properly. std::string path1result = Path::ResolveCasing(checkPath1); if (!path1result.empty()) { return path1result; } std::string path2result = Path::ResolveCasing(checkPath2); return path2result; } bool Csg1datPresentAtLocation(const utf8* path) { std::string location = FindCsg1datAtLocation(path); return !location.empty(); } std::string FindCsg1idatAtLocation(const utf8* path) { auto result1 = Path::ResolveCasing(Path::Combine(path, "Data", "CSG1I.DAT")); if (!result1.empty()) { return result1; } auto result2 = Path::ResolveCasing(Path::Combine(path, "RCTdeluxe_install", "Data", "CSG1I.DAT")); return result2; } bool Csg1idatPresentAtLocation(const utf8* path) { std::string location = FindCsg1idatAtLocation(path); return !location.empty(); } bool RCT1DataPresentAtLocation(const utf8* path) { return Csg1datPresentAtLocation(path) && Csg1idatPresentAtLocation(path) && CsgAtLocationIsUsable(path); } bool CsgIsUsable(const rct_gx& csg) { return csg.header.total_size == RCT1_LL_CSG1_DAT_FILE_SIZE && csg.header.num_entries == RCT1_NUM_LL_CSG_ENTRIES; } bool CsgAtLocationIsUsable(const utf8* path) { auto csg1HeaderPath = FindCsg1idatAtLocation(path); if (csg1HeaderPath.empty()) { return false; } auto csg1DataPath = FindCsg1datAtLocation(path); if (csg1DataPath.empty()) { return false; } auto fileHeader = FileStream(csg1HeaderPath, FILE_MODE_OPEN); auto fileData = FileStream(csg1DataPath, FILE_MODE_OPEN); size_t fileHeaderSize = fileHeader.GetLength(); size_t fileDataSize = fileData.GetLength(); rct_gx csg = {}; csg.header.num_entries = static_cast(fileHeaderSize / sizeof(rct_g1_element_32bit)); csg.header.total_size = static_cast(fileDataSize); return CsgIsUsable(csg); }