using System; using System.IO; using System.Linq; using BepInEx.Logging; namespace BepInEx.Configuration { /// /// Container for a single setting of a . /// Each config entry is linked to one config file. /// public sealed class ConfigEntry { internal ConfigEntry(ConfigFile configFile, ConfigDefinition definition, Type settingType, object defaultValue) : this(configFile, definition) { SetTypeAndDefaultValue(settingType, defaultValue, true); } internal ConfigEntry(ConfigFile configFile, ConfigDefinition definition) { ConfigFile = configFile ?? throw new ArgumentNullException(nameof(configFile)); Definition = definition ?? throw new ArgumentNullException(nameof(definition)); } internal void SetTypeAndDefaultValue(Type settingType, object defaultValue, bool uniqueDefaultValue) { if (settingType == null) throw new ArgumentNullException(nameof(settingType)); if (settingType == SettingType) { if (uniqueDefaultValue) DefaultValue = defaultValue; return; } if (SettingType != null) { throw new ArgumentException($"Tried to define setting \"{Definition}\" as type {settingType.Name} " + $"while it was already defined as type {SettingType.Name}. Use the same " + "Type for all Wrappers of a single setting."); } if (defaultValue == null && settingType.IsValueType) throw new ArgumentException("defaultValue is null while settingType is a value type"); if (defaultValue != null && !settingType.IsInstanceOfType(defaultValue)) throw new ArgumentException("defaultValue can not be assigned to type " + settingType.Name); SettingType = settingType; DefaultValue = defaultValue; } private object _convertedValue; private string _serializedValue; /// /// Config file this entry is a part of. /// public ConfigFile ConfigFile { get; } /// /// Category and name of this setting. Used as a unique key for identification within a . /// public ConfigDefinition Definition { get; } /// /// Description / metadata of this setting. /// public ConfigDescription Description { get; private set; } /// /// Type of the that this setting holds. /// public Type SettingType { get; private set; } /// /// Default value of this setting (set only if the setting was not changed before). /// public object DefaultValue { get; private set; } /// /// Is the type of this setting defined, and by extension can of this setting be accessed. /// Setting is defined when any objects reference it. /// public bool IsDefined => SettingType != null; /// /// Get or set the value of the setting. /// Can't be used when is false. /// public object Value { get { ProcessSerializedValue(); return _convertedValue; } set => SetValue(value, true, this); } internal void SetValue(object newValue, bool fireEvent, object sender) { bool wasChanged = ProcessSerializedValue(); newValue = ClampValue(newValue); wasChanged = wasChanged || !Equals(newValue, _convertedValue); if (wasChanged) { _convertedValue = newValue; if (fireEvent) OnSettingChanged(sender); } } /// /// Get the serialized representation of the value. /// public string GetSerializedValue() { if (_serializedValue != null) return _serializedValue; if (!IsDefined) return null; return TomlTypeConverter.ConvertToString(Value, SettingType); } /// /// Set the value by using its serialized form. /// public void SetSerializedValue(string newValue) => SetSerializedValue(newValue, true, this); internal void SetSerializedValue(string newValue, bool fireEvent, object sender) { string current = GetSerializedValue(); if (string.Equals(current, newValue)) return; _serializedValue = newValue; if (!IsDefined) return; if (ProcessSerializedValue()) { if (fireEvent) OnSettingChanged(sender); } } private bool ProcessSerializedValue() { if (!IsDefined) throw new InvalidOperationException("Can't get the value before the SettingType is specified"); if (_serializedValue != null) { string value = _serializedValue; _serializedValue = null; if (value != "") { try { var newValue = TomlTypeConverter.ConvertToValue(value, SettingType); newValue = ClampValue(newValue); if (!Equals(newValue, _convertedValue)) { _convertedValue = newValue; return true; } return false; } catch (Exception e) { Logger.Log(LogLevel.Warning, $"Config value of setting \"{Definition}\" could not be " + $"parsed and will be ignored. Reason: {e.Message}; Value: {value}"); } } } if (_convertedValue == null && DefaultValue != null) { _convertedValue = DefaultValue; return true; } return false; } internal void SetDescription(ConfigDescription configDescription) { Description = configDescription; SetValue(ClampValue(Value), true, this); } private object ClampValue(object value) { if (Description?.AcceptableValues != null) return Description.AcceptableValues.Clamp(value); return value; } private void OnSettingChanged(object sender) { ConfigFile.OnSettingChanged(sender, this); } /// /// Write a description of this setting using all available metadata. /// public void WriteDescription(StreamWriter writer) { bool hasDescription = Description != null; bool hasType = SettingType != null; if (hasDescription) writer.WriteLine(Description.ToSerializedString()); if (hasType) { writer.WriteLine("# Setting type: " + SettingType.Name); if (SettingType.IsEnum && SettingType.GetCustomAttributes(typeof(FlagsAttribute), true).Any()) writer.WriteLine("# Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)"); writer.WriteLine("# Default value: " + DefaultValue); } if (hasDescription) { if (Description.AcceptableValues != null) { writer.WriteLine(Description.AcceptableValues.ToSerializedString()); } else if (hasType) { if (SettingType == typeof(bool)) writer.WriteLine("# Acceptable values: True, False"); else if (SettingType.IsEnum) writer.WriteLine("# Acceptable values: " + string.Join(", ", Enum.GetNames(SettingType))); } } } } }