mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-22 23:33:04 +01:00
1372 lines
51 KiB
C
1372 lines
51 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/keyboard_shortcut.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 },
|
|
{ "CZK", CURRENCY_CZECH_KORUNA },
|
|
{ "HKD", CURRENCY_HKD },
|
|
{ "TWD", CURRENCY_TWD },
|
|
{ "CNY", CURRENCY_YUAN },
|
|
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 },
|
|
{ "jp-JP", LANGUAGE_JAPANESE },
|
|
END_OF_ENUM
|
|
};
|
|
|
|
config_enum_definition _dateFormatEnum[] = {
|
|
{ "DD/MM/YY", DATE_FORMAT_DMY },
|
|
{ "MM/DD/YY", DATE_FORMAT_MDY },
|
|
{ "YY/MM/DD", DATE_FORMAT_YMD },
|
|
{ "YY/DD/MM", DATE_FORMAT_YDM },
|
|
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_BOOLEAN, false, 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, scale_quality), "scale_quality", CONFIG_VALUE_TYPE_UINT8, 1, NULL },
|
|
{ offsetof(general_configuration, use_nn_at_integer_scales), "use_nn_at_integer_scales", CONFIG_VALUE_TYPE_BOOLEAN, true, 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, master_volume), "master_volume", CONFIG_VALUE_TYPE_UINT8, 100, NULL },
|
|
{ offsetof(sound_configuration, title_music), "title_music", CONFIG_VALUE_TYPE_UINT8, 2, NULL },
|
|
{ offsetof(sound_configuration, sound_enabled), "sound", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(sound_configuration, sound_volume), "sound_volume", CONFIG_VALUE_TYPE_UINT8, 100, NULL },
|
|
{ offsetof(sound_configuration, ride_music_enabled), "ride_music", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL },
|
|
{ offsetof(sound_configuration, ride_music_volume), "ride_music_volume", CONFIG_VALUE_TYPE_UINT8, 100, NULL },
|
|
{ offsetof(sound_configuration, audio_focus), "audio_focus", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL },
|
|
{ offsetof(sound_configuration, device), "audio_device", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, 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, default_password), "default_password", CONFIG_VALUE_TYPE_STRING, {.value_string = NULL }, 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) },
|
|
{ &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;
|
|
twitch_configuration gConfigTwitch;
|
|
network_configuration gConfigNetwork;
|
|
notification_configuration gConfigNotifications;
|
|
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());
|
|
}
|
|
|
|
static void rwopswritestresc(SDL_RWops *file, const char *str) {
|
|
int length = 0;
|
|
for (const char *c = str; *c != '\0'; ++c) {
|
|
if (*c == '\\') length += 2;
|
|
else ++length;
|
|
}
|
|
|
|
char *escaped = malloc(length + 1);
|
|
int j=0;
|
|
for (const char *c = str; *c != '\0'; ++c) {
|
|
if (*c == '\\') escaped[j++] = '\\';
|
|
escaped[j++] = *c;
|
|
}
|
|
escaped[length] = '\0';
|
|
|
|
rwopswritestr(file, escaped);
|
|
SafeFree(escaped);
|
|
}
|
|
|
|
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 if (strcmp(property->property_name, "player_name") == 0) {
|
|
utf8* username = platform_get_username();
|
|
|
|
if (username) {
|
|
destValue->value_string = _strdup(username);
|
|
} else {
|
|
destValue->value_string = _strdup(language_get_string(STR_PLAYER_DEFAULT_NAME));
|
|
}
|
|
}
|
|
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_release()
|
|
{
|
|
for (int i = 0; i < countof(_sectionDefinitions); i++) {
|
|
config_section_definition *section = &_sectionDefinitions[i];
|
|
for (int 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);
|
|
if (property->type == CONFIG_VALUE_TYPE_STRING) {
|
|
utf8 **dst = (utf8**)&(destValue->value_string);
|
|
SafeFree(*dst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
rwopswritestresc(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, *clast;
|
|
int c;
|
|
bool quotes;
|
|
|
|
ch = line;
|
|
utf8_skip_whitespace(&ch);
|
|
|
|
if (*ch == 0) return false;
|
|
*propertyName = ch;
|
|
|
|
bool equals = false;
|
|
while ((c = utf8_get_next(ch, (const utf8**)&ch)) != 0) {
|
|
if (isspace(c) || c == '=') {
|
|
if (c == '=') equals = true;
|
|
*propertyNameSize = ch - *propertyName - 1;
|
|
break;
|
|
} else if (c == '#') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (*ch == 0) return false;
|
|
utf8_skip_whitespace(&ch);
|
|
if (!equals) {
|
|
if (*ch != '=') return false;
|
|
ch++;
|
|
utf8_skip_whitespace(&ch);
|
|
}
|
|
if (*ch == 0) return false;
|
|
|
|
if (*ch == '"') {
|
|
ch++;
|
|
quotes = true;
|
|
} else {
|
|
quotes = false;
|
|
}
|
|
*value = ch;
|
|
|
|
clast = ch;
|
|
while ((c = utf8_get_next(ch, (const utf8**)&ch)) != 0) {
|
|
if (!quotes) {
|
|
if (c == '#') break;
|
|
if (c != ' ') clast = ch;
|
|
}
|
|
}
|
|
if (!quotes) *valueSize = clast - *value;
|
|
else *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;
|
|
}
|
|
|
|
static utf8string escape_string(const utf8 *value, int valueSize) {
|
|
int length = 0;
|
|
bool backslash = false;
|
|
for (int i=0; i < valueSize; ++i) {
|
|
if (value[i] == '\\') {
|
|
if (backslash) backslash = false;
|
|
else ++length, backslash = true;
|
|
} else ++length, backslash = false;
|
|
}
|
|
utf8string escaped = malloc(length + 1);
|
|
|
|
int j=0;
|
|
backslash = false;
|
|
for (int i=0; i < valueSize; ++i) {
|
|
if (value[i] == '\\') {
|
|
if (backslash) backslash = false;
|
|
else escaped[j++] = value[i], backslash = true;
|
|
} else escaped[j++] = value[i], backslash = false;
|
|
}
|
|
escaped[length] = '\0';
|
|
|
|
return escaped;
|
|
}
|
|
|
|
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 = escape_string(value, valueSize);
|
|
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_strcpy(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_strcpy(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
|
|
#define CMD 0x800
|
|
#ifdef __MACOSX__
|
|
#define PLATFORM_MODIFIER CMD
|
|
#else
|
|
#define PLATFORM_MODIFIER CTRL
|
|
#endif
|
|
|
|
// 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
|
|
PLATFORM_MODIFIER | SDL_SCANCODE_S, // SHORTCUT_SCREENSHOT
|
|
|
|
// New
|
|
SDL_SCANCODE_MINUS, // SHORTCUT_REDUCE_GAME_SPEED,
|
|
SDL_SCANCODE_EQUALS, // SHORTCUT_INCREASE_GAME_SPEED,
|
|
PLATFORM_MODIFIER | 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
|
|
PLATFORM_MODIFIER | SDL_SCANCODE_F10, // SHORTCUT_QUICK_SAVE_GAME
|
|
|
|
SHORTCUT_UNDEFINED, // SHORTCUT_SHOW_OPTIONS
|
|
};
|
|
|
|
#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) {
|
|
for (int i = 0; i < SHORTCUT_COUNT; i++) {
|
|
if (SDL_RWread(file, &gShortcutKeys[i], sizeof(uint16), 1) != 1) {
|
|
break;
|
|
}
|
|
}
|
|
} 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 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_strcpy(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_strcpy(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_strcpy(gConfigTitleSequences.presets[preset].name, name, TITLE_SEQUENCE_NAME_SIZE);
|
|
gConfigTitleSequences.presets[preset].path[0] = 0;
|
|
}
|
|
else {
|
|
safe_strcpy(gConfigTitleSequences.presets[preset].name, customName, TITLE_SEQUENCE_NAME_SIZE);
|
|
safe_strcpy(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_strcpy(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_strcpy(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_strcpy(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_strcpy(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
|