2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 * All actions handling saving and loading of the settings/configuration goes on in this file.
11 * The file consists of three parts:
13 * <li>Parsing the configuration file (openttd.cfg). This is achieved with the ini_ functions which
14 * handle various types, such as normal 'key = value' pairs, lists and value combinations of
15 * lists, strings, integers, 'bit'-masks and element selections.
16 * <li>Handle reading and writing to the setting-structures from inside the game either from
17 * the console for example or through the gui with CMD_ functions.
18 * <li>Handle saving/loading of the PATS chunk inside the savegame.
26 #include "settings_table.h"
29 #include "network/network.h"
30 #include "network/network_func.h"
31 #include "network/core/config.h"
32 #include "command_func.h"
33 #include "console_func.h"
35 #include "window_func.h"
36 #include "company_func.h"
40 #include "settings_func.h"
42 #include "ai/ai_config.hpp"
43 #include "game/game_config.hpp"
44 #include "newgrf_config.h"
45 #include "base_media_base.h"
47 #include "fileio_func.h"
48 #include "settings_cmd.h"
50 #include "table/strings.h"
52 #include "safeguards.h"
54 ClientSettings _settings_client
;
55 GameSettings _settings_game
; ///< Game settings of a running game or the scenario editor.
56 GameSettings _settings_newgame
; ///< Game settings for new games (updated from the intro screen).
57 VehicleDefaultSettings _old_vds
; ///< Used for loading default vehicles settings from old savegames.
58 std::string _config_file
; ///< Configuration file of OpenTTD.
59 std::string _private_file
; ///< Private configuration file of OpenTTD.
60 std::string _secrets_file
; ///< Secrets configuration file of OpenTTD.
62 static ErrorList _settings_error_list
; ///< Errors while loading minimal settings.
65 * List of all the generic setting tables.
67 * There are a few tables that are special and not processed like the rest:
68 * - _currency_settings
72 * As such, they are not part of this list.
74 static auto &GenericSettingTables()
76 static const SettingTable _generic_setting_tables
[] = {
85 _news_display_settings
,
86 _pathfinding_settings
,
90 return _generic_setting_tables
;
94 * List of all the private setting tables.
96 static auto &PrivateSettingTables()
98 static const SettingTable _private_setting_tables
[] = {
99 _network_private_settings
,
101 return _private_setting_tables
;
105 * List of all the secrets setting tables.
107 static auto &SecretSettingTables()
109 static const SettingTable _secrets_setting_tables
[] = {
110 _network_secrets_settings
,
112 return _secrets_setting_tables
;
115 typedef void SettingDescProc(IniFile
&ini
, const SettingTable
&desc
, const char *grpname
, void *object
, bool only_startup
);
116 typedef void SettingDescProcList(IniFile
&ini
, const char *grpname
, StringList
&list
);
118 static bool IsSignedVarMemType(VarType vt
)
120 switch (GetVarMemType(vt
)) {
131 * IniFile to store a configuration.
133 class ConfigIniFile
: public IniFile
{
135 inline static const IniGroupNameList list_group_names
= {
139 "server_bind_addresses",
143 ConfigIniFile(const std::string
&filename
) : IniFile(list_group_names
)
145 this->LoadFromDisk(filename
, NO_DIRECTORY
);
152 * Sometimes we move settings between different ini-files, as we need to know
153 * when we have to load/remove it from the old versus reading it from the new
154 * location. These versions assist with situations like that.
156 enum IniFileVersion
: uint32_t {
157 IFV_0
, ///< 0 All versions prior to introduction.
158 IFV_PRIVATE_SECRETS
, ///< 1 PR#9298 Moving of settings from openttd.cfg to private.cfg / secrets.cfg.
159 IFV_GAME_TYPE
, ///< 2 PR#9515 Convert server_advertise to server_game_type.
160 IFV_LINKGRAPH_SECONDS
, ///< 3 PR#10610 Store linkgraph update intervals in seconds instead of days.
161 IFV_NETWORK_PRIVATE_SETTINGS
, ///< 4 PR#10762 Move use_relay_service to private settings.
163 IFV_AUTOSAVE_RENAME
, ///< 5 PR#11143 Renamed values of autosave to be in minutes.
164 IFV_RIGHT_CLICK_CLOSE
, ///< 6 PR#10204 Add alternative right click to close windows setting.
165 IFV_REMOVE_GENERATION_SEED
, ///< 7 PR#11927 Remove "generation_seed" from configuration.
167 IFV_MAX_VERSION
, ///< Highest possible ini-file version.
170 const uint16_t INIFILE_VERSION
= (IniFileVersion
)(IFV_MAX_VERSION
- 1); ///< Current ini-file version of OpenTTD.
173 * Find the index value of a ONEofMANY type in a string separated by |
174 * @param str the current value of the setting for which a value needs found
175 * @param len length of the string
176 * @param many full domain of values the ONEofMANY setting can have
177 * @return the integer index of the full-list, or -1 if not found
179 size_t OneOfManySettingDesc::ParseSingleValue(const char *str
, size_t len
, const std::vector
<std::string
> &many
)
181 /* check if it's an integer */
182 if (isdigit(*str
)) return std::strtoul(str
, nullptr, 0);
185 for (auto one
: many
) {
186 if (one
.size() == len
&& strncmp(one
.c_str(), str
, len
) == 0) return idx
;
194 * Find whether a string was a boolean true or a boolean false.
196 * @param str the current value of the setting for which a value needs found.
197 * @return Either true/false, or nullopt if no boolean value found.
199 std::optional
<bool> BoolSettingDesc::ParseSingleValue(const char *str
)
201 if (strcmp(str
, "true") == 0 || strcmp(str
, "on") == 0 || strcmp(str
, "1") == 0) return true;
202 if (strcmp(str
, "false") == 0 || strcmp(str
, "off") == 0 || strcmp(str
, "0") == 0) return false;
208 * Find the set-integer value MANYofMANY type in a string
209 * @param many full domain of values the MANYofMANY setting can have
210 * @param str the current string value of the setting, each individual
211 * of separated by a whitespace,tab or | character
212 * @return the 'fully' set integer, or -1 if a set is not found
214 static size_t LookupManyOfMany(const std::vector
<std::string
> &many
, const char *str
)
221 /* skip "whitespace" */
222 while (*str
== ' ' || *str
== '\t' || *str
== '|') str
++;
223 if (*str
== 0) break;
226 while (*s
!= 0 && *s
!= ' ' && *s
!= '\t' && *s
!= '|') s
++;
228 r
= OneOfManySettingDesc::ParseSingleValue(str
, s
- str
, many
);
229 if (r
== (size_t)-1) return r
;
231 SetBit(res
, (uint8_t)r
); // value found, set it
239 * Parse an integerlist string and set each found value
240 * @param p the string to be parsed. Each element in the list is separated by a
241 * comma or a space character
242 * @param items pointer to the integerlist-array that will be filled with values
243 * @param maxitems the maximum number of elements the integerlist-array has
244 * @return returns the number of items found, or -1 on an error
247 static int ParseIntList(const char *p
, T
*items
, size_t maxitems
)
249 size_t n
= 0; // number of items read so far
250 bool comma
= false; // do we accept comma?
255 /* Do not accept multiple commas between numbers */
256 if (!comma
) return -1;
265 if (n
== maxitems
) return -1; // we don't accept that many numbers
267 unsigned long v
= std::strtoul(p
, &end
, 0);
268 if (p
== end
) return -1; // invalid character (not a number)
269 if (sizeof(T
) < sizeof(v
)) v
= Clamp
<unsigned long>(v
, std::numeric_limits
<T
>::min(), std::numeric_limits
<T
>::max());
271 p
= end
; // first non-number
272 comma
= true; // we accept comma now
278 /* If we have read comma but no number after it, fail.
279 * We have read comma when (n != 0) and comma is not allowed */
280 if (n
!= 0 && !comma
) return -1;
282 return ClampTo
<int>(n
);
286 * Load parsed string-values into an integer-array (intlist)
287 * @param str the string that contains the values (and will be parsed)
288 * @param array pointer to the integer-arrays that will be filled
289 * @param nelems the number of elements the array holds. Maximum is 64 elements
290 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
291 * @return return true on success and false on error
293 static bool LoadIntList(const char *str
, void *array
, int nelems
, VarType type
)
295 unsigned long items
[64];
298 if (str
== nullptr) {
299 memset(items
, 0, sizeof(items
));
302 nitems
= ParseIntList(str
, items
, lengthof(items
));
303 if (nitems
!= nelems
) return false;
310 for (i
= 0; i
!= nitems
; i
++) ((byte
*)array
)[i
] = items
[i
];
315 for (i
= 0; i
!= nitems
; i
++) ((uint16_t*)array
)[i
] = items
[i
];
320 for (i
= 0; i
!= nitems
; i
++) ((uint32_t*)array
)[i
] = items
[i
];
323 default: NOT_REACHED();
330 * Convert an integer-array (intlist) to a string representation. Each value
331 * is separated by a comma or a space character
332 * @param buf output buffer where the string-representation will be stored
333 * @param last last item to write to in the output buffer
334 * @param array pointer to the integer-arrays that is read from
335 * @param nelems the number of elements the array holds.
336 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
338 std::string
ListSettingDesc::FormatValue(const void *object
) const
340 const byte
*p
= static_cast<const byte
*>(GetVariableAddress(object
, this->save
));
343 for (size_t i
= 0; i
!= this->save
.length
; i
++) {
345 switch (GetVarMemType(this->save
.conv
)) {
347 case SLE_VAR_I8
: v
= *(const int8_t *)p
; p
+= 1; break;
348 case SLE_VAR_U8
: v
= *(const uint8_t *)p
; p
+= 1; break;
349 case SLE_VAR_I16
: v
= *(const int16_t *)p
; p
+= 2; break;
350 case SLE_VAR_U16
: v
= *(const uint16_t *)p
; p
+= 2; break;
351 case SLE_VAR_I32
: v
= *(const int32_t *)p
; p
+= 4; break;
352 case SLE_VAR_U32
: v
= *(const uint32_t *)p
; p
+= 4; break;
353 default: NOT_REACHED();
355 if (i
!= 0) result
+= ',';
356 result
+= std::to_string(v
);
361 std::string
OneOfManySettingDesc::FormatSingleValue(uint id
) const
363 if (id
>= this->many
.size()) {
364 return std::to_string(id
);
366 return this->many
[id
];
369 std::string
OneOfManySettingDesc::FormatValue(const void *object
) const
371 uint id
= (uint
)this->Read(object
);
372 return this->FormatSingleValue(id
);
375 std::string
ManyOfManySettingDesc::FormatValue(const void *object
) const
377 uint bitmask
= (uint
)this->Read(object
);
383 for (uint id
: SetBitIterator(bitmask
)) {
384 if (!result
.empty()) result
+= '|';
385 result
+= this->FormatSingleValue(id
);
391 * Convert a string representation (external) of an integer-like setting to an integer.
392 * @param str Input string that will be parsed based on the type of desc.
393 * @return The value from the parse string, or the default value of the setting.
395 size_t IntSettingDesc::ParseValue(const char *str
) const
398 size_t val
= std::strtoul(str
, &end
, 0);
400 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
401 msg
.SetDParamStr(0, str
);
402 msg
.SetDParamStr(1, this->GetName());
403 _settings_error_list
.push_back(msg
);
407 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_TRAILING_CHARACTERS
);
408 msg
.SetDParamStr(0, this->GetName());
409 _settings_error_list
.push_back(msg
);
414 size_t OneOfManySettingDesc::ParseValue(const char *str
) const
416 size_t r
= OneOfManySettingDesc::ParseSingleValue(str
, strlen(str
), this->many
);
417 /* if the first attempt of conversion from string to the appropriate value fails,
418 * look if we have defined a converter from old value to new value. */
419 if (r
== (size_t)-1 && this->many_cnvt
!= nullptr) r
= this->many_cnvt(str
);
420 if (r
!= (size_t)-1) return r
; // and here goes converted value
422 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
423 msg
.SetDParamStr(0, str
);
424 msg
.SetDParamStr(1, this->GetName());
425 _settings_error_list
.push_back(msg
);
429 size_t ManyOfManySettingDesc::ParseValue(const char *str
) const
431 size_t r
= LookupManyOfMany(this->many
, str
);
432 if (r
!= (size_t)-1) return r
;
433 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
434 msg
.SetDParamStr(0, str
);
435 msg
.SetDParamStr(1, this->GetName());
436 _settings_error_list
.push_back(msg
);
440 size_t BoolSettingDesc::ParseValue(const char *str
) const
442 auto r
= BoolSettingDesc::ParseSingleValue(str
);
443 if (r
.has_value()) return *r
;
445 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_VALUE
);
446 msg
.SetDParamStr(0, str
);
447 msg
.SetDParamStr(1, this->GetName());
448 _settings_error_list
.push_back(msg
);
453 * Get the title of the setting.
454 * The string should include a {STRING2} to show the current value.
455 * @return The title string.
457 StringID
IntSettingDesc::GetTitle() const
459 return this->get_title_cb
!= nullptr ? this->get_title_cb(*this) : this->str
;
463 * Get the help text of the setting.
464 * @return The requested help text.
466 StringID
IntSettingDesc::GetHelp() const
468 return this->get_help_cb
!= nullptr ? this->get_help_cb(*this) : this->str_help
;
472 * Set the DParams for drawing the value of the setting.
473 * @param first_param First DParam to use
474 * @param value Setting value to set params for.
476 void IntSettingDesc::SetValueDParams(uint first_param
, int32_t value
) const
478 if (this->set_value_dparams_cb
!= nullptr) {
479 this->set_value_dparams_cb(*this, first_param
, value
);
480 } else if (this->IsBoolSetting()) {
481 SetDParam(first_param
++, value
!= 0 ? STR_CONFIG_SETTING_ON
: STR_CONFIG_SETTING_OFF
);
483 if ((this->flags
& SF_GUI_DROPDOWN
) != 0) {
484 SetDParam(first_param
++, this->str_val
- this->min
+ value
);
486 SetDParam(first_param
++, this->str_val
+ ((value
== 0 && (this->flags
& SF_GUI_0_IS_SPECIAL
) != 0) ? 1 : 0));
488 SetDParam(first_param
++, value
);
493 * Make the value valid and then write it to the setting.
494 * See #MakeValidValid and #Write for more details.
495 * @param object The object the setting is to be saved in.
496 * @param val Signed version of the new value.
498 void IntSettingDesc::MakeValueValidAndWrite(const void *object
, int32_t val
) const
500 this->MakeValueValid(val
);
501 this->Write(object
, val
);
505 * Make the value valid given the limitations of this setting.
507 * In the case of int settings this is ensuring the value is between the minimum and
508 * maximum value, with a special case for 0 if SF_GUI_0_IS_SPECIAL is set.
509 * This is generally done by clamping the value so it is within the allowed value range.
510 * However, for SF_GUI_DROPDOWN the default is used when the value is not valid.
511 * @param val The value to make valid.
513 void IntSettingDesc::MakeValueValid(int32_t &val
) const
515 /* We need to take special care of the uint32_t type as we receive from the function
516 * a signed integer. While here also bail out on 64-bit settings as those are not
517 * supported. Unsigned 8 and 16-bit variables are safe since they fit into a signed
519 * TODO: Support 64-bit settings/variables; requires 64 bit over command protocol! */
520 switch (GetVarMemType(this->save
.conv
)) {
521 case SLE_VAR_NULL
: return;
528 /* Override the minimum value. No value below this->min, except special value 0 */
529 if (!(this->flags
& SF_GUI_0_IS_SPECIAL
) || val
!= 0) {
530 if (!(this->flags
& SF_GUI_DROPDOWN
)) {
531 /* Clamp value-type setting to its valid range */
532 val
= Clamp(val
, this->min
, this->max
);
533 } else if (val
< this->min
|| val
> (int32_t)this->max
) {
534 /* Reset invalid discrete setting (where different values change gameplay) to its default value */
541 /* Override the minimum value. No value below this->min, except special value 0 */
542 uint32_t uval
= (uint32_t)val
;
543 if (!(this->flags
& SF_GUI_0_IS_SPECIAL
) || uval
!= 0) {
544 if (!(this->flags
& SF_GUI_DROPDOWN
)) {
545 /* Clamp value-type setting to its valid range */
546 uval
= ClampU(uval
, this->min
, this->max
);
547 } else if (uval
< (uint
)this->min
|| uval
> this->max
) {
548 /* Reset invalid discrete setting to its default value */
549 uval
= (uint32_t)this->def
;
557 default: NOT_REACHED();
562 * Set the value of a setting.
563 * @param object The object the setting is to be saved in.
564 * @param val Signed version of the new value.
566 void IntSettingDesc::Write(const void *object
, int32_t val
) const
568 void *ptr
= GetVariableAddress(object
, this->save
);
569 WriteValue(ptr
, this->save
.conv
, (int64_t)val
);
573 * Read the integer from the the actual setting.
574 * @param object The object the setting is to be saved in.
575 * @return The value of the saved integer.
577 int32_t IntSettingDesc::Read(const void *object
) const
579 void *ptr
= GetVariableAddress(object
, this->save
);
580 return (int32_t)ReadValue(ptr
, this->save
.conv
);
584 * Make the value valid given the limitations of this setting.
586 * In the case of string settings this is ensuring the string contains only accepted
587 * Utf8 characters and is at most the maximum length defined in this setting.
588 * @param str The string to make valid.
590 void StringSettingDesc::MakeValueValid(std::string
&str
) const
592 if (this->max_length
== 0 || str
.size() < this->max_length
) return;
594 /* In case a maximum length is imposed by the setting, the length
595 * includes the '\0' termination for network transfer purposes.
596 * Also ensure the string is valid after chopping of some bytes. */
597 std::string
stdstr(str
, this->max_length
- 1);
598 str
.assign(StrMakeValid(stdstr
, SVS_NONE
));
602 * Write a string to the actual setting.
603 * @param object The object the setting is to be saved in.
604 * @param str The string to save.
606 void StringSettingDesc::Write(const void *object
, const std::string
&str
) const
608 reinterpret_cast<std::string
*>(GetVariableAddress(object
, this->save
))->assign(str
);
612 * Read the string from the the actual setting.
613 * @param object The object the setting is to be saved in.
614 * @return The value of the saved string.
616 const std::string
&StringSettingDesc::Read(const void *object
) const
618 return *reinterpret_cast<std::string
*>(GetVariableAddress(object
, this->save
));
622 * Load values from a group of an IniFile structure into the internal representation
623 * @param ini pointer to IniFile structure that holds administrative information
624 * @param settings_table table with SettingDesc structures whose internally pointed variables will
626 * @param grpname the group of the IniFile to search in for the new values
627 * @param object pointer to the object been loaded
628 * @param only_startup load only the startup settings set
630 static void IniLoadSettings(IniFile
&ini
, const SettingTable
&settings_table
, const char *grpname
, void *object
, bool only_startup
)
632 const IniGroup
*group
;
633 const IniGroup
*group_def
= ini
.GetGroup(grpname
);
635 for (auto &desc
: settings_table
) {
636 const SettingDesc
*sd
= GetSettingDesc(desc
);
637 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
638 if (sd
->startup
!= only_startup
) continue;
640 /* For settings.xx.yy load the settings from [xx] yy = ? */
641 std::string s
{ sd
->GetName() };
642 auto sc
= s
.find('.');
643 if (sc
!= std::string::npos
) {
644 group
= ini
.GetGroup(s
.substr(0, sc
));
645 if (group
== nullptr) group
= group_def
;
646 s
= s
.substr(sc
+ 1);
651 const IniItem
*item
= nullptr;
652 if (group
!= nullptr) item
= group
->GetItem(s
);
653 if (item
== nullptr && group
!= group_def
&& group_def
!= nullptr) {
654 /* For settings.xx.yy load the settings from [settings] yy = ? in case the previous
655 * did not exist (e.g. loading old config files with a [settings] section */
656 item
= group_def
->GetItem(s
);
658 if (item
== nullptr) {
659 /* For settings.xx.zz.yy load the settings from [zz] yy = ? in case the previous
660 * did not exist (e.g. loading old config files with a [yapf] section */
662 if (sc
!= std::string::npos
) {
663 if (group
= ini
.GetGroup(s
.substr(0, sc
)); group
!= nullptr) item
= group
->GetItem(s
.substr(sc
+ 1));
667 sd
->ParseValue(item
, object
);
671 void IntSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
673 size_t val
= (item
== nullptr) ? this->def
: this->ParseValue(item
->value
.has_value() ? item
->value
->c_str() : "");
674 this->MakeValueValidAndWrite(object
, (int32_t)val
);
677 void StringSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
679 std::string str
= (item
== nullptr) ? this->def
: item
->value
.value_or("");
680 this->MakeValueValid(str
);
681 this->Write(object
, str
);
684 void ListSettingDesc::ParseValue(const IniItem
*item
, void *object
) const
686 const char *str
= (item
== nullptr) ? this->def
: item
->value
.has_value() ? item
->value
->c_str() : nullptr;
687 void *ptr
= GetVariableAddress(object
, this->save
);
688 if (!LoadIntList(str
, ptr
, this->save
.length
, GetVarMemType(this->save
.conv
))) {
689 ErrorMessageData
msg(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
);
690 msg
.SetDParamStr(0, this->GetName());
691 _settings_error_list
.push_back(msg
);
694 LoadIntList(this->def
, ptr
, this->save
.length
, GetVarMemType(this->save
.conv
));
699 * Save the values of settings to the inifile.
700 * @param ini pointer to IniFile structure
701 * @param sd read-only SettingDesc structure which contains the unmodified,
702 * loaded values of the configuration file and various information about it
703 * @param grpname holds the name of the group (eg. [network]) where these will be saved
704 * @param object pointer to the object been saved
705 * The function works as follows: for each item in the SettingDesc structure we
706 * have a look if the value has changed since we started the game (the original
707 * values are reloaded when saving). If settings indeed have changed, we get
708 * these and save them.
710 static void IniSaveSettings(IniFile
&ini
, const SettingTable
&settings_table
, const char *grpname
, void *object
, bool)
712 IniGroup
*group_def
= nullptr, *group
;
714 for (auto &desc
: settings_table
) {
715 const SettingDesc
*sd
= GetSettingDesc(desc
);
716 /* If the setting is not saved to the configuration
717 * file, just continue with the next setting */
718 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
719 if (sd
->flags
& SF_NOT_IN_CONFIG
) continue;
721 /* XXX - wtf is this?? (group override?) */
722 std::string s
{ sd
->GetName() };
723 auto sc
= s
.find('.');
724 if (sc
!= std::string::npos
) {
725 group
= &ini
.GetOrCreateGroup(s
.substr(0, sc
));
726 s
= s
.substr(sc
+ 1);
728 if (group_def
== nullptr) group_def
= &ini
.GetOrCreateGroup(grpname
);
732 IniItem
&item
= group
->GetOrCreateItem(s
);
734 if (!item
.value
.has_value() || !sd
->IsSameValue(&item
, object
)) {
735 /* The value is different, that means we have to write it to the ini */
736 item
.value
.emplace(sd
->FormatValue(object
));
741 std::string
IntSettingDesc::FormatValue(const void *object
) const
744 if (IsSignedVarMemType(this->save
.conv
)) {
745 i
= this->Read(object
);
747 i
= (uint32_t)this->Read(object
);
749 return std::to_string(i
);
752 std::string
BoolSettingDesc::FormatValue(const void *object
) const
754 bool val
= this->Read(object
) != 0;
755 return val
? "true" : "false";
758 bool IntSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
760 int32_t item_value
= (int32_t)this->ParseValue(item
->value
->c_str());
761 int32_t object_value
= this->Read(object
);
762 return item_value
== object_value
;
765 bool IntSettingDesc::IsDefaultValue(void *object
) const
767 int32_t object_value
= this->Read(object
);
768 return this->def
== object_value
;
771 std::string
StringSettingDesc::FormatValue(const void *object
) const
773 const std::string
&str
= this->Read(object
);
774 switch (GetVarMemType(this->save
.conv
)) {
775 case SLE_VAR_STR
: return str
;
781 return fmt::format("\"{}\"", str
);
783 default: NOT_REACHED();
787 bool StringSettingDesc::IsSameValue(const IniItem
*item
, void *object
) const
789 /* The ini parsing removes the quotes, which are needed to retain the spaces in STRQs,
790 * so those values are always different in the parsed ini item than they should be. */
791 if (GetVarMemType(this->save
.conv
) == SLE_VAR_STRQ
) return false;
793 const std::string
&str
= this->Read(object
);
794 return item
->value
->compare(str
) == 0;
797 bool StringSettingDesc::IsDefaultValue(void *object
) const
799 const std::string
&str
= this->Read(object
);
800 return this->def
== str
;
803 bool ListSettingDesc::IsSameValue(const IniItem
*, void *) const
805 /* Checking for equality is way more expensive than just writing the value. */
809 bool ListSettingDesc::IsDefaultValue(void *) const
811 /* Defaults of lists are often complicated, and hard to compare. */
816 * Loads all items from a 'grpname' section into a list
817 * The list parameter can be a nullptr pointer, in this case nothing will be
818 * saved and a callback function should be defined that will take over the
819 * list-handling and store the data itself somewhere.
820 * @param ini IniFile handle to the ini file with the source data
821 * @param grpname character string identifying the section-header of the ini file that will be parsed
822 * @param list new list with entries of the given section
824 static void IniLoadSettingList(IniFile
&ini
, const char *grpname
, StringList
&list
)
826 const IniGroup
*group
= ini
.GetGroup(grpname
);
828 if (group
== nullptr) return;
832 for (const IniItem
&item
: group
->items
) {
833 if (!item
.name
.empty()) list
.push_back(item
.name
);
838 * Saves all items from a list into the 'grpname' section
839 * The list parameter can be a nullptr pointer, in this case a callback function
840 * should be defined that will provide the source data to be saved.
841 * @param ini IniFile handle to the ini file where the destination data is saved
842 * @param grpname character string identifying the section-header of the ini file
843 * @param list pointer to an string(pointer) array that will be used as the
844 * source to be saved into the relevant ini section
846 static void IniSaveSettingList(IniFile
&ini
, const char *grpname
, StringList
&list
)
848 IniGroup
&group
= ini
.GetOrCreateGroup(grpname
);
851 for (const auto &iter
: list
) {
852 group
.GetOrCreateItem(iter
).SetValue("");
857 * Load a WindowDesc from config.
858 * @param ini IniFile handle to the ini file with the source data
859 * @param grpname character string identifying the section-header of the ini file that will be parsed
860 * @param desc Destination WindowDesc
862 void IniLoadWindowSettings(IniFile
&ini
, const char *grpname
, void *desc
)
864 IniLoadSettings(ini
, _window_settings
, grpname
, desc
, false);
868 * Save a WindowDesc to config.
869 * @param ini IniFile handle to the ini file where the destination data is saved
870 * @param grpname character string identifying the section-header of the ini file
871 * @param desc Source WindowDesc
873 void IniSaveWindowSettings(IniFile
&ini
, const char *grpname
, void *desc
)
875 IniSaveSettings(ini
, _window_settings
, grpname
, desc
, false);
879 * Check whether the setting is editable in the current gamemode.
880 * @param do_command true if this is about checking a command from the server.
881 * @return true if editable.
883 bool SettingDesc::IsEditable(bool do_command
) const
885 if (!do_command
&& !(this->flags
& SF_NO_NETWORK_SYNC
) && _networking
&& !_network_server
&& !(this->flags
& SF_PER_COMPANY
)) return false;
886 if (do_command
&& (this->flags
& SF_NO_NETWORK_SYNC
)) return false;
887 if ((this->flags
& SF_NETWORK_ONLY
) && !_networking
&& _game_mode
!= GM_MENU
) return false;
888 if ((this->flags
& SF_NO_NETWORK
) && _networking
) return false;
889 if ((this->flags
& SF_NEWGAME_ONLY
) &&
890 (_game_mode
== GM_NORMAL
||
891 (_game_mode
== GM_EDITOR
&& !(this->flags
& SF_SCENEDIT_TOO
)))) return false;
892 if ((this->flags
& SF_SCENEDIT_ONLY
) && _game_mode
!= GM_EDITOR
) return false;
897 * Return the type of the setting.
898 * @return type of setting
900 SettingType
SettingDesc::GetType() const
902 if (this->flags
& SF_PER_COMPANY
) return ST_COMPANY
;
903 return (this->flags
& SF_NOT_IN_SAVE
) ? ST_CLIENT
: ST_GAME
;
907 * Get the setting description of this setting as an integer setting.
908 * @return The integer setting description.
910 const IntSettingDesc
*SettingDesc::AsIntSetting() const
912 assert(this->IsIntSetting());
913 return static_cast<const IntSettingDesc
*>(this);
917 * Get the setting description of this setting as a string setting.
918 * @return The string setting description.
920 const StringSettingDesc
*SettingDesc::AsStringSetting() const
922 assert(this->IsStringSetting());
923 return static_cast<const StringSettingDesc
*>(this);
926 void PrepareOldDiffCustom();
927 void HandleOldDiffCustom(bool savegame
);
930 /** Checks if any settings are set to incorrect values, and sets them to correct values in that case. */
931 static void ValidateSettings()
933 /* Do not allow a custom sea level with the original land generator. */
934 if (_settings_newgame
.game_creation
.land_generator
== LG_ORIGINAL
&&
935 _settings_newgame
.difficulty
.quantity_sea_lakes
== CUSTOM_SEA_LEVEL_NUMBER_DIFFICULTY
) {
936 _settings_newgame
.difficulty
.quantity_sea_lakes
= CUSTOM_SEA_LEVEL_MIN_PERCENTAGE
;
940 static void AILoadConfig(const IniFile
&ini
, const char *grpname
)
942 const IniGroup
*group
= ini
.GetGroup(grpname
);
944 /* Clean any configured AI */
945 for (CompanyID c
= COMPANY_FIRST
; c
< MAX_COMPANIES
; c
++) {
946 AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
)->Change(std::nullopt
);
949 /* If no group exists, return */
950 if (group
== nullptr) return;
952 CompanyID c
= COMPANY_FIRST
;
953 for (const IniItem
&item
: group
->items
) {
954 AIConfig
*config
= AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
);
956 config
->Change(item
.name
);
957 if (!config
->HasScript()) {
958 if (item
.name
!= "none") {
959 Debug(script
, 0, "The AI by the name '{}' was no longer found, and removed from the list.", item
.name
);
963 if (item
.value
.has_value()) config
->StringToSettings(*item
.value
);
965 if (c
>= MAX_COMPANIES
) break;
969 static void GameLoadConfig(const IniFile
&ini
, const char *grpname
)
971 const IniGroup
*group
= ini
.GetGroup(grpname
);
973 /* Clean any configured GameScript */
974 GameConfig::GetConfig(GameConfig::SSS_FORCE_NEWGAME
)->Change(std::nullopt
);
976 /* If no group exists, return */
977 if (group
== nullptr || group
->items
.empty()) return;
979 const IniItem
&item
= group
->items
.front();
981 GameConfig
*config
= GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME
);
983 config
->Change(item
.name
);
984 if (!config
->HasScript()) {
985 if (item
.name
!= "none") {
986 Debug(script
, 0, "The GameScript by the name '{}' was no longer found, and removed from the list.", item
.name
);
990 if (item
.value
.has_value()) config
->StringToSettings(*item
.value
);
994 * Load BaseGraphics set selection and configuration.
996 static void GraphicsSetLoadConfig(IniFile
&ini
)
998 if (const IniGroup
*group
= ini
.GetGroup("misc"); group
!= nullptr) {
999 /* Load old setting first. */
1000 if (const IniItem
*item
= group
->GetItem("graphicsset"); item
!= nullptr && item
->value
) BaseGraphics::ini_data
.name
= *item
->value
;
1003 if (const IniGroup
*group
= ini
.GetGroup("graphicsset"); group
!= nullptr) {
1004 /* Load new settings. */
1005 if (const IniItem
*item
= group
->GetItem("name"); item
!= nullptr && item
->value
) BaseGraphics::ini_data
.name
= *item
->value
;
1007 if (const IniItem
*item
= group
->GetItem("shortname"); item
!= nullptr && item
->value
&& item
->value
->size() == 8) {
1008 BaseGraphics::ini_data
.shortname
= BSWAP32(std::strtoul(item
->value
->c_str(), nullptr, 16));
1011 if (const IniItem
*item
= group
->GetItem("extra_version"); item
!= nullptr && item
->value
) BaseGraphics::ini_data
.extra_version
= std::strtoul(item
->value
->c_str(), nullptr, 10);
1013 if (const IniItem
*item
= group
->GetItem("extra_params"); item
!= nullptr && item
->value
) {
1014 auto &extra_params
= BaseGraphics::ini_data
.extra_params
;
1015 extra_params
.resize(lengthof(GRFConfig::param
));
1016 int count
= ParseIntList(item
->value
->c_str(), &extra_params
.front(), extra_params
.size());
1018 SetDParamStr(0, BaseGraphics::ini_data
.name
);
1019 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
, WL_CRITICAL
);
1022 extra_params
.resize(count
);
1028 * Load a GRF configuration
1029 * @param ini The configuration to read from.
1030 * @param grpname Group name containing the configuration of the GRF.
1031 * @param is_static GRF is static.
1033 static GRFConfig
*GRFLoadConfig(const IniFile
&ini
, const char *grpname
, bool is_static
)
1035 const IniGroup
*group
= ini
.GetGroup(grpname
);
1036 GRFConfig
*first
= nullptr;
1037 GRFConfig
**curr
= &first
;
1039 if (group
== nullptr) return nullptr;
1042 for (const IniItem
&item
: group
->items
) {
1043 GRFConfig
*c
= nullptr;
1045 std::array
<uint8_t, 4> grfid_buf
;
1047 std::string_view item_name
= item
.name
;
1048 bool has_md5sum
= false;
1050 /* Try reading "<grfid>|" and on success, "<md5sum>|". */
1051 auto grfid_pos
= item_name
.find("|");
1052 if (grfid_pos
!= std::string_view::npos
) {
1053 std::string_view grfid_str
= item_name
.substr(0, grfid_pos
);
1055 if (ConvertHexToBytes(grfid_str
, grfid_buf
)) {
1056 item_name
= item_name
.substr(grfid_pos
+ 1);
1058 auto md5sum_pos
= item_name
.find("|");
1059 if (md5sum_pos
!= std::string_view::npos
) {
1060 std::string_view md5sum_str
= item_name
.substr(0, md5sum_pos
);
1062 has_md5sum
= ConvertHexToBytes(md5sum_str
, md5sum
);
1063 if (has_md5sum
) item_name
= item_name
.substr(md5sum_pos
+ 1);
1066 uint32_t grfid
= grfid_buf
[0] | (grfid_buf
[1] << 8) | (grfid_buf
[2] << 16) | (grfid_buf
[3] << 24);
1068 const GRFConfig
*s
= FindGRFConfig(grfid
, FGCM_EXACT
, &md5sum
);
1069 if (s
!= nullptr) c
= new GRFConfig(*s
);
1071 if (c
== nullptr && !FioCheckFileExists(std::string(item_name
), NEWGRF_DIR
)) {
1072 const GRFConfig
*s
= FindGRFConfig(grfid
, FGCM_NEWEST_VALID
);
1073 if (s
!= nullptr) c
= new GRFConfig(*s
);
1077 std::string filename
= std::string(item_name
);
1079 if (c
== nullptr) c
= new GRFConfig(filename
);
1081 /* Parse parameters */
1082 if (item
.value
.has_value() && !item
.value
->empty()) {
1083 int count
= ParseIntList(item
.value
->c_str(), c
->param
.data(), c
->param
.size());
1085 SetDParamStr(0, filename
);
1086 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_ARRAY
, WL_CRITICAL
);
1089 c
->num_params
= count
;
1092 /* Check if item is valid */
1093 if (!FillGRFDetails(c
, is_static
) || HasBit(c
->flags
, GCF_INVALID
)) {
1094 if (c
->status
== GCS_NOT_FOUND
) {
1095 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_NOT_FOUND
);
1096 } else if (HasBit(c
->flags
, GCF_UNSAFE
)) {
1097 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_UNSAFE
);
1098 } else if (HasBit(c
->flags
, GCF_SYSTEM
)) {
1099 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_SYSTEM
);
1100 } else if (HasBit(c
->flags
, GCF_INVALID
)) {
1101 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_INCOMPATIBLE
);
1103 SetDParam(1, STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN
);
1106 SetDParamStr(0, filename
.empty() ? item
.name
.c_str() : filename
);
1107 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_INVALID_GRF
, WL_CRITICAL
);
1112 /* Check for duplicate GRFID (will also check for duplicate filenames) */
1113 bool duplicate
= false;
1114 for (const GRFConfig
*gc
= first
; gc
!= nullptr; gc
= gc
->next
) {
1115 if (gc
->ident
.grfid
== c
->ident
.grfid
) {
1116 SetDParamStr(0, c
->filename
);
1117 SetDParamStr(1, gc
->filename
);
1118 ShowErrorMessage(STR_CONFIG_ERROR
, STR_CONFIG_ERROR_DUPLICATE_GRFID
, WL_CRITICAL
);
1129 /* Mark file as static to avoid saving in savegame. */
1130 SetBit(c
->flags
, GCF_STATIC
);
1131 } else if (++num_grfs
> NETWORK_MAX_GRF_COUNT
) {
1132 /* Check we will not load more non-static NewGRFs than allowed. This could trigger issues for game servers. */
1133 ShowErrorMessage(STR_CONFIG_ERROR
, STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED
, WL_CRITICAL
);
1137 /* Add item to list */
1145 static IniFileVersion
LoadVersionFromConfig(const IniFile
&ini
)
1147 const IniGroup
*group
= ini
.GetGroup("version");
1148 if (group
== nullptr) return IFV_0
;
1150 auto version_number
= group
->GetItem("ini_version");
1151 /* Older ini-file versions don't have this key yet. */
1152 if (version_number
== nullptr || !version_number
->value
.has_value()) return IFV_0
;
1154 uint32_t version
= 0;
1155 std::from_chars(version_number
->value
->data(), version_number
->value
->data() + version_number
->value
->size(), version
);
1157 return static_cast<IniFileVersion
>(version
);
1160 static void AISaveConfig(IniFile
&ini
, const char *grpname
)
1162 IniGroup
&group
= ini
.GetOrCreateGroup(grpname
);
1165 for (CompanyID c
= COMPANY_FIRST
; c
< MAX_COMPANIES
; c
++) {
1166 AIConfig
*config
= AIConfig::GetConfig(c
, AIConfig::SSS_FORCE_NEWGAME
);
1168 std::string value
= config
->SettingsToString();
1170 if (config
->HasScript()) {
1171 name
= config
->GetName();
1176 group
.CreateItem(name
).SetValue(value
);
1180 static void GameSaveConfig(IniFile
&ini
, const char *grpname
)
1182 IniGroup
&group
= ini
.GetOrCreateGroup(grpname
);
1185 GameConfig
*config
= GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME
);
1187 std::string value
= config
->SettingsToString();
1189 if (config
->HasScript()) {
1190 name
= config
->GetName();
1195 group
.CreateItem(name
).SetValue(value
);
1199 * Save the version of OpenTTD to the ini file.
1200 * @param ini the ini to write to
1202 static void SaveVersionInConfig(IniFile
&ini
)
1204 IniGroup
&group
= ini
.GetOrCreateGroup("version");
1205 group
.GetOrCreateItem("version_string").SetValue(_openttd_revision
);
1206 group
.GetOrCreateItem("version_number").SetValue(fmt::format("{:08X}", _openttd_newgrf_version
));
1207 group
.GetOrCreateItem("ini_version").SetValue(std::to_string(INIFILE_VERSION
));
1211 * Save BaseGraphics set selection and configuration.
1213 static void GraphicsSetSaveConfig(IniFile
&ini
)
1215 const GraphicsSet
*used_set
= BaseGraphics::GetUsedSet();
1216 if (used_set
== nullptr) return;
1218 IniGroup
&group
= ini
.GetOrCreateGroup("graphicsset");
1221 group
.GetOrCreateItem("name").SetValue(used_set
->name
);
1222 group
.GetOrCreateItem("shortname").SetValue(fmt::format("{:08X}", BSWAP32(used_set
->shortname
)));
1224 const GRFConfig
*extra_cfg
= used_set
->GetExtraConfig();
1225 if (extra_cfg
!= nullptr && extra_cfg
->num_params
> 0) {
1226 group
.GetOrCreateItem("extra_version").SetValue(fmt::format("{}", extra_cfg
->version
));
1227 group
.GetOrCreateItem("extra_params").SetValue(GRFBuildParamList(extra_cfg
));
1231 /* Save a GRF configuration to the given group name */
1232 static void GRFSaveConfig(IniFile
&ini
, const char *grpname
, const GRFConfig
*list
)
1234 IniGroup
&group
= ini
.GetOrCreateGroup(grpname
);
1238 for (c
= list
; c
!= nullptr; c
= c
->next
) {
1239 std::string key
= fmt::format("{:08X}|{}|{}", BSWAP32(c
->ident
.grfid
),
1240 FormatArrayAsHex(c
->ident
.md5sum
), c
->filename
);
1241 group
.GetOrCreateItem(key
).SetValue(GRFBuildParamList(c
));
1245 /* Common handler for saving/loading variables to the configuration file */
1246 static void HandleSettingDescs(IniFile
&generic_ini
, IniFile
&private_ini
, IniFile
&secrets_ini
, SettingDescProc
*proc
, SettingDescProcList
*proc_list
, bool only_startup
= false)
1248 proc(generic_ini
, _misc_settings
, "misc", nullptr, only_startup
);
1249 #if defined(_WIN32) && !defined(DEDICATED)
1250 proc(generic_ini
, _win32_settings
, "win32", nullptr, only_startup
);
1253 /* The name "patches" is a fallback, as every setting should sets its own group. */
1255 for (auto &table
: GenericSettingTables()) {
1256 proc(generic_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1258 for (auto &table
: PrivateSettingTables()) {
1259 proc(private_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1261 for (auto &table
: SecretSettingTables()) {
1262 proc(secrets_ini
, table
, "patches", &_settings_newgame
, only_startup
);
1265 proc(generic_ini
, _currency_settings
, "currency", &_custom_currency
, only_startup
);
1266 proc(generic_ini
, _company_settings
, "company", &_settings_client
.company
, only_startup
);
1268 if (!only_startup
) {
1269 proc_list(private_ini
, "server_bind_addresses", _network_bind_list
);
1270 proc_list(private_ini
, "servers", _network_host_list
);
1271 proc_list(private_ini
, "bans", _network_ban_list
);
1276 * Remove all entries from a settings table from an ini-file.
1278 * This is only useful if those entries are moved to another file, and you
1279 * want to clean up what is left behind.
1281 * @param ini The ini file to remove the entries from.
1282 * @param table The table to look for entries to remove.
1284 static void RemoveEntriesFromIni(IniFile
&ini
, const SettingTable
&table
)
1286 for (auto &desc
: table
) {
1287 const SettingDesc
*sd
= GetSettingDesc(desc
);
1289 /* For settings.xx.yy load the settings from [xx] yy = ? */
1290 std::string s
{ sd
->GetName() };
1291 auto sc
= s
.find('.');
1292 if (sc
== std::string::npos
) continue;
1294 IniGroup
*group
= ini
.GetGroup(s
.substr(0, sc
));
1295 if (group
== nullptr) continue;
1296 s
= s
.substr(sc
+ 1);
1298 group
->RemoveItem(s
);
1303 * Check whether a conversion should be done, and based on what old setting information.
1305 * To prevent errors when switching back and forth between older and newer
1306 * version of OpenTTD, the type of a setting is never changed. Instead, the
1307 * setting is renamed, and this function is used to check whether a conversion
1308 * between the old and new setting is required.
1310 * This checks if the new setting doesn't exist, and if the old does.
1312 * Doing it this way means that if you switch to an older client, the old
1313 * setting is used, and only on the first time starting a new client, the
1314 * old setting is converted to the new. After that, they are independent
1315 * of each other. And you can safely, without errors on either, switch
1316 * between old and new client.
1318 * @param ini The ini-file to use.
1319 * @param group The group the setting is in.
1320 * @param old_var The old name of the setting.
1321 * @param new_var The new name of the setting.
1322 * @param[out] old_item The old item to base upgrading on.
1323 * @return Whether upgrading should happen; if false, old_item is a nullptr.
1325 bool IsConversionNeeded(const ConfigIniFile
&ini
, const std::string
&group
, const std::string
&old_var
, const std::string
&new_var
, const IniItem
**old_item
)
1327 *old_item
= nullptr;
1329 const IniGroup
*igroup
= ini
.GetGroup(group
);
1330 /* If the group doesn't exist, there is nothing to convert. */
1331 if (igroup
== nullptr) return false;
1333 const IniItem
*tmp_old_item
= igroup
->GetItem(old_var
);
1334 const IniItem
*new_item
= igroup
->GetItem(new_var
);
1336 /* If the old item doesn't exist, there is nothing to convert. */
1337 if (tmp_old_item
== nullptr) return false;
1339 /* If the new item exists, it means conversion was already done. We only
1340 * do the conversion the first time, and after that these settings are
1341 * independent. This allows users to freely change between older and
1342 * newer clients without breaking anything. */
1343 if (new_item
!= nullptr) return false;
1345 *old_item
= tmp_old_item
;
1350 * Load the values from the configuration files
1351 * @param startup Load the minimal amount of the configuration to "bootstrap" the blitter and such.
1353 void LoadFromConfig(bool startup
)
1355 ConfigIniFile
generic_ini(_config_file
);
1356 ConfigIniFile
private_ini(_private_file
);
1357 ConfigIniFile
secrets_ini(_secrets_file
);
1359 if (!startup
) ResetCurrencies(false); // Initialize the array of currencies, without preserving the custom one
1361 IniFileVersion generic_version
= LoadVersionFromConfig(generic_ini
);
1364 GraphicsSetLoadConfig(generic_ini
);
1367 /* Before the split of private/secrets, we have to look in the generic for these settings. */
1368 if (generic_version
< IFV_PRIVATE_SECRETS
) {
1369 HandleSettingDescs(generic_ini
, generic_ini
, generic_ini
, IniLoadSettings
, IniLoadSettingList
, startup
);
1371 HandleSettingDescs(generic_ini
, private_ini
, secrets_ini
, IniLoadSettings
, IniLoadSettingList
, startup
);
1374 /* Load basic settings only during bootstrap, load other settings not during bootstrap */
1376 if (generic_version
< IFV_LINKGRAPH_SECONDS
) {
1377 _settings_newgame
.linkgraph
.recalc_interval
*= CalendarTime::SECONDS_PER_DAY
;
1378 _settings_newgame
.linkgraph
.recalc_time
*= CalendarTime::SECONDS_PER_DAY
;
1381 /* Move use_relay_service from generic_ini to private_ini. */
1382 if (generic_version
< IFV_NETWORK_PRIVATE_SETTINGS
) {
1383 const IniGroup
*network
= generic_ini
.GetGroup("network");
1384 if (network
!= nullptr) {
1385 const IniItem
*use_relay_service
= network
->GetItem("use_relay_service");
1386 if (use_relay_service
!= nullptr) {
1387 if (use_relay_service
->value
== "never") {
1388 _settings_client
.network
.use_relay_service
= UseRelayService::URS_NEVER
;
1389 } else if (use_relay_service
->value
== "ask") {
1390 _settings_client
.network
.use_relay_service
= UseRelayService::URS_ASK
;
1391 } else if (use_relay_service
->value
== "allow") {
1392 _settings_client
.network
.use_relay_service
= UseRelayService::URS_ALLOW
;
1398 const IniItem
*old_item
;
1400 if (generic_version
< IFV_GAME_TYPE
&& IsConversionNeeded(generic_ini
, "network", "server_advertise", "server_game_type", &old_item
)) {
1401 auto old_value
= BoolSettingDesc::ParseSingleValue(old_item
->value
->c_str());
1402 _settings_client
.network
.server_game_type
= old_value
.value_or(false) ? SERVER_GAME_TYPE_PUBLIC
: SERVER_GAME_TYPE_LOCAL
;
1405 if (generic_version
< IFV_AUTOSAVE_RENAME
&& IsConversionNeeded(generic_ini
, "gui", "autosave", "autosave_interval", &old_item
)) {
1406 static std::vector
<std::string
> _old_autosave_interval
{"off", "monthly", "quarterly", "half year", "yearly"};
1407 auto old_value
= OneOfManySettingDesc::ParseSingleValue(old_item
->value
->c_str(), old_item
->value
->size(), _old_autosave_interval
);
1409 switch (old_value
) {
1410 case 0: _settings_client
.gui
.autosave_interval
= 0; break;
1411 case 1: _settings_client
.gui
.autosave_interval
= 10; break;
1412 case 2: _settings_client
.gui
.autosave_interval
= 30; break;
1413 case 3: _settings_client
.gui
.autosave_interval
= 60; break;
1414 case 4: _settings_client
.gui
.autosave_interval
= 120; break;
1419 /* Persist the right click close option from older versions. */
1420 if (generic_version
< IFV_RIGHT_CLICK_CLOSE
&& IsConversionNeeded(generic_ini
, "gui", "right_mouse_wnd_close", "right_click_wnd_close", &old_item
)) {
1421 auto old_value
= BoolSettingDesc::ParseSingleValue(old_item
->value
->c_str());
1422 _settings_client
.gui
.right_click_wnd_close
= old_value
.value_or(false) ? RCC_YES
: RCC_NO
;
1425 _grfconfig_newgame
= GRFLoadConfig(generic_ini
, "newgrf", false);
1426 _grfconfig_static
= GRFLoadConfig(generic_ini
, "newgrf-static", true);
1427 AILoadConfig(generic_ini
, "ai_players");
1428 GameLoadConfig(generic_ini
, "game_scripts");
1430 PrepareOldDiffCustom();
1431 IniLoadSettings(generic_ini
, _old_gameopt_settings
, "gameopt", &_settings_newgame
, false);
1432 HandleOldDiffCustom(false);
1435 DebugReconsiderSendRemoteMessages();
1437 /* Display scheduled errors */
1438 ScheduleErrorMessage(_settings_error_list
);
1439 if (FindWindowById(WC_ERRMSG
, 0) == nullptr) ShowFirstError();
1443 /** Save the values to the configuration file */
1446 ConfigIniFile
generic_ini(_config_file
);
1447 ConfigIniFile
private_ini(_private_file
);
1448 ConfigIniFile
secrets_ini(_secrets_file
);
1450 IniFileVersion generic_version
= LoadVersionFromConfig(generic_ini
);
1452 /* If we newly create the private/secrets file, add a dummy group on top
1453 * just so we can add a comment before it (that is how IniFile works).
1454 * This to explain what the file is about. After doing it once, never touch
1455 * it again, as otherwise we might be reverting user changes. */
1456 if (IniGroup
*group
= private_ini
.GetGroup("private"); group
!= nullptr) group
->comment
= "; This file possibly contains private information which can identify you as person.\n";
1457 if (IniGroup
*group
= secrets_ini
.GetGroup("secrets"); group
!= nullptr) group
->comment
= "; Do not share this file with others, not even if they claim to be technical support.\n; This file contains saved passwords and other secrets that should remain private to you!\n";
1459 if (generic_version
== IFV_0
) {
1460 /* Remove some obsolete groups. These have all been loaded into other groups. */
1461 generic_ini
.RemoveGroup("patches");
1462 generic_ini
.RemoveGroup("yapf");
1463 generic_ini
.RemoveGroup("gameopt");
1465 /* Remove all settings from the generic ini that are now in the private ini. */
1466 generic_ini
.RemoveGroup("server_bind_addresses");
1467 generic_ini
.RemoveGroup("servers");
1468 generic_ini
.RemoveGroup("bans");
1469 for (auto &table
: PrivateSettingTables()) {
1470 RemoveEntriesFromIni(generic_ini
, table
);
1473 /* Remove all settings from the generic ini that are now in the secrets ini. */
1474 for (auto &table
: SecretSettingTables()) {
1475 RemoveEntriesFromIni(generic_ini
, table
);
1479 if (generic_version
< IFV_REMOVE_GENERATION_SEED
) {
1480 IniGroup
*game_creation
= generic_ini
.GetGroup("game_creation");
1481 if (game_creation
!= nullptr) {
1482 game_creation
->RemoveItem("generation_seed");
1486 /* These variables are migrated from generic ini to private ini now. */
1487 if (generic_version
< IFV_NETWORK_PRIVATE_SETTINGS
) {
1488 IniGroup
*network
= generic_ini
.GetGroup("network");
1489 if (network
!= nullptr) {
1490 network
->RemoveItem("use_relay_service");
1494 HandleSettingDescs(generic_ini
, private_ini
, secrets_ini
, IniSaveSettings
, IniSaveSettingList
);
1495 GraphicsSetSaveConfig(generic_ini
);
1496 GRFSaveConfig(generic_ini
, "newgrf", _grfconfig_newgame
);
1497 GRFSaveConfig(generic_ini
, "newgrf-static", _grfconfig_static
);
1498 AISaveConfig(generic_ini
, "ai_players");
1499 GameSaveConfig(generic_ini
, "game_scripts");
1501 SaveVersionInConfig(generic_ini
);
1502 SaveVersionInConfig(private_ini
);
1503 SaveVersionInConfig(secrets_ini
);
1505 generic_ini
.SaveToDisk(_config_file
);
1506 private_ini
.SaveToDisk(_private_file
);
1507 secrets_ini
.SaveToDisk(_secrets_file
);
1511 * Get the list of known NewGrf presets.
1512 * @returns List of preset names.
1514 StringList
GetGRFPresetList()
1518 ConfigIniFile
ini(_config_file
);
1519 for (const IniGroup
&group
: ini
.groups
) {
1520 if (group
.name
.compare(0, 7, "preset-") == 0) {
1521 list
.push_back(group
.name
.substr(7));
1529 * Load a NewGRF configuration by preset-name.
1530 * @param config_name Name of the preset.
1531 * @return NewGRF configuration.
1532 * @see GetGRFPresetList
1534 GRFConfig
*LoadGRFPresetFromConfig(const char *config_name
)
1536 std::string
section("preset-");
1537 section
+= config_name
;
1539 ConfigIniFile
ini(_config_file
);
1540 GRFConfig
*config
= GRFLoadConfig(ini
, section
.c_str(), false);
1546 * Save a NewGRF configuration with a preset name.
1547 * @param config_name Name of the preset.
1548 * @param config NewGRF configuration to save.
1549 * @see GetGRFPresetList
1551 void SaveGRFPresetToConfig(const char *config_name
, GRFConfig
*config
)
1553 std::string
section("preset-");
1554 section
+= config_name
;
1556 ConfigIniFile
ini(_config_file
);
1557 GRFSaveConfig(ini
, section
.c_str(), config
);
1558 ini
.SaveToDisk(_config_file
);
1562 * Delete a NewGRF configuration by preset name.
1563 * @param config_name Name of the preset.
1565 void DeleteGRFPresetFromConfig(const char *config_name
)
1567 std::string
section("preset-");
1568 section
+= config_name
;
1570 ConfigIniFile
ini(_config_file
);
1571 ini
.RemoveGroup(section
);
1572 ini
.SaveToDisk(_config_file
);
1576 * Handle changing a value. This performs validation of the input value and
1577 * calls the appropriate callbacks, and saves it when the value is changed.
1578 * @param object The object the setting is in.
1579 * @param newval The new value for the setting.
1581 void IntSettingDesc::ChangeValue(const void *object
, int32_t newval
) const
1583 int32_t oldval
= this->Read(object
);
1584 this->MakeValueValid(newval
);
1585 if (this->pre_check
!= nullptr && !this->pre_check(newval
)) return;
1586 if (oldval
== newval
) return;
1588 this->Write(object
, newval
);
1589 if (this->post_callback
!= nullptr) this->post_callback(newval
);
1591 if (this->flags
& SF_NO_NETWORK
) {
1592 _gamelog
.StartAction(GLAT_SETTING
);
1593 _gamelog
.Setting(this->GetName(), oldval
, newval
);
1594 _gamelog
.StopAction();
1597 SetWindowClassesDirty(WC_GAME_OPTIONS
);
1599 if (_save_config
) SaveToConfig();
1603 * Given a name of setting, return a setting description from the table.
1604 * @param name Name of the setting to return a setting description of.
1605 * @param settings Table to look in for the setting.
1606 * @return Pointer to the setting description of setting \a name if it can be found,
1607 * \c nullptr indicates failure to obtain the description.
1609 static const SettingDesc
*GetSettingFromName(const std::string_view name
, const SettingTable
&settings
)
1611 /* First check all full names */
1612 for (auto &desc
: settings
) {
1613 const SettingDesc
*sd
= GetSettingDesc(desc
);
1614 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1615 if (sd
->GetName() == name
) return sd
;
1618 /* Then check the shortcut variant of the name. */
1619 std::string short_name_suffix
= std::string
{ "." }.append(name
);
1620 for (auto &desc
: settings
) {
1621 const SettingDesc
*sd
= GetSettingDesc(desc
);
1622 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1623 if (sd
->GetName().ends_with(short_name_suffix
)) return sd
;
1630 * Get the SaveLoad for all settings in the settings table.
1631 * @param settings The settings table to get the SaveLoad objects from.
1632 * @param saveloads A vector to store the result in.
1634 void GetSaveLoadFromSettingTable(SettingTable settings
, std::vector
<SaveLoad
> &saveloads
)
1636 for (auto &desc
: settings
) {
1637 const SettingDesc
*sd
= GetSettingDesc(desc
);
1638 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1639 saveloads
.push_back(sd
->save
);
1644 * Given a name of setting, return a company setting description of it.
1645 * @param name Name of the company setting to return a setting description of.
1646 * @return Pointer to the setting description of setting \a name if it can be found,
1647 * \c nullptr indicates failure to obtain the description.
1649 static const SettingDesc
*GetCompanySettingFromName(std::string_view name
)
1651 static const std::string_view company_prefix
= "company.";
1652 if (name
.starts_with(company_prefix
)) name
.remove_prefix(company_prefix
.size());
1653 return GetSettingFromName(name
, _company_settings
);
1657 * Given a name of any setting, return any setting description of it.
1658 * @param name Name of the setting to return a setting description of.
1659 * @return Pointer to the setting description of setting \a name if it can be found,
1660 * \c nullptr indicates failure to obtain the description.
1662 const SettingDesc
*GetSettingFromName(const std::string_view name
)
1664 for (auto &table
: GenericSettingTables()) {
1665 auto sd
= GetSettingFromName(name
, table
);
1666 if (sd
!= nullptr) return sd
;
1668 for (auto &table
: PrivateSettingTables()) {
1669 auto sd
= GetSettingFromName(name
, table
);
1670 if (sd
!= nullptr) return sd
;
1672 for (auto &table
: SecretSettingTables()) {
1673 auto sd
= GetSettingFromName(name
, table
);
1674 if (sd
!= nullptr) return sd
;
1677 return GetCompanySettingFromName(name
);
1681 * Network-safe changing of settings (server-only).
1682 * @param flags operation to perform
1683 * @param name the name of the setting to change
1684 * @param value the new value for the setting
1685 * The new value is properly clamped to its minimum/maximum when setting
1686 * @return the cost of this operation or an error
1689 CommandCost
CmdChangeSetting(DoCommandFlag flags
, const std::string
&name
, int32_t value
)
1691 if (name
.empty()) return CMD_ERROR
;
1692 const SettingDesc
*sd
= GetSettingFromName(name
);
1694 if (sd
== nullptr) return CMD_ERROR
;
1695 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) return CMD_ERROR
;
1696 if (!sd
->IsIntSetting()) return CMD_ERROR
;
1698 if (!sd
->IsEditable(true)) return CMD_ERROR
;
1700 if (flags
& DC_EXEC
) {
1701 sd
->AsIntSetting()->ChangeValue(&GetGameSettings(), value
);
1704 return CommandCost();
1708 * Change one of the per-company settings.
1709 * @param flags operation to perform
1710 * @param name the name of the company setting to change
1711 * @param value the new value for the setting
1712 * The new value is properly clamped to its minimum/maximum when setting
1713 * @return the cost of this operation or an error
1715 CommandCost
CmdChangeCompanySetting(DoCommandFlag flags
, const std::string
&name
, int32_t value
)
1717 if (name
.empty()) return CMD_ERROR
;
1718 const SettingDesc
*sd
= GetCompanySettingFromName(name
);
1720 if (sd
== nullptr) return CMD_ERROR
;
1721 if (!sd
->IsIntSetting()) return CMD_ERROR
;
1723 if (flags
& DC_EXEC
) {
1724 sd
->AsIntSetting()->ChangeValue(&Company::Get(_current_company
)->settings
, value
);
1727 return CommandCost();
1731 * Top function to save the new value of an element of the Settings struct
1732 * @param index offset in the SettingDesc array of the Settings struct which
1733 * identifies the setting member we want to change
1734 * @param value new value of the setting
1735 * @param force_newgame force the newgame settings
1737 bool SetSettingValue(const IntSettingDesc
*sd
, int32_t value
, bool force_newgame
)
1739 const IntSettingDesc
*setting
= sd
->AsIntSetting();
1740 if ((setting
->flags
& SF_PER_COMPANY
) != 0) {
1741 if (Company::IsValidID(_local_company
) && _game_mode
!= GM_MENU
) {
1742 return Command
<CMD_CHANGE_COMPANY_SETTING
>::Post(setting
->GetName(), value
);
1745 setting
->ChangeValue(&_settings_client
.company
, value
);
1749 /* If an item is company-based, we do not send it over the network
1750 * (if any) to change. Also *hack*hack* we update the _newgame version
1751 * of settings because changing a company-based setting in a game also
1752 * changes its defaults. At least that is the convention we have chosen */
1753 if (setting
->flags
& SF_NO_NETWORK_SYNC
) {
1754 if (_game_mode
!= GM_MENU
) {
1755 setting
->ChangeValue(&_settings_newgame
, value
);
1757 setting
->ChangeValue(&GetGameSettings(), value
);
1761 if (force_newgame
) {
1762 setting
->ChangeValue(&_settings_newgame
, value
);
1766 /* send non-company-based settings over the network */
1767 if (!_networking
|| (_networking
&& _network_server
)) {
1768 return Command
<CMD_CHANGE_SETTING
>::Post(setting
->GetName(), value
);
1774 * Set the company settings for a new company to their default values.
1776 void SetDefaultCompanySettings(CompanyID cid
)
1778 Company
*c
= Company::Get(cid
);
1779 for (auto &desc
: _company_settings
) {
1780 const IntSettingDesc
*int_setting
= GetSettingDesc(desc
)->AsIntSetting();
1781 int_setting
->MakeValueValidAndWrite(&c
->settings
, int_setting
->def
);
1786 * Sync all company settings in a multiplayer game.
1788 void SyncCompanySettings()
1790 const void *old_object
= &Company::Get(_current_company
)->settings
;
1791 const void *new_object
= &_settings_client
.company
;
1792 for (auto &desc
: _company_settings
) {
1793 const SettingDesc
*sd
= GetSettingDesc(desc
);
1794 uint32_t old_value
= (uint32_t)sd
->AsIntSetting()->Read(old_object
);
1795 uint32_t new_value
= (uint32_t)sd
->AsIntSetting()->Read(new_object
);
1796 if (old_value
!= new_value
) Command
<CMD_CHANGE_COMPANY_SETTING
>::SendNet(STR_NULL
, _local_company
, sd
->GetName(), new_value
);
1801 * Set a setting value with a string.
1802 * @param sd the setting to change.
1803 * @param value the value to write
1804 * @param force_newgame force the newgame settings
1805 * @note Strings WILL NOT be synced over the network
1807 bool SetSettingValue(const StringSettingDesc
*sd
, std::string value
, bool force_newgame
)
1809 assert(sd
->flags
& SF_NO_NETWORK_SYNC
);
1811 if (GetVarMemType(sd
->save
.conv
) == SLE_VAR_STRQ
&& value
.compare("(null)") == 0) {
1815 const void *object
= (_game_mode
== GM_MENU
|| force_newgame
) ? &_settings_newgame
: &_settings_game
;
1816 sd
->AsStringSetting()->ChangeValue(object
, value
);
1821 * Handle changing a string value. This performs validation of the input value
1822 * and calls the appropriate callbacks, and saves it when the value is changed.
1823 * @param object The object the setting is in.
1824 * @param newval The new value for the setting.
1826 void StringSettingDesc::ChangeValue(const void *object
, std::string
&newval
) const
1828 this->MakeValueValid(newval
);
1829 if (this->pre_check
!= nullptr && !this->pre_check(newval
)) return;
1831 this->Write(object
, newval
);
1832 if (this->post_callback
!= nullptr) this->post_callback(newval
);
1834 if (_save_config
) SaveToConfig();
1837 /* Those 2 functions need to be here, else we have to make some stuff non-static
1838 * and besides, it is also better to keep stuff like this at the same place */
1839 void IConsoleSetSetting(const char *name
, const char *value
, bool force_newgame
)
1841 const SettingDesc
*sd
= GetSettingFromName(name
);
1842 if (sd
== nullptr) {
1843 IConsolePrint(CC_ERROR
, "'{}' is an unknown setting.", name
);
1847 bool success
= true;
1848 if (sd
->IsStringSetting()) {
1849 success
= SetSettingValue(sd
->AsStringSetting(), value
, force_newgame
);
1850 } else if (sd
->IsIntSetting()) {
1851 const IntSettingDesc
*isd
= sd
->AsIntSetting();
1852 size_t val
= isd
->ParseValue(value
);
1853 if (!_settings_error_list
.empty()) {
1854 IConsolePrint(CC_ERROR
, "'{}' is not a valid value for this setting.", value
);
1855 _settings_error_list
.clear();
1858 success
= SetSettingValue(isd
, (int32_t)val
, force_newgame
);
1862 if (_network_server
) {
1863 IConsolePrint(CC_ERROR
, "This command/variable is not available during network games.");
1865 IConsolePrint(CC_ERROR
, "This command/variable is only available to a network server.");
1870 void IConsoleSetSetting(const char *name
, int value
)
1872 const SettingDesc
*sd
= GetSettingFromName(name
);
1873 assert(sd
!= nullptr);
1874 SetSettingValue(sd
->AsIntSetting(), value
);
1878 * Output value of a specific setting to the console
1879 * @param name Name of the setting to output its value
1880 * @param force_newgame force the newgame settings
1882 void IConsoleGetSetting(const char *name
, bool force_newgame
)
1884 const SettingDesc
*sd
= GetSettingFromName(name
);
1885 if (sd
== nullptr) {
1886 IConsolePrint(CC_ERROR
, "'{}' is an unknown setting.", name
);
1890 const void *object
= (_game_mode
== GM_MENU
|| force_newgame
) ? &_settings_newgame
: &_settings_game
;
1892 if (sd
->IsStringSetting()) {
1893 IConsolePrint(CC_INFO
, "Current value for '{}' is '{}'.", sd
->GetName(), sd
->AsStringSetting()->Read(object
));
1894 } else if (sd
->IsIntSetting()) {
1895 std::string value
= sd
->FormatValue(object
);
1896 const IntSettingDesc
*int_setting
= sd
->AsIntSetting();
1897 IConsolePrint(CC_INFO
, "Current value for '{}' is '{}' (min: {}{}, max: {}).",
1898 sd
->GetName(), value
, (sd
->flags
& SF_GUI_0_IS_SPECIAL
) ? "(0) " : "", int_setting
->min
, int_setting
->max
);
1902 static void IConsoleListSettingsTable(const SettingTable
&table
, const char *prefilter
)
1904 for (auto &desc
: table
) {
1905 const SettingDesc
*sd
= GetSettingDesc(desc
);
1906 if (!SlIsObjectCurrentlyValid(sd
->save
.version_from
, sd
->save
.version_to
)) continue;
1907 if (prefilter
!= nullptr && sd
->GetName().find(prefilter
) == std::string::npos
) continue;
1908 IConsolePrint(CC_DEFAULT
, "{} = {}", sd
->GetName(), sd
->FormatValue(&GetGameSettings()));
1913 * List all settings and their value to the console
1915 * @param prefilter If not \c nullptr, only list settings with names that begin with \a prefilter prefix
1917 void IConsoleListSettings(const char *prefilter
)
1919 IConsolePrint(CC_HELP
, "All settings with their current value:");
1921 for (auto &table
: GenericSettingTables()) {
1922 IConsoleListSettingsTable(table
, prefilter
);
1924 for (auto &table
: PrivateSettingTables()) {
1925 IConsoleListSettingsTable(table
, prefilter
);
1927 for (auto &table
: SecretSettingTables()) {
1928 IConsoleListSettingsTable(table
, prefilter
);
1931 IConsolePrint(CC_HELP
, "Use 'setting' command to change a value.");