mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
Merge with cecil branch
This commit is contained in:
commit
49c9749d38
34 files changed
+847
-469
No files matched your search
+1
-1
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
|
||||
</packages>
|
||||
+11
-49
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -150,4 +150,4 @@ namespace BepInEx.Configuration
|
||||
return TypeConverters.Keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace BepInEx.Logging
|
||||
level = LogLevel.Debug;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
LogSource.Log(level, $"{message}".Trim());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace BepInEx.Logging
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
|
||||
@@ -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
@@ -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,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
|
||||
</packages>
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
+1
-1
Submodule submodules/MonoMod updated: 934a8ae921...a70072cdf7.
Reference in new issue
Block a user