Merge pull request #64 from BepInEx/feature-preloader-refactor

Refactor preloader and separate it into its own DLL
This commit is contained in:
Bepis authored and GitHub committed 2019-02-05 03:14:41 +11:00
commit b598800dd4
20 files changed
+718 -608

No files matched your search

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BepInEx.Preloader</RootNamespace>
<AssemblyName>BepInEx.Preloader</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
<OutputPath>bin\v2018\</OutputPath>
<DefineConstants>TRACE;UNITY_2018</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Cecil">
<HintPath>..\lib\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Patcher\AssemblyPatcher.cs" />
<Compile Include="Entrypoint.cs" />
<Compile Include="Patcher\PatcherPlugin.cs" />
<Compile Include="Preloader.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UnityPatches.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BepInEx\BepInEx.csproj">
<Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
<Name>BepInEx</Name>
</ProjectReference>
<ProjectReference Include="..\submodules\BepInEx.Harmony\BepInEx.Harmony\BepInEx.Harmony.csproj">
<Project>{54161cfe-ff42-4dde-b161-3a49545db5cd}</Project>
<Name>BepInEx.Harmony</Name>
</ProjectReference>
<ProjectReference Include="..\submodules\BepInEx.Harmony\submodules\Harmony\Harmony\Harmony.csproj">
<Project>{a15d6ee6-f954-415b-8605-8a8470cc87dc}</Project>
<Name>Harmony</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -2,9 +2,9 @@
using System.Linq;
using System.Reflection;
namespace BepInEx.Bootstrap
namespace BepInEx.Preloader
{
public static class Entrypoint
internal static class Entrypoint
{
/// <summary>
/// The main entrypoint of BepInEx, called from Doorstop.
@@ -15,7 +15,9 @@ namespace BepInEx.Bootstrap
/// </param>
public static void Main(string[] args)
{
Paths.ExecutablePath = args[0];
// Manually set up the path for patchers to work
typeof(Paths).GetProperty(nameof(Paths.ExecutablePath)).SetValue(null, args[0], null);
//Paths.ExecutablePath = args[0];
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
Preloader.Run();
@@ -35,7 +37,8 @@ namespace BepInEx.Bootstrap
{
var assemblyName = new AssemblyName(args.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
if (foundAssembly != null)
return foundAssembly;
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using BepInEx.Logging;
using Mono.Cecil;
namespace BepInEx.Preloader.Patcher
{
/// <summary>
/// Delegate used in patching assemblies.
/// </summary>
/// <param name="assembly">The assembly that is being patched.</param>
internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
/// <summary>
/// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and
/// loading assemblies one at a time.
/// </summary>
internal static class AssemblyPatcher
{
private static readonly List<PatcherPlugin> patchers = new List<PatcherPlugin>();
/// <summary>
/// Configuration value of whether assembly dumping is enabled or not.
/// </summary>
private static bool DumpingEnabled =>
Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
/// <summary>
/// Adds a single assembly patcher to the pool of applicable patches.
/// </summary>
/// <param name="patcher">Patcher to apply.</param>
public static void AddPatcher(PatcherPlugin patcher)
{
patchers.Add(patcher);
}
/// <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)
{
if (!Directory.Exists(directory))
return;
var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
try
{
var assembly = Assembly.LoadFrom(assemblyPath);
foreach (var patcher in patcherLocator(assembly))
sortedPatchers.Add(patcher.Name, patcher);
}
catch (BadImageFormatException)
{
} //unmanaged DLL
catch (ReflectionTypeLoadException)
{
} //invalid references
foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
AddPatcher(patcher.Value);
}
private static void InitializePatchers()
{
foreach (var assemblyPatcher in patchers)
assemblyPatcher.Initializer?.Invoke();
}
private static void FinalizePatching()
{
foreach (var assemblyPatcher in patchers)
assemblyPatcher.Finalizer?.Invoke();
}
/// <summary>
/// Releases all patchers to let them be collected by GC.
/// </summary>
public static void DisposePatchers()
{
patchers.Clear();
}
/// <summary>
/// Applies patchers to all assemblies in the given directory and loads patched assemblies into memory.
/// </summary>
/// <param name="directory">Directory to load CLR assemblies from.</param>
public static void PatchAndLoad(string directory)
{
// First, load patchable assemblies into Cecil
var assemblies = new Dictionary<string, AssemblyDefinition>();
foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
{
var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
//NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
//System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
//It's also generally dangerous to change system.dll since so many things rely on it,
// and it's already loaded into the appdomain since this loader references it, so we might as well skip it
if (assembly.Name.Name == "System"
|| assembly.Name.Name == "mscorlib"
) //mscorlib is already loaded into the appdomain so it can't be patched
{
assembly.Dispose();
continue;
}
if (UnityPatches.AssemblyLocations.ContainsKey(assembly.FullName))
{
Logger.Log(LogLevel.Warning,
$"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
continue;
}
assemblies.Add(Path.GetFileName(assemblyPath), assembly);
UnityPatches.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
}
// Next, initialize all the patchers
InitializePatchers();
// Then, perform the actual patching
var patchedAssemblies = new HashSet<string>();
foreach (var assemblyPatcher in patchers)
foreach (string targetDll in assemblyPatcher.TargetDLLs)
if (assemblies.TryGetValue(targetDll, out var assembly))
{
assemblyPatcher.Patcher?.Invoke(ref assembly);
assemblies[targetDll] = assembly;
patchedAssemblies.Add(targetDll);
}
// Finally, load all assemblies into memory
foreach (KeyValuePair<string, AssemblyDefinition> kv in assemblies)
{
string filename = kv.Key;
var assembly = kv.Value;
if (DumpingEnabled && patchedAssemblies.Contains(filename))
using (var mem = new MemoryStream())
{
string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies");
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
assembly.Write(mem);
File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
}
Load(assembly);
assembly.Dispose();
}
//run all finalizers
FinalizePatching();
}
/// <summary>
/// Loads an individual assembly defintion into the CLR.
/// </summary>
/// <param name="assembly">The assembly to load.</param>
public static void Load(AssemblyDefinition assembly)
{
using (var assemblyStream = new MemoryStream())
{
assembly.Write(assemblyStream);
Assembly.Load(assemblyStream.ToArray());
}
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
namespace BepInEx.Preloader.Patcher
{
/// <summary>
/// A single assembly patcher.
/// </summary>
internal class PatcherPlugin
{
/// <summary>
/// Target assemblies to patch.
/// </summary>
public IEnumerable<string> TargetDLLs { get; set; } = null;
/// <summary>
/// Initializer method that is run before any patching occurs.
/// </summary>
public Action Initializer { get; set; } = null;
/// <summary>
/// Finalizer method that is run after all patching is done.
/// </summary>
public Action Finalizer { get; set; } = null;
/// <summary>
/// The main patcher method that is called on every DLL defined in <see cref="TargetDLLs" />.
/// </summary>
public AssemblyPatcherDelegate Patcher { get; set; } = null;
/// <summary>
/// Name of the patcher.
/// </summary>
public string Name { get; set; } = string.Empty;
}
}
+294
View File
@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BepInEx.Logging;
using BepInEx.Preloader.Patcher;
using Mono.Cecil;
using Mono.Cecil.Cil;
using UnityInjector.ConsoleUtil;
using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace BepInEx.Preloader
{
/// <summary>
/// The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
/// </summary>
internal static class Preloader
{
/// <summary>
/// The log writer that is specific to the preloader.
/// </summary>
private static PreloaderLogWriter PreloaderLog { get; set; }
public static void Run()
{
try
{
AllocateConsole();
UnityPatches.Apply();
PreloaderLog =
new PreloaderLogWriter(
Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
PreloaderLog.Enabled = true;
string consoleTile =
$"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
ConsoleWindow.Title = consoleTile;
Logger.SetLogger(PreloaderLog);
PreloaderLog.WriteLine(consoleTile);
//See BuildInfoAttribute for more information about this section.
object[] attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false);
if (attributes.Length > 0)
{
var attribute = (BuildInfoAttribute)attributes[0];
PreloaderLog.WriteLine(attribute.Info);
}
Logger.Log(LogLevel.Message, "Preloader started");
string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
AssemblyPatcher.AddPatcher(new PatcherPlugin
{TargetDLLs = new[] {entrypointAssembly}, Patcher = PatchEntrypoint});
AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods);
AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
AssemblyPatcher.DisposePatchers();
}
catch (Exception ex)
{
Logger.Log(LogLevel.Fatal, "Could not run preloader!");
Logger.Log(LogLevel.Fatal, ex);
PreloaderLog.Enabled = false;
try
{
if (!ConsoleWindow.IsAttatched)
{
//if we've already attached the console, then the log will already be written to the console
AllocateConsole();
Console.Write(PreloaderLog);
}
}
finally
{
File.WriteAllText(
Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
PreloaderLog + "\r\n" + ex);
PreloaderLog.Dispose();
PreloaderLog = null;
}
}
}
/// <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);
}
catch (Exception ex)
{
Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
Logger.Log(LogLevel.Warning, $"{ex}");
}
Logger.Log(LogLevel.Info,
$"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}");
return patcherMethods;
}
/// <summary>
/// Inserts BepInEx's own chainloader entrypoint into UnityEngine.
/// </summary>
/// <param name="assembly">The assembly that will be attempted to be patched.</param>
public static void PatchEntrypoint(ref AssemblyDefinition assembly)
{
if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
throw new Exception(
"BepInEx has been detected to be patched! Please unpatch before using a patchless variant!");
string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader");
bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor";
var entryType = assembly.MainModule.Types.FirstOrDefault(x => x.Name == entrypointType);
if (entryType == null) throw new Exception("The entrypoint type is invalid! Please check your config.ini");
using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
{
var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
.First(x => x.Name == "Initialize");
var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
.First(x => x.Name == "Start");
var initMethod = assembly.MainModule.ImportReference(originalInitMethod);
var startMethod = assembly.MainModule.ImportReference(originalStartMethod);
var methods = new List<MethodDefinition>();
if (isCctor)
{
var cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
if (cctor == null)
{
cctor = new MethodDefinition(".cctor",
MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
| MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
assembly.MainModule.ImportReference(typeof(void)));
entryType.Methods.Add(cctor);
var il = cctor.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ret));
}
methods.Add(cctor);
}
else
{
methods.AddRange(entryType.Methods.Where(x => x.Name == entrypointMethod));
}
if (!methods.Any())
throw new Exception("The entrypoint method is invalid! Please check your config.ini");
foreach (var method in methods)
{
var il = method.Body.GetILProcessor();
var ins = il.Body.Instructions.First();
il.InsertBefore(ins, il.Create(OpCodes.Ldstr, Paths.ExecutablePath)); //containerExePath
il.InsertBefore(ins,
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 containerExePath, bool startConsole = true)
il.InsertBefore(ins, il.Create(OpCodes.Call, startMethod));
}
}
}
/// <summary>
/// Allocates a console window for use by BepInEx safely.
/// </summary>
public static void AllocateConsole()
{
bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));
if (!console)
return;
try
{
ConsoleWindow.Attach();
var encoding = (uint) Encoding.UTF8.CodePage;
if (shiftjis)
encoding = 932;
ConsoleEncoding.ConsoleCodePage = encoding;
Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
}
catch (Exception ex)
{
Logger.Log(LogLevel.Error, "Failed to allocate console!");
Logger.Log(LogLevel.Error, ex);
}
}
}
}
@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("BepInEx.Preloader")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BepInEx.Preloader")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f7abbe07-c02f-4f7c-bf6e-c6656bf588ca")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+50
View File
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using BepInEx.Harmony;
using Harmony;
namespace BepInEx.Preloader
{
internal static class UnityPatches
{
public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepinex.unitypatches");
public static Dictionary<string, string> AssemblyLocations { get; } =
new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
public static void Apply()
{
HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance);
}
[HarmonyPostfix]
[HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)]
public static void GetLocation(ref string __result, Assembly __instance)
{
if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
__result = location;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)]
public static void GetCodeBase(ref string __result, Assembly __instance)
{
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+
*/
[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
}
}
+29 -33
View File
@@ -21,43 +21,38 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harmony", "submodules\BepIn
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Patcher", "Patcher", "{A9071994-3533-4C1B-89DC-D817B676AB41}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Preloader", "BepInEx.Preloader\BepInEx.Preloader.csproj", "{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
v2018 Release|Any CPU = v2018 Release|Any CPU
Legacy|Any CPU = Legacy|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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.ActiveCfg = v2018|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.Build.0 = v2018|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
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.ActiveCfg = v2018|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.Build.0 = v2018|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.Build.0 = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.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 Release|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|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 Release|Any CPU.ActiveCfg = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|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 Release|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018 Release|Any CPU.Build.0 = Release|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}.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}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.Build.0 = Release|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = Release|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|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}.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}.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}.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}.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}.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}.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}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.Build.0 = v2018|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -67,6 +62,7 @@ Global
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB} = {A9071994-3533-4C1B-89DC-D817B676AB41}
{54161CFE-FF42-4DDE-B161-3A49545DB5CD} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
{A15D6EE6-F954-415B-8605-8A8470CC87DC} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA} = {A9071994-3533-4C1B-89DC-D817B676AB41}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1}
+1 -15
View File
@@ -13,7 +13,7 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
@@ -27,16 +27,6 @@
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<Optimize>true</Optimize>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DebugSymbols>false</DebugSymbols>
<DocumentationFile>..\bin\BepInEx.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE;UNITY_2018</DefineConstants>
@@ -56,11 +46,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Bootstrap\Entrypoint.cs" />
<Compile Include="Bootstrap\UnityPatches.cs" />
<Compile Include="Contract\Attributes.cs" />
<Compile Include="Bootstrap\AssemblyPatcher.cs" />
<Compile Include="Bootstrap\Preloader.cs" />
<Compile Include="Config.cs" />
<Compile Include="ConfigWrapper.cs" />
<Compile Include="ConsoleUtil\ConsoleEncoding\ConsoleEncoding.Buffers.cs" />
-161
View File
@@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using BepInEx.Harmony;
using Harmony;
using Mono.Cecil;
namespace BepInEx.Bootstrap
{
/// <summary>
/// Delegate used in patching assemblies.
/// </summary>
/// <param name="assembly">The assembly that is being patched.</param>
public delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
/// <summary>
/// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and loading assemblies one at a time.
/// </summary>
public static class AssemblyPatcher
{
/// <summary>
/// Configuration value of whether assembly dumping is enabled or not.
/// </summary>
private static bool DumpingEnabled => Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
/// <summary>
/// Patches and loads an entire directory of assemblies.
/// </summary>
/// <param name="directory">The directory to load assemblies from.</param>
/// <param name="patcherMethodDictionary">The dictionary of patchers and their targeted assembly filenames which they are patching.</param>
/// <param name="initializers">List of initializers to run before any patching starts</param>
/// <param name="finalizers">List of finalizers to run before returning</param>
public static void PatchAll(string directory, IDictionary<AssemblyPatcherDelegate, IEnumerable<string>> patcherMethodDictionary, IEnumerable<Action> initializers = null, IEnumerable<Action> finalizers = null)
{
//run all initializers
if (initializers != null)
foreach (Action init in initializers)
init.Invoke();
//load all the requested assemblies
Dictionary<string, AssemblyDefinition> assemblies = new Dictionary<string, AssemblyDefinition>();
foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
{
var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
//NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
//System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
//It's also generally dangerous to change system.dll since so many things rely on it,
// and it's already loaded into the appdomain since this loader references it, so we might as well skip it
if (assembly.Name.Name == "System"
|| assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
{
assembly.Dispose();
continue;
}
assemblies.Add(Path.GetFileName(assemblyPath), assembly);
PatchedAssemblyResolver.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
}
HashSet<string> patchedAssemblies = new HashSet<string>();
//call the patchers on the assemblies
foreach (var patcherMethod in patcherMethodDictionary)
{
foreach (string assemblyFilename in patcherMethod.Value)
{
if (assemblies.TryGetValue(assemblyFilename, out var assembly))
{
Patch(ref assembly, patcherMethod.Key);
assemblies[assemblyFilename] = assembly;
patchedAssemblies.Add(assemblyFilename);
}
}
}
// Finally, load all assemblies into memory
foreach (var kv in assemblies)
{
string filename = kv.Key;
var assembly = kv.Value;
if (DumpingEnabled && patchedAssemblies.Contains(filename))
{
using (MemoryStream mem = new MemoryStream())
{
string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies");
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
assembly.Write(mem);
File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
}
}
Load(assembly);
assembly.Dispose();
}
// Patch Assembly.Location and Assembly.CodeBase only if the assemblies were loaded from memory
PatchedAssemblyResolver.ApplyPatch();
//run all finalizers
if (finalizers != null)
foreach (Action finalizer in finalizers)
finalizer.Invoke();
}
/// <summary>
/// Patches an individual assembly, without loading it.
/// </summary>
/// <param name="assembly">The assembly definition to apply the patch to.</param>
/// <param name="patcherMethod">The patcher to use to patch the assembly definition.</param>
public static void Patch(ref AssemblyDefinition assembly, AssemblyPatcherDelegate patcherMethod)
{
patcherMethod.Invoke(ref assembly);
}
/// <summary>
/// Loads an individual assembly defintion into the CLR.
/// </summary>
/// <param name="assembly">The assembly to load.</param>
public static void Load(AssemblyDefinition assembly)
{
using (MemoryStream assemblyStream = new MemoryStream())
{
assembly.Write(assemblyStream);
Assembly.Load(assemblyStream.ToArray());
}
}
}
internal static class PatchedAssemblyResolver
{
public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepis.bepinex.asmlocationfix");
public static Dictionary<string, string> AssemblyLocations { get; } = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
public static void ApplyPatch()
{
HarmonyWrapper.PatchAll(typeof(PatchedAssemblyResolver), HarmonyInstance);
}
[HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)]
public static void GetLocation(ref string __result, Assembly __instance)
{
if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
__result = location;
}
[HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)]
public static void GetCodeBase(ref string __result, Assembly __instance)
{
if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
__result = $"file://{location.Replace('\\', '/')}";
}
}
}
+5 -6
View File
@@ -15,17 +15,17 @@ namespace BepInEx.Bootstrap
/// <summary>
/// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
/// </summary>
public class Chainloader
public static class Chainloader
{
/// <summary>
/// The loaded and initialized list of plugins.
/// </summary>
public static List<BaseUnityPlugin> Plugins { get; protected set; } = new List<BaseUnityPlugin>();
public static List<BaseUnityPlugin> Plugins { get; private set; } = new List<BaseUnityPlugin>();
/// <summary>
/// The GameObject that all plugins are attached to as components.
/// </summary>
public static GameObject ManagerObject { get; protected set; } = new GameObject("BepInEx_Manager");
public static GameObject ManagerObject { get; private set; } = new GameObject("BepInEx_Manager");
private static bool _loaded = false;
@@ -54,12 +54,11 @@ namespace BepInEx.Bootstrap
UnityLogWriter unityLogWriter = new UnityLogWriter();
if (Preloader.PreloaderLog != null)
unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n");
if (Logger.CurrentLogger != null && Logger.CurrentLogger is PreloaderLogWriter preloaderLogger)
unityLogWriter.WriteToLog($"{preloaderLogger}\r\n");
Logger.SetLogger(unityLogWriter);
_initialized = true;
}
-342
View File
@@ -1,342 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BepInEx.Logging;
using Mono.Cecil;
using Mono.Cecil.Cil;
using UnityInjector.ConsoleUtil;
using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace BepInEx.Bootstrap
{
/// <summary>
/// The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
/// </summary>
internal static class Preloader
{
/// <summary>
/// The list of finalizers that were loaded from the patcher contract.
/// </summary>
public static List<Action> Finalizers { get; } = new List<Action>();
/// <summary>
/// The list of initializers that were loaded from the patcher contract.
/// </summary>
public static List<Action> Initializers { get; } = new List<Action>();
/// <summary>
/// The dictionary of currently loaded patchers. The key is the patcher delegate that will be used to patch, and the
/// value is a list of filenames of assemblies that the patcher is targeting.
/// </summary>
public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> PatcherDictionary { get; } =
new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
/// <summary>
/// The log writer that is specific to the preloader.
/// </summary>
public static PreloaderLogWriter PreloaderLog { get; private set; }
public static void Run()
{
try
{
AllocateConsole();
UnityPatches.Apply();
PreloaderLog =
new PreloaderLogWriter(Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
PreloaderLog.Enabled = true;
string consoleTile =
$"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
ConsoleWindow.Title = consoleTile;
Logger.SetLogger(PreloaderLog);
PreloaderLog.WriteLine(consoleTile);
#if DEBUG
object[] attributes = typeof(DebugInfoAttribute).Assembly.GetCustomAttributes(typeof(DebugInfoAttribute), false);
if (attributes.Length > 0)
{
var attribute = (DebugInfoAttribute)attributes[0];
PreloaderLog.WriteLine(attribute.Info);
}
#endif
Logger.Log(LogLevel.Message, "Preloader started");
string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
AddPatcher(new[] {entrypointAssembly}, PatchEntrypoint);
if (Directory.Exists(Paths.PatcherPluginPath))
{
var sortedPatchers = new SortedDictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
foreach (string assemblyPath in Directory.GetFiles(Paths.PatcherPluginPath, "*.dll"))
try
{
var assembly = Assembly.LoadFrom(assemblyPath);
foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in GetPatcherMethods(assembly))
try
{
sortedPatchers.Add(kv.Key, kv.Value);
}
catch (ArgumentException)
{
Logger.Log(LogLevel.Warning, $"Found duplicate of patcher {kv.Key}!");
}
}
catch (BadImageFormatException) { } //unmanaged DLL
catch (ReflectionTypeLoadException) { } //invalid references
foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in sortedPatchers)
AddPatcher(kv.Value.Value, kv.Value.Key);
}
AssemblyPatcher.PatchAll(Paths.ManagedPath, PatcherDictionary, Initializers, Finalizers);
}
catch (Exception ex)
{
Logger.Log(LogLevel.Fatal, "Could not run preloader!");
Logger.Log(LogLevel.Fatal, ex);
PreloaderLog.Enabled = false;
try
{
if (!ConsoleWindow.IsAttatched)
{
//if we've already attached the console, then the log will already be written to the console
AllocateConsole();
Console.Write(PreloaderLog);
}
}
finally
{
File.WriteAllText(Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
PreloaderLog + "\r\n" + ex);
PreloaderLog.Dispose();
}
}
}
/// <summary>
/// Scans the assembly for classes that use the patcher contract, and returns a dictionary of the patch methods.
/// </summary>
/// <param name="assembly">The assembly to scan.</param>
/// <returns>A dictionary of delegates which will be used to patch the targeted assemblies.</returns>
public static Dictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> GetPatcherMethods(Assembly assembly)
{
var patcherMethods = new Dictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
foreach (var type in assembly.GetExportedTypes())
try
{
if (type.IsInterface)
continue;
var targetsProperty = type.GetProperty("TargetDLLs",
BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
null,
typeof(IEnumerable<string>),
Type.EmptyTypes,
null);
//first try get the ref patcher method
var patcher = type.GetMethod("Patch",
BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
null,
CallingConventions.Any,
new[] {typeof(AssemblyDefinition).MakeByRefType()},
null);
if (patcher == null) //otherwise try getting the non-ref patcher method
patcher = type.GetMethod("Patch",
BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
null,
CallingConventions.Any,
new[] {typeof(AssemblyDefinition)},
null);
if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
continue;
AssemblyPatcherDelegate patchDelegate = (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];
};
var targets = (IEnumerable<string>) targetsProperty.GetValue(null, null);
patcherMethods[$"{assembly.GetName().Name}{type.FullName}"] = new KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>(patchDelegate, targets);
var initMethod = type.GetMethod("Initialize",
BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
null,
CallingConventions.Any,
Type.EmptyTypes,
null);
if (initMethod != null)
Initializers.Add(() => initMethod.Invoke(null, null));
var finalizeMethod = type.GetMethod("Finish",
BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
null,
CallingConventions.Any,
Type.EmptyTypes,
null);
if (finalizeMethod != null)
Finalizers.Add(() => finalizeMethod.Invoke(null, null));
}
catch (Exception ex)
{
Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
Logger.Log(LogLevel.Warning, $"{ex}");
}
Logger.Log(LogLevel.Info,
$"Loaded {patcherMethods.Select(x => x.Key).Distinct().Count()} patcher methods from {assembly.GetName().Name}");
return patcherMethods;
}
/// <summary>
/// Inserts BepInEx's own chainloader entrypoint into UnityEngine.
/// </summary>
/// <param name="assembly">The assembly that will be attempted to be patched.</param>
public static void PatchEntrypoint(ref AssemblyDefinition assembly)
{
if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
{
throw new Exception("BepInEx has been detected to be patched! Please unpatch before using a patchless variant!");
}
string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader");
bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor";
var entryType = assembly.MainModule.Types.FirstOrDefault(x => x.Name == entrypointType);
if (entryType == null)
{
throw new Exception("The entrypoint type is invalid! Please check your config.ini");
}
using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
{
var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
.First(x => x.Name == "Initialize");
var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
.First(x => x.Name == "Start");
var initMethod = assembly.MainModule.ImportReference(originalInitMethod);
var startMethod = assembly.MainModule.ImportReference(originalStartMethod);
List<MethodDefinition> methods = new List<MethodDefinition>();
if (isCctor)
{
MethodDefinition cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
if (cctor == null)
{
cctor = new MethodDefinition(".cctor",
MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
| MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
assembly.MainModule.ImportReference(typeof(void)));
entryType.Methods.Add(cctor);
ILProcessor il = cctor.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ret));
}
methods.Add(cctor);
}
else
{
methods.AddRange(entryType.Methods.Where(x => x.Name == entrypointMethod));
}
if (!methods.Any())
{
throw new Exception("The entrypoint method is invalid! Please check your config.ini");
}
foreach (var method in methods)
{
var il = method.Body.GetILProcessor();
Instruction ins = il.Body.Instructions.First();
il.InsertBefore(ins, il.Create(OpCodes.Ldstr, Paths.ExecutablePath)); //containerExePath
il.InsertBefore(ins, 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 containerExePath, bool startConsole = true)
il.InsertBefore(ins, il.Create(OpCodes.Call, startMethod));
}
}
}
/// <summary>
/// Allocates a console window for use by BepInEx safely.
/// </summary>
public static void AllocateConsole()
{
bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));
if (!console)
return;
try
{
ConsoleWindow.Attach();
var encoding = (uint) Encoding.UTF8.CodePage;
if (shiftjis)
encoding = 932;
ConsoleEncoding.ConsoleCodePage = encoding;
Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
}
catch (Exception ex)
{
Logger.Log(LogLevel.Error, "Failed to allocate console!");
Logger.Log(LogLevel.Error, ex);
}
}
/// <summary>
/// Adds the patcher to the patcher dictionary.
/// </summary>
/// <param name="dllNames">The list of DLL filenames to be patched.</param>
/// <param name="patcher">The method that will perform the patching.</param>
public static void AddPatcher(IEnumerable<string> dllNames, AssemblyPatcherDelegate patcher)
{
PatcherDictionary[patcher] = dllNames;
}
}
}
-29
View File
@@ -1,29 +0,0 @@
using System;
using BepInEx.Harmony;
using Harmony;
namespace BepInEx.Bootstrap
{
internal static class UnityPatches
{
public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepinex.unitypatches");
public static void Apply()
{
HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance);
}
#if UNITY_2018
/*
* DESC: Workaround for Trace class not working because of missing .config file
* AFFECTS: Unity 2018+
*/
[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
}
}
@@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil
// http://jonskeet.uk/csharp/ebcdic/
// using only safe (managed) code
// --------------------------------------------------
internal partial class ConsoleEncoding : Encoding
public partial class ConsoleEncoding : Encoding
{
private readonly uint _codePage;
public override int CodePage => (int) _codePage;
+1 -1
View File
@@ -10,7 +10,7 @@ using System.Text;
namespace UnityInjector.ConsoleUtil
{
internal class ConsoleWindow
public class ConsoleWindow
{
public static bool IsAttatched { get; private set; }
private static IntPtr _cOut;
+8 -8
View File
@@ -197,21 +197,21 @@ namespace BepInEx
#endregion
#region Debug
#region Build configuration
#if DEBUG
public class DebugInfoAttribute : Attribute
/// <summary>
/// This class is appended to AssemblyInfo.cs when BepInEx is built via a CI pipeline.
/// It is mainly intended to signify that the current build is not a release build and is special, like for instance a bleeding edge build.
/// </summary>
internal class BuildInfoAttribute : Attribute
{
public string Info { get; }
public DebugInfoAttribute(string info)
public BuildInfoAttribute(string info)
{
Info = info;
}
}
#endif
#endregion
}
}
+2 -2
View File
@@ -62,7 +62,7 @@ namespace BepInEx.Logging
if (IsRedirectingConsole)
Console.SetOut(this);
else
Console.SetOut(TextWriter.Null);
Console.SetOut(Null);
Trace.Listeners.Add(traceListener);
@@ -76,7 +76,7 @@ namespace BepInEx.Logging
{
if (!_enabled)
return;
Console.SetOut(stdout);
Trace.Listeners.Remove(traceListener);
+3 -5
View File
@@ -11,10 +11,8 @@ namespace BepInEx.Logging
/// Logs entries using Unity specific outputs.
/// </summary>
public class UnityLogWriter : BaseLogger
{
private delegate void WriteStringToUnityLogDelegate(string s);
private static readonly WriteStringToUnityLogDelegate WriteStringToUnityLog;
{
private static readonly Action<string> WriteStringToUnityLog;
[DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")]
private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer);
@@ -26,7 +24,7 @@ namespace BepInEx.Logging
if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero)
continue;
WriteStringToUnityLog = (WriteStringToUnityLogDelegate) Delegate.CreateDelegate(typeof(WriteStringToUnityLogDelegate), methodInfo);
WriteStringToUnityLog = (Action<string>) Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
break;
}
}
+2
View File
@@ -24,6 +24,8 @@ using BepInEx;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4ffba620-f5ed-47f9-b90c-dad1316fd9b9")]
[assembly: InternalsVisibleTo("BepInEx.Preloader")]
// Version information for an assembly consists of the following four values:
//
// Major Version
+1 -1
View File
@@ -2,4 +2,4 @@
# Specifies whether assembly executing is enabled
enabled=true
# Specifies the path (absolute, or relative to the game's exe) to the DLL/EXE that should be executed by Doorstop
targetAssembly=BepInEx\core\BepInEx.dll
targetAssembly=BepInEx\core\BepInEx.Preloader.dll