[WIP] UnityDoorstop 4 support

This commit is contained in:
Dusk Banks authored and ghorsington committed 2022-08-14 17:18:57 +03:00
1 parent 643ab34d11
commit 43c883153d
7 files changed
+277 -240

No files matched your search

+7 -74
View File
@@ -1,66 +1,18 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using BepInEx.IL2CPP;
using BepInEx.Preloader.Core;
namespace BepInEx.IL2CPP;
namespace Doorstop;
internal static class UnityPreloaderRunner
internal static class Entrypoint
{
public static void PreloaderMain(string[] args)
{
var bepinPath =
Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
PlatformUtils.SetPlatform();
Paths.SetExecutablePath(EnvVars.DOORSTOP_PROCESS_PATH, bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR,
EnvVars.DOORSTOP_DLL_SEARCH_DIRS);
// Cecil 0.11 requires one to manually set up list of trusted assemblies for assembly resolving
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(Paths.ManagedPath);
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
AppDomain.CurrentDomain.AssemblyResolve -= DoorstopEntrypoint.ResolveCurrentDirectory;
Preloader.Run();
}
internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
if (foundAssembly != null)
return foundAssembly;
if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Preloader.IL2CPPUnhollowedPath, out foundAssembly))
return foundAssembly;
return null;
}
}
internal static class DoorstopEntrypoint
{
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)
public static void Start()
{
// We set it to the current directory first as a fallback, but try to use the same location as the .exe file.
var silentExceptionLog = $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
@@ -73,17 +25,12 @@ internal static class DoorstopEntrypoint
silentExceptionLog =
Path.Combine(Path.GetDirectoryName(EnvVars.DOORSTOP_PROCESS_PATH), silentExceptionLog);
// 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));
mutex = new Mutex(false,
Process.GetCurrentProcess().ProcessName + EnvVars.DOORSTOP_PROCESS_PATH +
typeof(DoorstopEntrypoint).FullName);
typeof(Doorstop.Entrypoint).FullName);
mutex.WaitOne();
AppDomain.CurrentDomain.AssemblyResolve += ResolveCurrentDirectory;
UnityPreloaderRunner.PreloaderMain(args);
UnityPreloaderRunner.PreloaderMain();
}
catch (Exception ex)
{
@@ -94,18 +41,4 @@ internal static class DoorstopEntrypoint
mutex?.ReleaseMutex();
}
}
public 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;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Preloader.Core;
namespace BepInEx.IL2CPP;
internal static class UnityPreloaderRunner
{
public static void PreloaderMain()
{
var bepinPath =
Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
PlatformUtils.SetPlatform();
Paths.SetExecutablePath(EnvVars.DOORSTOP_PROCESS_PATH, bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR,
EnvVars.DOORSTOP_DLL_SEARCH_DIRS);
// Cecil 0.11 requires one to manually set up list of trusted assemblies for assembly resolving
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(Paths.ManagedPath);
Preloader.IL2CPPUnhollowedPath = Path.Combine(Paths.BepInExRootPath, "unhollowed");
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
Preloader.Run();
}
internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
if (foundAssembly != null)
return foundAssembly;
if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Preloader.IL2CPPUnhollowedPath, out foundAssembly))
return foundAssembly;
return null;
}
}
+8 -133
View File
@@ -1,117 +1,16 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Preloader.Core;
using BepInEx.Preloader.RuntimeFixes;
using BepInEx.Preloader.Unity;
namespace BepInEx.Preloader.Unity;
namespace Doorstop;
internal static class UnityPreloaderRunner
internal static class Entrypoint
{
// This is a list of important assemblies in BepInEx core folder that should be force-loaded
// Some games can ship these assemblies in Managed folder, in which case assembly resolving bypasses our LocalResolve
// On the other hand, renaming these assemblies is not viable because 3rd party assemblies
// that we don't build (e.g. MonoMod, Harmony, many plugins) depend on them
// As such, we load them early so that the game uses our version instead
// These assemblies should be known to be rarely edited and are known to be shipped as-is with Unity assets
private static readonly string[] CriticalAssemblies =
{
"Mono.Cecil.dll",
"Mono.Cecil.Mdb.dll",
"Mono.Cecil.Pdb.dll",
"Mono.Cecil.Rocks.dll"
};
private static void LoadCriticalAssemblies()
{
foreach (var criticalAssembly in CriticalAssemblies)
try
{
Assembly.LoadFile(Path.Combine(Paths.BepInExAssemblyDirectory, criticalAssembly));
}
catch (Exception)
{
// Suppress error for now
// TODO: Should we crash here if load fails? Can't use logging at this point
}
}
public static void PreloaderPreMain()
{
PlatformUtils.SetPlatform();
var bepinPath = Utility.ParentDirectory(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH), 2);
Paths.SetExecutablePath(EnvVars.DOORSTOP_PROCESS_PATH, bepinPath,
EnvVars.DOORSTOP_MANAGED_FOLDER_DIR,
EnvVars.DOORSTOP_DLL_SEARCH_DIRS);
LoadCriticalAssemblies();
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
// Remove temporary resolver early so it won't override local resolver
AppDomain.CurrentDomain.AssemblyResolve -= DoorstopEntrypoint.ResolveCurrentDirectory;
PreloaderMain();
}
private static void PreloaderMain()
{
if (UnityPreloader.ConfigApplyRuntimePatches.Value)
{
XTermFix.Apply();
ConsoleSetOutFix.Apply();
}
UnityPreloader.Run();
}
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
if (!Utility.TryParseAssemblyName(args.Name, out var assemblyName))
return null;
// Use parse assembly name on managed side because native GetName() can fail on some locales
// if the game path has "exotic" characters
var validAssemblies = AppDomain.CurrentDomain
.GetAssemblies()
.Select(a => new
{
assembly = a,
name = Utility.TryParseAssemblyName(a.FullName, out var name)
? name
: null
})
.Where(a => a.name != null && a.name.Name == assemblyName.Name)
.OrderByDescending(a => a.name.Version)
.ToList();
// First try to match by version, then just pick the best match (generally highest)
// This should mainly affect cases where the game itself loads some assembly (like Mono.Cecil)
var foundMatch = validAssemblies.FirstOrDefault(a => a.name.Version == assemblyName.Version) ??
validAssemblies.FirstOrDefault();
var foundAssembly = foundMatch?.assembly;
if (foundAssembly != null)
return foundAssembly;
if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
return foundAssembly;
return null;
}
}
internal static class DoorstopEntrypoint
{
private static string preloaderPath;
/// <summary>
/// The main entrypoint of BepInEx, called from Doorstop.
/// </summary>
public static void Main()
public static void Start()
{
// We set it to the current directory first as a fallback, but try to use the same location as the .exe file.
var silentExceptionLog = $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
@@ -123,39 +22,15 @@ internal static class DoorstopEntrypoint
var gamePath = Path.GetDirectoryName(EnvVars.DOORSTOP_PROCESS_PATH) ?? ".";
silentExceptionLog = Path.Combine(gamePath, silentExceptionLog);
// 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(DoorstopEntrypoint).Assembly.GetType($"BepInEx.Preloader.Unity.{nameof(UnityPreloaderRunner)}")
?.GetMethod(nameof(UnityPreloaderRunner.PreloaderPreMain))
?.Invoke(null, null);
typeof(Doorstop.Entrypoint).Assembly.GetType($"BepInEx.Preloader.Unity.{nameof(UnityPreloaderRunner)}")
?.GetMethod(nameof(UnityPreloaderRunner.PreloaderPreMain))
?.Invoke(null, null);
}
catch (Exception ex)
{
File.WriteAllText(silentExceptionLog, ex.ToString());
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;
}
}
internal static Assembly ResolveCurrentDirectory(object sender, ResolveEventArgs args)
{
// Can't use Utils here because it's not yet resolved
var name = new AssemblyName(args.Name);
try
{
return Assembly.LoadFile(Path.Combine(preloaderPath, $"{name.Name}.dll"));
}
catch (Exception)
{
return null;
}
}
}
@@ -0,0 +1,103 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Preloader.Core;
using BepInEx.Preloader.RuntimeFixes;
namespace BepInEx.Preloader.Unity;
internal static class UnityPreloaderRunner
{
// This is a list of important assemblies in BepInEx core folder that should be force-loaded
// Some games can ship these assemblies in Managed folder, in which case assembly resolving bypasses our LocalResolve
// On the other hand, renaming these assemblies is not viable because 3rd party assemblies
// that we don't build (e.g. MonoMod, Harmony, many plugins) depend on them
// As such, we load them early so that the game uses our version instead
// These assemblies should be known to be rarely edited and are known to be shipped as-is with Unity assets
private static readonly string[] CriticalAssemblies =
{
"Mono.Cecil.dll",
"Mono.Cecil.Mdb.dll",
"Mono.Cecil.Pdb.dll",
"Mono.Cecil.Rocks.dll"
};
private static void LoadCriticalAssemblies()
{
foreach (var criticalAssembly in CriticalAssemblies)
try
{
Assembly.LoadFile(Path.Combine(Paths.BepInExAssemblyDirectory, criticalAssembly));
}
catch (Exception)
{
// Suppress error for now
// TODO: Should we crash here if load fails? Can't use logging at this point
}
}
public static void PreloaderPreMain()
{
PlatformUtils.SetPlatform();
var bepinPath = Utility.ParentDirectory(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH), 2);
Paths.SetExecutablePath(EnvVars.DOORSTOP_PROCESS_PATH, bepinPath,
EnvVars.DOORSTOP_MANAGED_FOLDER_DIR,
EnvVars.DOORSTOP_DLL_SEARCH_DIRS);
LoadCriticalAssemblies();
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
PreloaderMain();
}
private static void PreloaderMain()
{
if (UnityPreloader.ConfigApplyRuntimePatches.Value)
{
XTermFix.Apply();
ConsoleSetOutFix.Apply();
}
UnityPreloader.Run();
}
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
if (!Utility.TryParseAssemblyName(args.Name, out var assemblyName))
return null;
// Use parse assembly name on managed side because native GetName() can fail on some locales
// if the game path has "exotic" characters
var validAssemblies = AppDomain.CurrentDomain
.GetAssemblies()
.Select(a => new
{
assembly = a,
name = Utility.TryParseAssemblyName(a.FullName, out var name)
? name
: null
})
.Where(a => a.name != null && a.name.Name == assemblyName.Name)
.OrderByDescending(a => a.name.Version)
.ToList();
// First try to match by version, then just pick the best match (generally highest)
// This should mainly affect cases where the game itself loads some assembly (like Mono.Cecil)
var foundMatch = validAssemblies.FirstOrDefault(a => a.name.Version == assemblyName.Version) ??
validAssemblies.FirstOrDefault();
var foundAssembly = foundMatch?.assembly;
if (foundAssembly != null)
return foundAssembly;
if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
return foundAssembly;
return null;
}
}
+18 -10
View File
@@ -82,10 +82,11 @@ Task("Build")
}
});
const string DOORSTOP_VER_WIN = "3.4.1.0";
const string DOORSTOP_VER_WIN = "4.0.0-alpha.1";
const string DOORSTOP_VER_UNIX = "1.5.1.0";
const string MONO_VER = "2021.6.24";
const string DOORSTOP_DLL = "winhttp.dll";
const string DOORSTOP_DLL = "doorstop.dll";
const string DOORSTOP_PROXY_DLL = "winhttp.dll";
var depCachePath = Directory($"./bin/{DEP_CACHE_NAME}");
var doorstopPath = depCachePath + Directory("doorstop");
@@ -112,15 +113,15 @@ Task("DownloadDependencies")
if (NeedsRedownload("NeighTools/UnityDoorstop", DOORSTOP_VER_WIN))
{
Information("Updating Doorstop (Windows)");
var doorstopX64Path = doorstopPath + File("doorstop_x64.zip");
var doorstopX86Path = doorstopPath + File("doorstop_x86.zip");
Information("Updating Doorstop");
// var doorstopX64Path = doorstopPath + File("doorstop_x64.zip");
// var doorstopX86Path = doorstopPath + File("doorstop_x86.zip");
DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER_WIN}/Doorstop_x64_{DOORSTOP_VER_WIN}.zip", doorstopX64Path);
DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER_WIN}/Doorstop_x86_{DOORSTOP_VER_WIN}.zip", doorstopX86Path);
// DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER_WIN}/Doorstop_x64_{DOORSTOP_VER_WIN}.zip", doorstopX64Path);
// DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER_WIN}/Doorstop_x86_{DOORSTOP_VER_WIN}.zip", doorstopX86Path);
ZipUncompress(doorstopX86Path, doorstopPath + Directory("x86"));
ZipUncompress(doorstopX64Path, doorstopPath + Directory("x64"));
// ZipUncompress(doorstopX86Path, doorstopPath + Directory("x86"));
// ZipUncompress(doorstopX64Path, doorstopPath + Directory("x64"));
}
if (NeedsRedownload("NeighTools/UnityDoorstop.Unix", DOORSTOP_VER_UNIX))
@@ -222,7 +223,14 @@ Task("MakeDist")
if (doorstopArchPath != null)
{
CopyFile("./doorstop/" + doorstopConfigFile, Directory(distArchDir) + File(isUnix ? "run_bepinex.sh" : "doorstop_config.ini"));
CopyFiles(doorstopArchPath, doorstopDir);
if (isUnix)
{
CopyFiles(doorstopArchPath, doorstopDir);
}
else
{
CopyFile(doorstopArchPath, Directory(doorstopDir) + File(DOORSTOP_PROXY_DLL));
}
if (isUnix)
{
+52 -17
View File
@@ -1,18 +1,53 @@
[UnityDoorstop]
# 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.IL2CPP.dll
# Specifies whether Unity's output log should be redirected to <current folder>\output_log.txt
redirectOutputLog=false
# General options for Unity Doorstop
[General]
[MonoBackend]
runtimeLib=mono\MonoBleedingEdge\EmbedRuntime\mono-2.0-sgen.dll
configDir=mono\MonoBleedingEdge\etc
corlibDir=mono\Managed
# Specifies whether the mono soft debugger is enabled
debugEnabled=false
# Specifies whether the mono soft debugger should suspend the process and wait for the remote debugger
debugSuspend=false
# Specifies the listening address the soft debugger
debugAddress=127.0.0.1:10000
# Enable Doorstop?
enabled=true
# Path to the assembly to load and execute
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
target_assembly=BepInEx\core\BepInEx.IL2CPP.dll
# If true, Unity's output log is redirected to <current folder>\output_log.txt
redirect_output_log=false
# If enabled, DOORSTOP_DISABLE env var value is ignored
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignore_disable_switch=false
# Options specific to running under Unity Mono runtime
[UnityMono]
# Overrides default Mono DLL search path
# Sometimes it is needed to instruct Mono to seek its assemblies from a different path
# (e.g. mscorlib is stripped in original game)
# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed
# Original Managed folder is added as a secondary folder in the search path
dll_search_path_override=
# If true, Mono debugger server will be enabled
debug_enabled=false
# When debug_enabled is true, this option specifies whether Doorstop should initialize the debugger server
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to false
debug_start_server=true
# When debug_enabled is true, specifies the address to use for the debugger server
debug_address=127.0.0.1:10000
# If true and debug_enabled is true, Mono debugger server will suspend the game execution until a debugger is attached
debug_suspend=false
# Options sepcific to running under Il2Cpp runtime
[Il2Cpp]
# Path to coreclr.dll that contains the CoreCLR runtime
# [MonoBackend]
# runtimeLib=mono\MonoBleedingEdge\EmbedRuntime\mono-2.0-sgen.dll
coreclr_path=
# Path to the directory containing the managed core libraries for CoreCLR (mscorlib, System, etc.)
# [MonoBackend]
# corlibDir=mono\Managed
corlib_dir=
+38 -6
View File
@@ -1,7 +1,39 @@
[UnityDoorstop]
# Specifies whether assembly executing is enabled
# General options for Unity Doorstop
[General]
# Enable Doorstop?
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.Preloader.Unity.dll
# Specifies whether Unity's output log should be redirected to <current folder>\output_log.txt
redirectOutputLog=false
# Path to the assembly to load and execute
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
target_assembly=BepInEx\core\BepInEx.Preloader.Unity.dll
# If true, Unity's output log is redirected to <current folder>\output_log.txt
redirect_output_log=false
# If enabled, DOORSTOP_DISABLE env var value is ignored
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignore_disable_switch=false
# Options specific to running under Unity Mono runtime
[UnityMono]
# Overrides default Mono DLL search path
# Sometimes it is needed to instruct Mono to seek its assemblies from a different path
# (e.g. mscorlib is stripped in original game)
# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed
# Original Managed folder is added as a secondary folder in the search path
dll_search_path_override=
# If true, Mono debugger server will be enabled
debug_enabled=false
# When debug_enabled is true, this option specifies whether Doorstop should initialize the debugger server
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to false
debug_start_server=true
# When debug_enabled is true, specifies the address to use for the debugger server
debug_address=127.0.0.1:10000
# If true and debug_enabled is true, Mono debugger server will suspend the game execution until a debugger is attached
debug_suspend=false