Browse Source

Implement prototype .NET loader

Bepis 4 years ago
parent
commit
9c8ad31af8

+ 5 - 0
BepInEx.Core/ConsoleManager.cs

@@ -85,6 +85,11 @@ namespace BepInEx
 			}
 		}
 
+		public static void ForceSetActive(bool value)
+		{
+			ConsoleActive = value;
+		}
+
 		public static readonly ConfigEntry<bool> ConfigConsoleEnabled = ConfigFile.CoreConfig.Bind(
 			"Logging.Console", "Enabled",
 			false,

+ 6 - 0
BepInEx.NetLauncher/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+    </startup>
+</configuration>

+ 21 - 0
BepInEx.NetLauncher/BasePlugin.cs

@@ -0,0 +1,21 @@
+namespace BepInEx.NetLauncher
+{
+	public abstract class BasePlugin
+	{
+		protected HarmonyLib.Harmony HarmonyInstance { get; }
+
+		protected BasePlugin()
+		{
+			//var info = PluginInfoHelper.GetPluginInfo(this);
+
+			//HarmonyInstance = new HarmonyLib.Harmony("BepInEx.Plugin." + info.GUID);
+		}
+
+		public abstract void Load();
+
+		public virtual bool Unload()
+		{
+			return false;
+		}
+	}
+}

+ 92 - 0
BepInEx.NetLauncher/BepInEx.NetLauncher.csproj

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{490B052B-7F23-4052-AD04-613856618901}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>BepInEx.NetLauncher</RootNamespace>
+    <AssemblyName>BepInEx.NetLauncher</AssemblyName>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>..\bin\net\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\bin\net\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <DocumentationFile>
+    </DocumentationFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Mono.Cecil, Version=0.11.2.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.11.2\lib\net40\Mono.Cecil.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil.Mdb, Version=0.11.2.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.11.2\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil.Pdb, Version=0.11.2.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.11.2\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil.Rocks, Version=0.11.2.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.11.2\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
+    </Reference>
+    <Reference Include="MonoMod.RuntimeDetour, Version=20.4.3.1, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MonoMod.RuntimeDetour.20.4.3.1\lib\net40\MonoMod.RuntimeDetour.dll</HintPath>
+    </Reference>
+    <Reference Include="MonoMod.Utils, Version=20.4.3.1, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MonoMod.Utils.20.4.3.1\lib\net40\MonoMod.Utils.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BasePlugin.cs" />
+    <Compile Include="NetChainloader.cs" />
+    <Compile Include="NetPreloader.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Utility.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">
+      <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
+      <Name>BepInEx.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj">
+      <Project>{15f8bc38-a761-4f93-8903-1b531ac5d9f9}</Project>
+      <Name>BepInEx.Preloader.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\submodules\BepInEx.Harmony\BepInEx.Harmony\BepInEx.Harmony.csproj">
+      <Project>{54161cfe-ff42-4dde-b161-3a49545db5cd}</Project>
+      <Name>BepInEx.Harmony</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\submodules\BepInEx.Harmony\submodules\Harmony\Harmony\Harmony.csproj">
+      <Project>{a15d6ee6-f954-415b-8605-8a8470cc87dc}</Project>
+      <Name>Harmony</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 20 - 0
BepInEx.NetLauncher/NetChainloader.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Reflection;
+using BepInEx.Bootstrap;
+
+namespace BepInEx.NetLauncher
+{
+	public class NetChainloader : BaseChainloader<BasePlugin>
+	{
+		public override BasePlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly)
+		{
+			var type = pluginAssembly.GetType(pluginInfo.TypeName);
+
+			var pluginInstance = (BasePlugin)Activator.CreateInstance(type);
+
+			pluginInstance.Load();
+
+			return pluginInstance;
+		}
+	}
+}

+ 118 - 0
BepInEx.NetLauncher/NetPreloader.cs

