Merge with cecil branch

This commit is contained in:
ManlyMarco committed 2019-08-17 00:41:16 +02:00
commit 49c9749d38
34 files changed
+847 -469

No files matched your search

+1 -1
View File
@@ -3,4 +3,4 @@
url = https://github.com/BepInEx/BepInEx.Harmony
[submodule "submodules/MonoMod"]
path = submodules/MonoMod
url = https://github.com/0x0ade/MonoMod.git
url = https://github.com/MonoMod/MonoMod.git
+4 -34
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\ILRepack.2.0.16\build\ILRepack.props" Condition="Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" />
<Import Project="..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props" Condition="Exists('..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -20,7 +19,7 @@
<PropertyGroup>
<StartupObject>BepInEx.Patcher.Program</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Legacy|AnyCPU'">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\bin\patcher\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
@@ -30,29 +29,10 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\bin\patcher\</OutputPath>
<DefineConstants>TRACE;UNITY_2018</DefineConstants>
<Optimize>true</Optimize>
<DebugType>embedded</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Cecil, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
@@ -62,6 +42,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="ILRepack.targets" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
@@ -73,19 +54,8 @@
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props'))" />
<Error Condition="!Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.16\build\ILRepack.props'))" />
<Error Condition="!Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets'))" />
</Target>
<!-- ILRepack -->
<Target Name="AfterBuild">
<Move SourceFiles="$(OutputPath)\$(AssemblyName).exe" DestinationFiles="$(OutputPath)\$(AssemblyName)1.exe" />
<ItemGroup>
<InputAssemblies Include="$(OutputPath)\$(AssemblyName)1.exe" />
<InputAssemblies Include="$(OutputPath)\Mono.Cecil.dll" />
</ItemGroup>
<ILRepack Parallel="true" Internalize="true" DebugInfo="true" PrimaryAssemblyFile="$(OutputPath)\$(AssemblyName)1.exe" InputAssemblies="@(InputAssemblies)" TargetKind="Exe" TargetPlatformVersion="v2" OutputFile="$(OutputPath)\$(AssemblyName).exe" />
<Delete Files="@(InputAssemblies)" />
<Delete Files="$(OutputPath)\BepInEx.Bootstrap.dll" />
</Target>
<!-- /ILRepack -->
<Import Project="..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets" Condition="Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets')" />
</Project>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ILRepacker" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<Copy SourceFiles="$(OutputPath)\$(AssemblyName).exe" DestinationFiles="$(OutputPath)\$(AssemblyName)1.exe"/>
<ItemGroup>
<InputAssemblies Include="$(OutputPath)\$(AssemblyName)1.exe" />
<InputAssemblies Include="$(OutputPath)\Mono.Cecil.dll" />
</ItemGroup>
<ILRepack
Parallel="false"
Internalize="true"
DebugInfo="true"
InputAssemblies="@(InputAssemblies)"
TargetKind="Exe"
TargetPlatformVersion="v2"
OutputFile="$(OutputPath)\$(AssemblyName).exe" />
<Delete Files="@(InputAssemblies)" />
<Delete Files="$(OutputPath)\BepInEx.Bootstrap.dll" />
</Target>
</Project>
+10 -11
View File
@@ -26,6 +26,14 @@ namespace BepInEx.Patcher
Console.ResetColor();
}
static string GetUnityEngineAssembly(string managedDir)
{
var path = Path.Combine(managedDir, "UnityEngine.CoreModule.dll");
if (File.Exists(path))
return path;
return Path.Combine(managedDir, "UnityEngine.dll");
}
static void Main(string[] args)
{
Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
@@ -43,12 +51,7 @@ namespace BepInEx.Patcher
string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
#if UNITY_2018
string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll");
#else
string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll");
#endif
string unityOutputDLL = GetUnityEngineAssembly(managedDir);
if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
continue;
@@ -101,11 +104,7 @@ namespace BepInEx.Patcher
AssemblyResolver = defaultResolver
};
#if UNITY_2018
string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll.bak");
#else
string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
#endif
string unityBackupDLL = $"{GetUnityEngineAssembly(managedDir)}.bak";
//determine which assembly to use as a base
AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ILRepack" version="2.0.16" targetFramework="net35" />
<package id="ILRepack.MSBuild.Task" version="2.0.13" targetFramework="net35" />
<package id="ILRepack.Lib.MSBuild.Task" version="2.0.16.1" targetFramework="net35" />
<package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
</packages>
+1 -27
View File
@@ -13,7 +13,7 @@
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\</OutputPath>
@@ -22,36 +22,10 @@
<WarningLevel>4</WarningLevel>
<DocumentationFile>..\bin\BepInEx.Preloader.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE;UNITY_2018</DefineConstants>
<Optimize>true</Optimize>
<DebugType>none</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;UNITY_2018</DefineConstants>
<Optimize>true</Optimize>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Cecil, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
+47 -23
View File
@@ -5,38 +5,18 @@ using System.Reflection;
namespace BepInEx.Preloader
{
internal static class Entrypoint
internal static class PreloaderRunner
{
/// <summary>
/// The main entrypoint of BepInEx, called from Doorstop.
/// </summary>
/// <param name="args">
/// The arguments passed in from Doorstop. First argument is the path of the currently executing
/// process.
/// </param>
public static void Main(string[] args)
public static void PreloaderMain(string[] args)
{
EnvVars.LoadVars();
string bepinPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
Paths.SetExecutablePath(args[0], bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
Preloader.Run();
}
/// <summary>
/// A handler for <see cref="AppDomain.AssemblyResolve" /> to perform some special handling.
/// <para>
/// It attempts to check currently loaded assemblies (ignoring the version), and then checks the BepInEx/core path,
/// BepInEx/patchers path and the BepInEx folder, all in that order.
/// </para>
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
/// <returns></returns>
internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
@@ -54,4 +34,48 @@ namespace BepInEx.Preloader
return null;
}
}
internal static class Entrypoint
{
private static string preloaderPath;
/// <summary>
/// The main entrypoint of BepInEx, called from Doorstop.
/// </summary>
/// <param name="args">
/// The arguments passed in from Doorstop. First argument is the path of the currently executing
/// process.
/// </param>
public static void Main(string[] args)
{
EnvVars.LoadVars();
// Get the path of this DLL via Doorstop env var because Assembly.Location mangles non-ASCII characters on some versions of Mono for unknown reasons
preloaderPath = Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH));
AppDomain.CurrentDomain.AssemblyResolve += ResolveCurrentDirectory;
// In some versions of Unity 4, Mono tries to resolve BepInEx.dll prematurely because of the call to Paths.SetExecutablePath
// To prevent that, we have to use reflection and a separate startup class so that we can install required assembly resolvers before the main code
typeof(Entrypoint).Assembly.GetType($"BepInEx.Preloader.{nameof(PreloaderRunner)}")
?.GetMethod(nameof(PreloaderRunner.PreloaderMain))
?.Invoke(null, new object[] { args });
AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;
}
private static Assembly ResolveCurrentDirectory(object sender, ResolveEventArgs args)
{
var name = new AssemblyName(args.Name);
try
{
return Assembly.LoadFile(Path.Combine(preloaderPath, $"{name.Name}.dll"));
}
catch (Exception)
{
return null;
}
}
}
}
@@ -56,7 +56,6 @@ namespace BepInEx.Preloader
public void Dispose()
{
if (LoggerSource != null)
{
Console.SetOut(StandardOut);
+95 -14
View File
@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Preloader.RuntimeFixes;
@@ -23,6 +24,8 @@ namespace BepInEx.Preloader.Patching
/// </summary>
internal static class AssemblyPatcher
{
private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
public static List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
@@ -36,29 +39,108 @@ namespace BepInEx.Preloader.Patching
PatcherPlugins.Add(patcher);
}
private static T CreateDelegate<T>(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
private static PatcherPlugin ToPatcherPlugin(TypeDefinition type)
{
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
return null;
var targetDlls = type.Methods.FirstOrDefault(m => m.Name.Equals("get_TargetDLLs", StringComparison.InvariantCultureIgnoreCase) &&
m.IsPublic &&
m.IsStatic);
if (targetDlls == null ||
targetDlls.ReturnType.FullName != "System.Collections.Generic.IEnumerable`1<System.String>")
return null;
var patch = type.Methods.FirstOrDefault(m => m.Name.Equals("Patch") &&
m.IsPublic &&
m.IsStatic &&
m.ReturnType.FullName == "System.Void" &&
m.Parameters.Count == 1 &&
(m.Parameters[0].ParameterType.FullName == "Mono.Cecil.AssemblyDefinition&" ||
m.Parameters[0].ParameterType.FullName == "Mono.Cecil.AssemblyDefinition"));
if (patch == null)
return null;
return new PatcherPlugin
{
TypeName = type.FullName
};
}
/// <summary>
/// Adds all patchers from all managed assemblies specified in a directory.
/// </summary>
/// <param name="directory">Directory to search patcher DLLs from.</param>
/// <param name="patcherLocator">A function that locates assembly patchers in a given managed assembly.</param>
public static void AddPatchersFromDirectory(string directory,
Func<Assembly, List<PatcherPlugin>> patcherLocator)
public static void AddPatchersFromDirectory(string directory)
{
if (!Directory.Exists(directory))
return;
var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories))
try
{
var assembly = Assembly.LoadFrom(assemblyPath);
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin);
foreach (var patcher in patcherLocator(assembly))
sortedPatchers.Add(patcher.Name, patcher);
foreach (var keyValuePair in patchers)
{
var assemblyPath = keyValuePair.Key;
var patcherCollection = keyValuePair.Value;
var ass = Assembly.LoadFile(assemblyPath);
foreach (var patcherPlugin in patcherCollection)
{
try
{
var type = ass.GetType(patcherPlugin.TypeName);
var methods = type.GetMethods(ALL);
patcherPlugin.Initializer = CreateDelegate<Action>(methods.FirstOrDefault(m => m.Name.Equals("Initialize", StringComparison.InvariantCultureIgnoreCase) &&
m.GetParameters().Length == 0 &&
m.ReturnType == typeof(void)));
patcherPlugin.Finalizer = CreateDelegate<Action>(methods.FirstOrDefault(m => m.Name.Equals("Finish", StringComparison.InvariantCultureIgnoreCase) &&
m.GetParameters().Length == 0 &&
m.ReturnType == typeof(void)));
patcherPlugin.TargetDLLs = CreateDelegate<Func<IEnumerable<string>>>(type.GetProperty("TargetDLLs", ALL).GetGetMethod());
var patcher = methods.FirstOrDefault(m => m.Name.Equals("Patch", StringComparison.CurrentCultureIgnoreCase) &&
m.ReturnType == typeof(void) &&
m.GetParameters().Length == 1 &&
(m.GetParameters()[0].ParameterType == typeof(AssemblyDefinition) ||
m.GetParameters()[0].ParameterType == typeof(AssemblyDefinition).MakeByRefType()));
patcherPlugin.Patcher = (ref AssemblyDefinition pAss) =>
{
//we do the array fuckery here to get the ref result out
object[] args = { pAss };
patcher.Invoke(null, args);
pAss = (AssemblyDefinition)args[0];
};
sortedPatchers.Add($"{ass.GetName().Name}/{type.FullName}", patcherPlugin);
}
catch (Exception e)
{
Logger.LogError($"Failed to load patcher [{patcherPlugin.TypeName}]: {e.Message}");
if (e is ReflectionTypeLoadException re)
Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
else
Logger.LogDebug(e.ToString());
}
}
catch (BadImageFormatException) { } //unmanaged DLL
catch (ReflectionTypeLoadException) { } //invalid references
Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
$"Loaded {patcherCollection.Count} patcher methods from {ass.GetName().FullName}");
}
foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
AddPatcher(patcher.Value);
@@ -123,10 +205,10 @@ namespace BepInEx.Preloader.Patching
// Then, perform the actual patching
var patchedAssemblies = new HashSet<string>();
foreach (var assemblyPatcher in PatcherPlugins)
foreach (string targetDll in assemblyPatcher.TargetDLLs)
foreach (string targetDll in assemblyPatcher.TargetDLLs())
if (assemblies.TryGetValue(targetDll, out var assembly))
{
Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.Name}]");
Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.TypeName}]");
assemblyPatcher.Patcher?.Invoke(ref assembly);
assemblies[targetDll] = assembly;
@@ -135,7 +217,6 @@ namespace BepInEx.Preloader.Patching
// Finally, load patched assemblies into memory
if (ConfigDumpAssemblies.Value || ConfigLoadDumpedAssemblies.Value)
{
if (!Directory.Exists(DumpedAssembliesPath))
@@ -153,7 +234,7 @@ namespace BepInEx.Preloader.Patching
if (ConfigBreakBeforeLoadAssemblies.Value)
{
Logger.LogInfo($"BepInEx is about load the following assemblies:\n{string.Join("\n", patchedAssemblies.ToArray())}");
Logger.LogInfo(data: $"BepInEx is about load the following assemblies:\n{String.Join("\n", patchedAssemblies.ToArray())}");
Logger.LogInfo($"The assemblies were dumped into {DumpedAssembliesPath}");
Logger.LogInfo("Load any assemblies into the debugger, set breakpoints and continue execution.");
Debugger.Break();
+16 -4
View File
@@ -1,17 +1,19 @@
using System;
using System.Collections.Generic;
using System.IO;
using BepInEx.Bootstrap;
namespace BepInEx.Preloader.Patching
{
/// <summary>
/// A single assembly patcher.
/// </summary>
internal class PatcherPlugin
internal class PatcherPlugin : ICacheable
{
/// <summary>
/// Target assemblies to patch.
/// </summary>
public IEnumerable<string> TargetDLLs { get; set; } = null;
public Func<IEnumerable<string>> TargetDLLs { get; set; } = null;
/// <summary>
/// Initializer method that is run before any patching occurs.
@@ -29,8 +31,18 @@ namespace BepInEx.Preloader.Patching
public AssemblyPatcherDelegate Patcher { get; set; } = null;
/// <summary>
/// Name of the patcher.
/// Type name of the patcher.
/// </summary>
public string Name { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
public void Save(BinaryWriter bw)
{
bw.Write(TypeName);
}
public void Load(BinaryReader br)
{
TypeName = br.ReadString();
}
}
}
+39 -122
View File
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BepInEx.Configuration;
using BepInEx.Logging;
@@ -11,6 +10,7 @@ using BepInEx.Preloader.Patching;
using BepInEx.Preloader.RuntimeFixes;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.RuntimeDetour;
using UnityInjector.ConsoleUtil;
using MethodAttributes = Mono.Cecil.MethodAttributes;
@@ -26,24 +26,32 @@ namespace BepInEx.Preloader
/// </summary>
private static PreloaderConsoleListener PreloaderLog { get; set; }
public static bool IsPostUnity2017 { get; } = File.Exists(Path.Combine(Paths.ManagedPath, "UnityEngine.CoreModule.dll"));
public static void Run()
{
try
{
AllocateConsole();
if (ConfigApplyRuntimePatches.Value)
UnityPatches.Apply();
Utility.TryDo(() =>
{
if (ConfigShimHarmony.Value)
HarmonyDetourBridge.Init();
}, out var harmonyBridgeException);
Utility.TryDo(() =>
{
if (ConfigApplyRuntimePatches.Value)
UnityPatches.Apply();
}, out var runtimePatchException);
Logger.Sources.Add(TraceLogSource.CreateSource());
PreloaderLog = new PreloaderConsoleListener(ConfigPreloaderCOutLogging.Value);
Logger.Listeners.Add(PreloaderLog);
string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} RC1 - {Process.GetCurrentProcess().ProcessName}";
string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
ConsoleWindow.Title = consoleTile;
Logger.LogMessage(consoleTile);
@@ -53,35 +61,33 @@ namespace BepInEx.Preloader
if (attributes.Length > 0)
{
var attribute = (BuildInfoAttribute)attributes[0];
Logger.LogMessage(attribute.Info);
}
#if UNITY_2018
Logger.LogMessage("Compiled in Unity v2018 mode");
#else
Logger.LogMessage("Compiled in Legacy Unity mode");
#endif
Logger.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
Logger.LogInfo($"CLR runtime version: {Environment.Version}");
Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
Logger.LogInfo($"Running under Unity v{Process.GetCurrentProcess().MainModule.FileVersionInfo.FileVersion}");
if (harmonyBridgeException != null)
Logger.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}");
if (runtimePatchException != null)
Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");
Logger.LogMessage("Preloader started");
AssemblyPatcher.AddPatcher(new PatcherPlugin
{ TargetDLLs = new[] { ConfigEntrypointAssembly.Value },
Patcher = PatchEntrypoint,
Name = "BepInEx.Chainloader"
});
{
TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
Patcher = PatchEntrypoint,
TypeName = "BepInEx.Chainloader"
});
AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods);
AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
AssemblyPatcher.DisposePatchers();
@@ -122,97 +128,6 @@ namespace BepInEx.Preloader
}
}
/// <summary>
/// Scans the assembly for classes that use the patcher contract, and returns a list of valid patchers.
/// </summary>
/// <param name="assembly">The assembly to scan.</param>
/// <returns>A list of assembly patchers that were found in the assembly.</returns>
public static List<PatcherPlugin> GetPatcherMethods(Assembly assembly)
{
var patcherMethods = new List<PatcherPlugin>();
var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase;
foreach (var type in assembly.GetExportedTypes())
try
{
if (type.IsInterface)
continue;
var targetsProperty = type.GetProperty("TargetDLLs",
flags,
null,
typeof(IEnumerable<string>),
Type.EmptyTypes,
null);
//first try get the ref patcher method
var patcher = type.GetMethod("Patch",
flags,
null,
CallingConventions.Any,
new[] { typeof(AssemblyDefinition).MakeByRefType() },
null);
if (patcher == null) //otherwise try getting the non-ref patcher method
patcher = type.GetMethod("Patch",
flags,
null,
CallingConventions.Any,
new[] { typeof(AssemblyDefinition) },
null);
if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
continue;
var assemblyPatcher = new PatcherPlugin();
assemblyPatcher.Name = $"{assembly.GetName().Name}/{type.FullName}";
assemblyPatcher.Patcher = (ref AssemblyDefinition ass) =>
{
//we do the array fuckery here to get the ref result out
object[] args = { ass };
patcher.Invoke(null, args);
ass = (AssemblyDefinition)args[0];
};
assemblyPatcher.TargetDLLs = (IEnumerable<string>)targetsProperty.GetValue(null, null);
var initMethod = type.GetMethod("Initialize",
flags,
null,
CallingConventions.Any,
Type.EmptyTypes,
null);
if (initMethod != null)
assemblyPatcher.Initializer = () => initMethod.Invoke(null, null);
var finalizeMethod = type.GetMethod("Finish",
flags,
null,
CallingConventions.Any,
Type.EmptyTypes,
null);
if (finalizeMethod != null)
assemblyPatcher.Finalizer = () => finalizeMethod.Invoke(null, null);
patcherMethods.Add(assemblyPatcher);
}
catch (Exception ex)
{
Logger.LogWarning($"Could not load patcher methods from {assembly.GetName().Name}");
Logger.LogWarning(ex);
}
Logger.Log(patcherMethods.Count > 0 ? LogLevel.Info : LogLevel.Debug,
$"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}");
return patcherMethods;
}
/// <summary>
/// Inserts BepInEx's own chainloader entrypoint into UnityEngine.
/// </summary>
@@ -281,9 +196,9 @@ namespace BepInEx.Preloader
il.InsertBefore(ins,
il.Create(OpCodes.Ldnull)); // gameExePath (always null, we initialize the Paths class in Entrypoint
il.InsertBefore(ins,
il.Create(OpCodes.Ldc_I4_0)); // startConsole (always false, we already load the console in Preloader)
il.Create(OpCodes.Ldc_I4_0)); //startConsole (always false, we already load the console in Preloader)
il.InsertBefore(ins,
il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gameExePath, bool startConsole = true)
il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gamePath, string managedPath = null, bool startConsole = true)
il.InsertBefore(ins,
il.Create(OpCodes.Call, startMethod));
}
@@ -323,12 +238,8 @@ namespace BepInEx.Preloader
"Preloader.Entrypoint",
"Assembly",
"The local filename of the assembly to target.",
#if UNITY_2018
"UnityEngine.CoreModule.dll"
#else
"UnityEngine.dll"
#endif
);
IsPostUnity2017 ? "UnityEngine.CoreModule.dll" : "UnityEngine.dll"
);
private static readonly ConfigWrapper<string> ConfigEntrypointType = ConfigFile.CoreConfig.Wrap(
"Preloader.Entrypoint",
@@ -348,6 +259,12 @@ namespace BepInEx.Preloader
"Enables or disables runtime patches.\nThis should always be true, unless you cannot start the game due to a Harmony related issue (such as running .NET Standard runtime) or you know what you're doing.",
true);
private static readonly ConfigWrapper<bool> ConfigShimHarmony = ConfigFile.CoreConfig.Wrap(
"Preloader",
"ShimHarmonySupport",
"If enabled, basic Harmony functionality is patched to use MonoMod's RuntimeDetour instead.\nTry using this if Harmony does not work in a game.",
!Utility.CLRSupportsDynamicAssemblies);
private static readonly ConfigWrapper<bool> ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Wrap(
"Logging",
"PreloaderConsoleOutRedirection",
@@ -39,18 +39,5 @@ namespace BepInEx.Preloader.RuntimeFixes
if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
__result = $"file://{location.Replace('\\', '/')}";
}
#if UNITY_2018
/*
* DESC: Workaround for Trace class not working because of missing .config file
* AFFECTS: Unity 2018+ (not .NET Standard / MonoBleedingEdge runtimes)
*/
[HarmonyPostfix, HarmonyPatch(typeof(AppDomain), nameof(AppDomain.SetupInformation), MethodType.Getter)]
public static void GetExeConfigName(AppDomainSetup __result)
{
__result.ApplicationBase = $"file://{Paths.GameRootPath}";
__result.ConfigurationFile = "app.config";
}
#endif
}
}
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
</packages>
+11 -49
View File
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
VisualStudioVersion = 15.0.28307.757
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx", "BepInEx\BepInEx.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
EndProject
@@ -35,82 +35,44 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Legacy|Any CPU = Legacy|Any CPU
Release|Any CPU = Release|Any CPU
v2018|Any CPU = v2018|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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}.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}.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}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.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}.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}.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}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = v2018|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
{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|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}.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}.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}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|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}.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}.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}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|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}.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}.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}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.Build.0 = Release|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Debug|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}.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}.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}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Release|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = Release|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}.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}.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}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.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}.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}.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}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|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}.Legacy|Any CPU.ActiveCfg = Release|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.Build.0 = Release|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.ActiveCfg = Release|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+3 -2
View File
@@ -13,7 +13,7 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
@@ -37,7 +37,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;UNITY_2018</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<Optimize>false</Optimize>
@@ -70,6 +70,7 @@
<Compile Include="Configuration\AcceptableValueBase.cs" />
<Compile Include="Configuration\AcceptableValueList.cs" />
<Compile Include="Configuration\AcceptableValueRange.cs" />
<Compile Include="Contract\PluginInfo.cs" />
<Compile Include="Configuration\ConfigDefinition.cs" />
<Compile Include="Configuration\ConfigDescription.cs" />
<Compile Include="Configuration\ConfigEntry.cs" />
+123 -99
View File
@@ -8,6 +8,8 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx.Contract;
using Mono.Cecil;
using UnityEngine;
using UnityInjector.ConsoleUtil;
using Logger = BepInEx.Logging.Logger;
@@ -22,7 +24,9 @@ namespace BepInEx.Bootstrap
/// <summary>
/// The loaded and initialized list of plugins.
/// </summary>
public static List<BaseUnityPlugin> Plugins { get; private set; } = new List<BaseUnityPlugin>();
public static Dictionary<string, PluginInfo> PluginInfos { get; } = new Dictionary<string, PluginInfo>();
public static List<BaseUnityPlugin> Plugins { get; } = new List<BaseUnityPlugin>();
/// <summary>
/// The GameObject that all plugins are attached to as components.
@@ -34,15 +38,14 @@ namespace BepInEx.Bootstrap
private static bool _initialized = false;
/// <summary>
/// Initializes BepInEx to be able to start the chainloader.
/// </summary>
public static void Initialize(string gameExePath, bool startConsole = true)
/// Initializes BepInEx to be able to start the chainloader.
/// </summary>
public static void Initialize(string gameExePath, bool startConsole = true)
{
if (_initialized)
return;
// Set vitals
if (gameExePath != null)
{
// Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize
@@ -50,14 +53,12 @@ namespace BepInEx.Bootstrap
Paths.SetExecutablePath(gameExePath);
}
Paths.SetPluginPath(ConfigPluginsDirectory.Value);
// Start logging
if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole)
// Start logging
if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole)
{
ConsoleWindow.Attach();
Logger.Listeners.Add(new ConsoleLogListener());
}
}
// Fix for standard output getting overwritten by UnityLogger
if (ConsoleWindow.StandardOut != null)
@@ -69,7 +70,7 @@ namespace BepInEx.Bootstrap
Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
}
Logger.Listeners.Add(new UnityLogListener());
Logger.Listeners.Add(new UnityLogListener());
Logger.Listeners.Add(new DiskLogListener());
if (!TraceLogSource.IsListening)
@@ -84,7 +85,71 @@ namespace BepInEx.Bootstrap
_initialized = true;
}
private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._]+$");
private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._\-]+$");
public static PluginInfo ToPluginInfo(TypeDefinition type)
{
if (type.IsInterface || type.IsAbstract || !type.IsSubtypeOf(typeof(BaseUnityPlugin)))
return null;
var metadata = BepInPlugin.FromCecilType(type);
if (metadata == null)
{
Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified");
return null;
}
if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
{
Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
return null;
}
if (metadata.Version == null)
{
Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid.");
return null;
}
if (metadata.Name == null)
{
Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null.");
return null;
}
//Perform a filter for currently running process
var filters = BepInProcess.FromCecilType(type);
bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase));
if (invalidProcessName)
{
Logger.LogWarning($"Skipping over plugin [{metadata.GUID}] due to process filter");
return null;
}
var dependencies = BepInDependency.FromCecilType(type);
return new PluginInfo
{
Metadata = metadata,
Processes = filters,
Dependencies = dependencies,
TypeName = type.FullName
};
}
private static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
private static bool HasBepinPlugins(AssemblyDefinition ass)
{
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName))
return false;
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BaseUnityPlugin).FullName))
return false;
return true;
}
/// <summary>
/// The entrypoint for the BepInEx plugin system.
@@ -107,8 +172,7 @@ namespace BepInEx.Bootstrap
{
var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
if (productNameProp != null)
ConsoleWindow.Title =
$"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
Logger.LogMessage("Chainloader started");
@@ -116,76 +180,34 @@ namespace BepInEx.Bootstrap
UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader");
foreach (var keyValuePair in pluginsToLoad)
foreach (var pluginInfo in keyValuePair.Value)
pluginInfo.Location = keyValuePair.Key;
var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList();
var loadedAssemblies = new Dictionary<string, Assembly>();
string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
var globalPluginTypes = TypeLoader.LoadTypes<BaseUnityPlugin>(Paths.PluginPath).ToList();
Dictionary<Type, BepInPlugin> selectedPluginTypes = new Dictionary<Type, BepInPlugin>(globalPluginTypes.Count);
foreach (var pluginType in globalPluginTypes)
{
//Ensure metadata exists
var metadata = MetadataHelper.GetMetadata(pluginType);
if (metadata == null)
{
Logger.LogWarning($"Skipping type [{pluginType.FullName}] as no metadata attribute is specified");
continue;
}
if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
{
Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
continue;
}
if (selectedPluginTypes.Any(x => x.Value.GUID.Equals(metadata.GUID, StringComparison.OrdinalIgnoreCase)))
{
Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its GUID [{metadata.GUID}] is already used by another plugin.");
continue;
}
if (metadata.Version == null)
{
Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its version is invalid.");
continue;
}
if (metadata.Name == null)
{
Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its name is null.");
continue;
}
//Perform a filter for currently running process
var filters = MetadataHelper.GetAttributes<BepInProcess>(pluginType);
if (filters.Length != 0)
{
var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
if (!result)
{
Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
continue;
}
}
selectedPluginTypes.Add(pluginType, metadata);
}
Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load");
Logger.LogInfo($"{pluginInfos.Count} plugins to load");
var dependencyDict = new Dictionary<string, IEnumerable<string>>();
var pluginsByGUID = new Dictionary<string, Type>();
var pluginsByGUID = new Dictionary<string, PluginInfo>();
foreach (var kv in selectedPluginTypes)
foreach (var pluginInfo in pluginInfos)
{
var dependencies = MetadataHelper.GetDependencies(kv.Key, selectedPluginTypes.Keys);
if (pluginInfo.Metadata.GUID == null)
{
Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it does not have a valid GUID.");
continue;
}
dependencyDict[kv.Value.GUID] = dependencies.Select(d => d.DependencyGUID);
pluginsByGUID[kv.Value.GUID] = kv.Key;
if (dependencyDict.ContainsKey(pluginInfo.Metadata.GUID))
{
Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because its GUID ({pluginInfo.Metadata.GUID}) is already used by another plugin.");
continue;
}
dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo;
}
var emptyDependencies = new string[0];
@@ -200,14 +222,12 @@ namespace BepInEx.Bootstrap
foreach (var pluginGUID in sortedPlugins)
{
// If the plugin is missing, don't process it
if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginType))
if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo))
continue;
var metadata = MetadataHelper.GetMetadata(pluginType);
var dependencies = MetadataHelper.GetDependencies(pluginType, selectedPluginTypes.Keys);
var dependsOnInvalidPlugin = false;
var missingDependencies = new List<string>();
foreach (var dependency in dependencies)
foreach (var dependency in pluginInfo.Dependencies)
{
// If the depenency wasn't already processed, it's missing altogether
if (!processedPlugins.Contains(dependency.DependencyGUID))
@@ -230,14 +250,14 @@ namespace BepInEx.Bootstrap
if (dependsOnInvalidPlugin)
{
Logger.LogWarning($"Skipping [{metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
continue;
}
if (missingDependencies.Count != 0)
{
Logger.LogError($@"Missing the following dependencies for [{metadata.Name}]: {"\r\n"}{
string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
Logger.LogError($@"Missing the following dependencies for [{pluginInfo.Metadata.Name}]: {"\r\n"}{
string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
}{"\r\n"}Loading will be skipped; expect further errors and unstabilities.");
invalidPlugins.Add(pluginGUID);
@@ -246,14 +266,26 @@ namespace BepInEx.Bootstrap
try
{
Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]");
Plugins.Add((BaseUnityPlugin)ManagerObject.AddComponent(pluginType));
Logger.LogInfo($"Loading [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
if (!loadedAssemblies.TryGetValue(pluginInfo.Location, out var ass))
loadedAssemblies[pluginInfo.Location] = ass = Assembly.LoadFile(pluginInfo.Location);
PluginInfos[pluginGUID] = pluginInfo;
pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.TypeName));
Plugins.Add(pluginInfo.Instance);
}
catch (Exception ex)
{
invalidPlugins.Add(pluginGUID);
Logger.LogError($"Error loading [{metadata.Name}] : {ex.Message}");
Logger.LogDebug(ex);
PluginInfos.Remove(pluginGUID);
Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}");
if (ex is ReflectionTypeLoadException re)
Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
else
Logger.LogDebug(ex);
}
}
}
@@ -272,17 +304,9 @@ namespace BepInEx.Bootstrap
#region Config
private static readonly ConfigWrapper<string> ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap(
"Paths",
"PluginsDirectory",
"The relative directory to the BepInEx folder where plugins are loaded.",
"plugins");
private static readonly ConfigWrapper<string> ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap("Paths", "PluginsDirectory", "The relative directory to the BepInEx folder where plugins are loaded.", "plugins");
private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap(
"Logging",
"UnityLogListening",
"Enables showing unity log messages in the BepInEx logging system.",
true);
private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap("Logging", "UnityLogListening", "Enables showing unity log messages in the BepInEx logging system.", true);
#endregion
}
+214 -25
View File
@@ -1,56 +1,235 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BepInEx.Configuration;
using BepInEx.Logging;
using Mono.Cecil;
namespace BepInEx.Bootstrap
{
/// <summary>
/// Provides methods for loading specified types from an assembly.
/// A cacheable metadata item. Can be used with <see cref="TypeLoader.LoadAssemblyCache{T}"/> and <see cref="TypeLoader.SaveAssemblyCache{T}"/> to cache plugin metadata.
/// </summary>
public interface ICacheable
{
/// <summary>
/// Serialize the object into a binary format.
/// </summary>
/// <param name="bw"></param>
void Save(BinaryWriter bw);
/// <summary>
/// Loads the object from binary format.
/// </summary>
/// <param name="br"></param>
void Load(BinaryReader br);
}
/// <summary>
/// A cached assembly.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CachedAssembly<T> where T : ICacheable
{
/// <summary>
/// List of cached items inside the assembly.
/// </summary>
public List<T> CacheItems { get; set; }
/// <summary>
/// Timestamp of the assembly. Used to check the age of the cache.
/// </summary>
public long Timestamp { get; set; }
}
/// <summary>
/// Provides methods for loading specified types from an assembly.
/// </summary>
public static class TypeLoader
{
private static readonly DefaultAssemblyResolver resolver;
private static readonly ReaderParameters readerParameters;
static TypeLoader()
{
resolver = new DefaultAssemblyResolver();
readerParameters = new ReaderParameters { AssemblyResolver = resolver };
resolver.ResolveFailure += (sender, reference) =>
{
var name = new AssemblyName(reference.FullName);
if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out var assembly) ||
Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly) ||
Utility.TryResolveDllAssembly(name, Paths.ManagedPath, readerParameters, out assembly))
return assembly;
return AssemblyResolve?.Invoke(sender, reference);
};
}
public static event AssemblyResolveEventHandler AssemblyResolve;
/// <summary>
/// Loads a list of types from a directory containing assemblies, that derive from a base type.
/// Looks up assemblies in the given directory and locates all types that can be loaded and collects their metadata.
/// </summary>
/// <typeparam name="T">The specific base type to search for.</typeparam>
/// <param name="directory">The directory to search for assemblies.</param>
/// <returns>Returns a list of found derivative types.</returns>
public static IEnumerable<Type> LoadTypes<T>(string directory)
/// <param name="typeSelector">A function to check if a type should be selected and to build the type metadata.</param>
/// <param name="assemblyFilter">A filter function to quickly determine if the assembly can be loaded.</param>
/// <param name="cacheName">The name of the cache to get cached types from.</param>
/// <returns>A list of all loadable type metadatas indexed by the full path to the assembly that contains the types.</returns>
public static Dictionary<string, List<T>> FindPluginTypes<T>(string directory, Func<TypeDefinition, T> typeSelector, Func<AssemblyDefinition, bool> assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
{
List<Type> types = new List<Type>();
Type pluginType = typeof(T);
var result = new Dictionary<string, List<T>>();
Dictionary<string, CachedAssembly<T>> cache = null;
if (cacheName != null)
cache = LoadAssemblyCache<T>(cacheName);
foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
{
try
{
AssemblyName an = AssemblyName.GetAssemblyName(dll);
Assembly assembly = Assembly.Load(an);
foreach (Type type in assembly.GetTypes())
if (cache != null && cache.TryGetValue(dll, out var cacheEntry))
{
if (!type.IsInterface && !type.IsAbstract && pluginType.IsAssignableFrom(type))
types.Add(type);
long lastWrite = File.GetLastWriteTimeUtc(dll).Ticks;
if (lastWrite == cacheEntry.Timestamp)
{
result[dll] = cacheEntry.CacheItems;
continue;
}
}
}
catch (BadImageFormatException) { } //unmanaged DLL
catch (ReflectionTypeLoadException ex)
{
Logger.LogError($"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
Logger.LogDebug(TypeLoadExceptionToString(ex));
}
}
return types;
var ass = AssemblyDefinition.ReadAssembly(dll, readerParameters);
if (!assemblyFilter?.Invoke(ass) ?? false)
{
ass.Dispose();
continue;
}
var matches = ass.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
if (matches.Count == 0)
{
ass.Dispose();
continue;
}
result[dll] = matches;
ass.Dispose();
}
catch (Exception e)
{
Logger.LogError(e.ToString());
}
if (cacheName != null)
SaveAssemblyCache(cacheName, result);
return result;
}
private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
/// <summary>
/// Loads an index of type metadatas from a cache.
/// </summary>
/// <param name="cacheName">Name of the cache</param>
/// <typeparam name="T">Cacheable item</typeparam>
/// <returns>Cached type metadatas indexed by the path of the assembly that defines the type. If no cache is defined, return null.</returns>
public static Dictionary<string, CachedAssembly<T>> LoadAssemblyCache<T>(string cacheName) where T : ICacheable, new()
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
if (!EnableAssemblyCache.Value)
return null;
var result = new Dictionary<string, CachedAssembly<T>>();
try
{
string path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
if (!File.Exists(path))
return null;
using (var br = new BinaryReader(File.OpenRead(path)))
{
int entriesCount = br.ReadInt32();
for (var i = 0; i < entriesCount; i++)
{
string entryIdentifier = br.ReadString();
long entryDate = br.ReadInt64();
int itemsCount = br.ReadInt32();
var items = new List<T>();
for (var j = 0; j < itemsCount; j++)
{
var entry = new T();
entry.Load(br);
items.Add(entry);
}
result[entryIdentifier] = new CachedAssembly<T> { Timestamp = entryDate, CacheItems = items };
}
}
}
catch (Exception e)
{
Logger.LogWarning($"Failed to load cache \"{cacheName}\"; skipping loading cache. Reason: {e.Message}.");
}
return result;
}
/// <summary>
/// Saves indexed type metadata into a cache.
/// </summary>
/// <param name="cacheName">Name of the cache</param>
/// <param name="entries">List of plugin metadatas indexed by the path to the assembly that contains the types</param>
/// <typeparam name="T">Cacheable item</typeparam>
public static void SaveAssemblyCache<T>(string cacheName, Dictionary<string, List<T>> entries) where T : ICacheable
{
if (!EnableAssemblyCache.Value)
return;
try
{
if (!Directory.Exists(Paths.CachePath))
Directory.CreateDirectory(Paths.CachePath);
string path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
using (var bw = new BinaryWriter(File.OpenWrite(path)))
{
bw.Write(entries.Count);
foreach (var kv in entries)
{
bw.Write(kv.Key);
bw.Write(File.GetLastWriteTimeUtc(kv.Key).Ticks);
bw.Write(kv.Value.Count);
foreach (var item in kv.Value)
item.Save(bw);
}
}
}
catch (Exception e)
{
Logger.LogWarning($"Failed to save cache \"{cacheName}\"; skipping saving cache. Reason: {e.Message}.");
}
}
/// <summary>
/// Converts TypeLoadException to a readable string.
/// </summary>
/// <param name="ex">TypeLoadException</param>
/// <returns>Readable representation of the exception</returns>
public static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
{
var sb = new StringBuilder();
foreach (var exSub in ex.LoaderExceptions)
{
sb.AppendLine(exSub.Message);
if (exSub is FileNotFoundException exFileNotFound)
@@ -75,5 +254,15 @@ namespace BepInEx.Bootstrap
return sb.ToString();
}
#region Config
private static ConfigWrapper<bool> EnableAssemblyCache = ConfigFile.CoreConfig.Wrap(
"Caching",
"EnableAssemblyCache",
"Enable/disable assembly metadata cache\nEnabling this will speed up discovery of plugins and patchers by caching the metadata of all types BepInEx discovers.",
true);
#endregion
}
}
+1 -1
View File
@@ -54,7 +54,7 @@ namespace BepInEx.Configuration
/// <param name="owner">The plugin that owns this setting.</param>
public ConfigFile(string configPath, bool saveOnInit, BaseUnityPlugin owner = null)
{
_ownerMetadata = owner?.Metadata;
_ownerMetadata = owner?.Info.Metadata;
if (configPath == null) throw new ArgumentNullException(nameof(configPath));
configPath = Path.GetFullPath(configPath);
+1 -1
View File
@@ -150,4 +150,4 @@ namespace BepInEx.Configuration
return TypeConverters.Keys;
}
}
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ namespace UnityInjector.ConsoleUtil
"If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.",
false);
public static bool IsAttached { get; private set; }
public static bool IsAttached { get; private set; }
private static IntPtr _cOut;
private static IntPtr _oOut;
+44 -3
View File
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BepInEx.Logging;
using Mono.Cecil;
using Mono.Collections.Generic;
namespace BepInEx
{
@@ -46,6 +50,16 @@ namespace BepInEx
Version = null;
}
}
internal static BepInPlugin FromCecilType(TypeDefinition td)
{
var attr = MetadataHelper.GetCustomAttributes<BepInPlugin>(td, false).FirstOrDefault();
if (attr == null)
return null;
return new BepInPlugin((string)attr.ConstructorArguments[0].Value, (string)attr.ConstructorArguments[1].Value, (string)attr.ConstructorArguments[2].Value);
}
}
/// <summary>
@@ -84,6 +98,12 @@ namespace BepInEx
this.DependencyGUID = DependencyGUID;
this.Flags = Flags;
}
internal static IEnumerable<BepInDependency> FromCecilType(TypeDefinition td)
{
var attrs = MetadataHelper.GetCustomAttributes<BepInDependency>(td, true);
return attrs.Select(customAttribute => new BepInDependency((string)customAttribute.ConstructorArguments[0].Value, (DependencyFlags)customAttribute.ConstructorArguments[1].Value)).ToList();
}
}
/// <summary>
@@ -102,6 +122,12 @@ namespace BepInEx
{
this.ProcessName = ProcessName;
}
internal static List<BepInProcess> FromCecilType(TypeDefinition td)
{
var attrs = MetadataHelper.GetCustomAttributes<BepInProcess>(td, true);
return attrs.Select(customAttribute => new BepInProcess((string)customAttribute.ConstructorArguments[0].Value)).ToList();
}
}
#endregion
@@ -113,6 +139,22 @@ namespace BepInEx
/// </summary>
public static class MetadataHelper
{
internal static IEnumerable<CustomAttribute> GetCustomAttributes<T>(TypeDefinition td, bool inherit) where T : Attribute
{
var result = new List<CustomAttribute>();
var type = typeof(T);
var currentType = td;
do
{
result.AddRange(currentType.CustomAttributes.Where(ca => ca.AttributeType.FullName == type.FullName));
currentType = currentType.BaseType?.Resolve();
} while (inherit && currentType?.FullName != "System.Object");
return result;
}
/// <summary>
/// Retrieves the BepInPlugin metadata from a plugin type.
/// </summary>
@@ -159,10 +201,9 @@ namespace BepInEx
/// <summary>
/// Retrieves the dependencies of the specified plugin type.
/// </summary>
/// <param name="plugin">The plugin type.</param>
/// <param name="allPlugins">All currently loaded plugin types.</param>
/// <param name="Plugin">The plugin type.</param>
/// <returns>A list of all plugin types that the specified plugin type depends upon.</returns>
public static IEnumerable<BepInDependency> GetDependencies(Type plugin, IEnumerable<Type> allPlugins)
public static IEnumerable<BepInDependency> GetDependencies(Type plugin)
{
return plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast<BepInDependency>();
}
+21 -6
View File
@@ -1,4 +1,6 @@
using BepInEx.Configuration;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Contract;
using BepInEx.Logging;
using UnityEngine;
@@ -12,8 +14,7 @@ namespace BepInEx
/// <summary>
/// Information about this plugin as it was loaded.
/// </summary>
public BepInPlugin Metadata { get; }
public PluginInfo Info { get; }
/// <summary>
/// Logger instance tied to this plugin.
/// </summary>
@@ -30,11 +31,25 @@ namespace BepInEx
/// </summary>
protected BaseUnityPlugin()
{
Metadata = MetadataHelper.GetMetadata(this);
var metadata = MetadataHelper.GetMetadata(this);
Logger = Logging.Logger.CreateLogSource(Metadata.Name);
if (Chainloader.PluginInfos.TryGetValue(metadata.GUID, out var info))
Info = info;
else
{
Info = new PluginInfo
{
Metadata = metadata,
Instance = this,
Dependencies = MetadataHelper.GetDependencies(GetType()),
Processes = MetadataHelper.GetAttributes<BepInProcess>(GetType()),
Location = GetType().Assembly.Location
};
}
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, Metadata.GUID + ".cfg"), false, this);
Logger = Logging.Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, this);
}
}
}
+63
View File
@@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BepInEx.Bootstrap;
namespace BepInEx.Contract
{
public class PluginInfo : ICacheable
{
public BepInPlugin Metadata { get; internal set; }
public IEnumerable<BepInProcess> Processes { get; internal set; }
public IEnumerable<BepInDependency> Dependencies { get; internal set; }
public string Location { get; internal set; }
public BaseUnityPlugin Instance { get; internal set; }
internal string TypeName { get; set; }
public void Save(BinaryWriter bw)
{
bw.Write(TypeName);
bw.Write(Metadata.GUID);
bw.Write(Metadata.Name);
bw.Write(Metadata.Version.ToString());
var processList = Processes.ToList();
bw.Write(processList.Count);
foreach (var bepInProcess in processList)
bw.Write(bepInProcess.ProcessName);
var depList = Dependencies.ToList();
bw.Write(depList.Count);
foreach (var bepInDependency in depList)
{
bw.Write(bepInDependency.DependencyGUID);
bw.Write((int)bepInDependency.Flags);
}
}
public void Load(BinaryReader br)
{
TypeName = br.ReadString();
Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString());
var processListCount = br.ReadInt32();
var processList = new List<BepInProcess>(processListCount);
for (int i = 0; i < processListCount; i++)
processList.Add(new BepInProcess(br.ReadString()));
Processes = processList;
var depCount = br.ReadInt32();
var depList = new List<BepInDependency>(depCount);
for (int i = 0; i < depCount; i++)
depList.Add(new BepInDependency(br.ReadString(), (BepInDependency.DependencyFlags) br.ReadInt32()));
Dependencies = depList;
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace BepInEx.Logging
if (eventArgs.Level.GetHighestLevel() > DisplayedLogLevel)
return;
string log = $"[{eventArgs.Level, -7}:{((ILogSource)sender).SourceName, 10}] {eventArgs.Data}\r\n";
string log = $"[{eventArgs.Level,-7}:{((ILogSource)sender).SourceName,10}] {eventArgs.Data}\r\n";
Kon.ForegroundColor = eventArgs.Level.GetConsoleColor();
Console.Write(log);
+2 -5
View File
@@ -39,12 +39,9 @@ namespace BepInEx.Logging
}
LogWriter = TextWriter.Synchronized(new StreamWriter(fileStream, Encoding.UTF8));
FlushTimer = new Timer(o =>
{
LogWriter?.Flush();
}, null, 2000, 2000);
FlushTimer = new Timer(o => { LogWriter?.Flush(); }, null, 2000, 2000);
}
public void LogEvent(object sender, LogEventArgs eventArgs)
+1 -1
View File
@@ -76,7 +76,7 @@ namespace BepInEx.Logging
level = LogLevel.Debug;
break;
}
LogSource.Log(level, $"{message}".Trim());
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ namespace BepInEx.Logging
WriteStringToUnityLog = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
break;
}
if (WriteStringToUnityLog == null)
Logger.LogError("Unable to start Unity log writer");
}
+1
View File
@@ -24,6 +24,7 @@ namespace BepInEx.Logging
}
private bool disposed = false;
public void Dispose()
{
if (!disposed)
+13
View File
@@ -21,6 +21,14 @@ namespace BepInEx
PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers");
BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core");
BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.dll");
CachePath = Path.Combine(BepInExRootPath, "cache");
}
internal static void SetManagedPath(string managedPath)
{
if (managedPath == null)
return;
ManagedPath = managedPath;
}
internal static void SetPluginPath(string pluginPath)
@@ -68,6 +76,11 @@ namespace BepInEx
/// </summary>
public static string BepInExConfigPath { get; private set; }
/// <summary>
/// The path to temporary cache files.
/// </summary>
public static string CachePath { get; private set; }
/// <summary>
/// The path to the patcher plugin folder which resides in the BepInEx folder.
/// </summary>
+92 -12
View File
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Mono.Cecil;
namespace BepInEx
{
@@ -12,11 +14,50 @@ namespace BepInEx
public static class Utility
{
/// <summary>
/// Combines multiple paths together, as the specific method is not available in .NET 3.5.
/// Whether current Common Language Runtime supports dynamic method generation using <see cref="System.Reflection.Emit"/> namespace.
/// </summary>
/// <param name="parts">The multiple paths to combine together.</param>
/// <returns>A combined path.</returns>
public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
public static bool CLRSupportsDynamicAssemblies { get; }
static Utility()
{
try
{
var m = new DynamicMethod("SRE_Test", null, null);
CLRSupportsDynamicAssemblies = true;
}
catch (PlatformNotSupportedException)
{
CLRSupportsDynamicAssemblies = false;
}
}
/// <summary>
/// Try to perform an action.
/// </summary>
/// <param name="action">Action to perform.</param>
/// <param name="exception">Possible exception that gets returned.</param>
/// <returns>True, if action succeeded, false if an exception occured.</returns>
public static bool TryDo(Action action, out Exception exception)
{
exception = null;
try
{
action();
return true;
}
catch (Exception e)
{
exception = e;
return false;
}
}
/// <summary>
/// Combines multiple paths together, as the specific method is not available in .NET 3.5.
/// </summary>
/// <param name="parts">The multiple paths to combine together.</param>
/// <returns>A combined path.</returns>
public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
/// <summary>
/// Tries to parse a bool, with a default value if unable to parse.
@@ -26,7 +67,7 @@ namespace BepInEx
/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
public static bool SafeParseBool(string input, bool defaultValue = false)
{
return bool.TryParse(input, out bool result) ? result : defaultValue;
return Boolean.TryParse(input, out bool result) ? result : defaultValue;
}
/// <summary>
@@ -46,7 +87,7 @@ namespace BepInEx
/// <returns>True if the value parameter is null or empty, or if value consists exclusively of white-space characters.</returns>
public static bool IsNullOrWhiteSpace(this string self)
{
return self == null || self.All(char.IsWhiteSpace);
return self == null || self.All(Char.IsWhiteSpace);
}
public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
@@ -61,9 +102,8 @@ namespace BepInEx
Stack<TNode> currentStack = new Stack<TNode>();
if (!Visit(input, currentStack))
{
throw new Exception("Cyclic Dependency:\r\n" + currentStack
.Select(x => $" - {x}") //append dashes
.Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
throw new Exception("Cyclic Dependency:\r\n" + currentStack.Select(x => $" - {x}") //append dashes
.Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
}
}
@@ -107,7 +147,7 @@ namespace BepInEx
/// <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)
private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName, string directory, Func<string, T> loader, out T assembly) where T : class
{
assembly = null;
@@ -124,7 +164,7 @@ namespace BepInEx
try
{
assembly = Assembly.LoadFile(path);
assembly = loader(path);
}
catch (Exception)
{
@@ -137,7 +177,47 @@ namespace BepInEx
return false;
}
public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.None)
public static bool IsSubtypeOf(this TypeDefinition self, Type td)
{
if (self.FullName == td.FullName)
return true;
return self.FullName != "System.Object" && (self.BaseType?.Resolve()?.IsSubtypeOf(td) ?? 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);
}
/// <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, ReaderParameters readerParameters, out AssemblyDefinition assembly)
{
return TryResolveDllAssembly(assemblyName, directory, s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly);
}
/// <summary>
/// Tries to create a file with the given name
/// </summary>
/// <param name="path">Path of the file to create</param>
/// <param name="mode">File open mode</param>
/// <param name="fileStream">Resulting filestream</param>
/// <param name="access">File access options</param>
/// <param name="share">File share options</param>
/// <returns></returns>
public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.Read)
{
try
{
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
</packages>
+2 -2
View File
@@ -21,9 +21,9 @@ Unity plugin framework
**[User and developer guides](https://github.com/BepInEx/BepInEx/wiki)**
## Used libraries
- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - 2.7.1.0 ([df7d636](https://github.com/NeighTools/UnityDoorstop/commit/df7d6366d8dc69f024c61cd31e6f690eb44ce57a))
- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - 2.11.0.0 ([68a4e2d](https://github.com/NeighTools/UnityDoorstop/commit/68a4e2db1f09e5f5cc2f479d293b09764d09c80b))
- [pardeike/Harmony](https://github.com/pardeike/Harmony) - pre-2.0 ([443f551](https://github.com/pardeike/Harmony/commit/443f551ec45ecf409755b5979a4466343197de03))
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v19.05.01.01 ([934a8ae](https://github.com/0x0ade/MonoMod/commit/934a8ae921affac0093757d23c6f3ead34e996ac))
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v19.08.02.03 ([a70072c](https://github.com/MonoMod/MonoMod/tree/a70072cdf759ac0cfa80991fcd2cca67d3eec130))
- [jbevain/cecil](https://github.com/jbevain/cecil) - 0.10.3 ([fb289a7](https://github.com/jbevain/cecil/commit/fb289a7cd80ceb6af5c86e7c7ecce9bf1e98b8fe))
## Credits
+8 -8
View File
@@ -46,12 +46,12 @@ Changes since ${latestTag}:
}
dir('Doorstop') {
sh ''' tag="v2.7.1.0";
version="2.7.1.0";
sh ''' tag="v2.11.0.0";
version="2.11.0.0";
wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x64_$version.zip;
wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x86_$version.zip;
unzip -o Doorstop_x86_$version.zip winhttp.dll -d x86;
unzip -o Doorstop_x64_$version.zip winhttp.dll -d x64;'''
unzip -o Doorstop_x86_$version.zip version.dll -d x86;
unzip -o Doorstop_x64_$version.zip version.dll -d x64;'''
}
}
}
@@ -120,7 +120,7 @@ Changes since ${latestTag}:
sh 'rm -rf BepInEx/core/patcher'
sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
sh 'cp -f ../../../Doorstop/x86/winhttp.dll winhttp.dll'
sh 'cp -f ../../../Doorstop/x86/version.dll version.dll'
script {
if(params.IS_BE) {
@@ -134,7 +134,7 @@ Changes since ${latestTag}:
sh "zip -r9 BepInEx_Legacy_x86${commitPrefix}${versionNumber}.zip ./*"
sh 'cp -f ../../../Doorstop/x64/winhttp.dll winhttp.dll'
sh 'cp -f ../../../Doorstop/x64/version.dll version.dll'
sh 'unix2dos doorstop_config.ini'
@@ -167,7 +167,7 @@ Changes since ${latestTag}:
sh 'rm -f BepInEx/core/UnityEngine.CoreModule.dll'
sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
sh 'cp -f ../../../Doorstop/x86/winhttp.dll winhttp.dll'
sh 'cp -f ../../../Doorstop/x86/version.dll version.dll'
script {
if(params.IS_BE) {
@@ -181,7 +181,7 @@ Changes since ${latestTag}:
sh "zip -r9 BepInEx_v2018_x86${commitPrefix}${versionNumber}.zip ./*"
sh 'cp -f ../../../Doorstop/x64/winhttp.dll winhttp.dll'
sh 'cp -f ../../../Doorstop/x64/version.dll version.dll'
sh 'unix2dos doorstop_config.ini'