diff --git a/BepInEx.IL2CPP/DoorstopEntrypoint.cs b/BepInEx.IL2CPP/DoorstopEntrypoint.cs
index e05b1aa..18b2d93 100644
--- a/BepInEx.IL2CPP/DoorstopEntrypoint.cs
+++ b/BepInEx.IL2CPP/DoorstopEntrypoint.cs
@@ -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;
-
///
/// The main entrypoint of BepInEx, called from Doorstop.
///
- ///
- /// The arguments passed in from Doorstop. First argument is the path of the currently executing
- /// process.
- ///
- 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;
- }
- }
}
diff --git a/BepInEx.IL2CPP/UnityPreloadRunner.cs b/BepInEx.IL2CPP/UnityPreloadRunner.cs
new file mode 100644
index 0000000..aaf821b
--- /dev/null
+++ b/BepInEx.IL2CPP/UnityPreloadRunner.cs
@@ -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;
+ }
+}
diff --git a/BepInEx.Preloader.Unity/DoorstopEntrypoint.cs b/BepInEx.Preloader.Unity/DoorstopEntrypoint.cs
index 30cfe11..57b96e5 100644
--- a/BepInEx.Preloader.Unity/DoorstopEntrypoint.cs
+++ b/BepInEx.Preloader.Unity/DoorstopEntrypoint.cs
@@ -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;
-
///
/// The main entrypoint of BepInEx, called from Doorstop.
///
- 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;
- }
}
}
diff --git a/BepInEx.Preloader.Unity/UnityPreloaderRunner.cs b/BepInEx.Preloader.Unity/UnityPreloaderRunner.cs
new file mode 100644
index 0000000..ff5caa2
--- /dev/null
+++ b/BepInEx.Preloader.Unity/UnityPreloaderRunner.cs
@@ -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;
+ }
+}
diff --git a/build.cake b/build.cake
index 5e1e053..b26b9d1 100644
--- a/build.cake
+++ b/build.cake
@@ -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)
{
diff --git a/doorstop/doorstop_config_il2cpp.ini b/doorstop/doorstop_config_il2cpp.ini
index 2d92cca..ec3e4ce 100644
--- a/doorstop/doorstop_config_il2cpp.ini
+++ b/doorstop/doorstop_config_il2cpp.ini
@@ -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 \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
\ No newline at end of file
+# 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 \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=
diff --git a/doorstop/doorstop_config_mono.ini b/doorstop/doorstop_config_mono.ini
index 36d29a0..94390cd 100644
--- a/doorstop/doorstop_config_mono.ini
+++ b/doorstop/doorstop_config_mono.ini
@@ -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 \output_log.txt
-redirectOutputLog=false
\ No newline at end of file
+
+# 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 \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