@@ -0,0 +1,118 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using BepInEx.Bootstrap;
+using BepInEx.Configuration;
+using BepInEx.Logging;
+using BepInEx.Preloader.Core;
+using BepInEx.Preloader.Core.RuntimeFixes;
+using MonoMod.RuntimeDetour;
+
+namespace BepInEx.NetLauncher
+{
+	public static class NetPreloader
+	{
+		private static readonly ManualLogSource Log = PreloaderLogger.Log;
+
+
+		public static void Start(string[] args)
+		{
+			if (ConfigEntrypointExecutable.Value == null)
+			{
+				Log.LogFatal($"Entry executable was not set. Please set this in your config before launching the application");
+				Program.ReadExit();
+				return;
+			}
+
+			string executablePath = Path.GetFullPath(ConfigEntrypointExecutable.Value);
+
+			if (!File.Exists(executablePath))
+			{
+				Log.LogFatal($"Unable to locate executable: {ConfigEntrypointExecutable.Value}");
+				Program.ReadExit();
+				return;
+			}
+
+			Paths.SetExecutablePath(executablePath);
+			Program.ResolveDirectories.Add(Paths.GameRootPath);
+			TypeLoader.SearchDirectories.Add(Paths.GameRootPath);
+
+			bool bridgeInitialized = Utility.TryDo(() =>
+			{
+				if (ConfigShimHarmony.Value)
+					HarmonyDetourBridge.Init();
+			}, out var harmonyBridgeException);
+
+			Logger.Sources.Add(TraceLogSource.CreateSource());
+
+			HarmonyFixes.Apply();
+
+			string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
+			Log.LogMessage(consoleTile);
+
+			if (ConsoleManager.ConsoleActive)
+				ConsoleManager.SetConsoleTitle(consoleTile);
+
+			//See BuildInfoAttribute for more information about this section.
+			object[] attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false);
+
+			if (attributes.Length > 0)
+			{
+				var attribute = (BuildInfoAttribute)attributes[0];
+				Log.LogMessage(attribute.Info);
+			}
+
+			Log.LogInfo($"CLR runtime version: {Environment.Version}");
+
+			if (harmonyBridgeException != null)
+				Log.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}");
+
+			Log.LogMessage("Preloader started");
+
+
+			using (var assemblyPatcher = new AssemblyPatcher())
+			{
+				assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
+
+				Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
+
+				assemblyPatcher.LoadAssemblyDirectory(Paths.GameRootPath, "dll", "exe");
+
+				Log.LogInfo($"{assemblyPatcher.AssembliesToPatch.Count} assemblies discovered");
+
+				assemblyPatcher.PatchAndLoad();
+			}
+
+			Log.LogMessage("Preloader finished");
+
+			var chainloader = new NetChainloader();
+			chainloader.Initialize();
+			chainloader.Execute();
+
+			var assemblyName = AssemblyName.GetAssemblyName(executablePath);
+			var entrypointAssembly = Assembly.Load(assemblyName);
+
+			entrypointAssembly.EntryPoint.Invoke(null, new [] { args });
+		}
+
+		#region Config
+
+		private static readonly ConfigEntry<string> ConfigEntrypointExecutable = ConfigFile.CoreConfig.Bind<string>(
+			"Preloader.Entrypoint", "Assembly",
+			null,
+			"The local filename of the .NET executable to target.");
+
+		private static readonly ConfigEntry<bool> ConfigShimHarmony = ConfigFile.CoreConfig.Bind(
+			"Preloader", "ShimHarmonySupport",
+			!Utility.CLRSupportsDynamicAssemblies,
+			"If enabled, basic Harmony functionality is patched to use MonoMod's RuntimeDetour instead.\nTry using this if Harmony does not work in a game.");
+
+		private static readonly ConfigEntry<bool> ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Bind(
+			"Logging", "PreloaderConsoleOutRedirection",
+			true,
+			"Redirects text from Console.Out during preloader patch loading to the BepInEx logging system.");
+
+		#endregion
+	}
+}

