using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using Logger = BepInEx.Logging.Logger;
namespace BepInEx.Configuration
{
///
/// Serializer/deserializer used by the config system.
///
public static class TomlTypeConverter
{
// Don't put anything from UnityEngine here or it will break preloader, use LazyTomlConverterLoader instead
private static Dictionary TypeConverters { get; } = new Dictionary
{
[typeof(string)] = new TypeConverter
{
ConvertToString = (obj, type) => Escape((string)obj),
ConvertToObject = (str, type) =>
{
// Check if the string is a file path with unescaped \ path separators (e.g. D:\test and not D:\\test)
if (Regex.IsMatch(str, @"^""?\w:\\(?!\\)(?!.+\\\\)"))
return str;
return Unescape(str);
},
},
[typeof(bool)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString().ToLowerInvariant(),
ConvertToObject = (str, type) => bool.Parse(str),
},
[typeof(byte)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => byte.Parse(str),
},
//integral types
[typeof(sbyte)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => sbyte.Parse(str),
},
[typeof(byte)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => byte.Parse(str),
},
[typeof(short)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => short.Parse(str),
},
[typeof(ushort)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => ushort.Parse(str),
},
[typeof(int)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => int.Parse(str),
},
[typeof(uint)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => uint.Parse(str),
},
[typeof(long)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => long.Parse(str),
},
[typeof(ulong)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => ulong.Parse(str),
},
//floating point types
[typeof(float)] = new TypeConverter
{
ConvertToString = (obj, type) => ((float)obj).ToString(NumberFormatInfo.InvariantInfo),
ConvertToObject = (str, type) => float.Parse(str, NumberFormatInfo.InvariantInfo),
},
[typeof(double)] = new TypeConverter
{
ConvertToString = (obj, type) => ((double)obj).ToString(NumberFormatInfo.InvariantInfo),
ConvertToObject = (str, type) => double.Parse(str, NumberFormatInfo.InvariantInfo),
},
[typeof(decimal)] = new TypeConverter
{
ConvertToString = (obj, type) => ((decimal)obj).ToString(NumberFormatInfo.InvariantInfo),
ConvertToObject = (str, type) => decimal.Parse(str, NumberFormatInfo.InvariantInfo),
},
//enums are special
[typeof(Enum)] = new TypeConverter
{
ConvertToString = (obj, type) => obj.ToString(),
ConvertToObject = (str, type) => Enum.Parse(type, str, true),
},
};
///
/// Convert object of a given type to a string using available converters.
///
public static string ConvertToString(object value, Type valueType)
{
var conv = GetConverter(valueType);
if (conv == null)
throw new InvalidOperationException($"Cannot convert from type {valueType}");
return conv.ConvertToString(value, valueType);
}
///
/// Convert string to an object of a given type using available converters.
///
public static T ConvertToValue(string value)
{
return (T)ConvertToValue(value, typeof(T));
}
///
/// Convert string to an object of a given type using available converters.
///
public static object ConvertToValue(string value, Type valueType)
{
var conv = GetConverter(valueType);
if (conv == null)
throw new InvalidOperationException($"Cannot convert to type {valueType.Name}");
return conv.ConvertToObject(value, valueType);
}
///
/// Get a converter for a given type if there is any.
///
public static TypeConverter GetConverter(Type valueType)
{
if (valueType == null) throw new ArgumentNullException(nameof(valueType));
if (valueType.IsEnum)
return TypeConverters[typeof(Enum)];
if (!TypeConverters.TryGetValue(valueType, out var result) && !_lazyLoadedConverters)
{
_lazyLoadedConverters = true;
LazyLoadConverters();
TypeConverters.TryGetValue(valueType, out result);
}
return result;
}
///
/// Add a new type converter for a given type.
/// If a different converter is already added, this call is ignored and false is returned.
///
public static bool AddConverter(Type type, TypeConverter converter)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (converter == null) throw new ArgumentNullException(nameof(converter));
if (CanConvert(type))
{
Logger.LogWarning("Tried to add a TomlConverter when one already exists for type " + type.FullName);
return false;
}
TypeConverters.Add(type, converter);
return true;
}
///
/// Check if a given type can be converted to and from string.
///
public static bool CanConvert(Type type)
{
return GetConverter(type) != null;
}
///
/// Give a list of types with registered converters.
///
public static IEnumerable GetSupportedTypes()
{
return TypeConverters.Keys;
}
private static bool _lazyLoadedConverters;
private static void LazyLoadConverters()
{
try { LazyTomlConverterLoader.AddUnityEngineConverters(); }
catch (Exception ex) { Logger.LogWarning("Failed to load UnityEngine Toml converters - " + ex.Message); }
}
private static string Escape(this string txt)
{
if (string.IsNullOrEmpty(txt)) return string.Empty;
var stringBuilder = new StringBuilder(txt.Length + 2);
foreach (char c in txt)
switch (c)
{
case '\0':
stringBuilder.Append(@"\0");
break;
case '\a':
stringBuilder.Append(@"\a");
break;
case '\b':
stringBuilder.Append(@"\b");
break;
case '\t':
stringBuilder.Append(@"\t");
break;
case '\n':
stringBuilder.Append(@"\n");
break;
case '\v':
stringBuilder.Append(@"\v");
break;
case '\f':
stringBuilder.Append(@"\f");
break;
case '\r':
stringBuilder.Append(@"\r");
break;
case '\'':
stringBuilder.Append(@"\'");
break;
case '\\':
stringBuilder.Append(@"\");
break;
case '\"':
stringBuilder.Append(@"\""");
break;
default:
stringBuilder.Append(c);
break;
}
return stringBuilder.ToString();
}
private static string Unescape(this string txt)
{
if (string.IsNullOrEmpty(txt))
return txt;
var stringBuilder = new StringBuilder(txt.Length);
for (int i = 0; i < txt.Length;)
{
int num = txt.IndexOf('\\', i);
if (num < 0 || num == txt.Length - 1)
num = txt.Length;
stringBuilder.Append(txt, i, num - i);
if (num >= txt.Length)
break;
char c = txt[num + 1];
switch (c)
{
case '0':
stringBuilder.Append('\0');
break;
case 'a':
stringBuilder.Append('\a');
break;
case 'b':
stringBuilder.Append('\b');
break;
case 't':
stringBuilder.Append('\t');
break;
case 'n':
stringBuilder.Append('\n');
break;
case 'v':
stringBuilder.Append('\v');
break;
case 'f':
stringBuilder.Append('\f');
break;
case 'r':
stringBuilder.Append('\r');
break;
case '\'':
stringBuilder.Append('\'');
break;
case '\"':
stringBuilder.Append('\"');
break;
case '\\':
stringBuilder.Append('\\');
break;
default:
stringBuilder.Append('\\').Append(c);
break;
}
i = num + 2;
}
return stringBuilder.ToString();
}
}
///
/// For types that are in assemblies that can't get loaded before preloader runs (or it won't work on these assemblies)
///
internal static class LazyTomlConverterLoader
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddUnityEngineConverters()
{
var colorConverter = new TypeConverter
{
ConvertToString = (obj, type) => ColorUtility.ToHtmlStringRGBA((Color)obj),
ConvertToObject = (str, type) =>
{
if (!ColorUtility.TryParseHtmlString("#" + str.Trim('#', ' '), out var c))
throw new FormatException("Invalid color string, expected hex #RRGGBBAA");
return c;
},
};
TomlTypeConverter.AddConverter(typeof(Color), colorConverter);
var jsonConverter = new TypeConverter
{
ConvertToString = (obj, type) => JsonUtility.ToJson(obj),
ConvertToObject = (str, type) => JsonUtility.FromJson(type: type, json: str),
};
TomlTypeConverter.AddConverter(typeof(Vector2), jsonConverter);
TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
}
}
}