Config.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text.RegularExpressions;
  5. using BepInEx.Common;
  6. using BepInEx.Logging;
  7. namespace BepInEx
  8. {
  9. /// <summary>
  10. /// A helper class to handle persistent data.
  11. /// </summary>
  12. public static class Config
  13. {
  14. private static readonly Dictionary<string, Dictionary<string, string>> cache = new Dictionary<string, Dictionary<string, string>>();
  15. private static string configPath => Path.Combine(Common.Utility.PluginsDirectory, "config.ini");
  16. private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
  17. private static void RaiseConfigReloaded()
  18. {
  19. ConfigReloaded?.Invoke();
  20. }
  21. /// <summary>
  22. /// An event that is fired every time the config is reloaded.
  23. /// </summary>
  24. public static event Action ConfigReloaded;
  25. /// <summary>
  26. /// If enabled, writes the config to disk every time a value is set.
  27. /// </summary>
  28. public static bool SaveOnConfigSet { get; set; } = true;
  29. static Config()
  30. {
  31. if (File.Exists(configPath))
  32. {
  33. ReloadConfig();
  34. }
  35. else
  36. {
  37. SaveConfig();
  38. }
  39. }
  40. /// <summary>
  41. /// Returns the value of the key if found, otherwise returns the default value.
  42. /// </summary>
  43. /// <param name="key">The key to search for.</param>
  44. /// <param name="defaultValue">The default value to return if the key is not found.</param>
  45. /// <param name="section">The section of the config to search the key for.</param>
  46. /// <returns>The value of the key.</returns>
  47. public static string GetEntry(string key, string defaultValue = "", string section = "")
  48. {
  49. try
  50. {
  51. key = Sanitize(key);
  52. section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
  53. if (!cache.TryGetValue(section, out Dictionary<string, string> subdict))
  54. {
  55. SetEntry(key, defaultValue, section);
  56. return defaultValue;
  57. }
  58. if (subdict.TryGetValue(key, out string value))
  59. return value;
  60. SetEntry(key, defaultValue, section);
  61. return defaultValue;
  62. }
  63. catch (Exception ex)
  64. {
  65. Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to read config entry!");
  66. Logger.Log(LogLevel.Error, ex);
  67. return defaultValue;
  68. }
  69. }
  70. /// <summary>
  71. /// Reloads the config from disk. Unwritten changes are lost.
  72. /// </summary>
  73. public static void ReloadConfig()
  74. {
  75. cache.Clear();
  76. string currentSection = "";
  77. foreach (string rawLine in File.ReadAllLines(configPath))
  78. {
  79. string line = rawLine.Trim();
  80. bool commentIndex = line.StartsWith(";") || line.StartsWith("#");
  81. if (commentIndex) //trim comment
  82. continue;
  83. if (line.StartsWith("[") && line.EndsWith("]")) //section
  84. {
  85. currentSection = line.Substring(1, line.Length - 2);
  86. continue;
  87. }
  88. string[] split = line.Split('='); //actual config line
  89. if (split.Length != 2)
  90. continue; //empty/invalid line
  91. if (!cache.ContainsKey(currentSection))
  92. cache[currentSection] = new Dictionary<string, string>();
  93. cache[currentSection][split[0]] = split[1];
  94. }
  95. RaiseConfigReloaded();
  96. }
  97. /// <summary>
  98. /// Writes the config to disk.
  99. /// </summary>
  100. public static void SaveConfig()
  101. {
  102. using (StreamWriter writer = new StreamWriter(File.Create(configPath), System.Text.Encoding.UTF8))
  103. foreach (var sectionKv in cache)
  104. {
  105. writer.WriteLine($"[{sectionKv.Key}]");
  106. foreach (var entryKv in sectionKv.Value)
  107. writer.WriteLine($"{entryKv.Key}={entryKv.Value}");
  108. writer.WriteLine();
  109. }
  110. }
  111. /// <summary>
  112. /// Sets the value of the key in the config.
  113. /// </summary>
  114. /// <param name="key">The key to set the value to.</param>
  115. /// <param name="value">The value to set.</param>
  116. public static void SetEntry(string key, string value, string section = "")
  117. {
  118. try
  119. {
  120. key = Sanitize(key);
  121. section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
  122. if (!cache.TryGetValue(section, out Dictionary<string, string> subdict))
  123. {
  124. subdict = new Dictionary<string, string>();
  125. cache[section] = subdict;
  126. }
  127. subdict[key] = value;
  128. if (SaveOnConfigSet)
  129. SaveConfig();
  130. }
  131. catch (Exception ex)
  132. {
  133. Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to save config entry!");
  134. Logger.Log(LogLevel.Error, ex);
  135. }
  136. }
  137. /// <summary>
  138. /// Returns wether a value is currently set.
  139. /// </summary>
  140. /// <param name="key">The key to check against</param>
  141. /// <param name="section">The section to check in</param>
  142. /// <returns>True if the key is present</returns>
  143. public static bool HasEntry(string key, string section = "")
  144. {
  145. key = Sanitize(key);
  146. section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
  147. return cache.ContainsKey(section) && cache[section].ContainsKey(key);
  148. }
  149. /// <summary>
  150. /// Removes a value from the config.
  151. /// </summary>
  152. /// <param name="key">The key to remove</param>
  153. /// <param name="section">The section to remove from</param>
  154. /// <returns>True if the key was removed</returns>
  155. public static bool UnsetEntry(string key, string section = "")
  156. {
  157. key = Sanitize(key);
  158. section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
  159. if (!HasEntry(key, section))
  160. return false;
  161. cache[section].Remove(key);
  162. return true;
  163. }
  164. /// <summary>
  165. /// Replaces any potentially breaking input with underscores.
  166. /// </summary>
  167. /// <param name="text">The text to sanitize.</param>
  168. /// <returns>Sanitized text.</returns>
  169. public static string Sanitize(string text)
  170. {
  171. return sanitizeKeyRegex.Replace(text, "_");
  172. }
  173. #region Extensions
  174. public static string GetEntry(this BaseUnityPlugin plugin, string key, string defaultValue = "")
  175. {
  176. return GetEntry(key, defaultValue, MetadataHelper.GetMetadata(plugin).GUID);
  177. }
  178. public static void SetEntry(this BaseUnityPlugin plugin, string key, string value)
  179. {
  180. SetEntry(key, value, MetadataHelper.GetMetadata(plugin).GUID);
  181. }
  182. public static bool HasEntry(this BaseUnityPlugin plugin, string key)
  183. {
  184. return HasEntry(key, MetadataHelper.GetMetadata(plugin).GUID);
  185. }
  186. #endregion Extensions
  187. }
  188. }