+ 131 - 0
BepInEx.NetLauncher/Program.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using BepInEx.Logging;
+using BepInEx.Preloader.Core;
+
+namespace BepInEx.NetLauncher
+{
+	internal static class NetPreloaderRunner
+	{
+		internal static void PreloaderMain(string[] args)
+		{
+			Logger.Listeners.Add(new ConsoleLogListener());
+
+			ConsoleManager.ForceSetActive(true);
+
+			NetPreloader.Start(args);
+		}
+
+		internal static void OuterMain(string[] args, string filename)
+		{
+			try
+			{
+				Paths.SetExecutablePath(filename);
+
+				AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
+
+				PreloaderMain(args);
+			}
+			catch (Exception ex)
+			{
+				PreloaderLogger.Log.LogFatal("Unhandled exception");
+				PreloaderLogger.Log.LogFatal(ex);
+				Program.ReadExit();
+			}
+		}
+
+		private static Assembly LocalResolve(object sender, ResolveEventArgs args)
+		{
+			var assemblyName = new AssemblyName(args.Name);
+
+			var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
+										 .FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
+
+			if (foundAssembly != null)
+				return foundAssembly;
+
+			if (LocalUtility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
+				|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
+				|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
+				return foundAssembly;
+
+			return null;
+		}
+	}
+
+	class Program
+	{
+		internal static void ReadExit()
+		{
+			Console.WriteLine("Press enter to exit...");
+			Console.ReadLine();
+			Environment.Exit(-1);
+		}
+
+		static void Main(string[] args)
+		{
+			try
+			{
+				Console.WriteLine("test");
+
+				string filename;
+
+#if DEBUG
+				filename = Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName));
+
+#else
+				filename = Process.GetCurrentProcess().MainModule.FileName;
+#endif
+
+				ResolveDirectories.Add(Path.Combine(Path.GetDirectoryName(filename), "BepInEx", "Core"));
+
+				AppDomain.CurrentDomain.AssemblyResolve += RemoteResolve;
+
+				NetPreloaderRunner.OuterMain(args, filename);
+			}
+			catch (Exception ex)
+			{
+				Console.WriteLine("Unhandled exception");
+				Console.WriteLine(ex);
+				ReadExit();
+			}
+		}
+
+		public static List<string> ResolveDirectories { get; set; } = new List<string>
+		{
+			"C:\\Windows\\Microsoft.NET\\assembly\\GAC_32\\Microsoft.Xna.Framework.Game\\v4.0_4.0.0.0__842cf8be1de50553\\"
+		};
+
+		private static Assembly RemoteResolve(object sender, ResolveEventArgs reference)
+		{
+			var assemblyName = new AssemblyName(reference.Name);
+
+			foreach (var directory in ResolveDirectories)
+			{
+				var potentialDirectories = new List<string> { directory };
+
+				potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
+
+				var potentialFiles = potentialDirectories.Select(x => Path.Combine(x, $"{assemblyName.Name}.dll"))
+														 .Concat(potentialDirectories.Select(x => Path.Combine(x, $"{assemblyName.Name}.exe")));
+
+				foreach (string path in potentialFiles)
+				{
+					if (!File.Exists(path))
+						continue;
+
+					var assembly = Assembly.LoadFrom(path);
+
+					if (assembly.GetName().Name == assemblyName.Name)
+						return assembly;
+				}
+			}
+
+			return null;
+		}
+	}
+}

+ 36 - 0
BepInEx.NetLauncher/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("BepInEx.NetLauncher")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BepInEx.NetLauncher")]
+[assembly: AssemblyCopyright("Copyright ©  2020")]
+[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("490b052b-7f23-4052-ad04-613856618901")]
+
+// 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")]

+ 62 - 0
BepInEx.NetLauncher/Utility.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+
+namespace BepInEx.NetLauncher
+{
+	/// <summary>
+	/// Generic helper properties and methods.
+	/// </summary>
+	public static class LocalUtility
+	{
+		/// <summary>
+		/// Try to resolve and load the given assembly DLL.
+		/// </summary>
+		/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
+		/// <param name="directory">Directory to search the assembly from.</param>
+		/// <param name="assembly">The loaded assembly.</param>
+		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
+		private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName, string directory, Func<string, T> loader, out T assembly) where T : class
+		{
+			assembly = null;
+
+			var potentialDirectories = new List<string> { directory };
+
+			potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
+
+			foreach (string subDirectory in potentialDirectories)
+			{
+				string path = Path.Combine(subDirectory, $"{assemblyName.Name}.dll");
+
+				if (!File.Exists(path))
+					continue;
+
+				try
+				{
+					assembly = loader(path);
+				}
+				catch (Exception)
+				{
+					continue;
+				}
+
+				return true;
+			}
+
+			return false;
+		}
+
+		/// <summary>
+		/// Try to resolve and load the given assembly DLL.
+		/// </summary>
+		/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
+		/// <param name="directory">Directory to search the assembly from.</param>
+		/// <param name="assembly">The loaded assembly.</param>
+		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
+		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
+		{
+			return TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
+		}
+	}
+}

+ 6 - 0
BepInEx.NetLauncher/packages.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Mono.Cecil" version="0.11.2" targetFramework="net452" />
+  <package id="MonoMod.RuntimeDetour" version="20.4.3.1" targetFramework="net452" />
+  <package id="MonoMod.Utils" version="20.4.3.1" targetFramework="net452" />
+</packages>

