1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Config.h"
11 
12 #include "../Context.h"
13 #include "../OpenRCT2.h"
14 #include "../PlatformEnvironment.h"
15 #include "../core/Console.hpp"
16 #include "../core/File.h"
17 #include "../core/FileStream.h"
18 #include "../core/Memory.hpp"
19 #include "../core/Path.hpp"
20 #include "../core/String.hpp"
21 #include "../drawing/IDrawingEngine.h"
22 #include "../interface/Window.h"
23 #include "../localisation/Currency.h"
24 #include "../localisation/Date.h"
25 #include "../localisation/Language.h"
26 #include "../localisation/Localisation.h"
27 #include "../localisation/StringIds.h"
28 #include "../network/network.h"
29 #include "../paint/VirtualFloor.h"
30 #include "../platform/Platform2.h"
31 #include "../platform/platform.h"
32 #include "../rct1/RCT1.h"
33 #include "../scenario/Scenario.h"
34 #include "../ui/UiContext.h"
35 #include "../util/Util.h"
36 #include "ConfigEnum.hpp"
37 #include "IniReader.hpp"
38 #include "IniWriter.hpp"
39 
40 using namespace OpenRCT2;
41 using namespace OpenRCT2::Ui;
42 
43 namespace Config
44 {
45 #pragma region Enums
46 
47     static const auto Enum_MeasurementFormat = ConfigEnum<MeasurementFormat>({
48         ConfigEnumEntry<MeasurementFormat>("IMPERIAL", MeasurementFormat::Imperial),
49         ConfigEnumEntry<MeasurementFormat>("METRIC", MeasurementFormat::Metric),
50         ConfigEnumEntry<MeasurementFormat>("SI", MeasurementFormat::SI),
51     });
52 
53     static const auto Enum_Currency = ConfigEnum<CurrencyType>({
54         ConfigEnumEntry<CurrencyType>("GBP", CurrencyType::Pounds),
55         ConfigEnumEntry<CurrencyType>("USD", CurrencyType::Dollars),
56         ConfigEnumEntry<CurrencyType>("FRF", CurrencyType::Franc),
57         ConfigEnumEntry<CurrencyType>("DEM", CurrencyType::DeutscheMark),
58         ConfigEnumEntry<CurrencyType>("JPY", CurrencyType::Yen),
59         ConfigEnumEntry<CurrencyType>("ESP", CurrencyType::Peseta),
60         ConfigEnumEntry<CurrencyType>("ITL", CurrencyType::Lira),
61         ConfigEnumEntry<CurrencyType>("NLG", CurrencyType::Guilders),
62         ConfigEnumEntry<CurrencyType>("SEK", CurrencyType::Krona),
63         ConfigEnumEntry<CurrencyType>("EUR", CurrencyType::Euros),
64         ConfigEnumEntry<CurrencyType>("KRW", CurrencyType::Won),
65         ConfigEnumEntry<CurrencyType>("RUB", CurrencyType::Rouble),
66         ConfigEnumEntry<CurrencyType>("CZK", CurrencyType::CzechKoruna),
67         ConfigEnumEntry<CurrencyType>("HKD", CurrencyType::HKD),
68         ConfigEnumEntry<CurrencyType>("TWD", CurrencyType::TWD),
69         ConfigEnumEntry<CurrencyType>("CNY", CurrencyType::Yuan),
70         ConfigEnumEntry<CurrencyType>("HUF", CurrencyType::Forint),
71         ConfigEnumEntry<CurrencyType>("CUSTOM", CurrencyType::Custom),
72     });
73 
74     static const auto Enum_CurrencySymbolAffix = ConfigEnum<CurrencyAffix>({
75         ConfigEnumEntry<CurrencyAffix>("PREFIX", CurrencyAffix::Prefix),
76         ConfigEnumEntry<CurrencyAffix>("SUFFIX", CurrencyAffix::Suffix),
77     });
78 
79     static const auto Enum_DateFormat = ConfigEnum<int32_t>({
80         ConfigEnumEntry<int32_t>("DD/MM/YY", DATE_FORMAT_DAY_MONTH_YEAR),
81         ConfigEnumEntry<int32_t>("MM/DD/YY", DATE_FORMAT_MONTH_DAY_YEAR),
82         ConfigEnumEntry<int32_t>("YY/MM/DD", DATE_FORMAT_YEAR_MONTH_DAY),
83         ConfigEnumEntry<int32_t>("YY/DD/MM", DATE_FORMAT_YEAR_DAY_MONTH),
84     });
85 
86     static const auto Enum_DrawingEngine = ConfigEnum<DrawingEngine>({
87         ConfigEnumEntry<DrawingEngine>("SOFTWARE", DrawingEngine::Software),
88         ConfigEnumEntry<DrawingEngine>("SOFTWARE_HWD", DrawingEngine::SoftwareWithHardwareDisplay),
89         ConfigEnumEntry<DrawingEngine>("OPENGL", DrawingEngine::OpenGL),
90     });
91 
92     static const auto Enum_Temperature = ConfigEnum<TemperatureUnit>({
93         ConfigEnumEntry<TemperatureUnit>("CELSIUS", TemperatureUnit::Celsius),
94         ConfigEnumEntry<TemperatureUnit>("FAHRENHEIT", TemperatureUnit::Fahrenheit),
95     });
96 
97     static const auto Enum_ScaleQuality = ConfigEnum<ScaleQuality>({
98         ConfigEnumEntry<ScaleQuality>("NEAREST_NEIGHBOUR", ScaleQuality::NearestNeighbour),
99         ConfigEnumEntry<ScaleQuality>("LINEAR", ScaleQuality::Linear),
100         ConfigEnumEntry<ScaleQuality>("SMOOTH_NEAREST_NEIGHBOUR", ScaleQuality::SmoothNearestNeighbour),
101     });
102 
103     static const auto Enum_Sort = ConfigEnum<Sort>({
104         ConfigEnumEntry<Sort>("NAME_ASCENDING", Sort::NameAscending),
105         ConfigEnumEntry<Sort>("NAME_DESCENDING", Sort::NameDescending),
106         ConfigEnumEntry<Sort>("DATE_ASCENDING", Sort::DateAscending),
107         ConfigEnumEntry<Sort>("DATE_DESCENDING", Sort::DateDescending),
108     });
109 
110     static const auto Enum_VirtualFloorStyle = ConfigEnum<VirtualFloorStyles>({
111         ConfigEnumEntry<VirtualFloorStyles>("OFF", VirtualFloorStyles::Off),
112         ConfigEnumEntry<VirtualFloorStyles>("CLEAR", VirtualFloorStyles::Clear),
113         ConfigEnumEntry<VirtualFloorStyles>("GLASSY", VirtualFloorStyles::Glassy),
114     });
115 
116     /**
117      * Config enum wrapping LanguagesDescriptors.
118      */
119     static class LanguageConfigEnum final : public IConfigEnum<int32_t>
120     {
121     public:
GetName(int32_t value) const122         std::string GetName(int32_t value) const override
123         {
124             return LanguagesDescriptors[value].locale;
125         }
126 
GetValue(const std::string & key,int32_t defaultValue) const127         int32_t GetValue(const std::string& key, int32_t defaultValue) const override
128         {
129             int32_t i = 0;
130             for (const auto& langDesc : LanguagesDescriptors)
131             {
132                 if (String::Equals(key.c_str(), langDesc.locale))
133                 {
134                     return i;
135                 }
136                 i++;
137             }
138             return defaultValue;
139         }
140     } Enum_LanguageEnum;
141 
142 #pragma endregion
143 
ReadGeneral(IIniReader * reader)144     static void ReadGeneral(IIniReader* reader)
145     {
146         if (reader->ReadSection("general"))
147         {
148             auto model = &gConfigGeneral;
149             model->always_show_gridlines = reader->GetBoolean("always_show_gridlines", false);
150             model->autosave_frequency = reader->GetInt32("autosave", AUTOSAVE_EVERY_5MINUTES);
151             model->autosave_amount = reader->GetInt32("autosave_amount", DEFAULT_NUM_AUTOSAVES_TO_KEEP);
152             model->confirmation_prompt = reader->GetBoolean("confirmation_prompt", false);
153             model->currency_format = reader->GetEnum<CurrencyType>(
154                 "currency_format", platform_get_locale_currency(), Enum_Currency);
155             model->custom_currency_rate = reader->GetInt32("custom_currency_rate", 10);
156             model->custom_currency_affix = reader->GetEnum<CurrencyAffix>(
157                 "custom_currency_affix", CurrencyAffix::Suffix, Enum_CurrencySymbolAffix);
158             model->custom_currency_symbol = reader->GetCString("custom_currency_symbol", "Ctm");
159             model->edge_scrolling = reader->GetBoolean("edge_scrolling", true);
160             model->edge_scrolling_speed = reader->GetInt32("edge_scrolling_speed", 12);
161             model->fullscreen_mode = reader->GetInt32("fullscreen_mode", 0);
162             model->fullscreen_height = reader->GetInt32("fullscreen_height", -1);
163             model->fullscreen_width = reader->GetInt32("fullscreen_width", -1);
164             model->rct1_path = reader->GetCString("rct1_path", nullptr);
165             model->rct2_path = reader->GetCString("game_path", nullptr);
166             model->landscape_smoothing = reader->GetBoolean("landscape_smoothing", true);
167             model->language = reader->GetEnum<int32_t>("language", platform_get_locale_language(), Enum_LanguageEnum);
168             model->measurement_format = reader->GetEnum<MeasurementFormat>(
169                 "measurement_format", platform_get_locale_measurement_format(), Enum_MeasurementFormat);
170             model->play_intro = reader->GetBoolean("play_intro", false);
171             model->save_plugin_data = reader->GetBoolean("save_plugin_data", true);
172             model->debugging_tools = reader->GetBoolean("debugging_tools", false);
173             model->show_height_as_units = reader->GetBoolean("show_height_as_units", false);
174             model->temperature_format = reader->GetEnum<TemperatureUnit>(
175                 "temperature_format", platform_get_locale_temperature_format(), Enum_Temperature);
176             model->window_height = reader->GetInt32("window_height", -1);
177             model->window_snap_proximity = reader->GetInt32("window_snap_proximity", 5);
178             model->window_width = reader->GetInt32("window_width", -1);
179             model->default_display = reader->GetInt32("default_display", 0);
180             model->drawing_engine = reader->GetEnum<DrawingEngine>(
181                 "drawing_engine", DrawingEngine::Software, Enum_DrawingEngine);
182             model->uncap_fps = reader->GetBoolean("uncap_fps", false);
183             model->use_vsync = reader->GetBoolean("use_vsync", true);
184             model->virtual_floor_style = reader->GetEnum<VirtualFloorStyles>(
185                 "virtual_floor_style", VirtualFloorStyles::Glassy, Enum_VirtualFloorStyle);
186             model->date_format = reader->GetEnum<int32_t>("date_format", platform_get_locale_date_format(), Enum_DateFormat);
187             model->auto_staff_placement = reader->GetBoolean("auto_staff", true);
188             model->handymen_mow_default = reader->GetBoolean("handymen_mow_default", false);
189             model->default_inspection_interval = reader->GetInt32("default_inspection_interval", 2);
190             model->last_run_version = reader->GetCString("last_run_version", nullptr);
191             model->invert_viewport_drag = reader->GetBoolean("invert_viewport_drag", false);
192             model->load_save_sort = reader->GetEnum<Sort>("load_save_sort", Sort::NameAscending, Enum_Sort);
193             model->minimize_fullscreen_focus_loss = reader->GetBoolean("minimize_fullscreen_focus_loss", true);
194             model->disable_screensaver = reader->GetBoolean("disable_screensaver", true);
195 
196             // Default config setting is false until the games canvas can be separated from the effect
197             model->day_night_cycle = reader->GetBoolean("day_night_cycle", false);
198             const bool isHardware = model->drawing_engine != DrawingEngine::Software;
199             model->enable_light_fx = isHardware && reader->GetBoolean("enable_light_fx", false);
200             model->enable_light_fx_for_vehicles = isHardware && reader->GetBoolean("enable_light_fx_for_vehicles", false);
201             model->upper_case_banners = reader->GetBoolean("upper_case_banners", false);
202             model->disable_lightning_effect = reader->GetBoolean("disable_lightning_effect", false);
203             model->allow_loading_with_incorrect_checksum = reader->GetBoolean("allow_loading_with_incorrect_checksum", true);
204             model->steam_overlay_pause = reader->GetBoolean("steam_overlay_pause", true);
205             model->window_scale = reader->GetFloat("window_scale", platform_get_default_scale());
206             model->scale_quality = reader->GetEnum<ScaleQuality>(
207                 "scale_quality", ScaleQuality::SmoothNearestNeighbour, Enum_ScaleQuality);
208             model->show_fps = reader->GetBoolean("show_fps", false);
209             model->multithreading = reader->GetBoolean("multi_threading", false);
210             model->trap_cursor = reader->GetBoolean("trap_cursor", false);
211             model->auto_open_shops = reader->GetBoolean("auto_open_shops", false);
212             model->scenario_select_mode = reader->GetInt32("scenario_select_mode", SCENARIO_SELECT_MODE_ORIGIN);
213             model->scenario_unlocking_enabled = reader->GetBoolean("scenario_unlocking_enabled", true);
214             model->scenario_hide_mega_park = reader->GetBoolean("scenario_hide_mega_park", true);
215             model->last_save_game_directory = reader->GetCString("last_game_directory", nullptr);
216             model->last_save_landscape_directory = reader->GetCString("last_landscape_directory", nullptr);
217             model->last_save_scenario_directory = reader->GetCString("last_scenario_directory", nullptr);
218             model->last_save_track_directory = reader->GetCString("last_track_directory", nullptr);
219             model->use_native_browse_dialog = reader->GetBoolean("use_native_browse_dialog", false);
220             model->window_limit = reader->GetInt32("window_limit", WINDOW_LIMIT_MAX);
221             model->zoom_to_cursor = reader->GetBoolean("zoom_to_cursor", true);
222             model->render_weather_effects = reader->GetBoolean("render_weather_effects", true);
223             model->render_weather_gloom = reader->GetBoolean("render_weather_gloom", true);
224             model->show_guest_purchases = reader->GetBoolean("show_guest_purchases", false);
225             model->show_real_names_of_guests = reader->GetBoolean("show_real_names_of_guests", true);
226             model->allow_early_completion = reader->GetBoolean("allow_early_completion", false);
227             model->transparent_screenshot = reader->GetBoolean("transparent_screenshot", true);
228             model->transparent_water = reader->GetBoolean("transparent_water", true);
229             model->last_version_check_time = reader->GetInt64("last_version_check_time", 0);
230         }
231     }
232 
WriteGeneral(IIniWriter * writer)233     static void WriteGeneral(IIniWriter* writer)
234     {
235         auto model = &gConfigGeneral;
236         writer->WriteSection("general");
237         writer->WriteBoolean("always_show_gridlines", model->always_show_gridlines);
238         writer->WriteInt32("autosave", model->autosave_frequency);
239         writer->WriteInt32("autosave_amount", model->autosave_amount);
240         writer->WriteBoolean("confirmation_prompt", model->confirmation_prompt);
241         writer->WriteEnum<CurrencyType>("currency_format", model->currency_format, Enum_Currency);
242         writer->WriteInt32("custom_currency_rate", model->custom_currency_rate);
243         writer->WriteEnum<CurrencyAffix>("custom_currency_affix", model->custom_currency_affix, Enum_CurrencySymbolAffix);
244         writer->WriteString("custom_currency_symbol", model->custom_currency_symbol);
245         writer->WriteBoolean("edge_scrolling", model->edge_scrolling);
246         writer->WriteInt32("edge_scrolling_speed", model->edge_scrolling_speed);
247         writer->WriteInt32("fullscreen_mode", model->fullscreen_mode);
248         writer->WriteInt32("fullscreen_height", model->fullscreen_height);
249         writer->WriteInt32("fullscreen_width", model->fullscreen_width);
250         writer->WriteString("rct1_path", model->rct1_path);
251         writer->WriteString("game_path", model->rct2_path);
252         writer->WriteBoolean("landscape_smoothing", model->landscape_smoothing);
253         writer->WriteEnum<int32_t>("language", model->language, Enum_LanguageEnum);
254         writer->WriteEnum<MeasurementFormat>("measurement_format", model->measurement_format, Enum_MeasurementFormat);
255         writer->WriteBoolean("play_intro", model->play_intro);
256         writer->WriteBoolean("save_plugin_data", model->save_plugin_data);
257         writer->WriteBoolean("debugging_tools", model->debugging_tools);
258         writer->WriteBoolean("show_height_as_units", model->show_height_as_units);
259         writer->WriteEnum<TemperatureUnit>("temperature_format", model->temperature_format, Enum_Temperature);
260         writer->WriteInt32("window_height", model->window_height);
261         writer->WriteInt32("window_snap_proximity", model->window_snap_proximity);
262         writer->WriteInt32("window_width", model->window_width);
263         writer->WriteInt32("default_display", model->default_display);
264         writer->WriteEnum<DrawingEngine>("drawing_engine", model->drawing_engine, Enum_DrawingEngine);
265         writer->WriteBoolean("uncap_fps", model->uncap_fps);
266         writer->WriteBoolean("use_vsync", model->use_vsync);
267         writer->WriteEnum<int32_t>("date_format", model->date_format, Enum_DateFormat);
268         writer->WriteBoolean("auto_staff", model->auto_staff_placement);
269         writer->WriteBoolean("handymen_mow_default", model->handymen_mow_default);
270         writer->WriteInt32("default_inspection_interval", model->default_inspection_interval);
271         writer->WriteString("last_run_version", model->last_run_version);
272         writer->WriteBoolean("invert_viewport_drag", model->invert_viewport_drag);
273         writer->WriteEnum<Sort>("load_save_sort", model->load_save_sort, Enum_Sort);
274         writer->WriteBoolean("minimize_fullscreen_focus_loss", model->minimize_fullscreen_focus_loss);
275         writer->WriteBoolean("disable_screensaver", model->disable_screensaver);
276         writer->WriteBoolean("day_night_cycle", model->day_night_cycle);
277         writer->WriteBoolean("enable_light_fx", model->enable_light_fx);
278         writer->WriteBoolean("enable_light_fx_for_vehicles", model->enable_light_fx_for_vehicles);
279         writer->WriteBoolean("upper_case_banners", model->upper_case_banners);
280         writer->WriteBoolean("disable_lightning_effect", model->disable_lightning_effect);
281         writer->WriteBoolean("allow_loading_with_incorrect_checksum", model->allow_loading_with_incorrect_checksum);
282         writer->WriteBoolean("steam_overlay_pause", model->steam_overlay_pause);
283         writer->WriteFloat("window_scale", model->window_scale);
284         writer->WriteEnum<ScaleQuality>("scale_quality", model->scale_quality, Enum_ScaleQuality);
285         writer->WriteBoolean("show_fps", model->show_fps);
286         writer->WriteBoolean("multi_threading", model->multithreading);
287         writer->WriteBoolean("trap_cursor", model->trap_cursor);
288         writer->WriteBoolean("auto_open_shops", model->auto_open_shops);
289         writer->WriteInt32("scenario_select_mode", model->scenario_select_mode);
290         writer->WriteBoolean("scenario_unlocking_enabled", model->scenario_unlocking_enabled);
291         writer->WriteBoolean("scenario_hide_mega_park", model->scenario_hide_mega_park);
292         writer->WriteString("last_game_directory", model->last_save_game_directory);
293         writer->WriteString("last_landscape_directory", model->last_save_landscape_directory);
294         writer->WriteString("last_scenario_directory", model->last_save_scenario_directory);
295         writer->WriteString("last_track_directory", model->last_save_track_directory);
296         writer->WriteBoolean("use_native_browse_dialog", model->use_native_browse_dialog);
297         writer->WriteInt32("window_limit", model->window_limit);
298         writer->WriteBoolean("zoom_to_cursor", model->zoom_to_cursor);
299         writer->WriteBoolean("render_weather_effects", model->render_weather_effects);
300         writer->WriteBoolean("render_weather_gloom", model->render_weather_gloom);
301         writer->WriteBoolean("show_guest_purchases", model->show_guest_purchases);
302         writer->WriteBoolean("show_real_names_of_guests", model->show_real_names_of_guests);
303         writer->WriteBoolean("allow_early_completion", model->allow_early_completion);
304         writer->WriteEnum<VirtualFloorStyles>("virtual_floor_style", model->virtual_floor_style, Enum_VirtualFloorStyle);
305         writer->WriteBoolean("transparent_screenshot", model->transparent_screenshot);
306         writer->WriteBoolean("transparent_water", model->transparent_water);
307         writer->WriteInt64("last_version_check_time", model->last_version_check_time);
308     }
309 
ReadInterface(IIniReader * reader)310     static void ReadInterface(IIniReader* reader)
311     {
312         if (reader->ReadSection("interface"))
313         {
314             auto model = &gConfigInterface;
315             model->toolbar_show_finances = reader->GetBoolean("toolbar_show_finances", true);
316             model->toolbar_show_research = reader->GetBoolean("toolbar_show_research", true);
317             model->toolbar_show_cheats = reader->GetBoolean("toolbar_show_cheats", false);
318             model->toolbar_show_news = reader->GetBoolean("toolbar_show_news", false);
319             model->toolbar_show_mute = reader->GetBoolean("toolbar_show_mute", false);
320             model->toolbar_show_chat = reader->GetBoolean("toolbar_show_chat", false);
321             model->toolbar_show_zoom = reader->GetBoolean("toolbar_show_zoom", true);
322             model->console_small_font = reader->GetBoolean("console_small_font", false);
323             model->current_theme_preset = reader->GetCString("current_theme", "*RCT2");
324             model->current_title_sequence_preset = reader->GetCString("current_title_sequence", "*OPENRCT2");
325             model->random_title_sequence = reader->GetBoolean("random_title_sequence", false);
326             model->object_selection_filter_flags = reader->GetInt32("object_selection_filter_flags", 0x3FFF);
327             model->scenarioselect_last_tab = reader->GetInt32("scenarioselect_last_tab", 0);
328         }
329     }
330 
WriteInterface(IIniWriter * writer)331     static void WriteInterface(IIniWriter* writer)
332     {
333         auto model = &gConfigInterface;
334         writer->WriteSection("interface");
335         writer->WriteBoolean("toolbar_show_finances", model->toolbar_show_finances);
336         writer->WriteBoolean("toolbar_show_research", model->toolbar_show_research);
337         writer->WriteBoolean("toolbar_show_cheats", model->toolbar_show_cheats);
338         writer->WriteBoolean("toolbar_show_news", model->toolbar_show_news);
339         writer->WriteBoolean("toolbar_show_mute", model->toolbar_show_mute);
340         writer->WriteBoolean("toolbar_show_chat", model->toolbar_show_chat);
341         writer->WriteBoolean("toolbar_show_zoom", model->toolbar_show_zoom);
342         writer->WriteBoolean("console_small_font", model->console_small_font);
343         writer->WriteString("current_theme", model->current_theme_preset);
344         writer->WriteString("current_title_sequence", model->current_title_sequence_preset);
345         writer->WriteBoolean("random_title_sequence", model->random_title_sequence);
346         writer->WriteInt32("object_selection_filter_flags", model->object_selection_filter_flags);
347         writer->WriteInt32("scenarioselect_last_tab", model->scenarioselect_last_tab);
348     }
349 
ReadSound(IIniReader * reader)350     static void ReadSound(IIniReader* reader)
351     {
352         if (reader->ReadSection("sound"))
353         {
354             auto model = &gConfigSound;
355             model->device = reader->GetCString("audio_device", nullptr);
356             model->master_sound_enabled = reader->GetBoolean("master_sound", true);
357             model->master_volume = reader->GetInt32("master_volume", 100);
358             model->title_music = reader->GetInt32("title_music", 2);
359             model->sound_enabled = reader->GetBoolean("sound", true);
360             model->sound_volume = reader->GetInt32("sound_volume", 100);
361             model->ride_music_enabled = reader->GetBoolean("ride_music", true);
362             model->ride_music_volume = reader->GetInt32("ride_music_volume", 100);
363             model->audio_focus = reader->GetBoolean("audio_focus", false);
364         }
365     }
366 
WriteSound(IIniWriter * writer)367     static void WriteSound(IIniWriter* writer)
368     {
369         auto model = &gConfigSound;
370         writer->WriteSection("sound");
371         writer->WriteString("audio_device", model->device);
372         writer->WriteBoolean("master_sound", model->master_sound_enabled);
373         writer->WriteInt32("master_volume", model->master_volume);
374         writer->WriteInt32("title_music", model->title_music);
375         writer->WriteBoolean("sound", model->sound_enabled);
376         writer->WriteInt32("sound_volume", model->sound_volume);
377         writer->WriteBoolean("ride_music", model->ride_music_enabled);
378         writer->WriteInt32("ride_music_volume", model->ride_music_volume);
379         writer->WriteBoolean("audio_focus", model->audio_focus);
380     }
381 
ReadNetwork(IIniReader * reader)382     static void ReadNetwork(IIniReader* reader)
383     {
384         if (reader->ReadSection("network"))
385         {
386             // If the `player_name` setting is missing or equal to the empty string
387             // use the logged-in user's username instead
388             auto playerName = reader->GetString("player_name", "");
389             if (playerName.empty())
390             {
391                 playerName = platform_get_username();
392                 if (playerName.empty())
393                 {
394                     playerName = "Player";
395                 }
396             }
397 
398             // Trim any whitespace before or after the player's name,
399             // to avoid people pretending to be someone else
400             playerName = String::Trim(playerName);
401 
402             auto model = &gConfigNetwork;
403             model->player_name = playerName;
404             model->default_port = reader->GetInt32("default_port", NETWORK_DEFAULT_PORT);
405             model->listen_address = reader->GetString("listen_address", "");
406             model->default_password = reader->GetString("default_password", "");
407             model->stay_connected = reader->GetBoolean("stay_connected", true);
408             model->advertise = reader->GetBoolean("advertise", true);
409             model->advertise_address = reader->GetString("advertise_address", "");
410             model->maxplayers = reader->GetInt32("maxplayers", 16);
411             model->server_name = reader->GetString("server_name", "Server");
412             model->server_description = reader->GetString("server_description", "");
413             model->server_greeting = reader->GetString("server_greeting", "");
414             model->master_server_url = reader->GetString("master_server_url", "");
415             model->provider_name = reader->GetString("provider_name", "");
416             model->provider_email = reader->GetString("provider_email", "");
417             model->provider_website = reader->GetString("provider_website", "");
418             model->known_keys_only = reader->GetBoolean("known_keys_only", false);
419             model->log_chat = reader->GetBoolean("log_chat", false);
420             model->log_server_actions = reader->GetBoolean("log_server_actions", false);
421             model->pause_server_if_no_clients = reader->GetBoolean("pause_server_if_no_clients", false);
422             model->desync_debugging = reader->GetBoolean("desync_debugging", false);
423         }
424     }
425 
WriteNetwork(IIniWriter * writer)426     static void WriteNetwork(IIniWriter* writer)
427     {
428         auto model = &gConfigNetwork;
429         writer->WriteSection("network");
430         writer->WriteString("player_name", model->player_name);
431         writer->WriteInt32("default_port", model->default_port);
432         writer->WriteString("listen_address", model->listen_address);
433         writer->WriteString("default_password", model->default_password);
434         writer->WriteBoolean("stay_connected", model->stay_connected);
435         writer->WriteBoolean("advertise", model->advertise);
436         writer->WriteString("advertise_address", model->advertise_address);
437         writer->WriteInt32("maxplayers", model->maxplayers);
438         writer->WriteString("server_name", model->server_name);
439         writer->WriteString("server_description", model->server_description);
440         writer->WriteString("server_greeting", model->server_greeting);
441         writer->WriteString("master_server_url", model->master_server_url);
442         writer->WriteString("provider_name", model->provider_name);
443         writer->WriteString("provider_email", model->provider_email);
444         writer->WriteString("provider_website", model->provider_website);
445         writer->WriteBoolean("known_keys_only", model->known_keys_only);
446         writer->WriteBoolean("log_chat", model->log_chat);
447         writer->WriteBoolean("log_server_actions", model->log_server_actions);
448         writer->WriteBoolean("pause_server_if_no_clients", model->pause_server_if_no_clients);
449         writer->WriteBoolean("desync_debugging", model->desync_debugging);
450     }
451 
ReadNotifications(IIniReader * reader)452     static void ReadNotifications(IIniReader* reader)
453     {
454         if (reader->ReadSection("notifications"))
455         {
456             auto model = &gConfigNotifications;
457             model->park_award = reader->GetBoolean("park_award", true);
458             model->park_marketing_campaign_finished = reader->GetBoolean("park_marketing_campaign_finished", true);
459             model->park_warnings = reader->GetBoolean("park_warnings", true);
460             model->park_rating_warnings = reader->GetBoolean("park_rating_warnings", true);
461             model->ride_broken_down = reader->GetBoolean("ride_broken_down", true);
462             model->ride_crashed = reader->GetBoolean("ride_crashed", true);
463             model->ride_casualties = reader->GetBoolean("ride_casualties", true);
464             model->ride_warnings = reader->GetBoolean("ride_warnings", true);
465             model->ride_researched = reader->GetBoolean("ride_researched", true);
466             model->ride_stalled_vehicles = reader->GetBoolean("ride_stalled_vehicles", true);
467             model->guest_warnings = reader->GetBoolean("guest_warnings", true);
468             model->guest_left_park = reader->GetBoolean("guest_left_park", true);
469             model->guest_queuing_for_ride = reader->GetBoolean("guest_queuing_for_ride", true);
470             model->guest_on_ride = reader->GetBoolean("guest_on_ride", true);
471             model->guest_left_ride = reader->GetBoolean("guest_left_ride", true);
472             model->guest_bought_item = reader->GetBoolean("guest_bought_item", true);
473             model->guest_used_facility = reader->GetBoolean("guest_used_facility", true);
474             model->guest_died = reader->GetBoolean("guest_died", true);
475         }
476     }
477 
WriteNotifications(IIniWriter * writer)478     static void WriteNotifications(IIniWriter* writer)
479     {
480         auto model = &gConfigNotifications;
481         writer->WriteSection("notifications");
482         writer->WriteBoolean("park_award", model->park_award);
483         writer->WriteBoolean("park_marketing_campaign_finished", model->park_marketing_campaign_finished);
484         writer->WriteBoolean("park_warnings", model->park_warnings);
485         writer->WriteBoolean("park_rating_warnings", model->park_rating_warnings);
486         writer->WriteBoolean("ride_broken_down", model->ride_broken_down);
487         writer->WriteBoolean("ride_crashed", model->ride_crashed);
488         writer->WriteBoolean("ride_casualties", model->ride_casualties);
489         writer->WriteBoolean("ride_warnings", model->ride_warnings);
490         writer->WriteBoolean("ride_researched", model->ride_researched);
491         writer->WriteBoolean("ride_stalled_vehicles", model->ride_stalled_vehicles);
492         writer->WriteBoolean("guest_warnings", model->guest_warnings);
493         writer->WriteBoolean("guest_left_park", model->guest_left_park);
494         writer->WriteBoolean("guest_queuing_for_ride", model->guest_queuing_for_ride);
495         writer->WriteBoolean("guest_on_ride", model->guest_on_ride);
496         writer->WriteBoolean("guest_left_ride", model->guest_left_ride);
497         writer->WriteBoolean("guest_bought_item", model->guest_bought_item);
498         writer->WriteBoolean("guest_used_facility", model->guest_used_facility);
499         writer->WriteBoolean("guest_died", model->guest_died);
500     }
501 
ReadFont(IIniReader * reader)502     static void ReadFont(IIniReader* reader)
503     {
504         if (reader->ReadSection("font"))
505         {
506             auto model = &gConfigFonts;
507             model->file_name = reader->GetCString("file_name", nullptr);
508             model->font_name = reader->GetCString("font_name", nullptr);
509             model->x_offset = reader->GetInt32("x_offset", false);
510             model->y_offset = reader->GetInt32("y_offset", true);
511             model->size_tiny = reader->GetInt32("size_tiny", true);
512             model->size_small = reader->GetInt32("size_small", false);
513             model->size_medium = reader->GetInt32("size_medium", false);
514             model->size_big = reader->GetInt32("size_big", false);
515             model->height_tiny = reader->GetInt32("height_tiny", false);
516             model->height_small = reader->GetInt32("height_small", false);
517             model->height_medium = reader->GetInt32("height_medium", false);
518             model->height_big = reader->GetInt32("height_big", false);
519             model->enable_hinting = reader->GetBoolean("enable_hinting", true);
520             model->hinting_threshold = reader->GetInt32("hinting_threshold", false);
521         }
522     }
523 
WriteFont(IIniWriter * writer)524     static void WriteFont(IIniWriter* writer)
525     {
526         auto model = &gConfigFonts;
527         writer->WriteSection("font");
528         writer->WriteString("file_name", model->file_name);
529         writer->WriteString("font_name", model->font_name);
530         writer->WriteInt32("x_offset", model->x_offset);
531         writer->WriteInt32("y_offset", model->y_offset);
532         writer->WriteInt32("size_tiny", model->size_tiny);
533         writer->WriteInt32("size_small", model->size_small);
534         writer->WriteInt32("size_medium", model->size_medium);
535         writer->WriteInt32("size_big", model->size_big);
536         writer->WriteInt32("height_tiny", model->height_tiny);
537         writer->WriteInt32("height_small", model->height_small);
538         writer->WriteInt32("height_medium", model->height_medium);
539         writer->WriteInt32("height_big", model->height_big);
540         writer->WriteBoolean("enable_hinting", model->enable_hinting);
541         writer->WriteInt32("hinting_threshold", model->hinting_threshold);
542     }
543 
ReadPlugin(IIniReader * reader)544     static void ReadPlugin(IIniReader* reader)
545     {
546         if (reader->ReadSection("plugin"))
547         {
548             auto model = &gConfigPlugin;
549             model->enable_hot_reloading = reader->GetBoolean("enable_hot_reloading", false);
550             model->allowed_hosts = reader->GetString("allowed_hosts", "");
551         }
552     }
553 
WritePlugin(IIniWriter * writer)554     static void WritePlugin(IIniWriter* writer)
555     {
556         auto model = &gConfigPlugin;
557         writer->WriteSection("plugin");
558         writer->WriteBoolean("enable_hot_reloading", model->enable_hot_reloading);
559         writer->WriteString("allowed_hosts", model->allowed_hosts);
560     }
561 
SetDefaults()562     static bool SetDefaults()
563     {
564         try
565         {
566             auto reader = CreateDefaultIniReader();
567             ReadGeneral(reader.get());
568             ReadInterface(reader.get());
569             ReadSound(reader.get());
570             ReadNetwork(reader.get());
571             ReadNotifications(reader.get());
572             ReadFont(reader.get());
573             ReadPlugin(reader.get());
574             return true;
575         }
576         catch (const std::exception&)
577         {
578             return false;
579         }
580     }
581 
ReadFile(const std::string & path)582     static bool ReadFile(const std::string& path)
583     {
584         try
585         {
586             auto fs = FileStream(path, FILE_MODE_OPEN);
587             auto reader = CreateIniReader(&fs);
588             ReadGeneral(reader.get());
589             ReadInterface(reader.get());
590             ReadSound(reader.get());
591             ReadNetwork(reader.get());
592             ReadNotifications(reader.get());
593             ReadFont(reader.get());
594             ReadPlugin(reader.get());
595             return true;
596         }
597         catch (const std::exception&)
598         {
599             return false;
600         }
601     }
602 
WriteFile(const std::string & path)603     static bool WriteFile(const std::string& path)
604     {
605         try
606         {
607             auto directory = Path::GetDirectory(path);
608             Path::CreateDirectory(directory);
609 
610             auto fs = FileStream(path, FILE_MODE_WRITE);
611             auto writer = CreateIniWriter(&fs);
612             WriteGeneral(writer.get());
613             WriteInterface(writer.get());
614             WriteSound(writer.get());
615             WriteNetwork(writer.get());
616             WriteNotifications(writer.get());
617             WriteFont(writer.get());
618             WritePlugin(writer.get());
619             return true;
620         }
621         catch (const std::exception& ex)
622         {
623             Console::WriteLine("Error saving to '%s'", path.c_str());
624             Console::WriteLine(ex.what());
625             return false;
626         }
627     }
628 
629     /**
630      * Attempts to find the RCT1 installation directory.
631      * @returns Path to RCT1, if found. Empty string otherwise.
632      */
FindRCT1Path()633     static std::string FindRCT1Path()
634     {
635         log_verbose("config_find_rct1_path(...)");
636 
637         static constexpr const utf8* searchLocations[] = {
638             R"(C:\Program Files\Steam\steamapps\common\Rollercoaster Tycoon Deluxe)",
639             R"(C:\Program Files (x86)\Steam\steamapps\common\Rollercoaster Tycoon Deluxe)",
640             R"(C:\GOG Games\RollerCoaster Tycoon Deluxe)",
641             R"(C:\Program Files\GalaxyClient\Games\RollerCoaster Tycoon Deluxe)",
642             R"(C:\Program Files (x86)\GalaxyClient\Games\RollerCoaster Tycoon Deluxe)",
643             R"(C:\Program Files\Hasbro Interactive\RollerCoaster Tycoon)",
644             R"(C:\Program Files (x86)\Hasbro Interactive\RollerCoaster Tycoon)",
645         };
646 
647         for (const utf8* location : searchLocations)
648         {
649             if (RCT1DataPresentAtLocation(location))
650             {
651                 return location;
652             }
653         }
654 
655         utf8 steamPath[2048] = { 0 };
656         if (platform_get_steam_path(steamPath, sizeof(steamPath)))
657         {
658             std::string location = Path::Combine(steamPath, platform_get_rct1_steam_dir());
659             if (RCT1DataPresentAtLocation(location.c_str()))
660             {
661                 return location;
662             }
663         }
664 
665         auto exePath = Path::GetDirectory(Platform::GetCurrentExecutablePath());
666         if (RCT1DataPresentAtLocation(exePath.c_str()))
667         {
668             return exePath;
669         }
670         return std::string();
671     }
672 
673     /**
674      * Attempts to find the RCT2 installation directory.
675      * This should be created from some other resource when OpenRCT2 grows.
676      * @returns Path to RCT2, if found. Empty string otherwise.
677      */
FindRCT2Path()678     static std::string FindRCT2Path()
679     {
680 
681 	return "/usr/local/share/openrct2";
682 
683         log_verbose("config_find_rct2_path(...)");
684 
685         static constexpr const utf8* searchLocations[] = {
686             R"(C:\Program Files\Steam\steamapps\common\Rollercoaster Tycoon 2)",
687             R"(C:\Program Files (x86)\Steam\steamapps\common\Rollercoaster Tycoon 2)",
688             R"(C:\GOG Games\RollerCoaster Tycoon 2 Triple Thrill Pack)",
689             R"(C:\Program Files\GalaxyClient\Games\RollerCoaster Tycoon 2 Triple Thrill Pack)",
690             R"(C:\Program Files (x86)\GalaxyClient\Games\RollerCoaster Tycoon 2 Triple Thrill Pack)",
691             R"(C:\Program Files\Atari\RollerCoaster Tycoon 2)",
692             R"(C:\Program Files (x86)\Atari\RollerCoaster Tycoon 2)",
693             R"(C:\Program Files\Infogrames\RollerCoaster Tycoon 2)",
694             R"(C:\Program Files (x86)\Infogrames\RollerCoaster Tycoon 2)",
695             R"(C:\Program Files\Infogrames Interactive\RollerCoaster Tycoon 2)",
696             R"(C:\Program Files (x86)\Infogrames Interactive\RollerCoaster Tycoon 2)",
697         };
698 
699         for (const utf8* location : searchLocations)
700         {
701             if (platform_original_game_data_exists(location))
702             {
703                 return location;
704             }
705         }
706 
707         utf8 steamPath[2048] = { 0 };
708         if (platform_get_steam_path(steamPath, sizeof(steamPath)))
709         {
710             std::string location = Path::Combine(steamPath, platform_get_rct2_steam_dir());
711             if (platform_original_game_data_exists(location.c_str()))
712             {
713                 return location;
714             }
715         }
716 
717         auto discordPath = Platform::GetFolderPath(SPECIAL_FOLDER::RCT2_DISCORD);
718         if (!discordPath.empty() && platform_original_game_data_exists(discordPath.c_str()))
719         {
720             return discordPath;
721         }
722 
723         auto exePath = Path::GetDirectory(Platform::GetCurrentExecutablePath());
724         if (platform_original_game_data_exists(exePath.c_str()))
725         {
726             return exePath;
727         }
728         return std::string();
729     }
730 
SelectGogInstaller(utf8 * installerPath)731     static bool SelectGogInstaller(utf8* installerPath)
732     {
733         file_dialog_desc desc;
734         memset(&desc, 0, sizeof(desc));
735         desc.type = FileDialogType::Open;
736         desc.title = language_get_string(STR_SELECT_GOG_INSTALLER);
737         desc.filters[0].name = language_get_string(STR_GOG_INSTALLER);
738         desc.filters[0].pattern = "*.exe";
739         desc.filters[1].name = language_get_string(STR_ALL_FILES);
740         desc.filters[1].pattern = "*";
741         desc.filters[2].name = nullptr;
742 
743         const auto userHomePath = Platform::GetFolderPath(SPECIAL_FOLDER::USER_HOME);
744         desc.initial_directory = userHomePath.c_str();
745 
746         return platform_open_common_file_dialog(installerPath, &desc, 4096);
747     }
748 
ExtractGogInstaller(const utf8 * installerPath,const utf8 * targetPath)749     static bool ExtractGogInstaller(const utf8* installerPath, const utf8* targetPath)
750     {
751         std::string path;
752         std::string output;
753 
754         if (!Platform::FindApp("innoextract", &path))
755         {
756             log_error("Please install innoextract to extract files from GOG.");
757             return false;
758         }
759         int32_t exit_status = Platform::Execute(
760             String::Format("%s '%s' --exclude-temp --output-dir '%s'", path.c_str(), installerPath, targetPath), &output);
761         log_info("Exit status %d", exit_status);
762         return exit_status == 0;
763     }
764 } // namespace Config
765 
766 GeneralConfiguration gConfigGeneral;
767 InterfaceConfiguration gConfigInterface;
768 SoundConfiguration gConfigSound;
769 NetworkConfiguration gConfigNetwork;
770 NotificationConfiguration gConfigNotifications;
771 FontConfiguration gConfigFonts;
772 PluginConfiguration gConfigPlugin;
773 
config_set_defaults()774 void config_set_defaults()
775 {
776     config_release();
777     Config::SetDefaults();
778 }
779 
config_open(const utf8 * path)780 bool config_open(const utf8* path)
781 {
782     if (!File::Exists(path))
783     {
784         return false;
785     }
786 
787     config_release();
788     auto result = Config::ReadFile(path);
789     if (result)
790     {
791         currency_load_custom_currency_config();
792     }
793     return result;
794 }
795 
config_save(const utf8 * path)796 bool config_save(const utf8* path)
797 {
798     return Config::WriteFile(path);
799 }
800 
config_release()801 void config_release()
802 {
803     SafeFree(gConfigGeneral.rct1_path);
804     SafeFree(gConfigGeneral.rct2_path);
805     SafeFree(gConfigGeneral.custom_currency_symbol);
806     SafeFree(gConfigGeneral.last_save_game_directory);
807     SafeFree(gConfigGeneral.last_save_landscape_directory);
808     SafeFree(gConfigGeneral.last_save_scenario_directory);
809     SafeFree(gConfigGeneral.last_save_track_directory);
810     SafeFree(gConfigGeneral.last_run_version);
811     SafeFree(gConfigInterface.current_theme_preset);
812     SafeFree(gConfigInterface.current_title_sequence_preset);
813     SafeFree(gConfigSound.device);
814     SafeFree(gConfigFonts.file_name);
815     SafeFree(gConfigFonts.font_name);
816 }
817 
config_get_default_path(utf8 * outPath,size_t size)818 void config_get_default_path(utf8* outPath, size_t size)
819 {
820     platform_get_user_directory(outPath, nullptr, size);
821     Path::Append(outPath, size, "config.ini");
822 }
823 
config_save_default()824 bool config_save_default()
825 {
826     utf8 path[MAX_PATH];
827     config_get_default_path(path, sizeof(path));
828     return config_save(path);
829 }
830 
config_find_or_browse_install_directory()831 bool config_find_or_browse_install_directory()
832 {
833     std::string path = Config::FindRCT2Path();
834     if (!path.empty())
835     {
836         Memory::Free(gConfigGeneral.rct2_path);
837         gConfigGeneral.rct2_path = String::Duplicate(path.c_str());
838     }
839     else
840     {
841         if (gOpenRCT2Headless)
842         {
843             return false;
844         }
845 
846         auto uiContext = GetContext()->GetUiContext();
847         if (!uiContext->HasFilePicker())
848         {
849             uiContext->ShowMessageBox(format_string(STR_NEEDS_RCT2_FILES_MANUAL, nullptr));
850             return false;
851         }
852 
853         try
854         {
855             const char* g1DatPath = PATH_SEPARATOR "Data" PATH_SEPARATOR "g1.dat";
856             while (true)
857             {
858                 uiContext->ShowMessageBox(format_string(STR_NEEDS_RCT2_FILES, nullptr));
859                 std::string gog = language_get_string(STR_OWN_ON_GOG);
860                 std::string hdd = language_get_string(STR_INSTALLED_ON_HDD);
861 
862                 std::vector<std::string> options;
863                 std::string chosenOption;
864 
865                 if (uiContext->HasMenuSupport())
866                 {
867                     options.push_back(hdd);
868                     options.push_back(gog);
869                     int optionIndex = uiContext->ShowMenuDialog(
870                         options, language_get_string(STR_OPENRCT2_SETUP), language_get_string(STR_WHICH_APPLIES_BEST));
871                     if (optionIndex < 0 || static_cast<uint32_t>(optionIndex) >= options.size())
872                     {
873                         // graceful fallback if app errors or user exits out of window
874                         chosenOption = hdd;
875                     }
876                     else
877                     {
878                         chosenOption = options[optionIndex];
879                     }
880                 }
881                 else
882                 {
883                     chosenOption = hdd;
884                 }
885 
886                 std::string installPath;
887                 if (chosenOption == hdd)
888                 {
889                     installPath = uiContext->ShowDirectoryDialog(language_get_string(STR_PICK_RCT2_DIR));
890                 }
891                 else if (chosenOption == gog)
892                 {
893                     // Check if innoextract is installed. If not, prompt the user to install it.
894                     std::string dummy;
895                     if (!Platform::FindApp("innoextract", &dummy))
896                     {
897                         uiContext->ShowMessageBox(format_string(STR_INSTALL_INNOEXTRACT, nullptr));
898                         return false;
899                     }
900 
901                     const std::string dest = Path::Combine(
902                         GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::CONFIG), "rct2");
903 
904                     while (true)
905                     {
906                         uiContext->ShowMessageBox(language_get_string(STR_PLEASE_SELECT_GOG_INSTALLER));
907                         utf8 gogPath[4096];
908                         if (!Config::SelectGogInstaller(gogPath))
909                         {
910                             // The user clicked "Cancel", so stop trying.
911                             return false;
912                         }
913 
914                         uiContext->ShowMessageBox(language_get_string(STR_THIS_WILL_TAKE_A_FEW_MINUTES));
915 
916                         if (Config::ExtractGogInstaller(gogPath, dest.c_str()))
917                             break;
918 
919                         uiContext->ShowMessageBox(language_get_string(STR_NOT_THE_GOG_INSTALLER));
920                     }
921 
922                     installPath = Path::Combine(dest, "app");
923                 }
924                 if (installPath.empty())
925                 {
926                     return false;
927                 }
928                 Memory::Free(gConfigGeneral.rct2_path);
929                 gConfigGeneral.rct2_path = String::Duplicate(installPath.c_str());
930 
931                 if (platform_original_game_data_exists(installPath.c_str()))
932                 {
933                     return true;
934                 }
935 
936                 uiContext->ShowMessageBox(format_string(STR_COULD_NOT_FIND_AT_PATH, &g1DatPath));
937             }
938         }
939         catch (const std::exception& ex)
940         {
941             Console::Error::WriteLine(ex.what());
942             return false;
943         }
944     }
945     // While we're at it, also check if the player has RCT1
946     std::string rct1Path = Config::FindRCT1Path();
947     if (!rct1Path.empty())
948     {
949         free(gConfigGeneral.rct1_path);
950         gConfigGeneral.rct1_path = String::Duplicate(rct1Path.c_str());
951     }
952 
953     return true;
954 }
955 
FindCsg1datAtLocation(const utf8 * path)956 std::string FindCsg1datAtLocation(const utf8* path)
957 {
958     char buffer[MAX_PATH], checkPath1[MAX_PATH], checkPath2[MAX_PATH];
959     safe_strcpy(buffer, path, MAX_PATH);
960     safe_strcat_path(buffer, "Data", MAX_PATH);
961     safe_strcpy(checkPath1, buffer, MAX_PATH);
962     safe_strcpy(checkPath2, buffer, MAX_PATH);
963     safe_strcat_path(checkPath1, "CSG1.DAT", MAX_PATH);
964     safe_strcat_path(checkPath2, "CSG1.1", MAX_PATH);
965 
966     // Since Linux is case sensitive (and macOS sometimes too), make sure we handle case properly.
967     std::string path1result = Path::ResolveCasing(checkPath1);
968     if (!path1result.empty())
969     {
970         return path1result;
971     }
972 
973     std::string path2result = Path::ResolveCasing(checkPath2);
974     return path2result;
975 }
976 
Csg1datPresentAtLocation(const utf8 * path)977 bool Csg1datPresentAtLocation(const utf8* path)
978 {
979     std::string location = FindCsg1datAtLocation(path);
980     return !location.empty();
981 }
982 
FindCsg1idatAtLocation(const utf8 * path)983 std::string FindCsg1idatAtLocation(const utf8* path)
984 {
985     auto result1 = Path::ResolveCasing(Path::Combine(path, "Data", "CSG1I.DAT"));
986     if (!result1.empty())
987     {
988         return result1;
989     }
990     auto result2 = Path::ResolveCasing(Path::Combine(path, "RCTdeluxe_install", "Data", "CSG1I.DAT"));
991     return result2;
992 }
993 
Csg1idatPresentAtLocation(const utf8 * path)994 bool Csg1idatPresentAtLocation(const utf8* path)
995 {
996     std::string location = FindCsg1idatAtLocation(path);
997     return !location.empty();
998 }
999 
RCT1DataPresentAtLocation(const utf8 * path)1000 bool RCT1DataPresentAtLocation(const utf8* path)
1001 {
1002     return Csg1datPresentAtLocation(path) && Csg1idatPresentAtLocation(path) && CsgAtLocationIsUsable(path);
1003 }
1004 
CsgIsUsable(const rct_gx & csg)1005 bool CsgIsUsable(const rct_gx& csg)
1006 {
1007     return csg.header.total_size == RCT1::RCT1_LL_CSG1_DAT_FILE_SIZE && csg.header.num_entries == RCT1::RCT1_NUM_LL_CSG_ENTRIES;
1008 }
1009 
CsgAtLocationIsUsable(const utf8 * path)1010 bool CsgAtLocationIsUsable(const utf8* path)
1011 {
1012     auto csg1HeaderPath = FindCsg1idatAtLocation(path);
1013     if (csg1HeaderPath.empty())
1014     {
1015         return false;
1016     }
1017 
1018     auto csg1DataPath = FindCsg1datAtLocation(path);
1019     if (csg1DataPath.empty())
1020     {
1021         return false;
1022     }
1023 
1024     auto fileHeader = FileStream(csg1HeaderPath, FILE_MODE_OPEN);
1025     auto fileData = FileStream(csg1DataPath, FILE_MODE_OPEN);
1026     size_t fileHeaderSize = fileHeader.GetLength();
1027     size_t fileDataSize = fileData.GetLength();
1028 
1029     rct_gx csg = {};
1030     csg.header.num_entries = static_cast<uint32_t>(fileHeaderSize / sizeof(rct_g1_element_32bit));
1031     csg.header.total_size = static_cast<uint32_t>(fileDataSize);
1032     return CsgIsUsable(csg);
1033 }
1034