mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-16 03:23:15 +01:00
This commit makes sure config.ini gets created even when RCT2 path is not selected. This makes for a better out-of-box experience on Linux, where no directory selector is provided yet, as we provide default config file and state in the error message how to fix it.
1680 lines
62 KiB
C
1680 lines
62 KiB
C
/*****************************************************************************
|
|
* Copyright (c) 2014 Ted John
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
*
|
|
* This file is part of OpenRCT2.
|
|
*
|
|
* OpenRCT2 is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*****************************************************************************/
|
|
|
|
#include "addresses.h"
|
|
#include "config.h"
|
|
#include "interface/themes.h"
|
|
#include "interface/title_sequences.h"
|
|
#include "localisation/language.h"
|
|
#include "localisation/localisation.h"
|
|
#include "network/network.h"
|
|
#include "openrct2.h"
|
|
#include "util/util.h"
|
|
|
|
// Magic number for original game cfg file
|
|
static const int MagicNumber = 0x0003113A;
|
|
|
|
enum {
|
|
CONFIG_VALUE_TYPE_BOOLEAN,
|
|
CONFIG_VALUE_TYPE_UINT8,
|
|
CONFIG_VALUE_TYPE_UINT16,
|
|
CONFIG_VALUE_TYPE_UINT32,
|
|
CONFIG_VALUE_TYPE_SINT8,
|
|
CONFIG_VALUE_TYPE_SINT16,
|
|
CONFIG_VALUE_TYPE_SINT32,
|
|
CONFIG_VALUE_TYPE_FLOAT,
|
|
CONFIG_VALUE_TYPE_DOUBLE,
|
|
CONFIG_VALUE_TYPE_STRING
|
|
};
|
|
|
|
size_t _configValueTypeSize[] = {
|
|
sizeof(bool),
|
|
sizeof(uint8),
|
|
sizeof(uint16),
|
|
sizeof(uint32),
|
|
sizeof(sint8),
|
|
sizeof(sint16),
|
|
sizeof(sint32),
|
|
sizeof(float),
|
|
sizeof(double),
|
|
sizeof(utf8string)
|
|
};
|
|
|
|
typedef union {
|
|
sint32 value_sint32;
|
|
|
|
bool value_boolean;
|
|
sint8 value_sint8;
|
|
sint16 value_sint16;
|
|
uint8 value_uint8;
|
|
uint16 value_uint16;
|
|
uint32 value_uint32;
|
|
float value_float;
|
|
double value_double;
|
|
utf8string value_string;
|
|
} value_union;
|
|
|
|
typedef struct {
|
|
const_utf8string key;
|
|
value_union value;
|
|
} config_enum_definition;
|
|
|
|
#define END_OF_ENUM { NULL, 0 }
|
|
|
|
typedef struct {
|
|
size_t offset;
|
|
const_utf8string property_name;
|
|
uint8 type;
|
|
value_union default_value;
|
|
config_enum_definition *enum_definitions;
|
|
} config_property_definition;
|
|
|
|
typedef struct {
|
|
void *base_address;
|
|
const_utf8string section_name;
|
|
config_property_definition *property_definitions;
|
|
int property_definitions_count;
|
|
} config_section_definition;
|
|
|
|
#pragma region Enum definitions
|
|
|
|
config_enum_definition _screenShotFormatEnum[] = {
|
|
{ "BMP", SCREENSHOT_FORMAT_BMP },
|
|
{ "PNG", SCREENSHOT_FORMAT_PNG },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
config_enum_definition _measurementFormatEnum[] = {
|
|
{ "IMPERIAL", MEASUREMENT_FORMAT_IMPERIAL },
|
|
{ "METRIC", MEASUREMENT_FORMAT_METRIC },
|
|
{ "SI", MEASUREMENT_FORMAT_SI },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
config_enum_definition _temperatureFormatEnum[] = {
|
|
{ "CELSIUS", TEMPERATURE_FORMAT_C },
|
|
{ "FAHRENHEIT", TEMPERATURE_FORMAT_F },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
config_enum_definition _currencyEnum[] = {
|
|
{ "GBP", CURRENCY_POUNDS },
|
|
{ "USD", CURRENCY_DOLLARS },
|
|
{ "FRF", CURRENCY_FRANC },
|
|
{ "DEM", CURRENCY_DEUTSCHMARK },
|
|
{ "JPY", CURRENCY_YEN },
|
|
{ "ESP", CURRENCY_PESETA },
|
|
{ "ITL", CURRENCY_LIRA },
|
|
{ "NLG", CURRENCY_GUILDERS },
|
|
{ "SEK", CURRENCY_KRONA },
|
|
{ "EUR", CURRENCY_EUROS },
|
|
{ "KRW", CURRENCY_WON },
|
|
{ "RUB", CURRENCY_ROUBLE },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
config_enum_definition _languageEnum[] = {
|
|
{ "en-GB", LANGUAGE_ENGLISH_UK },
|
|
{ "en-US", LANGUAGE_ENGLISH_US },
|
|
{ "de-DE", LANGUAGE_GERMAN },
|
|
{ "nl-NL", LANGUAGE_DUTCH },
|
|
{ "fr-FR", LANGUAGE_FRENCH },
|
|
{ "hu-HU", LANGUAGE_HUNGARIAN },
|
|
{ "pl-PL", LANGUAGE_POLISH },
|
|
{ "es-ES", LANGUAGE_SPANISH },
|
|
{ "sv-SE", LANGUAGE_SWEDISH },
|
|
{ "it-IT", LANGUAGE_ITALIAN },
|
|
{ "pt-BR", LANGUAGE_PORTUGUESE_BR },
|
|
{ "zh-Hant",LANGUAGE_CHINESE_TRADITIONAL },
|
|
{ "zh-Hans",LANGUAGE_CHINESE_SIMPLIFIED },
|
|
{ "fi-FI", LANGUAGE_FINNISH },
|
|
{ "ko", LANGUAGE_KOREAN },
|
|
{ "ru-RU", LANGUAGE_RUSSIAN },
|
|
{ "cz-CZ", LANGUAGE_CZECH },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
config_enum_definition _dateFormatEnum[] = {
|
|
{ "DD/MM/YY", DATE_FORMAT_DMY },
|
|
{ "MM/DD/YY", DATE_FORMAT_MDY },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Section / property definitions
|
|
|
|
config_property_definition _generalDefinitions[] = {
|
|
{ offsetof(general_configuration, always_show_gridlines), "always_show_gridlines", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, autosave_frequency), "autosave", CONFIG_VALUE_TYPE_UINT8, AUTOSAVE_EVERY_5MINUTES, NULL },
|
|
{ offsetof(general_configuration, confirmation_prompt), "confirmation_prompt", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(general_configuration, construction_marker_colour), "construction_marker_colour", CONFIG_VALUE_TYPE_UINT8, false, NULL },
|
|
{ offsetof(general_configuration, currency_format), "currency_format", CONFIG_VALUE_TYPE_UINT8, CURRENCY_POUNDS, _currencyEnum },
|
|
{ offsetof(general_configuration, edge_scrolling), "edge_scrolling", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(general_configuration, fullscreen_mode), "fullscreen_mode", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(general_configuration, fullscreen_height), "fullscreen_height", CONFIG_VALUE_TYPE_SINT32, -1, NULL },
|
|
{ offsetof(general_configuration, fullscreen_width), "fullscreen_width", CONFIG_VALUE_TYPE_SINT32, -1, NULL },
|
|
{ offsetof(general_configuration, game_path), "game_path", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, NULL },
|
|
{ offsetof(general_configuration, landscape_smoothing), "landscape_smoothing", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(general_configuration, language), "language", CONFIG_VALUE_TYPE_UINT16, LANGUAGE_ENGLISH_UK, _languageEnum },
|
|
{ offsetof(general_configuration, measurement_format), "measurement_format", CONFIG_VALUE_TYPE_UINT8, MEASUREMENT_FORMAT_METRIC, _measurementFormatEnum },
|
|
{ offsetof(general_configuration, play_intro), "play_intro", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, save_plugin_data), "save_plugin_data", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, debugging_tools), "debugging_tools", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, screenshot_format), "screenshot_format", CONFIG_VALUE_TYPE_UINT8, SCREENSHOT_FORMAT_PNG, _screenShotFormatEnum },
|
|
{ offsetof(general_configuration, show_height_as_units), "show_height_as_units", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, temperature_format), "temperature_format", CONFIG_VALUE_TYPE_UINT8, TEMPERATURE_FORMAT_C, _temperatureFormatEnum },
|
|
{ offsetof(general_configuration, window_height), "window_height", CONFIG_VALUE_TYPE_SINT32, -1, NULL },
|
|
{ offsetof(general_configuration, window_snap_proximity), "window_snap_proximity", CONFIG_VALUE_TYPE_UINT8, 5, NULL },
|
|
{ offsetof(general_configuration, window_width), "window_width", CONFIG_VALUE_TYPE_SINT32, -1, NULL },
|
|
{ offsetof(general_configuration, hardware_display), "hardware_display", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, uncap_fps), "uncap_fps", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, test_unfinished_tracks), "test_unfinished_tracks", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, no_test_crashes), "no_test_crashes", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, date_format), "date_format", CONFIG_VALUE_TYPE_UINT8, DATE_FORMAT_DMY, _dateFormatEnum },
|
|
{ offsetof(general_configuration, auto_staff_placement), "auto_staff", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, handymen_mow_default), "handymen_mow_default", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, last_run_version), "last_run_version", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, NULL },
|
|
{ offsetof(general_configuration, invert_viewport_drag), "invert_viewport_drag", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, load_save_sort), "load_save_sort", CONFIG_VALUE_TYPE_UINT8, SORT_NAME_ASCENDING, NULL },
|
|
{ offsetof(general_configuration, minimize_fullscreen_focus_loss), "minimize_fullscreen_focus_loss",CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(general_configuration, day_night_cycle), "day_night_cycle", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, upper_case_banners), "upper_case_banners", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, allow_loading_with_incorrect_checksum),"allow_loading_with_incorrect_checksum", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, steam_overlay_pause), "steam_overlay_pause", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(general_configuration, window_scale), "window_scale", CONFIG_VALUE_TYPE_FLOAT, { .value_float = 1.0f }, NULL },
|
|
{ offsetof(general_configuration, show_fps), "show_fps", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, trap_cursor), "trap_cursor", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, auto_open_shops), "auto_open_shops", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(general_configuration, scenario_select_mode), "scenario_select_mode", CONFIG_VALUE_TYPE_UINT8, SCENARIO_SELECT_MODE_ORIGIN, NULL },
|
|
{ offsetof(general_configuration, scenario_unlocking_enabled), "scenario_unlocking_enabled", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(general_configuration, scenario_hide_mega_park), "scenario_hide_mega_park", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
|
|
};
|
|
|
|
config_property_definition _interfaceDefinitions[] = {
|
|
{ offsetof(interface_configuration, toolbar_show_finances), "toolbar_show_finances", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(interface_configuration, toolbar_show_research), "toolbar_show_research", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(interface_configuration, toolbar_show_cheats), "toolbar_show_cheats", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(interface_configuration, toolbar_show_news), "toolbar_show_news", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(interface_configuration, select_by_track_type), "select_by_track_type", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(interface_configuration, console_small_font), "console_small_font", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(interface_configuration, current_theme_preset), "current_theme", CONFIG_VALUE_TYPE_STRING, { .value_string = "*RCT2" }, NULL },
|
|
{ offsetof(interface_configuration, current_title_sequence_preset), "current_title_sequence", CONFIG_VALUE_TYPE_STRING, { .value_string = "*OPENRCT2" },NULL },
|
|
{ offsetof(interface_configuration, object_selection_filter_flags), "object_selection_filter_flags",CONFIG_VALUE_TYPE_UINT32, 0x7EF, NULL },
|
|
};
|
|
|
|
config_property_definition _soundDefinitions[] = {
|
|
{ offsetof(sound_configuration, title_music), "title_music", CONFIG_VALUE_TYPE_UINT8, 2, NULL },
|
|
{ offsetof(sound_configuration, sound), "sound", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(sound_configuration, ride_music), "ride_music", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(sound_configuration, audio_focus), "audio_focus", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(sound_configuration, master_volume), "master_volume", CONFIG_VALUE_TYPE_UINT8, 100, NULL },
|
|
{ offsetof(sound_configuration, music_volume), "music_volume", CONFIG_VALUE_TYPE_UINT8, 100, NULL },
|
|
{ offsetof(sound_configuration, device), "audio_device", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, NULL },
|
|
};
|
|
|
|
config_property_definition _cheatDefinitions[] = {
|
|
{ offsetof(cheat_configuration, fast_lift_hill), "fast_lift_hill", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(cheat_configuration, disable_brakes_failure), "disable_brakes_failure", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(cheat_configuration, disable_all_breakdowns), "disable_all_breakdowns", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(cheat_configuration, unlock_all_prices), "unlock_all_prices", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(cheat_configuration, build_in_pause_mode), "build_in_pause_mode", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(cheat_configuration, ignore_ride_intensity), "ignore_ride_intensity", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(cheat_configuration, disable_vandalism), "disable_vandalism", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
};
|
|
|
|
config_property_definition _twitchDefinitions[] = {
|
|
{ offsetof(twitch_configuration, channel), "channel", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, NULL },
|
|
{ offsetof(twitch_configuration, enable_follower_peep_names), "follower_peep_names", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(twitch_configuration, enable_follower_peep_tracking), "follower_peep_tracking", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(twitch_configuration, enable_chat_peep_names), "chat_peep_names", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(twitch_configuration, enable_chat_peep_tracking), "chat_peep_tracking", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(twitch_configuration, enable_news), "news", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL }
|
|
};
|
|
|
|
config_property_definition _networkDefinitions[] = {
|
|
{ offsetof(network_configuration, player_name), "player_name", CONFIG_VALUE_TYPE_STRING, {.value_string = "Player" }, NULL },
|
|
{ offsetof(network_configuration, default_port), "default_port", CONFIG_VALUE_TYPE_UINT32, NETWORK_DEFAULT_PORT, NULL },
|
|
{ offsetof(network_configuration, stay_connected), "stay_connected", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(network_configuration, advertise), "advertise", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(network_configuration, maxplayers), "maxplayers", CONFIG_VALUE_TYPE_UINT8, 16, NULL },
|
|
{ offsetof(network_configuration, server_name), "server_name", CONFIG_VALUE_TYPE_STRING, {.value_string = "Server" }, NULL },
|
|
{ offsetof(network_configuration, server_description), "server_description", CONFIG_VALUE_TYPE_STRING, {.value_string = NULL }, NULL },
|
|
{ offsetof(network_configuration, master_server_url), "master_server_url", CONFIG_VALUE_TYPE_STRING, {.value_string = NULL }, NULL },
|
|
{ offsetof(network_configuration, provider_name), "provider_name", CONFIG_VALUE_TYPE_STRING, {.value_string = NULL }, NULL },
|
|
{ offsetof(network_configuration, provider_email), "provider_email", CONFIG_VALUE_TYPE_STRING, {.value_string = NULL }, NULL },
|
|
{ offsetof(network_configuration, provider_website), "provider_website", CONFIG_VALUE_TYPE_STRING, {.value_string = NULL }, NULL }
|
|
};
|
|
|
|
config_property_definition _notificationsDefinitions[] = {
|
|
{ offsetof(notification_configuration, park_award), "park_award", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, park_marketing_campaign_finished), "park_marketing_campaign_finished", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, park_warnings), "park_warnings", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, park_rating_warnings), "park_rating_warnings", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, ride_broken_down), "ride_broken_down", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, ride_crashed), "ride_crashed", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, ride_warnings), "ride_warnings", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, ride_researched), "ride_researched", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_warnings), "guest_warnings", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_lost), "guest_lost", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(notification_configuration, guest_left_park), "guest_entered_left_park", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_queuing_for_ride), "guest_queuing_for_ride", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_on_ride), "guest_on_ride", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_left_ride), "guest_left_ride", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_bought_item), "guest_bought_item", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_used_facility), "guest_used_facility", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(notification_configuration, guest_died), "guest_died", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
};
|
|
|
|
config_section_definition _sectionDefinitions[] = {
|
|
{ &gConfigGeneral, "general", _generalDefinitions, countof(_generalDefinitions) },
|
|
{ &gConfigInterface, "interface", _interfaceDefinitions, countof(_interfaceDefinitions) },
|
|
{ &gConfigSound, "sound", _soundDefinitions, countof(_soundDefinitions) },
|
|
{ &gConfigCheat, "cheat", _cheatDefinitions, countof(_cheatDefinitions) },
|
|
{ &gConfigTwitch, "twitch", _twitchDefinitions, countof(_twitchDefinitions) },
|
|
{ &gConfigNetwork, "network", _networkDefinitions, countof(_networkDefinitions) },
|
|
{ &gConfigNotifications, "notifications", _notificationsDefinitions, countof(_notificationsDefinitions) },
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
general_configuration gConfigGeneral;
|
|
interface_configuration gConfigInterface;
|
|
sound_configuration gConfigSound;
|
|
cheat_configuration gConfigCheat;
|
|
twitch_configuration gConfigTwitch;
|
|
network_configuration gConfigNetwork;
|
|
notification_configuration gConfigNotifications;
|
|
themes_configuration gConfigThemes;
|
|
title_sequences_configuration gConfigTitleSequences;
|
|
|
|
static bool config_open(const utf8string path);
|
|
static bool config_save(const utf8string path);
|
|
static void config_read_properties(config_section_definition **currentSection, const_utf8string line);
|
|
static void config_save_property_value(SDL_RWops *file, uint8 type, value_union *value);
|
|
static bool config_read_enum(void *dest, int destSize, const utf8 *key, int keySize, config_enum_definition *enumDefinitions);
|
|
static void config_write_enum(SDL_RWops *file, uint8 type, value_union *value, config_enum_definition *enumDefinitions);
|
|
|
|
static void utf8_skip_whitespace(utf8 **outch);
|
|
static void utf8_skip_non_whitespace(utf8 **outch);
|
|
|
|
void config_apply_to_old_addresses();
|
|
|
|
static int rwopsreadc(SDL_RWops *file)
|
|
{
|
|
int c = 0;
|
|
if (SDL_RWread(file, &c, 1, 1) != 1)
|
|
c = EOF;
|
|
return c;
|
|
}
|
|
|
|
static void rwopswritec(SDL_RWops *file, char c)
|
|
{
|
|
SDL_RWwrite(file, &c, 1, 1);
|
|
}
|
|
|
|
static void rwopswritestr(SDL_RWops *file, const char *str)
|
|
{
|
|
SDL_RWwrite(file, str, strlen(str), 1);
|
|
}
|
|
|
|
static void rwopsprintf(SDL_RWops *file, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
char buffer[64];
|
|
vsprintf(buffer, format, args);
|
|
|
|
SDL_RWwrite(file, buffer, strlen(buffer), 1);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
static void rwopswritenewline(SDL_RWops *file)
|
|
{
|
|
rwopswritestr(file, platform_get_new_line());
|
|
}
|
|
|
|
void config_set_defaults()
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < countof(_sectionDefinitions); i++) {
|
|
config_section_definition *section = &_sectionDefinitions[i];
|
|
for (j = 0; j < section->property_definitions_count; j++) {
|
|
config_property_definition *property = §ion->property_definitions[j];
|
|
value_union *destValue = (value_union*)((size_t)section->base_address + (size_t)property->offset);
|
|
|
|
// Special dynamic defaults
|
|
if (strcmp(property->property_name, "language") == 0){
|
|
destValue->value_uint16 = platform_get_locale_language();
|
|
if (destValue->value_uint16 == LANGUAGE_UNDEFINED)
|
|
memcpy(destValue, &property->default_value, _configValueTypeSize[property->type]);
|
|
}
|
|
else if (strcmp(property->property_name, "currency_format") == 0){
|
|
destValue->value_uint8 = platform_get_locale_currency();
|
|
}
|
|
else if (strcmp(property->property_name, "measurement_format") == 0){
|
|
destValue->value_uint8 = platform_get_locale_measurement_format();
|
|
}
|
|
else if (strcmp(property->property_name, "temperature_format") == 0){
|
|
destValue->value_uint8 = platform_get_locale_temperature_format();
|
|
}
|
|
else {
|
|
// Use static default
|
|
if (property->type == CONFIG_VALUE_TYPE_STRING) {
|
|
// Copy the string to new memory
|
|
const utf8 *src = property->default_value.value_string;
|
|
const utf8 **dst = (const utf8**)&(destValue->value_string);
|
|
if (src != NULL) {
|
|
*dst = _strdup(property->default_value.value_string);
|
|
}
|
|
} else {
|
|
memcpy(destValue, &property->default_value, _configValueTypeSize[property->type]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void config_get_default_path(utf8 *outPath)
|
|
{
|
|
platform_get_user_directory(outPath, NULL);
|
|
strcat(outPath, "config.ini");
|
|
}
|
|
|
|
bool config_open_default()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
|
|
config_get_default_path(path);
|
|
if (config_open(path)) {
|
|
config_apply_to_old_addresses();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool config_save_default()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
|
|
config_get_default_path(path);
|
|
if (config_save(path)) {
|
|
config_apply_to_old_addresses();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool config_open(const utf8string path)
|
|
{
|
|
SDL_RWops *file;
|
|
utf8string lineBuffer;
|
|
size_t lineBufferCapacity;
|
|
size_t lineLength;
|
|
int c;
|
|
config_section_definition *currentSection;
|
|
|
|
file = SDL_RWFromFile(path, "rb");
|
|
if (file == NULL)
|
|
return false;
|
|
|
|
currentSection = NULL;
|
|
lineBufferCapacity = 64;
|
|
lineBuffer = malloc(lineBufferCapacity);
|
|
lineLength = 0;
|
|
|
|
// Skim UTF-8 byte order mark
|
|
SDL_RWread(file, lineBuffer, 3, 1);
|
|
if (!utf8_is_bom(lineBuffer))
|
|
SDL_RWseek(file, 0, RW_SEEK_SET);
|
|
|
|
while ((c = rwopsreadc(file)) != EOF) {
|
|
if (c == '\n' || c == '\r') {
|
|
lineBuffer[lineLength++] = 0;
|
|
config_read_properties(¤tSection, (const_utf8string)lineBuffer);
|
|
lineLength = 0;
|
|
} else {
|
|
lineBuffer[lineLength++] = c;
|
|
}
|
|
|
|
if (lineLength >= lineBufferCapacity) {
|
|
lineBufferCapacity *= 2;
|
|
lineBuffer = realloc(lineBuffer, lineBufferCapacity);
|
|
}
|
|
}
|
|
|
|
if (lineLength > 0) {
|
|
lineBuffer[lineLength++] = 0;
|
|
config_read_properties(¤tSection, lineBuffer);
|
|
}
|
|
|
|
free(lineBuffer);
|
|
SDL_RWclose(file);
|
|
return true;
|
|
}
|
|
|
|
bool config_save(const utf8string path)
|
|
{
|
|
SDL_RWops *file;
|
|
int i, j;
|
|
value_union *value;
|
|
|
|
file = SDL_RWFromFile(path, "wb");
|
|
if (file == NULL) {
|
|
log_error("Unable to write to config file.");
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < countof(_sectionDefinitions); i++) {
|
|
config_section_definition *section = &_sectionDefinitions[i];
|
|
|
|
rwopswritec(file, '[');
|
|
rwopswritestr(file, section->section_name);
|
|
rwopswritec(file, ']');
|
|
rwopswritenewline(file);
|
|
|
|
for (j = 0; j < section->property_definitions_count; j++) {
|
|
config_property_definition *property = §ion->property_definitions[j];
|
|
|
|
rwopswritestr(file, property->property_name);
|
|
rwopswritestr(file, " = ");
|
|
|
|
value = (value_union*)((size_t)section->base_address + (size_t)property->offset);
|
|
if (property->enum_definitions != NULL)
|
|
config_write_enum(file, property->type, value, property->enum_definitions);
|
|
else
|
|
config_save_property_value(file, property->type, value);
|
|
rwopswritenewline(file);
|
|
}
|
|
rwopswritenewline(file);
|
|
}
|
|
|
|
SDL_RWclose(file);
|
|
return true;
|
|
}
|
|
|
|
static void config_save_property_value(SDL_RWops *file, uint8 type, value_union *value)
|
|
{
|
|
switch (type) {
|
|
case CONFIG_VALUE_TYPE_BOOLEAN:
|
|
if (value->value_boolean) rwopswritestr(file, "true");
|
|
else rwopswritestr(file, "false");
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT8:
|
|
rwopsprintf(file, "%u", value->value_uint8);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT16:
|
|
rwopsprintf(file, "%u", value->value_uint16);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT32:
|
|
rwopsprintf(file, "%lu", value->value_uint32);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT8:
|
|
rwopsprintf(file, "%d", value->value_sint8);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT16:
|
|
rwopsprintf(file, "%d", value->value_sint16);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT32:
|
|
rwopsprintf(file, "%ld", value->value_sint32);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_FLOAT:
|
|
rwopsprintf(file, "%.3f", value->value_float);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_DOUBLE:
|
|
rwopsprintf(file, "%.6f", value->value_double);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_STRING:
|
|
rwopswritec(file, '"');
|
|
if (value->value_string != NULL) {
|
|
rwopswritestr(file, value->value_string);
|
|
}
|
|
rwopswritec(file, '"');
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool config_get_section(const utf8string line, const utf8 **sectionName, int *sectionNameSize)
|
|
{
|
|
utf8 *ch;
|
|
int c;
|
|
|
|
ch = line;
|
|
utf8_skip_whitespace(&ch);
|
|
if (*ch != '[') return false;
|
|
*sectionName = ++ch;
|
|
|
|
while ((c = utf8_get_next(ch, (const utf8**)&ch)) != 0) {
|
|
if (c == '#') return false;
|
|
if (c == '[') return false;
|
|
if (c == ' ') break;
|
|
if (c == ']') break;
|
|
}
|
|
|
|
*sectionNameSize = ch - *sectionName - 1;
|
|
return true;
|
|
}
|
|
|
|
bool config_get_property_name_value(const utf8string line, utf8 **propertyName, int *propertyNameSize, utf8 **value, int *valueSize)
|
|
{
|
|
utf8 *ch;
|
|
int c, lastC;
|
|
bool quotes;
|
|
|
|
ch = line;
|
|
utf8_skip_whitespace(&ch);
|
|
|
|
if (*ch == 0) return false;
|
|
*propertyName = ch;
|
|
|
|
while ((c = utf8_get_next(ch, (const utf8**)&ch)) != 0) {
|
|
if (isspace(c) || c == '=') {
|
|
*propertyNameSize = ch - *propertyName - 1;
|
|
break;
|
|
} else if (c == '#') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (*ch == 0) return false;
|
|
utf8_skip_whitespace(&ch);
|
|
if (*ch != '=') return false;
|
|
ch++;
|
|
utf8_skip_whitespace(&ch);
|
|
if (*ch == 0) return false;
|
|
|
|
if (*ch == '"') {
|
|
ch++;
|
|
quotes = true;
|
|
} else {
|
|
quotes = false;
|
|
}
|
|
*value = ch;
|
|
|
|
while ((c = utf8_get_next(ch, (const utf8**)&ch)) != 0) {
|
|
if (isspace(c) || c == '#') {
|
|
if (!quotes) break;
|
|
}
|
|
lastC = c;
|
|
}
|
|
*valueSize = ch - *value - 1;
|
|
if (quotes) (*valueSize)--;
|
|
return true;
|
|
}
|
|
|
|
config_section_definition *config_get_section_def(const utf8 *name, int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < countof(_sectionDefinitions); i++) {
|
|
const_utf8string sectionName = _sectionDefinitions[i].section_name;
|
|
const int sectionNameSize = strnlen(sectionName, size);
|
|
if (sectionNameSize == size && sectionName[size] == 0 && _strnicmp(sectionName, name, size) == 0)
|
|
return &_sectionDefinitions[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
config_property_definition *config_get_property_def(config_section_definition *section, const utf8 *name, int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < section->property_definitions_count; i++) {
|
|
const_utf8string propertyName = section->property_definitions[i].property_name;
|
|
const int propertyNameSize = strnlen(propertyName, size);
|
|
if (propertyNameSize == size && propertyName[size] == 0 && _strnicmp(propertyName, name, size) == 0)
|
|
{
|
|
return §ion->property_definitions[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void config_set_property(const config_section_definition *section, const config_property_definition *property, const utf8 *value, int valueSize)
|
|
{
|
|
value_union *destValue = (value_union*)((size_t)section->base_address + (size_t)property->offset);
|
|
|
|
if (property->enum_definitions != NULL)
|
|
if (config_read_enum(destValue, _configValueTypeSize[property->type], value, valueSize, property->enum_definitions))
|
|
return;
|
|
|
|
switch (property->type) {
|
|
case CONFIG_VALUE_TYPE_BOOLEAN:
|
|
if (_strnicmp(value, "false", valueSize) == 0) destValue->value_boolean = false;
|
|
else if (_strnicmp(value, "true", valueSize) == 0) destValue->value_boolean = true;
|
|
else destValue->value_boolean = strtol(value, NULL, 0) != 0;
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT8:
|
|
destValue->value_uint8 = (uint8)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT16:
|
|
destValue->value_uint16 = (uint16)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT32:
|
|
destValue->value_uint32 = (uint32)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT8:
|
|
destValue->value_sint8 = (sint8)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT16:
|
|
destValue->value_sint16 = (sint16)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT32:
|
|
destValue->value_sint32 = (sint32)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_FLOAT:
|
|
destValue->value_float = strtof(value, NULL);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_DOUBLE:
|
|
destValue->value_double = strtod(value, NULL);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_STRING:
|
|
SafeFree(destValue->value_string);
|
|
destValue->value_string = malloc(valueSize + 1);
|
|
memcpy(destValue->value_string, value, valueSize);
|
|
destValue->value_string[valueSize] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void config_read_properties(config_section_definition **currentSection, const_utf8string line)
|
|
{
|
|
utf8 *ch = (utf8*)line;
|
|
utf8_skip_whitespace(&ch);
|
|
|
|
if (*ch == '[') {
|
|
const utf8 *sectionName;
|
|
int sectionNameSize;
|
|
if (config_get_section(ch, §ionName, §ionNameSize))
|
|
*currentSection = config_get_section_def(sectionName, sectionNameSize);
|
|
} else {
|
|
if (*currentSection != NULL) {
|
|
utf8 *propertyName, *value;
|
|
int propertyNameSize, valueSize;
|
|
if (config_get_property_name_value(ch, &propertyName, &propertyNameSize, &value, &valueSize)) {
|
|
config_property_definition *property;
|
|
property = config_get_property_def(*currentSection, propertyName, propertyNameSize);
|
|
if (property != NULL)
|
|
config_set_property(*currentSection, property, value, valueSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool config_read_enum(void *dest, int destSize, const utf8 *key, int keySize, config_enum_definition *enumDefinitions)
|
|
{
|
|
while (enumDefinitions->key != NULL) {
|
|
if (strlen(enumDefinitions->key) == keySize && _strnicmp(enumDefinitions->key, key, keySize) == 0) {
|
|
memcpy(dest, &enumDefinitions->value.value_uint32, destSize);
|
|
return true;
|
|
}
|
|
enumDefinitions++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void config_write_enum(SDL_RWops *file, uint8 type, value_union *value, config_enum_definition *enumDefinitions)
|
|
{
|
|
uint32 enumValue = (value->value_uint32) & ((1 << (_configValueTypeSize[type] * 8)) - 1);
|
|
while (enumDefinitions->key != NULL) {
|
|
if (enumDefinitions->value.value_uint32 == enumValue) {
|
|
rwopswritestr(file, enumDefinitions->key);
|
|
return;
|
|
}
|
|
enumDefinitions++;
|
|
}
|
|
config_save_property_value(file, type, value);
|
|
}
|
|
|
|
static void utf8_skip_whitespace(utf8 **outch)
|
|
{
|
|
utf8 *ch;
|
|
while (**outch != 0) {
|
|
ch = *outch;
|
|
if (!isspace(utf8_get_next(*outch, (const utf8**)outch))) {
|
|
*outch = ch;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void utf8_skip_non_whitespace(utf8 **outch)
|
|
{
|
|
while (**outch != 0) {
|
|
if (isspace(utf8_get_next(*outch, (const utf8**)outch)))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
Code reserved for when we want more intelligent saving of config file which preserves comments and layout
|
|
|
|
enum {
|
|
CONFIG_LINE_TYPE_WHITESPACE,
|
|
CONFIG_LINE_TYPE_COMMENT,
|
|
CONFIG_LINE_TYPE_SECTION,
|
|
CONFIG_LINE_TYPE_PROPERTY,
|
|
CONFIG_LINE_TYPE_INVALID
|
|
};
|
|
|
|
typedef struct {
|
|
uint8 type;
|
|
utf8string line;
|
|
} config_line;
|
|
|
|
static config_line *_configLines = NULL;
|
|
|
|
*/
|
|
|
|
/**
|
|
* Attempts to find the RCT2 installation directory.
|
|
* This should be created from some other resource when OpenRCT2 grows.
|
|
* @param resultPath Pointer to where the absolute path of the RCT2 installation directory will be copied to.
|
|
* @returns 1 if successful, otherwise 0.
|
|
*/
|
|
static bool config_find_rct2_path(utf8 *resultPath)
|
|
{
|
|
int i;
|
|
|
|
log_verbose("searching common installation locations.");
|
|
|
|
const utf8 *searchLocations[] = {
|
|
"C:\\Program Files\\Infogrames\\RollerCoaster Tycoon 2",
|
|
"C:\\Program Files (x86)\\Infogrames\\RollerCoaster Tycoon 2",
|
|
"C:\\Program Files\\Infogrames Interactive\\RollerCoaster Tycoon 2",
|
|
"C:\\Program Files (x86)\\Infogrames Interactive\\RollerCoaster Tycoon 2",
|
|
"C:\\Program Files\\Atari\\RollerCoaster Tycoon 2",
|
|
"C:\\Program Files (x86)\\Atari\\RollerCoaster Tycoon 2",
|
|
"C:\\GOG Games\\RollerCoaster Tycoon 2 Triple Thrill Pack",
|
|
gExePath
|
|
};
|
|
|
|
for (i = 0; i < countof(searchLocations); i++) {
|
|
if (platform_original_game_data_exists(searchLocations[i])) {
|
|
safe_strncpy(resultPath, searchLocations[i], MAX_PATH);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool config_find_or_browse_install_directory()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
utf8 *installPath;
|
|
|
|
if (config_find_rct2_path(path)) {
|
|
SafeFree(gConfigGeneral.game_path);
|
|
gConfigGeneral.game_path = malloc(strlen(path) + 1);
|
|
safe_strncpy(gConfigGeneral.game_path, path, MAX_PATH);
|
|
} else {
|
|
platform_show_messagebox("Unable to find RCT2 installation directory. Please select the directory where you installed RCT2!");
|
|
installPath = platform_open_directory_browser("Please select your RCT2 directory");
|
|
if (installPath == NULL)
|
|
return false;
|
|
|
|
SafeFree(gConfigGeneral.game_path);
|
|
gConfigGeneral.game_path = installPath;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#pragma region Obsolete
|
|
|
|
/**
|
|
* Any code not implemented in OpenRCT2 will still uses the old configuration option addresses. This function copies all the
|
|
* OpenRCT2 configuration options to those addresses until the process is no longer necessary.
|
|
*/
|
|
void config_apply_to_old_addresses()
|
|
{
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_EDGE_SCROLLING, sint8) = gConfigGeneral.edge_scrolling;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_CURRENCY, sint8) = gConfigGeneral.currency_format;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_METRIC, sint8) = gConfigGeneral.measurement_format;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_TEMPERATURE, sint8) = gConfigGeneral.temperature_format;
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_CONSTRUCTION_MARKER, uint8) = gConfigGeneral.construction_marker_colour;
|
|
if (gConfigGeneral.show_height_as_units) {
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_HEIGHT_MARKERS, sint16) = 0;
|
|
} else {
|
|
switch (gConfigGeneral.measurement_format) {
|
|
default:
|
|
case MEASUREMENT_FORMAT_IMPERIAL:
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_HEIGHT_MARKERS, sint16) = 1 * 256;
|
|
break;
|
|
case MEASUREMENT_FORMAT_METRIC:
|
|
case MEASUREMENT_FORMAT_SI:
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_HEIGHT_MARKERS, sint16) = 2 * 256;
|
|
break;
|
|
}
|
|
}
|
|
int configFlags = 0;
|
|
if (gConfigGeneral.always_show_gridlines)
|
|
configFlags |= CONFIG_FLAG_ALWAYS_SHOW_GRIDLINES;
|
|
if (!gConfigGeneral.landscape_smoothing)
|
|
configFlags |= CONFIG_FLAG_DISABLE_SMOOTH_LANDSCAPE;
|
|
if (gConfigGeneral.show_height_as_units)
|
|
configFlags |= CONFIG_FLAG_SHOW_HEIGHT_AS_UNITS;
|
|
if (gConfigGeneral.save_plugin_data)
|
|
configFlags |= CONFIG_FLAG_SAVE_PLUGIN_DATA;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_FLAGS, uint8) = configFlags;
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Shortcuts
|
|
|
|
#define SHIFT 0x100
|
|
#define CTRL 0x200
|
|
#define ALT 0x400
|
|
|
|
// Current keyboard shortcuts
|
|
uint16 gShortcutKeys[SHORTCUT_COUNT];
|
|
|
|
// Default keyboard shortcuts
|
|
static const uint16 _defaultShortcutKeys[SHORTCUT_COUNT] = {
|
|
SDL_SCANCODE_BACKSPACE, // SHORTCUT_CLOSE_TOP_MOST_WINDOW
|
|
SHIFT | SDL_SCANCODE_BACKSPACE, // SHORTCUT_CLOSE_ALL_FLOATING_WINDOWS
|
|
SDL_SCANCODE_ESCAPE, // SHORTCUT_CANCEL_CONSTRUCTION_MODE
|
|
SDL_SCANCODE_PAUSE, // SHORTCUT_PAUSE_GAME
|
|
SDL_SCANCODE_PAGEUP, // SHORTCUT_ZOOM_VIEW_OUT
|
|
SDL_SCANCODE_PAGEDOWN, // SHORTCUT_ZOOM_VIEW_IN
|
|
SDL_SCANCODE_RETURN, // SHORTCUT_ROTATE_VIEW_CLOCKWISE
|
|
SHIFT | SDL_SCANCODE_RETURN, // SHORTCUT_ROTATE_VIEW_ANTICLOCKWISE
|
|
SDL_SCANCODE_Z, // SHORTCUT_ROTATE_CONSTRUCTION_OBJECT
|
|
SDL_SCANCODE_1, // SHORTCUT_UNDERGROUND_VIEW_TOGGLE
|
|
SDL_SCANCODE_H, // SHORTCUT_REMOVE_BASE_LAND_TOGGLE
|
|
SDL_SCANCODE_V, // SHORTCUT_REMOVE_VERTICAL_LAND_TOGGLE
|
|
SDL_SCANCODE_3, // SHORTCUT_SEE_THROUGH_RIDES_TOGGLE
|
|
SDL_SCANCODE_4, // SHORTCUT_SEE_THROUGH_SCENERY_TOGGLE
|
|
SDL_SCANCODE_5, // SHORTCUT_INVISIBLE_SUPPORTS_TOGGLE
|
|
SDL_SCANCODE_6, // SHORTCUT_INVISIBLE_PEOPLE_TOGGLE
|
|
SDL_SCANCODE_8, // SHORTCUT_HEIGHT_MARKS_ON_LAND_TOGGLE
|
|
SDL_SCANCODE_9, // SHORTCUT_HEIGHT_MARKS_ON_RIDE_TRACKS_TOGGLE
|
|
SDL_SCANCODE_0, // SHORTCUT_HEIGHT_MARKS_ON_PATHS_TOGGLE
|
|
SDL_SCANCODE_F1, // SHORTCUT_ADJUST_LAND
|
|
SDL_SCANCODE_F2, // SHORTCUT_ADJUST_WATER
|
|
SDL_SCANCODE_F3, // SHORTCUT_BUILD_SCENERY
|
|
SDL_SCANCODE_F4, // SHORTCUT_BUILD_PATHS
|
|
SDL_SCANCODE_F5, // SHORTCUT_BUILD_NEW_RIDE
|
|
SDL_SCANCODE_F, // SHORTCUT_SHOW_FINANCIAL_INFORMATION
|
|
SDL_SCANCODE_D, // SHORTCUT_SHOW_RESEARCH_INFORMATION
|
|
SDL_SCANCODE_R, // SHORTCUT_SHOW_RIDES_LIST
|
|
SDL_SCANCODE_P, // SHORTCUT_SHOW_PARK_INFORMATION
|
|
SDL_SCANCODE_G, // SHORTCUT_SHOW_GUEST_LIST
|
|
SDL_SCANCODE_S, // SHORTCUT_SHOW_STAFF_LIST
|
|
SDL_SCANCODE_M, // SHORTCUT_SHOW_RECENT_MESSAGES
|
|
SDL_SCANCODE_TAB, // SHORTCUT_SHOW_MAP
|
|
CTRL | SDL_SCANCODE_S, // SHORTCUT_SCREENSHOT
|
|
|
|
// New
|
|
SDL_SCANCODE_MINUS, // SHORTCUT_REDUCE_GAME_SPEED,
|
|
SDL_SCANCODE_EQUALS, // SHORTCUT_INCREASE_GAME_SPEED,
|
|
CTRL | ALT | SDL_SCANCODE_C, // SHORTCUT_OPEN_CHEAT_WINDOW,
|
|
SDL_SCANCODE_T, // SHORTCUT_REMOVE_TOP_BOTTOM_TOOLBAR_TOGGLE,
|
|
SDL_SCANCODE_UP, // SHORTCUT_SCROLL_MAP_UP
|
|
SDL_SCANCODE_LEFT, // SHORTCUT_SCROLL_MAP_LEFT
|
|
SDL_SCANCODE_DOWN, // SHORTCUT_SCROLL_MAP_DOWN
|
|
SDL_SCANCODE_RIGHT, // SHORTCUT_SCROLL_MAP_RIGHT
|
|
SDL_SCANCODE_C, // SHORTCUT_OPEN_CHAT_WINDOW
|
|
CTRL | SDL_SCANCODE_F10, // SHORTCUT_QUICK_SAVE_GAME
|
|
};
|
|
|
|
#define SHORTCUT_FILE_VERSION 1
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E3604
|
|
*/
|
|
void config_reset_shortcut_keys()
|
|
{
|
|
memcpy(gShortcutKeys, _defaultShortcutKeys, sizeof(gShortcutKeys));
|
|
}
|
|
|
|
void config_shortcut_keys_get_path(utf8 *outPath)
|
|
{
|
|
platform_get_user_directory(outPath, NULL);
|
|
strcat(outPath, "hotkeys.cfg");
|
|
}
|
|
|
|
bool config_shortcut_keys_load()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
SDL_RWops *file;
|
|
bool result;
|
|
uint16 version;
|
|
|
|
config_shortcut_keys_get_path(path);
|
|
|
|
file = SDL_RWFromFile(path, "rb");
|
|
if (file != NULL) {
|
|
result = SDL_RWread(file, &version, sizeof(version), 1) == 1;
|
|
if (result && version == SHORTCUT_FILE_VERSION) {
|
|
result = SDL_RWread(file, gShortcutKeys, sizeof(gShortcutKeys), 1) == 1;
|
|
} else {
|
|
result = false;
|
|
}
|
|
SDL_RWclose(file);
|
|
} else {
|
|
result = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool config_shortcut_keys_save()
|
|
{
|
|
const uint16 version = SHORTCUT_FILE_VERSION;
|
|
|
|
utf8 path[MAX_PATH];
|
|
SDL_RWops *file;
|
|
bool result;
|
|
|
|
config_shortcut_keys_get_path(path);
|
|
|
|
file = SDL_RWFromFile(path, "wb");
|
|
if (file != NULL) {
|
|
result = SDL_RWwrite(file, &version, sizeof(version), 1) == 1;
|
|
if (result) {
|
|
result = SDL_RWwrite(file, gShortcutKeys, sizeof(gShortcutKeys), 1) == 1;
|
|
}
|
|
SDL_RWclose(file);
|
|
} else {
|
|
result = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
|
|
#pragma region Themes
|
|
|
|
typedef struct {
|
|
size_t offset;
|
|
const_utf8string property_name;
|
|
uint8 type;
|
|
value_union default_value;
|
|
config_enum_definition *enum_definitions;
|
|
} theme_property_definition;
|
|
|
|
typedef struct {
|
|
size_t offset;
|
|
const_utf8string section_name;
|
|
theme_property_definition *property_definitions;
|
|
int property_definitions_count;
|
|
} theme_section_definition;
|
|
|
|
|
|
theme_property_definition _themeWindowDefinitions[] = {
|
|
{ offsetof(theme_window, colours[0]), "colour_0", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(theme_window, colours[1]), "colour_1", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(theme_window, colours[2]), "colour_2", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(theme_window, colours[3]), "colour_3", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(theme_window, colours[4]), "colour_4", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
{ offsetof(theme_window, colours[5]), "colour_5", CONFIG_VALUE_TYPE_UINT8, 0, NULL },
|
|
};
|
|
|
|
theme_property_definition _themeFeaturesDefinitions[] = {
|
|
{ offsetof(theme_features, rct1_ride_lights), "rct1_ride_lights", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(theme_features, rct1_park_lights), "rct1_park_lights", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(theme_features, rct1_scenario_font), "rct1_scenario_font", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
};
|
|
|
|
|
|
theme_section_definition _themeSectionDefinitions[] = {
|
|
// Special definition for theme windows
|
|
{ 0, "", _themeWindowDefinitions, countof(_themeWindowDefinitions) },
|
|
{ offsetof(theme_preset, features), "features", _themeFeaturesDefinitions, countof(_themeFeaturesDefinitions) },
|
|
};
|
|
|
|
static bool themes_open(const_utf8string path);
|
|
static bool themes_save(const_utf8string path, int preset);
|
|
static void themes_read_properties(theme_preset *theme, theme_section_definition **currentSection, utf8string line);
|
|
static void themes_set_property(theme_preset *theme, const theme_section_definition *section, const theme_property_definition *property, utf8string value, int valueSize);
|
|
static theme_section_definition* themes_get_section_def(utf8string name, int size);
|
|
static theme_property_definition *themes_get_property_def(theme_section_definition *section, utf8string name, int size);
|
|
|
|
void themes_set_default()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
|
|
platform_get_user_directory(path, "themes");
|
|
platform_ensure_directory_exists(path);
|
|
|
|
gConfigThemes.num_presets = 2;
|
|
gConfigThemes.presets = malloc(sizeof(theme_preset) * gConfigThemes.num_presets);
|
|
|
|
// Set RCT2 theme
|
|
safe_strncpy(gConfigThemes.presets[0].name, language_get_string(2741), THEME_PRESET_NAME_SIZE);
|
|
gConfigThemes.presets[0].windows = malloc(sizeof(theme_window) * gNumThemeWindows);
|
|
|
|
// Define the defaults for RCT2 here
|
|
gConfigThemes.presets[0].features.rct1_ride_lights = false;
|
|
gConfigThemes.presets[0].features.rct1_park_lights = false;
|
|
gConfigThemes.presets[0].features.rct1_scenario_font = false;
|
|
|
|
|
|
for (int i = 0; i < (int)gNumThemeWindows; i++) {
|
|
gConfigThemes.presets[0].windows[i] = gThemeWindowDefinitions[i].window;
|
|
}
|
|
|
|
// Set RCT1 theme
|
|
safe_strncpy(gConfigThemes.presets[1].name, language_get_string(2740), THEME_PRESET_NAME_SIZE);
|
|
gConfigThemes.presets[1].windows = malloc(sizeof(theme_window) * gNumThemeWindows);
|
|
|
|
// Define the defaults for RCT1 here
|
|
gConfigThemes.presets[1].features.rct1_ride_lights = true;
|
|
gConfigThemes.presets[1].features.rct1_park_lights = true;
|
|
gConfigThemes.presets[1].features.rct1_scenario_font = true;
|
|
|
|
|
|
for (int i = 0; i < (int)gNumThemeWindows; i++) {
|
|
uint8 changed_colour = 0xFF;
|
|
for (int k = 0; gThemeWindowsRCT1[k].classification != 0xFF; k++) {
|
|
if (gThemeWindowsRCT1[k].classification == gThemeWindowDefinitions[i].classification) {
|
|
changed_colour = (uint8)k;
|
|
break;
|
|
}
|
|
}
|
|
gConfigThemes.presets[1].windows[i] = (changed_colour != 0xFF ? gThemeWindowsRCT1[changed_colour].window : gThemeWindowDefinitions[i].window);
|
|
}
|
|
}
|
|
|
|
void themes_load_presets()
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
file_info file;
|
|
int fileEnumHandle, i;
|
|
|
|
platform_get_user_directory(path, "themes");
|
|
strcat(path, "*.ini");
|
|
fileEnumHandle = platform_enumerate_files_begin(path);
|
|
while (platform_enumerate_files_next(fileEnumHandle, &file)) {
|
|
platform_get_user_directory(path, "themes");
|
|
strcat(path, file.path);
|
|
themes_open(path);
|
|
}
|
|
platform_enumerate_files_end(fileEnumHandle);
|
|
|
|
if (strcmp(gConfigInterface.current_theme_preset, "*RCT2") == 0) {
|
|
theme_change_preset(0);
|
|
}
|
|
else if (strcmp(gConfigInterface.current_theme_preset, "*RCT1") == 0) {
|
|
theme_change_preset(1);
|
|
}
|
|
else {
|
|
for (i = 2; i < gConfigThemes.num_presets; i++) {
|
|
if (strcmp(gConfigInterface.current_theme_preset, gConfigThemes.presets[i].name) == 0) {
|
|
theme_change_preset(i);
|
|
break;
|
|
}
|
|
}
|
|
if (i == gConfigThemes.num_presets) {
|
|
theme_change_preset(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool themes_save_preset(int preset)
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
|
|
platform_get_user_directory(path, "themes");
|
|
strcat(path, gConfigThemes.presets[preset].name);
|
|
strcat(path, ".ini");
|
|
if (themes_save(path, preset)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool themes_open(const_utf8string path)
|
|
{
|
|
SDL_RWops *file;
|
|
utf8string lineBuffer;
|
|
size_t lineBufferCapacity;
|
|
size_t lineLength;
|
|
int c, preset;
|
|
theme_section_definition *currentSection;
|
|
|
|
file = SDL_RWFromFile(path, "rb");
|
|
if (file == NULL)
|
|
return false;
|
|
|
|
// Check if the colour scheme is already loaded
|
|
// No nead to read the first two presets as they're hardcoded in
|
|
for (preset = 2; preset < gConfigThemes.num_presets; preset++) {
|
|
if (strcmp(path, gConfigThemes.presets[preset].name) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
// Otherwise allocate one
|
|
if (preset == gConfigThemes.num_presets) {
|
|
gConfigThemes.num_presets++;
|
|
gConfigThemes.presets = realloc(gConfigThemes.presets, sizeof(theme_preset) * gConfigThemes.num_presets);
|
|
safe_strncpy(gConfigThemes.presets[preset].name, path_get_filename(path), THEME_PRESET_NAME_SIZE);
|
|
path_remove_extension(gConfigThemes.presets[preset].name);
|
|
gConfigThemes.presets[preset].windows = malloc(sizeof(theme_window) * gNumThemeWindows);
|
|
gConfigThemes.presets[preset].features.rct1_ride_lights = false;
|
|
gConfigThemes.presets[preset].features.rct1_park_lights = false;
|
|
gConfigThemes.presets[preset].features.rct1_scenario_font = false;
|
|
for (int i = 0; i < (int)gNumThemeWindows; i++) {
|
|
gConfigThemes.presets[preset].windows[i] = gThemeWindowDefinitions[i].window;
|
|
}
|
|
}
|
|
|
|
currentSection = NULL;
|
|
lineBufferCapacity = 64;
|
|
lineBuffer = malloc(lineBufferCapacity);
|
|
lineLength = 0;
|
|
|
|
// Skim UTF-8 byte order mark
|
|
SDL_RWread(file, lineBuffer, 3, 1);
|
|
if (!(lineBuffer[0] == (utf8)0xEF && lineBuffer[1] == (utf8)0xBB && lineBuffer[2] == (utf8)0xBF))
|
|
SDL_RWseek(file, 0, SEEK_SET);
|
|
|
|
while ((c = rwopsreadc(file)) != EOF) {
|
|
if (c == '\n' || c == '\r') {
|
|
lineBuffer[lineLength++] = 0;
|
|
themes_read_properties(&gConfigThemes.presets[preset], ¤tSection, (utf8string)lineBuffer);
|
|
lineLength = 0;
|
|
}
|
|
else {
|
|
lineBuffer[lineLength++] = c;
|
|
}
|
|
|
|
if (lineLength >= lineBufferCapacity) {
|
|
lineBufferCapacity *= 2;
|
|
lineBuffer = realloc(lineBuffer, lineBufferCapacity);
|
|
}
|
|
}
|
|
|
|
if (lineLength > 0) {
|
|
lineBuffer[lineLength++] = 0;
|
|
themes_read_properties(&gConfigThemes.presets[preset], ¤tSection, lineBuffer);
|
|
}
|
|
|
|
free(lineBuffer);
|
|
SDL_RWclose(file);
|
|
return true;
|
|
}
|
|
|
|
static bool themes_save(const_utf8string path, int preset)
|
|
{
|
|
SDL_RWops *file;
|
|
int i, j;
|
|
value_union *value;
|
|
|
|
file = SDL_RWFromFile(path, "wb");
|
|
if (file == NULL) {
|
|
log_error("Unable to write to theme file.");
|
|
return false;
|
|
}
|
|
|
|
// Skip the window definition, we'll do that after
|
|
for (i = 1; i < countof(_themeSectionDefinitions); i++) {
|
|
theme_section_definition *section = &_themeSectionDefinitions[i];
|
|
|
|
rwopswritec(file, '[');
|
|
rwopswritestr(file, section->section_name);
|
|
rwopswritec(file, ']');
|
|
rwopswritenewline(file);
|
|
|
|
for (j = 0; j < section->property_definitions_count; j++) {
|
|
theme_property_definition *property = §ion->property_definitions[j];
|
|
|
|
rwopswritestr(file, property->property_name);
|
|
rwopswritestr(file, " = ");
|
|
|
|
value = (value_union*)((size_t)&gConfigThemes.presets[preset] + (size_t)section->offset + (size_t)property->offset);
|
|
|
|
if (property->enum_definitions != NULL)
|
|
config_write_enum(file, property->type, value, property->enum_definitions);
|
|
else
|
|
config_save_property_value(file, property->type, value);
|
|
rwopswritenewline(file);
|
|
}
|
|
rwopswritenewline(file);
|
|
}
|
|
|
|
for (i = 0; i < (int)gNumThemeWindows; i++) {
|
|
theme_section_definition *section = &_themeSectionDefinitions[0];
|
|
|
|
rwopswritec(file, '[');
|
|
rwopswritestr(file, gThemeWindowDefinitions[i].section_name);
|
|
rwopswritec(file, ']');
|
|
rwopswritenewline(file);
|
|
|
|
for (j = 0; j < section->property_definitions_count; j++) {
|
|
theme_property_definition *property = §ion->property_definitions[j];
|
|
|
|
rwopswritestr(file, property->property_name);
|
|
rwopswritestr(file, " = ");
|
|
|
|
value = (value_union*)((size_t)gConfigThemes.presets[preset].windows + (size_t)(sizeof(theme_window) * i) + (size_t)property->offset);
|
|
|
|
if (property->enum_definitions != NULL)
|
|
config_write_enum(file, property->type, value, property->enum_definitions);
|
|
else
|
|
config_save_property_value(file, property->type, value);
|
|
rwopswritenewline(file);
|
|
}
|
|
}
|
|
|
|
SDL_RWclose(file);
|
|
return true;
|
|
}
|
|
|
|
|
|
static void themes_read_properties(theme_preset *theme, theme_section_definition **currentSection, utf8string line)
|
|
{
|
|
utf8 *ch = (utf8*)line;
|
|
utf8_skip_whitespace(&ch);
|
|
|
|
if (*ch == '[') {
|
|
const utf8 *sectionName;
|
|
int sectionNameSize;
|
|
if (config_get_section(ch, §ionName, §ionNameSize))
|
|
*currentSection = themes_get_section_def((utf8string)sectionName, sectionNameSize);
|
|
} else {
|
|
if (*currentSection != NULL) {
|
|
utf8 *propertyName, *value;
|
|
int propertyNameSize, valueSize;
|
|
if (config_get_property_name_value(ch, &propertyName, &propertyNameSize, &value, &valueSize)) {
|
|
theme_property_definition *property;
|
|
property = themes_get_property_def(*currentSection, propertyName, propertyNameSize);
|
|
if (property != NULL)
|
|
themes_set_property(theme, *currentSection, property, value, valueSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static void themes_set_property(theme_preset *theme, const theme_section_definition *section, const theme_property_definition *property, utf8string value, int valueSize)
|
|
{
|
|
value_union *destValue = (value_union*)((size_t)theme + (size_t)section->offset + (size_t)property->offset);
|
|
|
|
// Get getting the address from the windows pointer instead
|
|
if (section == &_themeSectionDefinitions[0])
|
|
destValue = (value_union*)((size_t)theme->windows + (size_t)section->offset + (size_t)property->offset);
|
|
|
|
if (property->enum_definitions != NULL)
|
|
if (config_read_enum(destValue, _configValueTypeSize[property->type], value, valueSize, property->enum_definitions))
|
|
return;
|
|
|
|
switch (property->type) {
|
|
case CONFIG_VALUE_TYPE_BOOLEAN:
|
|
if (_strnicmp(value, "false", valueSize) == 0) destValue->value_boolean = false;
|
|
else if (_strnicmp(value, "true", valueSize) == 0) destValue->value_boolean = true;
|
|
else destValue->value_boolean = strtol(value, NULL, 0) != 0;
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT8:
|
|
destValue->value_uint8 = (uint8)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT16:
|
|
destValue->value_uint16 = (uint16)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_UINT32:
|
|
destValue->value_uint32 = (uint32)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT8:
|
|
destValue->value_sint8 = (sint8)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT16:
|
|
destValue->value_sint16 = (sint16)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_SINT32:
|
|
destValue->value_sint32 = (sint32)strtol(value, NULL, 0);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_FLOAT:
|
|
destValue->value_float = strtof(value, NULL);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_DOUBLE:
|
|
destValue->value_double = strtod(value, NULL);
|
|
break;
|
|
case CONFIG_VALUE_TYPE_STRING:
|
|
SafeFree(destValue->value_string);
|
|
destValue->value_string = malloc(valueSize + 1);
|
|
memcpy(destValue->value_string, value, valueSize);
|
|
destValue->value_string[valueSize] = 0;
|
|
break;
|
|
}
|
|
}
|
|
static theme_section_definition* themes_get_section_def(utf8string name, int size)
|
|
{
|
|
int i;
|
|
|
|
// Skip the special definition
|
|
for (i = 1; i < countof(_themeSectionDefinitions); i++) {
|
|
const_utf8string sectionName = _themeSectionDefinitions[i].section_name;
|
|
if (sectionName[size] == 0 && _strnicmp(sectionName, name, size) == 0)
|
|
return &_themeSectionDefinitions[i];
|
|
}
|
|
// Check for window definitions
|
|
for (i = 0; i < (int)gNumThemeWindows; i++) {
|
|
const_utf8string sectionName = gThemeWindowDefinitions[i].section_name;
|
|
if (sectionName[size] == 0 && _strnicmp(sectionName, name, size) == 0) {
|
|
_themeSectionDefinitions[0].offset = (size_t)(sizeof(theme_window) * i);
|
|
return &_themeSectionDefinitions[0];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
static theme_property_definition *themes_get_property_def(theme_section_definition *section, utf8string name, int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < section->property_definitions_count; i++) {
|
|
const_utf8string propertyName = section->property_definitions[i].property_name;
|
|
if (propertyName[size] == 0 && _strnicmp(propertyName, name, size) == 0)
|
|
return §ion->property_definitions[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Title Sequences
|
|
|
|
static void title_sequence_open(const char *path, const char *customName);
|
|
|
|
void title_sequences_set_default()
|
|
{
|
|
char path[MAX_PATH];
|
|
char dataPath[MAX_PATH];
|
|
char sep = platform_get_path_separator();
|
|
|
|
platform_get_user_directory(path, "title sequences");
|
|
platform_ensure_directory_exists(path);
|
|
|
|
gConfigTitleSequences.presets = malloc(0);
|
|
gConfigTitleSequences.num_presets = 0;
|
|
|
|
platform_get_openrct_data_path(dataPath);
|
|
|
|
// RCT1 title sequence
|
|
sprintf(path, "%s%c%s%c%s%c", dataPath, sep, "title", sep, "rct1", sep);
|
|
title_sequence_open(path, language_get_string(5305));
|
|
|
|
// RCT1 (AA) title sequence
|
|
sprintf(path, "%s%c%s%c%s%c", dataPath, sep, "title", sep, "rct1aa", sep);
|
|
title_sequence_open(path, language_get_string(5306));
|
|
|
|
// RCT1 (AA + LL) title sequence
|
|
sprintf(path, "%s%c%s%c%s%c", dataPath, sep, "title", sep, "rct1aall", sep);
|
|
title_sequence_open(path, language_get_string(5307));
|
|
|
|
// RCT2 title sequence
|
|
sprintf(path, "%s%c%s%c%s%c", dataPath, sep, "title", sep, "rct2", sep);
|
|
title_sequence_open(path, language_get_string(5308));
|
|
|
|
// OpenRCT2 title sequence
|
|
sprintf(path, "%s%c%s%c%s%c", dataPath, sep, "title", sep, "openrct2", sep);
|
|
title_sequence_open(path, language_get_string(5309));
|
|
}
|
|
|
|
void title_sequences_load_presets()
|
|
{
|
|
utf8 path[MAX_PATH], titleDir[MAX_PATH];
|
|
int dirEnumHandle, i;
|
|
|
|
// Find all directories in the title sequences folder
|
|
platform_get_user_directory(path, "title sequences");
|
|
dirEnumHandle = platform_enumerate_directories_begin(path);
|
|
while (platform_enumerate_directories_next(dirEnumHandle, titleDir)) {
|
|
platform_get_user_directory(path, "title sequences");
|
|
strcat(path, titleDir);
|
|
title_sequence_open(path, NULL);
|
|
}
|
|
platform_enumerate_directories_end(dirEnumHandle);
|
|
|
|
// Check which title sequence is the current one
|
|
if (_stricmp(gConfigInterface.current_title_sequence_preset, "*RCT1") == 0) {
|
|
gCurrentTitleSequence = 0;
|
|
}
|
|
else if (_stricmp(gConfigInterface.current_title_sequence_preset, "*RCT1AA") == 0) {
|
|
gCurrentTitleSequence = 1;
|
|
}
|
|
else if (_stricmp(gConfigInterface.current_title_sequence_preset, "*RCT1AALL") == 0) {
|
|
gCurrentTitleSequence = 2;
|
|
}
|
|
else if (_stricmp(gConfigInterface.current_title_sequence_preset, "*RCT2") == 0) {
|
|
gCurrentTitleSequence = 3;
|
|
}
|
|
else if (_stricmp(gConfigInterface.current_title_sequence_preset, "*OPENRCT2") == 0) {
|
|
gCurrentTitleSequence = 4;
|
|
}
|
|
else {
|
|
for (i = TITLE_SEQUENCE_DEFAULT_PRESETS; i < gConfigTitleSequences.num_presets; i++) {
|
|
if (_stricmp(gConfigInterface.current_title_sequence_preset, gConfigTitleSequences.presets[i].name) == 0) {
|
|
gCurrentTitleSequence = i;
|
|
break;
|
|
}
|
|
}
|
|
if (i == gConfigTitleSequences.num_presets) {
|
|
gCurrentTitleSequence = 0;
|
|
}
|
|
}
|
|
gCurrentPreviewTitleSequence = gCurrentTitleSequence;
|
|
}
|
|
|
|
static void title_sequence_open(const char *path, const char *customName)
|
|
{
|
|
utf8 titlePath[MAX_PATH], scriptPath[MAX_PATH];
|
|
file_info fileInfo;
|
|
SDL_RWops *file;
|
|
int fileEnumHandle, i, preset;
|
|
char parts[3 * 128], *token, *part1, *part2;
|
|
char separator = platform_get_path_separator();
|
|
|
|
// Check for the script file
|
|
safe_strncpy(scriptPath, path, MAX_PATH);
|
|
strcat(scriptPath, "script.txt");
|
|
if (!platform_file_exists(scriptPath)) {
|
|
// No script file, title sequence is invalid
|
|
return;
|
|
}
|
|
|
|
// Check if the preset is already loaded
|
|
// No nead to read the first two presets as they're hardcoded in
|
|
for (preset = 0; preset < gConfigTitleSequences.num_presets; preset++) {
|
|
if (_stricmp(path, gConfigTitleSequences.presets[preset].name) == 0) {
|
|
return;
|
|
}
|
|
}
|
|
// Otherwise allocate one
|
|
if (preset == gConfigTitleSequences.num_presets) {
|
|
gConfigTitleSequences.num_presets++;
|
|
gConfigTitleSequences.presets = realloc(gConfigTitleSequences.presets, sizeof(title_sequence) * (size_t)gConfigTitleSequences.num_presets);
|
|
|
|
if (customName == NULL) {
|
|
char nameBuffer[MAX_PATH];
|
|
safe_strncpy(nameBuffer, path, MAX_PATH);
|
|
// Get folder name
|
|
// First strip off the last folder separator
|
|
*strrchr(nameBuffer, platform_get_path_separator()) = '\0';
|
|
// Then find the name of the folder
|
|
char *name = strrchr(nameBuffer, platform_get_path_separator()) + 1;
|
|
safe_strncpy(gConfigTitleSequences.presets[preset].name, name, TITLE_SEQUENCE_NAME_SIZE);
|
|
gConfigTitleSequences.presets[preset].path[0] = 0;
|
|
}
|
|
else {
|
|
safe_strncpy(gConfigTitleSequences.presets[preset].name, customName, TITLE_SEQUENCE_NAME_SIZE);
|
|
safe_strncpy(gConfigTitleSequences.presets[preset].path, path, MAX_PATH);
|
|
}
|
|
|
|
gConfigTitleSequences.presets[preset].saves = malloc(0);
|
|
gConfigTitleSequences.presets[preset].commands = malloc(0);
|
|
gConfigTitleSequences.presets[preset].num_saves = 0;
|
|
gConfigTitleSequences.presets[preset].num_commands = 0;
|
|
}
|
|
|
|
// Get the save file list
|
|
safe_strncpy(titlePath, path, MAX_PATH);
|
|
strcat(titlePath, "*.sv6");
|
|
fileEnumHandle = platform_enumerate_files_begin(titlePath);
|
|
while (platform_enumerate_files_next(fileEnumHandle, &fileInfo)) {
|
|
gConfigTitleSequences.presets[preset].num_saves++;
|
|
gConfigTitleSequences.presets[preset].saves = realloc(gConfigTitleSequences.presets[preset].saves, sizeof(char[TITLE_SEQUENCE_MAX_SAVE_LENGTH]) * (size_t)gConfigTitleSequences.presets[preset].num_saves);
|
|
safe_strncpy(gConfigTitleSequences.presets[preset].saves[gConfigTitleSequences.presets[preset].num_saves - 1], fileInfo.path, TITLE_SEQUENCE_MAX_SAVE_LENGTH);
|
|
gConfigTitleSequences.presets[preset].saves[gConfigTitleSequences.presets[preset].num_saves - 1][TITLE_SEQUENCE_MAX_SAVE_LENGTH - 1] = '\0';
|
|
}
|
|
platform_enumerate_files_end(fileEnumHandle);
|
|
safe_strncpy(titlePath, path, MAX_PATH);
|
|
strcat(titlePath, "*.sc6");
|
|
fileEnumHandle = platform_enumerate_files_begin(titlePath);
|
|
while (platform_enumerate_files_next(fileEnumHandle, &fileInfo)) {
|
|
gConfigTitleSequences.presets[preset].num_saves++;
|
|
gConfigTitleSequences.presets[preset].saves = realloc(gConfigTitleSequences.presets[preset].saves, sizeof(char[TITLE_SEQUENCE_MAX_SAVE_LENGTH]) * (size_t)gConfigTitleSequences.presets[preset].num_saves);
|
|
safe_strncpy(gConfigTitleSequences.presets[preset].saves[gConfigTitleSequences.presets[preset].num_saves - 1], fileInfo.path, TITLE_SEQUENCE_MAX_SAVE_LENGTH);
|
|
gConfigTitleSequences.presets[preset].saves[gConfigTitleSequences.presets[preset].num_saves - 1][TITLE_SEQUENCE_MAX_SAVE_LENGTH - 1] = '\0';
|
|
}
|
|
platform_enumerate_files_end(fileEnumHandle);
|
|
|
|
// Load the script file
|
|
file = SDL_RWFromFile(scriptPath, "r");
|
|
sint64 fileSize = SDL_RWsize(file);
|
|
do {
|
|
title_script_get_line(file, parts);
|
|
|
|
token = &parts[0 * 128];
|
|
part1 = &parts[1 * 128];
|
|
part2 = &parts[2 * 128];
|
|
title_command command = { 0 };
|
|
command.command = 0xFF;
|
|
|
|
if (token[0] != 0) {
|
|
if (_stricmp(token, "LOAD") == 0) {
|
|
command.command = TITLE_SCRIPT_LOAD;
|
|
command.saveIndex = 0xFF;
|
|
for (i = 0; i < gConfigTitleSequences.presets[preset].num_saves && command.saveIndex == 0xFF; i++) {
|
|
if (_stricmp(part1, gConfigTitleSequences.presets[preset].saves[i]) == 0)
|
|
command.saveIndex = i;
|
|
}
|
|
} else if (_stricmp(token, "LOCATION") == 0) {
|
|
command.command = TITLE_SCRIPT_LOCATION;
|
|
command.x = atoi(part1) & 0xFF;
|
|
command.y = atoi(part2) & 0xFF;
|
|
} else if (_stricmp(token, "ROTATE") == 0) {
|
|
command.command = TITLE_SCRIPT_ROTATE;
|
|
command.rotations = atoi(part1) & 0xFF;
|
|
} else if (_stricmp(token, "ZOOM") == 0) {
|
|
command.command = TITLE_SCRIPT_ZOOM;
|
|
command.zoom = atoi(part1) & 0xFF;
|
|
} else if (_stricmp(token, "SPEED") == 0) {
|
|
command.command = TITLE_SCRIPT_SPEED;
|
|
command.speed = max(1, min(4, atoi(part1) & 0xFF));
|
|
} else if (_stricmp(token, "WAIT") == 0) {
|
|
command.command = TITLE_SCRIPT_WAIT;
|
|
command.seconds = atoi(part1) & 0xFF;
|
|
} else if (_stricmp(token, "RESTART") == 0) {
|
|
command.command = TITLE_SCRIPT_RESTART;
|
|
} else if (_stricmp(token, "END") == 0) {
|
|
command.command = TITLE_SCRIPT_END;
|
|
} else if (_stricmp(token, "LOADMM") == 0) {
|
|
command.command = TITLE_SCRIPT_LOADMM;
|
|
} else if (_stricmp(token, "LOADRCT1") == 0) {
|
|
command.command = TITLE_SCRIPT_LOADRCT1;
|
|
command.saveIndex = atoi(part1) & 0xFF;
|
|
}
|
|
}
|
|
if (command.command != 0xFF) {
|
|
gConfigTitleSequences.presets[preset].num_commands++;
|
|
gConfigTitleSequences.presets[preset].commands = realloc(gConfigTitleSequences.presets[preset].commands, sizeof(title_command) * (size_t)gConfigTitleSequences.presets[preset].num_commands);
|
|
gConfigTitleSequences.presets[preset].commands[gConfigTitleSequences.presets[preset].num_commands - 1] = command;
|
|
}
|
|
} while (SDL_RWtell(file) < fileSize);
|
|
SDL_RWclose(file);
|
|
}
|
|
|
|
void title_sequence_save_preset_script(int preset)
|
|
{
|
|
utf8 path[MAX_PATH];
|
|
SDL_RWops *file;
|
|
int i;
|
|
char separator = platform_get_path_separator();
|
|
|
|
|
|
platform_get_user_directory(path, "title sequences");
|
|
strcat(path, gConfigTitleSequences.presets[preset].name);
|
|
strncat(path, &separator, 1);
|
|
strcat(path, "script.txt");
|
|
|
|
file = SDL_RWFromFile(path, "wb");
|
|
if (file == NULL) {
|
|
log_error("Unable to write to script file.");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < gConfigTitleSequences.presets[preset].num_commands; i++) {
|
|
title_command *command = &gConfigTitleSequences.presets[preset].commands[i];
|
|
switch (command->command) {
|
|
case TITLE_SCRIPT_LOAD:
|
|
if (command->saveIndex == 0xFF)
|
|
rwopsprintf(file, "LOAD <No save file>");
|
|
else
|
|
rwopsprintf(file, "LOAD %s", gConfigTitleSequences.presets[preset].saves[command->saveIndex]);
|
|
break;
|
|
case TITLE_SCRIPT_LOCATION:
|
|
rwopsprintf(file, "LOCATION %i %i", command->x, command->y);
|
|
break;
|
|
case TITLE_SCRIPT_ROTATE:
|
|
rwopsprintf(file, "ROTATE %i", command->rotations);
|
|
break;
|
|
case TITLE_SCRIPT_ZOOM:
|
|
rwopsprintf(file, "ZOOM %i", command->zoom);
|
|
break;
|
|
case TITLE_SCRIPT_SPEED:
|
|
rwopsprintf(file, "SPEED %i", command->speed);
|
|
break;
|
|
case TITLE_SCRIPT_WAIT:
|
|
rwopsprintf(file, "WAIT %i", command->seconds);
|
|
rwopswritenewline(file);
|
|
break;
|
|
case TITLE_SCRIPT_RESTART:
|
|
rwopsprintf(file, "RESTART");
|
|
break;
|
|
case TITLE_SCRIPT_END:
|
|
rwopsprintf(file, "END");
|
|
break;
|
|
}
|
|
rwopswritenewline(file);
|
|
}
|
|
|
|
SDL_RWclose(file);
|
|
}
|
|
|
|
|
|
#pragma endregion
|