diff --git a/JotunnLib/Extensions/ConfigFileExtensions.cs b/JotunnLib/Extensions/ConfigFileExtensions.cs
new file mode 100644
index 000000000..885688332
--- /dev/null
+++ b/JotunnLib/Extensions/ConfigFileExtensions.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using BepInEx.Configuration;
+
+namespace Jotunn.Extensions
+{
+ ///
+ /// Extends ConfigFile with a convenience method to bind config entries with less boilerplate code
+ /// and explicitly expose commonly used configuration manager attributes.
+ ///
+ public static class ConfigFileExtensions
+ {
+ internal class ConfigFileOrderingMaps
+ {
+ public Dictionary sectionToSectionNumber = new Dictionary();
+ public Dictionary sectionToSettingOrder = new Dictionary();
+ }
+ private static readonly Dictionary _configFileOrderingMaps = new Dictionary();
+
+ ///
+ /// Formats section name as "{sectionNumber} - {section}" based on how
+ /// many sections have been bound to this config.
+ ///
+ ///
+ ///
+ private static string GetOrderedSectionName(this ConfigFile configFile, string section)
+ {
+ if (!_configFileOrderingMaps.TryGetValue(configFile.ConfigFilePath, out ConfigFileOrderingMaps orderingMaps))
+ {
+ orderingMaps = new ConfigFileOrderingMaps();
+ _configFileOrderingMaps.Add(configFile.ConfigFilePath, orderingMaps);
+ }
+
+ if (!orderingMaps.sectionToSectionNumber.TryGetValue(section, out int number))
+ {
+ number = orderingMaps.sectionToSectionNumber.Count + 1;
+ orderingMaps.sectionToSectionNumber[section] = number;
+ }
+
+ return $"{number} - {section}";
+ }
+
+ ///
+ /// Orders settings within a section.
+ ///
+ ///
+ ///
+ private static int GetSettingOrder(this ConfigFile configFile, string section)
+ {
+ if (!_configFileOrderingMaps.TryGetValue(configFile.ConfigFilePath, out ConfigFileOrderingMaps orderingMaps))
+ {
+ orderingMaps = new ConfigFileOrderingMaps();
+ _configFileOrderingMaps.Add(configFile.ConfigFilePath, orderingMaps);
+ }
+
+ if (!orderingMaps.sectionToSettingOrder.TryGetValue(section, out int order))
+ {
+ order = 0;
+ }
+
+ orderingMaps.sectionToSettingOrder[section] = order - 1;
+ return order;
+ }
+
+ internal static string GetExtendedDescription(string description, bool synchronizedSetting)
+ {
+ return description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]");
+ }
+
+ ///
+ /// Bind a new config entry to the config file and modify description to state whether the config entry is synced or not.
+ ///
+ /// Type of the value the config entry holds.
+ /// Configuration file to bind the config entry to.
+ /// Configuration file section to list the config entry in. Settings are grouped by this.
+ /// Name of the setting.
+ /// Default value of the config entry.
+ /// Plain text description of the config entry to display as hover text in configuration manager.
+ /// Whether the config entry IsAdminOnly and should be synced with server.
+ /// Whether to number the section names using a prefix based on the order they are bound to the config file.
+ /// Whether to order the settings in each section based on the order they are bound to the config file.
+ /// Acceptable values for config entry as an AcceptableValueRange, AcceptableValueList, or custom subclass.
+ /// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager).
+ /// Config manager attributes for additional user specified functionality. Any fields of BindConfig will overwrite properties in configAttributes.
+ /// ConfigEntry bound to the config file.
+ public static ConfigEntry BindConfigInOrder(
+ this ConfigFile configFile,
+ string section,
+ string key,
+ T defaultValue,
+ string description,
+ bool synced = true,
+ bool sectionOrder = true,
+ bool settingOrder = true,
+ AcceptableValueBase acceptableValues = null,
+ Action customDrawer = null,
+ ConfigurationManagerAttributes configAttributes = null
+ )
+ {
+ section = sectionOrder ? configFile.GetOrderedSectionName(section) : section;
+ int order = settingOrder ? configFile.GetSettingOrder(section) : 0;
+ return configFile.BindConfig(section, key, defaultValue, description, synced, order, acceptableValues, customDrawer, configAttributes);
+ }
+
+ ///
+ /// Bind a new config entry to the config file and modify description to state whether the config entry is synced or not.
+ ///
+ /// Type of the value the config entry holds.
+ /// Configuration file to bind the config entry to.
+ /// Configuration file section to list the config entry in. Settings are grouped by this.
+ /// Name of the setting.
+ /// Default value of the config entry.
+ /// Plain text description of the config entry to display as hover text in configuration manager.
+ /// Whether the config entry IsAdminOnly and should be synced with server.
+ /// Order of the setting on the settings list relative to other settings in a category. 0 by default, higher number is higher on the list.
+ /// Acceptable values for config entry as an AcceptableValueRange, AcceptableValueList, or custom subclass.
+ /// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager).
+ /// Config manager attributes for additional user specified functionality. Any fields of BindConfig will overwrite properties in configAttributes.
+ /// ConfigEntry bound to the config file.
+ public static ConfigEntry BindConfig(
+ this ConfigFile configFile,
+ string section,
+ string key,
+ T defaultValue,
+ string description,
+ bool synced = true,
+ int? order = null,
+ AcceptableValueBase acceptableValues = null,
+ Action customDrawer = null,
+ ConfigurationManagerAttributes configAttributes = null
+ )
+ {
+ string extendedDescription = GetExtendedDescription(description, synced);
+
+ configAttributes ??= new ConfigurationManagerAttributes();
+ configAttributes.IsAdminOnly = synced;
+ configAttributes.Order = order;
+ configAttributes.CustomDrawer = customDrawer;
+
+ ConfigEntry configEntry = configFile.Bind(
+ section,
+ key,
+ defaultValue,
+ new ConfigDescription(extendedDescription, acceptableValues, configAttributes)
+ );
+
+ return configEntry;
+ }
+ }
+}