TomlTypeConverter.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Runtime.CompilerServices;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using UnityEngine;
  8. using Logger = BepInEx.Logging.Logger;
  9. namespace BepInEx.Configuration
  10. {
  11. /// <summary>
  12. /// Serializer/deserializer used by the config system.
  13. /// </summary>
  14. public static class TomlTypeConverter
  15. {
  16. // Don't put anything from UnityEngine here or it will break preloader, use LazyTomlConverterLoader instead
  17. private static Dictionary<Type, TypeConverter> TypeConverters { get; } = new Dictionary<Type, TypeConverter>
  18. {
  19. [typeof(string)] = new TypeConverter
  20. {
  21. ConvertToString = (obj, type) => Escape((string)obj),
  22. ConvertToObject = (str, type) =>
  23. {
  24. // Check if the string is a file path with unescaped \ path separators (e.g. D:\test and not D:\\test)
  25. if (Regex.IsMatch(str, @"^""?\w:\\(?!\\)(?!.+\\\\)"))
  26. return str;
  27. return Unescape(str);
  28. },
  29. },
  30. [typeof(bool)] = new TypeConverter
  31. {
  32. ConvertToString = (obj, type) => obj.ToString().ToLowerInvariant(),
  33. ConvertToObject = (str, type) => bool.Parse(str),
  34. },
  35. [typeof(byte)] = new TypeConverter
  36. {
  37. ConvertToString = (obj, type) => obj.ToString(),
  38. ConvertToObject = (str, type) => byte.Parse(str),
  39. },
  40. //integral types
  41. [typeof(sbyte)] = new TypeConverter
  42. {
  43. ConvertToString = (obj, type) => obj.ToString(),
  44. ConvertToObject = (str, type) => sbyte.Parse(str),
  45. },
  46. [typeof(byte)] = new TypeConverter
  47. {
  48. ConvertToString = (obj, type) => obj.ToString(),
  49. ConvertToObject = (str, type) => byte.Parse(str),
  50. },
  51. [typeof(short)] = new TypeConverter
  52. {
  53. ConvertToString = (obj, type) => obj.ToString(),
  54. ConvertToObject = (str, type) => short.Parse(str),
  55. },
  56. [typeof(ushort)] = new TypeConverter
  57. {
  58. ConvertToString = (obj, type) => obj.ToString(),
  59. ConvertToObject = (str, type) => ushort.Parse(str),
  60. },
  61. [typeof(int)] = new TypeConverter
  62. {
  63. ConvertToString = (obj, type) => obj.ToString(),
  64. ConvertToObject = (str, type) => int.Parse(str),
  65. },
  66. [typeof(uint)] = new TypeConverter
  67. {
  68. ConvertToString = (obj, type) => obj.ToString(),
  69. ConvertToObject = (str, type) => uint.Parse(str),
  70. },
  71. [typeof(long)] = new TypeConverter
  72. {
  73. ConvertToString = (obj, type) => obj.ToString(),
  74. ConvertToObject = (str, type) => long.Parse(str),
  75. },
  76. [typeof(ulong)] = new TypeConverter
  77. {
  78. ConvertToString = (obj, type) => obj.ToString(),
  79. ConvertToObject = (str, type) => ulong.Parse(str),
  80. },
  81. //floating point types
  82. [typeof(float)] = new TypeConverter
  83. {
  84. ConvertToString = (obj, type) => ((float)obj).ToString(NumberFormatInfo.InvariantInfo),
  85. ConvertToObject = (str, type) => float.Parse(str, NumberFormatInfo.InvariantInfo),
  86. },
  87. [typeof(double)] = new TypeConverter
  88. {
  89. ConvertToString = (obj, type) => ((double)obj).ToString(NumberFormatInfo.InvariantInfo),
  90. ConvertToObject = (str, type) => double.Parse(str, NumberFormatInfo.InvariantInfo),
  91. },
  92. [typeof(decimal)] = new TypeConverter
  93. {
  94. ConvertToString = (obj, type) => ((decimal)obj).ToString(NumberFormatInfo.InvariantInfo),
  95. ConvertToObject = (str, type) => decimal.Parse(str, NumberFormatInfo.InvariantInfo),
  96. },
  97. //enums are special
  98. [typeof(Enum)] = new TypeConverter
  99. {
  100. ConvertToString = (obj, type) => obj.ToString(),
  101. ConvertToObject = (str, type) => Enum.Parse(type, str, true),
  102. },
  103. };
  104. /// <summary>
  105. /// Convert object of a given type to a string using available converters.
  106. /// </summary>
  107. public static string ConvertToString(object value, Type valueType)
  108. {
  109. var conv = GetConverter(valueType);
  110. if (conv == null)
  111. throw new InvalidOperationException($"Cannot convert from type {valueType}");
  112. return conv.ConvertToString(value, valueType);
  113. }
  114. /// <summary>
  115. /// Convert string to an object of a given type using available converters.
  116. /// </summary>
  117. public static T ConvertToValue<T>(string value)
  118. {
  119. return (T)ConvertToValue(value, typeof(T));
  120. }
  121. /// <summary>
  122. /// Convert string to an object of a given type using available converters.
  123. /// </summary>
  124. public static object ConvertToValue(string value, Type valueType)
  125. {
  126. var conv = GetConverter(valueType);
  127. if (conv == null)
  128. throw new InvalidOperationException($"Cannot convert to type {valueType.Name}");
  129. return conv.ConvertToObject(value, valueType);
  130. }
  131. /// <summary>
  132. /// Get a converter for a given type if there is any.
  133. /// </summary>
  134. public static TypeConverter GetConverter(Type valueType)
  135. {
  136. if (valueType == null) throw new ArgumentNullException(nameof(valueType));
  137. if (valueType.IsEnum)
  138. return TypeConverters[typeof(Enum)];
  139. if (!TypeConverters.TryGetValue(valueType, out var result) && !_lazyLoadedConverters)
  140. {
  141. _lazyLoadedConverters = true;
  142. LazyLoadConverters();
  143. TypeConverters.TryGetValue(valueType, out result);
  144. }
  145. return result;
  146. }
  147. /// <summary>
  148. /// Add a new type converter for a given type.
  149. /// If a different converter is already added, this call is ignored and false is returned.
  150. /// </summary>
  151. public static bool AddConverter(Type type, TypeConverter converter)
  152. {
  153. if (type == null) throw new ArgumentNullException(nameof(type));
  154. if (converter == null) throw new ArgumentNullException(nameof(converter));
  155. if (CanConvert(type))
  156. {
  157. Logger.LogWarning("Tried to add a TomlConverter when one already exists for type " + type.FullName);
  158. return false;
  159. }
  160. TypeConverters.Add(type, converter);
  161. return true;
  162. }
  163. /// <summary>
  164. /// Check if a given type can be converted to and from string.
  165. /// </summary>
  166. public static bool CanConvert(Type type)
  167. {
  168. return GetConverter(type) != null;
  169. }
  170. /// <summary>
  171. /// Give a list of types with registered converters.
  172. /// </summary>
  173. public static IEnumerable<Type> GetSupportedTypes()
  174. {
  175. return TypeConverters.Keys;
  176. }
  177. private static bool _lazyLoadedConverters;
  178. private static void LazyLoadConverters()
  179. {
  180. try { LazyTomlConverterLoader.AddUnityEngineConverters(); }
  181. catch (Exception ex) { Logger.LogWarning("Failed to load UnityEngine Toml converters - " + ex.Message); }
  182. }
  183. private static string Escape(this string txt)
  184. {
  185. var stringBuilder = new StringBuilder(txt.Length + 2);
  186. foreach (char c in txt)
  187. switch (c)
  188. {
  189. case '\0':
  190. stringBuilder.Append(@"\0");
  191. break;
  192. case '\a':
  193. stringBuilder.Append(@"\a");
  194. break;
  195. case '\b':
  196. stringBuilder.Append(@"\b");
  197. break;
  198. case '\t':
  199. stringBuilder.Append(@"\t");
  200. break;
  201. case '\n':
  202. stringBuilder.Append(@"\n");
  203. break;
  204. case '\v':
  205. stringBuilder.Append(@"\v");
  206. break;
  207. case '\f':
  208. stringBuilder.Append(@"\f");
  209. break;
  210. case '\r':
  211. stringBuilder.Append(@"\r");
  212. break;
  213. case '\'':
  214. stringBuilder.Append(@"\'");
  215. break;
  216. case '\\':
  217. stringBuilder.Append(@"\");
  218. break;
  219. case '\"':
  220. stringBuilder.Append(@"\""");
  221. break;
  222. default:
  223. stringBuilder.Append(c);
  224. break;
  225. }
  226. return stringBuilder.ToString();
  227. }
  228. private static string Unescape(this string txt)
  229. {
  230. if (string.IsNullOrEmpty(txt))
  231. return txt;
  232. var stringBuilder = new StringBuilder(txt.Length);
  233. for (int i = 0; i < txt.Length;)
  234. {
  235. int num = txt.IndexOf('\\', i);
  236. if (num < 0 || num == txt.Length - 1)
  237. num = txt.Length;
  238. stringBuilder.Append(txt, i, num - i);
  239. if (num >= txt.Length)
  240. break;
  241. char c = txt[num + 1];
  242. switch (c)
  243. {
  244. case '0':
  245. stringBuilder.Append('\0');
  246. break;
  247. case 'a':
  248. stringBuilder.Append('\a');
  249. break;
  250. case 'b':
  251. stringBuilder.Append('\b');
  252. break;
  253. case 't':
  254. stringBuilder.Append('\t');
  255. break;
  256. case 'n':
  257. stringBuilder.Append('\n');
  258. break;
  259. case 'v':
  260. stringBuilder.Append('\v');
  261. break;
  262. case 'f':
  263. stringBuilder.Append('\f');
  264. break;
  265. case 'r':
  266. stringBuilder.Append('\r');
  267. break;
  268. case '\'':
  269. stringBuilder.Append('\'');
  270. break;
  271. case '\"':
  272. stringBuilder.Append('\"');
  273. break;
  274. case '\\':
  275. stringBuilder.Append('\\');
  276. break;
  277. default:
  278. stringBuilder.Append('\\').Append(c);
  279. break;
  280. }
  281. i = num + 2;
  282. }
  283. return stringBuilder.ToString();
  284. }
  285. }
  286. /// <summary>
  287. /// For types that are in assemblies that can't get loaded before preloader runs (or it won't work on these assemblies)
  288. /// </summary>
  289. internal static class LazyTomlConverterLoader
  290. {
  291. [MethodImpl(MethodImplOptions.NoInlining)]
  292. public static void AddUnityEngineConverters()
  293. {
  294. var colorConverter = new TypeConverter
  295. {
  296. ConvertToString = (obj, type) => ColorUtility.ToHtmlStringRGBA((Color)obj),
  297. ConvertToObject = (str, type) =>
  298. {
  299. if (!ColorUtility.TryParseHtmlString("#" + str.Trim('#', ' '), out var c))
  300. throw new FormatException("Invalid color string, expected hex #RRGGBBAA");
  301. return c;
  302. },
  303. };
  304. TomlTypeConverter.AddConverter(typeof(Color), colorConverter);
  305. var jsonConverter = new TypeConverter
  306. {
  307. ConvertToString = (obj, type) => JsonUtility.ToJson(obj),
  308. ConvertToObject = (str, type) => JsonUtility.FromJson(type: type, json: str),
  309. };
  310. TomlTypeConverter.AddConverter(typeof(Vector2), jsonConverter);
  311. TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
  312. TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
  313. TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
  314. }
  315. }
  316. }