+ 6 - 2
BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs

@@ -43,7 +43,7 @@ namespace BepInEx.Preloader.Core
 		/// </summary>
 		public string DumpedAssembliesPath { get; set; } = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
 
-		public ManualLogSource Logger { get; } = new ManualLogSource("AssemblyPatcher");
+		public ManualLogSource Logger { get; } = BepInEx.Logging.Logger.CreateLogSource("AssemblyPatcher");
 
 		private static T CreateDelegate<T>(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
 
@@ -173,7 +173,7 @@ namespace BepInEx.Preloader.Core
 		public void LoadAssemblyDirectory(string directory, params string[] assemblyExtensions)
 		{
 			var filesToSearch = assemblyExtensions
-				.SelectMany(ext => Directory.GetFiles(directory, "*." + ext));
+				.SelectMany(ext => Directory.GetFiles(directory, "*." + ext, SearchOption.TopDirectoryOnly));
 
 			foreach (string assemblyPath in filesToSearch)
 			{
@@ -192,6 +192,8 @@ namespace BepInEx.Preloader.Core
 
 				AssembliesToPatch.Add(Path.GetFileName(assemblyPath), assembly);
 
+				Logger.LogDebug($"Assembly loaded: {Path.GetFileName(assemblyPath)}");
+
 				//if (UnityPatches.AssemblyLocations.ContainsKey(assembly.FullName))
 				//{
 				//	Logger.LogWarning($"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
@@ -343,6 +345,8 @@ namespace BepInEx.Preloader.Core
 							Assembly.Load(assemblyStream.ToArray());
 						}
 					}
+
+					Logger.LogDebug($"Loaded '{assembly.FullName}' into memory");
 				}
 
 				// Though we have to dispose of all assemblies regardless of them being patched or not

+ 42 - 0
BepInEx.sln

@@ -34,47 +34,88 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Core", "BepInEx.Cor
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Unity", "Unity", "{6E2DD21E-0854-4F4A-B925-D90E0016D03D}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.NetLauncher", "BepInEx.NetLauncher\BepInEx.NetLauncher.csproj", "{490B052B-7F23-4052-AD04-613856618901}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetFramework", "NetFramework", "{6EC98884-A3DC-4828-85EF-5F10F7519429}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
+		Release_NetFramework|Any CPU = Release_NetFramework|Any CPU
+		Release_Unity|Any CPU = Release_Unity|Any CPU
 		Release|Any CPU = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = Release|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}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release_Unity|Any CPU.ActiveCfg = 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
 		{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}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release_Unity|Any CPU.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
 		{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}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release_Unity|Any CPU.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
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.ActiveCfg = Debug|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.Build.0 = Debug|x86
+		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release_NetFramework|Any CPU.ActiveCfg = Release|x86
+		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release_Unity|Any CPU.ActiveCfg = Release|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|x86
 		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU
+		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release_Unity|Any CPU.Build.0 = Release|Any CPU
 		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release|Any CPU.Build.0 = Release|Any CPU
 		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release_Unity|Any CPU.Build.0 = Release|Any CPU
 		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release|Any CPU.Build.0 = Release|Any CPU
 		{D404C973-441D-48ED-B266-C21320BA0D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{D404C973-441D-48ED-B266-C21320BA0D87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D404C973-441D-48ED-B266-C21320BA0D87}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{D404C973-441D-48ED-B266-C21320BA0D87}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{D404C973-441D-48ED-B266-C21320BA0D87}.Release_Unity|Any CPU.Build.0 = Release|Any CPU
 		{D404C973-441D-48ED-B266-C21320BA0D87}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{D404C973-441D-48ED-B266-C21320BA0D87}.Release|Any CPU.Build.0 = Release|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release_Unity|Any CPU.Build.0 = Release|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{490B052B-7F23-4052-AD04-613856618901}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -86,6 +127,7 @@ Global
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
 		{EAE9FAE6-8011-45A3-8B6E-0C7F14210533} = {6E2DD21E-0854-4F4A-B925-D90E0016D03D}
 		{D404C973-441D-48ED-B266-C21320BA0D87} = {6E2DD21E-0854-4F4A-B925-D90E0016D03D}
+		{490B052B-7F23-4052-AD04-613856618901} = {6EC98884-A3DC-4828-85EF-5F10F7519429}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1}