diff --git a/BepInEx.sln b/BepInEx.sln
index 9d302f8..377c327 100644
--- a/BepInEx.sln
+++ b/BepInEx.sln
@@ -27,44 +27,167 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod.RuntimeDetour", "su
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod.Utils", "submodules\MonoMod\MonoMod.Utils\MonoMod.Utils.csproj", "{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInExTests", "BepInExTests\BepInExTests.csproj", "{E7CD429A-D057-48E3-8C51-E5C934E8E07B}"
+ ProjectSection(ProjectDependencies) = postProject
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9} = {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Test = Debug|Test
Legacy|Any CPU = Legacy|Any CPU
+ Legacy|Test = Legacy|Test
+ Release|Any CPU = Release|Any CPU
+ Release|Test = Release|Test
v2018|Any CPU = v2018|Any CPU
+ v2018|Test = v2018|Test
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Test.ActiveCfg = v2018|x86
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Test.Build.0 = v2018|x86
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Test.ActiveCfg = Legacy|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Test.Build.0 = Legacy|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Test.ActiveCfg = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Test.Build.0 = v2018|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.Build.0 = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Test.ActiveCfg = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Test.Build.0 = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.ActiveCfg = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Test.ActiveCfg = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Test.Build.0 = v2018|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Test.ActiveCfg = Legacy|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Test.Build.0 = Legacy|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Test.ActiveCfg = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Test.Build.0 = v2018|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Test.ActiveCfg = v2018|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Test.Build.0 = v2018|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Test.ActiveCfg = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Test.Build.0 = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.Build.0 = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Test.ActiveCfg = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Test.Build.0 = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Test.ActiveCfg = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Test.Build.0 = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Any CPU.Build.0 = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Test.ActiveCfg = Release|Any CPU
+ {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Test.Build.0 = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Test.ActiveCfg = Debug|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Test.Build.0 = Debug|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Any CPU.Build.0 = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Test.ActiveCfg = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Test.Build.0 = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Test.ActiveCfg = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Test.Build.0 = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.Build.0 = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Test.ActiveCfg = Release|Any CPU
+ {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Test.Build.0 = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Test.ActiveCfg = Debug|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Test.Build.0 = Debug|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Any CPU.Build.0 = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Test.ActiveCfg = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Test.Build.0 = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Test.ActiveCfg = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Test.Build.0 = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.Build.0 = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Test.ActiveCfg = Release|Any CPU
+ {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Test.Build.0 = Release|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.ActiveCfg = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Test.ActiveCfg = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Test.Build.0 = v2018|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Test.ActiveCfg = Legacy|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Test.Build.0 = Legacy|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.ActiveCfg = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Test.ActiveCfg = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Test.Build.0 = v2018|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.Build.0 = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Test.ActiveCfg = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Test.Build.0 = v2018|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Test.ActiveCfg = Debug|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Test.Build.0 = Debug|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Any CPU.Build.0 = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Test.ActiveCfg = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Test.Build.0 = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Test.ActiveCfg = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Test.Build.0 = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.Build.0 = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Test.ActiveCfg = Release|Any CPU
+ {D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Test.Build.0 = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Test.ActiveCfg = Debug|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Test.Build.0 = Debug|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Any CPU.Build.0 = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Test.ActiveCfg = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Test.Build.0 = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Test.ActiveCfg = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Test.Build.0 = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Any CPU.Build.0 = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Test.ActiveCfg = Release|Any CPU
+ {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Test.Build.0 = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Test.ActiveCfg = Debug|x86
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Test.Build.0 = Debug|x86
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.Build.0 = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Test.ActiveCfg = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Test.Build.0 = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Test.ActiveCfg = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Test.Build.0 = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.ActiveCfg = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.Build.0 = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Test.ActiveCfg = Release|Any CPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Test.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BepInEx/BepInEx.csproj b/BepInEx/BepInEx.csproj
index 5bf383b..ee37d48 100644
--- a/BepInEx/BepInEx.csproj
+++ b/BepInEx/BepInEx.csproj
@@ -33,11 +33,41 @@
AnyCPU
prompt
MinimumRecommendedRules.ruleset
+ ..\bin\BepInEx.xml
+
+
+ bin\x86\Legacy\
+ DEBUG;TRACE
+ false
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ full
+ true
+
+
+ bin\x86\v2018\
+ DEBUG;TRACE;UNITY_2018
+ false
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ full
+ true
..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll
+
+ ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll
+
+
+ ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll
+
+
+ ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll
+
..\lib\UnityEngine.dll
@@ -46,8 +76,11 @@
+
+
+
diff --git a/BepInEx/Configuration/ConfigDefinition.cs b/BepInEx/Configuration/ConfigDefinition.cs
index 304332d..ff49c71 100644
--- a/BepInEx/Configuration/ConfigDefinition.cs
+++ b/BepInEx/Configuration/ConfigDefinition.cs
@@ -2,27 +2,6 @@
namespace BepInEx.Configuration
{
- public class ConfigDescription
- {
- public ConfigDescription(string description, Type settingType, object defaultValue)
- {
- Description = description ?? throw new ArgumentNullException(nameof(description));
- SettingType = settingType ?? throw new ArgumentNullException(nameof(settingType));
- DefaultValue = defaultValue;
-
- if(defaultValue == null && settingType.IsByRef)
- 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);
- }
-
- public string Description { get; }
- public Type SettingType { get; }
- public object DefaultValue { get; }
- //todo value range
- }
-
public class ConfigDefinition : IEquatable
{
public string Section { get; }
@@ -39,7 +18,7 @@ namespace BepInEx.Configuration
{
if (other == null) return false;
return string.Equals(Key, other.Key)
- && string.Equals(Section, other.Section);
+ && string.Equals(Section, other.Section);
}
public override bool Equals(object obj)
@@ -67,5 +46,10 @@ namespace BepInEx.Configuration
public static bool operator !=(ConfigDefinition left, ConfigDefinition right)
=> !Equals(left, right);
+
+ public override string ToString()
+ {
+ return Section + " / " + Key;
+ }
}
}
\ No newline at end of file
diff --git a/BepInEx/Configuration/ConfigDescription.cs b/BepInEx/Configuration/ConfigDescription.cs
new file mode 100644
index 0000000..f14216f
--- /dev/null
+++ b/BepInEx/Configuration/ConfigDescription.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace BepInEx.Configuration
+{
+ public class ConfigDescription
+ {
+ public ConfigDescription(string description)
+ {
+ Description = description ?? throw new ArgumentNullException(nameof(description));
+ }
+
+ public string Description { get; }
+
+ //todo value range
+
+ public string ToSerializedString()
+ {
+ return $"# {Description.Replace("\n", "\n# ")}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/BepInEx/Configuration/ConfigEntry.cs b/BepInEx/Configuration/ConfigEntry.cs
new file mode 100644
index 0000000..6ba9900
--- /dev/null
+++ b/BepInEx/Configuration/ConfigEntry.cs
@@ -0,0 +1,182 @@
+using System;
+using System.IO;
+using System.Linq;
+using BepInEx.Logging;
+
+namespace BepInEx.Configuration
+{
+ 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;
+
+ public ConfigFile ConfigFile { get; }
+ public ConfigDefinition Definition { get; }
+
+ public ConfigDescription Description { get; internal set; }
+
+ public Type SettingType { get; private set; }
+ 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;
+
+ ///
+ /// 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();
+ wasChanged = wasChanged || !Equals(newValue, _convertedValue);
+
+ if (wasChanged)
+ {
+ _convertedValue = newValue;
+
+ if (fireEvent)
+ OnSettingChanged(sender);
+ }
+ }
+
+ public string GetSerializedValue()
+ {
+ if (_serializedValue != null)
+ return _serializedValue;
+
+ if (!IsDefined)
+ return null;
+
+ return TomlTypeConverter.ConvertToString(Value, SettingType);
+ }
+
+ public 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);
+ 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;
+ }
+
+ private void OnSettingChanged(object sender)
+ {
+ ConfigFile.OnSettingChanged(sender, this);
+ }
+
+ public void WriteDescription(StreamWriter writer)
+ {
+ if (Description != null)
+ writer.WriteLine(Description.ToSerializedString());
+
+ if (SettingType != null)
+ {
+ writer.WriteLine("# Setting type: " + SettingType.Name);
+ writer.WriteLine("# Default value: " + DefaultValue);
+
+ // todo acceptable values
+
+ 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)");
+ }
+ }
+ }
+}
diff --git a/BepInEx/Configuration/ConfigFile.cs b/BepInEx/Configuration/ConfigFile.cs
index 7be36a4..85ae3c1 100644
--- a/BepInEx/Configuration/ConfigFile.cs
+++ b/BepInEx/Configuration/ConfigFile.cs
@@ -3,7 +3,8 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
-using System.Text.RegularExpressions;
+using System.Text;
+using BepInEx.Logging;
namespace BepInEx.Configuration
{
@@ -12,28 +13,30 @@ namespace BepInEx.Configuration
///
public class ConfigFile
{
- private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
+ // Need to be lazy evaluated to not cause problems for unit tests
+ private static ConfigFile _coreConfig;
+ internal static ConfigFile CoreConfig => _coreConfig ?? (_coreConfig = new ConfigFile(Paths.BepInExConfigPath, true));
- internal static ConfigFile CoreConfig { get; } = new ConfigFile(Paths.BepInExConfigPath, true);
+ protected Dictionary Entries { get; } = new Dictionary();
- protected internal Dictionary Cache { get; } = new Dictionary();
+ [Obsolete("Use ConfigEntries instead")]
+ public ReadOnlyCollection ConfigDefinitions => Entries.Keys.ToList().AsReadOnly();
- public ReadOnlyCollection ConfigDefinitions => Cache.Keys.ToList().AsReadOnly();
-
- ///
- /// An event that is fired every time the config is reloaded.
- ///
- public event EventHandler ConfigReloaded;
+ public ReadOnlyCollection ConfigEntries => Entries.Values.ToList().AsReadOnly();
public string ConfigFilePath { get; }
///
- /// If enabled, writes the config to disk every time a value is set.
+ /// If enabled, writes the config to disk every time a value is set. If disabled, you have to manually save or the changes will be lost!
///
public bool SaveOnConfigSet { get; set; } = true;
public ConfigFile(string configPath, bool saveOnInit)
{
+ if (configPath == null) throw new ArgumentNullException(nameof(configPath));
+
+ configPath = Path.GetFullPath(configPath);
+
ConfigFilePath = configPath;
if (File.Exists(ConfigFilePath))
@@ -44,9 +47,13 @@ namespace BepInEx.Configuration
{
Save();
}
+
+ StartWatching();
}
- private object _ioLock = new object();
+ #region Save/Load
+
+ private readonly object _ioLock = new object();
///
/// Reloads the config from disk. Unsaved changes are lost.
@@ -55,9 +62,7 @@ namespace BepInEx.Configuration
{
lock (_ioLock)
{
- Dictionary descriptions = Cache.ToDictionary(x => x.Key, x => x.Key.Description);
-
- string currentSection = "";
+ string currentSection = string.Empty;
foreach (string rawLine in File.ReadAllLines(ConfigFilePath))
{
@@ -81,14 +86,18 @@ namespace BepInEx.Configuration
var definition = new ConfigDefinition(currentSection, currentKey);
- if (descriptions.ContainsKey(definition))
- definition.Description = descriptions[definition];
+ Entries.TryGetValue(definition, out ConfigEntry entry);
+ if (entry == null)
+ {
+ entry = new ConfigEntry(this, definition);
+ Entries[definition] = entry;
+ }
- Cache[definition] = currentValue;
+ entry.SetSerializedValue(currentValue, true, this);
}
-
- ConfigReloaded?.Invoke(this, EventArgs.Empty);
}
+
+ OnConfigReloaded();
}
///
@@ -98,51 +107,177 @@ namespace BepInEx.Configuration
{
lock (_ioLock)
{
- if (!Directory.Exists(Paths.ConfigPath))
- Directory.CreateDirectory(Paths.ConfigPath);
+ StopWatching();
- using (StreamWriter writer = new StreamWriter(File.Create(ConfigFilePath), System.Text.Encoding.UTF8))
- foreach (var sectionKv in Cache.GroupBy(x => x.Key.Section).OrderBy(x => x.Key))
+ string directoryName = Path.GetDirectoryName(ConfigFilePath);
+ if (directoryName != null) Directory.CreateDirectory(directoryName);
+
+ using (var writer = new StreamWriter(File.Create(ConfigFilePath), Encoding.UTF8))
+ {
+ foreach (var sectionKv in Entries.GroupBy(x => x.Key.Section).OrderBy(x => x.Key))
{
+ // Section heading
writer.WriteLine($"[{sectionKv.Key}]");
- foreach (var entryKv in sectionKv)
+ foreach (var configEntry in sectionKv.Select(x => x.Value))
{
writer.WriteLine();
- if (!string.IsNullOrEmpty(entryKv.Key.Description))
- writer.WriteLine($"# {entryKv.Key.Description.Replace("\n", "\n# ")}");
+ configEntry.WriteDescription(writer);
- writer.WriteLine($"{entryKv.Key.Key} = {entryKv.Value}");
+ writer.WriteLine($"{configEntry.Definition.Key} = {configEntry.GetSerializedValue()}");
}
writer.WriteLine();
}
+ }
+
+ StartWatching();
}
}
- public ConfigWrapper Wrap(ConfigDefinition configDefinition, T defaultValue = default(T))
+ #endregion
+
+ #region Wraps
+
+ public ConfigWrapper Wrap(ConfigDefinition configDefinition, T defaultValue, ConfigDescription configDescription = null)
{
- if (!Cache.ContainsKey(configDefinition))
+ if (!TomlTypeConverter.CanConvert(typeof(T)))
+ throw new ArgumentException($"Type {typeof(T)} is not supported by the config system. Supported types: {string.Join(", ", TomlTypeConverter.GetSupportedTypes().Select(x => x.Name).ToArray())}");
+
+ Entries.TryGetValue(configDefinition, out var entry);
+
+ if (entry == null)
{
- Cache.Add(configDefinition, TomlTypeConverter.ConvertToString(defaultValue));
- Save();
+ entry = new ConfigEntry(this, configDefinition, typeof(T), defaultValue);
+ Entries[configDefinition] = entry;
}
else
{
- var original = Cache.Keys.First(x => x.Equals(configDefinition));
+ entry.SetTypeAndDefaultValue(typeof(T), defaultValue, !Equals(defaultValue, default(T)));
+ }
- if (original.Description != configDefinition.Description)
+ if (configDescription != null)
+ {
+ if (entry.Description != null)
+ Logger.Log(LogLevel.Warning, $"Tried to add configDescription to setting {configDefinition} when it already had one defined. Only add configDescription once or a random one will be used.");
+
+ entry.Description = configDescription;
+ }
+
+ return new ConfigWrapper(entry);
+ }
+
+ [Obsolete("Use other Wrap overloads instead")]
+ public ConfigWrapper Wrap(string section, string key, string description = null, T defaultValue = default(T))
+ => Wrap(new ConfigDefinition(section, key), defaultValue, string.IsNullOrEmpty(description) ? null : new ConfigDescription(description));
+
+ public ConfigWrapper Wrap(string section, string key, T defaultValue, ConfigDescription configDescription = null)
+ => Wrap(new ConfigDefinition(section, key), defaultValue, configDescription);
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// An event that is fired every time the config is reloaded.
+ ///
+ public event EventHandler ConfigReloaded;
+
+ ///
+ /// Fired when one of the settings is changed.
+ ///
+ public event EventHandler SettingChanged;
+
+ protected internal void OnSettingChanged(object sender, ConfigEntry changedEntry)
+ {
+ if (changedEntry == null) throw new ArgumentNullException(nameof(changedEntry));
+
+ if (SettingChanged != null)
+ {
+ var args = new SettingChangedEventArgs(changedEntry);
+
+ foreach (var callback in SettingChanged.GetInvocationList().Cast>())
{
- original.Description = configDefinition.Description;
- Save();
+ try
+ {
+ callback(sender, args);
+ }
+ catch (Exception e)
+ {
+ Logger.Log(LogLevel.Error, e);
+ }
}
}
- return new ConfigWrapper(this, configDefinition);
+ // todo better way to prevent write loop? maybe do some caching?
+ if (sender != this && SaveOnConfigSet)
+ Save();
}
- public ConfigWrapper Wrap(string section, string key, string description = null, T defaultValue = default(T))
- => Wrap(new ConfigDefinition(section, key, description), defaultValue);
+ protected void OnConfigReloaded()
+ {
+ if (ConfigReloaded != null)
+ {
+ foreach (var callback in ConfigReloaded.GetInvocationList().Cast())
+ {
+ try
+ {
+ callback(this, EventArgs.Empty);
+ }
+ catch (Exception e)
+ {
+ Logger.Log(LogLevel.Error, e);
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region File watcher
+
+ private FileSystemWatcher _watcher;
+
+ ///
+ /// Start watching the config file on disk for changes.
+ ///
+ public void StartWatching()
+ {
+ lock (_ioLock)
+ {
+ if (_watcher != null) return;
+
+ _watcher = new FileSystemWatcher
+ {
+ Path = Path.GetDirectoryName(ConfigFilePath) ?? throw new ArgumentException("Invalid config path"),
+ Filter = Path.GetFileName(ConfigFilePath),
+ IncludeSubdirectories = false,
+ NotifyFilter = NotifyFilters.LastWrite,
+ EnableRaisingEvents = true
+ };
+
+ _watcher.Changed += (sender, args) => Reload();
+ }
+ }
+
+ ///
+ /// Stop watching the config file on disk for changes.
+ ///
+ public void StopWatching()
+ {
+ lock (_ioLock)
+ {
+ _watcher?.Dispose();
+ _watcher = null;
+ }
+ }
+
+ ~ConfigFile()
+ {
+ StopWatching();
+ }
+
+ #endregion
}
-}
\ No newline at end of file
+}
diff --git a/BepInEx/Configuration/ConfigWrapper.cs b/BepInEx/Configuration/ConfigWrapper.cs
index e3c1145..8f919f6 100644
--- a/BepInEx/Configuration/ConfigWrapper.cs
+++ b/BepInEx/Configuration/ConfigWrapper.cs
@@ -2,11 +2,12 @@
namespace BepInEx.Configuration
{
- public class ConfigWrapper
+ public sealed class ConfigWrapper
{
- public ConfigDefinition Definition { get; protected set; }
+ public ConfigEntry ConfigEntry { get; }
- public ConfigFile ConfigFile { get; protected set; }
+ public ConfigDefinition Definition => ConfigEntry.Definition;
+ public ConfigFile ConfigFile => ConfigEntry.ConfigFile;
///
/// Fired when the setting is changed. Does not detect changes made outside from this object.
@@ -15,25 +16,18 @@ namespace BepInEx.Configuration
public T Value
{
- get => TomlTypeConverter.ConvertToValue(ConfigFile.Cache[Definition]);
- set
- {
- ConfigFile.Cache[Definition] = TomlTypeConverter.ConvertToString(value);
-
- if (ConfigFile.SaveOnConfigSet)
- ConfigFile.Save();
-
- SettingChanged?.Invoke(this, EventArgs.Empty);
- }
+ get => (T)ConfigEntry.Value;
+ set => ConfigEntry.SetValue(value, true, this);
}
- public ConfigWrapper(ConfigFile configFile, ConfigDefinition definition)
+ internal ConfigWrapper(ConfigEntry configEntry)
{
- if (!TomlTypeConverter.TypeConverters.ContainsKey(typeof(T)))
- throw new ArgumentException("Unsupported config wrapper type");
+ ConfigEntry = configEntry ?? throw new ArgumentNullException(nameof(configEntry));
- ConfigFile = configFile;
- Definition = definition;
+ configEntry.ConfigFile.SettingChanged += (sender, args) =>
+ {
+ if (args.ChangedSetting == configEntry) SettingChanged?.Invoke(sender, args);
+ };
}
}
}
\ No newline at end of file
diff --git a/BepInEx/Configuration/SettingChangedEventArgs.cs b/BepInEx/Configuration/SettingChangedEventArgs.cs
new file mode 100644
index 0000000..3767e9b
--- /dev/null
+++ b/BepInEx/Configuration/SettingChangedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace BepInEx.Configuration
+{
+ public sealed class SettingChangedEventArgs : EventArgs
+ {
+ public SettingChangedEventArgs(ConfigEntry changedSetting)
+ {
+ ChangedSetting = changedSetting;
+ }
+
+ public ConfigEntry ChangedSetting { get; }
+ }
+}
\ No newline at end of file
diff --git a/BepInEx/Configuration/TomlTypeConverter.cs b/BepInEx/Configuration/TomlTypeConverter.cs
index 25c4f03..8a836a3 100644
--- a/BepInEx/Configuration/TomlTypeConverter.cs
+++ b/BepInEx/Configuration/TomlTypeConverter.cs
@@ -98,10 +98,8 @@ namespace BepInEx.Configuration
},
};
- public static string ConvertToString(T value)
+ public static string ConvertToString(object value, Type valueType)
{
- var valueType = typeof(T);
-
var conv = GetConverter(valueType);
if (conv == null)
throw new InvalidOperationException($"Cannot convert from type {valueType}");
@@ -111,13 +109,16 @@ namespace BepInEx.Configuration
public static T ConvertToValue(string value)
{
- var valueType = typeof(T);
+ return (T)ConvertToValue(value, typeof(T));
+ }
+ public static object ConvertToValue(string value, Type valueType)
+ {
var conv = GetConverter(valueType);
if (conv == null)
throw new InvalidOperationException($"Cannot convert to type {valueType}");
- return (T)conv.ConvertToObject(value, valueType);
+ return conv.ConvertToObject(value, valueType);
}
private static TypeConverter GetConverter(Type valueType)
diff --git a/BepInExTests/BepInExTests.csproj b/BepInExTests/BepInExTests.csproj
new file mode 100644
index 0000000..b9ebf21
--- /dev/null
+++ b/BepInExTests/BepInExTests.csproj
@@ -0,0 +1,131 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {E7CD429A-D057-48E3-8C51-E5C934E8E07B}
+ Library
+ Properties
+ BepInExTests
+ BepInExTests
+ v4.5
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+
+
+
+ 15.0
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {4ffba620-f5ed-47f9-b90c-dad1316fd9b9}
+ BepInEx
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BepInExTests/Configuration/ConfigFileTests.cs b/BepInExTests/Configuration/ConfigFileTests.cs
new file mode 100644
index 0000000..3fde291
--- /dev/null
+++ b/BepInExTests/Configuration/ConfigFileTests.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Concurrent;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.IO;
+using System.Linq;
+using System.Threading;
+
+namespace BepInEx.Configuration.Tests
+{
+ [TestClass]
+ public class ConfigFileTests
+ {
+ private static ConcurrentBag _toRemove;
+
+ [ClassInitialize]
+ public static void Init(TestContext context)
+ {
+ _toRemove = new ConcurrentBag();
+ }
+
+ [ClassCleanup]
+ public static void Cleanup()
+ {
+ foreach (var configFile in _toRemove)
+ {
+ configFile.StopWatching();
+ File.Delete(configFile.ConfigFilePath);
+ }
+ }
+
+ private static ConfigFile MakeConfig()
+ {
+ string configPath = Path.GetTempFileName();
+ if (configPath == null) throw new InvalidOperationException("Wtf");
+ var config = new ConfigFile(configPath, true);
+ _toRemove.Add(config);
+ return config;
+ }
+
+ [TestMethod]
+ public void SaveTest()
+ {
+ MakeConfig().Save();
+ }
+
+ [TestMethod]
+ public void SaveTestValueChange()
+ {
+ var c = MakeConfig();
+
+ var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+ var lines = File.ReadAllLines(c.ConfigFilePath);
+ Assert.AreEqual(0, lines.Count(x => x.Equals("[Cat]")));
+ Assert.AreEqual(0, lines.Count(x => x.Equals("# Test")));
+ Assert.AreEqual(0, lines.Count(x => x.Equals("Key = 0")));
+
+ c.Save();
+ lines = File.ReadAllLines(c.ConfigFilePath);
+ Assert.AreEqual(1, lines.Count(x => x.Equals("[Cat]")));
+ Assert.AreEqual(1, lines.Count(x => x.Equals("# Test")));
+ Assert.AreEqual(1, lines.Count(x => x.Equals("Key = 0")));
+
+ w.Value = 69;
+ lines = File.ReadAllLines(c.ConfigFilePath);
+ Assert.AreEqual(1, lines.Count(x => x.Equals("[Cat]")));
+ Assert.AreEqual(1, lines.Count(x => x.Equals("# Test")));
+ Assert.AreEqual(1, lines.Count(x => x.Equals("Key = 69")));
+ }
+
+ [TestMethod]
+ public void AutoSaveTest()
+ {
+ var c = MakeConfig();
+ c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+
+ var eventFired = new AutoResetEvent(false);
+ c.ConfigReloaded += (sender, args) => eventFired.Set();
+
+ c.Save();
+
+ Assert.IsFalse(eventFired.WaitOne(200));
+ }
+
+ [TestMethod]
+ public void ReadTest()
+ {
+ var c = MakeConfig();
+ File.WriteAllText(c.ConfigFilePath, "[Cat]\n# Test\nKey=1\n");
+ c.Reload();
+ var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+ Assert.AreEqual(w.Value, 1);
+ var w2 = c.Wrap("Cat", "Key2", 0, new ConfigDescription("Test"));
+ Assert.AreEqual(w2.Value, 0);
+ }
+
+ [TestMethod]
+ public void ReadTest2()
+ {
+ var c = MakeConfig();
+ var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+ Assert.AreEqual(w.Value, 0);
+
+ File.WriteAllText(c.ConfigFilePath, "[Cat]\n# Test\nKey = 1 \n");
+
+ c.Reload();
+ Assert.AreEqual(w.Value, 1);
+ }
+
+ [TestMethod]
+ public void FileWatchTest()
+ {
+ var c = MakeConfig();
+ var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+ Assert.AreEqual(w.Value, 0);
+
+ var eventFired = new AutoResetEvent(false);
+ w.SettingChanged += (sender, args) => eventFired.Set();
+
+ File.WriteAllText(c.ConfigFilePath, "[Cat]\n# Test\nKey = 1 \n");
+
+ Assert.IsTrue(eventFired.WaitOne(500));
+
+ Assert.AreEqual(w.Value, 1);
+ }
+
+ [TestMethod]
+ public void FileWatchTestNoSelfReload()
+ {
+ var c = MakeConfig();
+
+ var eventFired = new AutoResetEvent(false);
+ c.ConfigReloaded += (sender, args) => eventFired.Set();
+
+ c.Save();
+
+ Assert.IsFalse(eventFired.WaitOne(200));
+ }
+
+ [TestMethod]
+ public void EventTestWrapper()
+ {
+ var c = MakeConfig();
+ var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+
+ File.WriteAllText(c.ConfigFilePath, "[Cat]\n# Test\nKey=1\n");
+
+ var eventFired = false;
+ w.SettingChanged += (sender, args) => eventFired = true;
+
+ c.Reload();
+
+ Assert.IsTrue(eventFired);
+ }
+
+ [TestMethod]
+ public void EventTestReload()
+ {
+ var c = MakeConfig();
+ var eventFired = false;
+
+ var w = c.Wrap("Cat", "Key", 0, new ConfigDescription("Test"));
+ w.SettingChanged += (sender, args) => eventFired = true;
+
+ Assert.IsFalse(eventFired);
+
+ File.WriteAllText(c.ConfigFilePath, "[Cat]\n# Test\nKey=1\n");
+ c.Reload();
+
+ Assert.IsTrue(eventFired);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BepInExTests/Properties/AssemblyInfo.cs b/BepInExTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..1487903
--- /dev/null
+++ b/BepInExTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BepInExTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BepInExTests")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("e7cd429a-d057-48e3-8c51-e5c934e8e07b")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BepInExTests/packages.config b/BepInExTests/packages.config
new file mode 100644
index 0000000..d2da0eb
--- /dev/null
+++ b/BepInExTests/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file