From a23612f4fe59b309d6c8d65a7e7d3b1d9041c1b8 Mon Sep 17 00:00:00 2001
From: Kasuromi <29818914+Kasuromi@users.noreply.github.com>
Date: Thu, 16 May 2024 23:29:50 +0300
Subject: [PATCH 01/65] Bump Cpp2IL & Il2CppInterop (#869)
---
.../BepInEx.Unity.Common/BepInEx.Unity.Common.csproj | 2 +-
Runtimes/Unity/BepInEx.Unity.Common/UnityInfo.cs | 4 ++--
.../BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj | 12 ++++++------
.../BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs | 2 +-
.../EntrypointPatcher.cs | 4 ++--
5 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
index 1abdd26..77791ba 100644
--- a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/Runtimes/Unity/BepInEx.Unity.Common/UnityInfo.cs b/Runtimes/Unity/BepInEx.Unity.Common/UnityInfo.cs
index ae1ad3d..663a736 100644
--- a/Runtimes/Unity/BepInEx.Unity.Common/UnityInfo.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Common/UnityInfo.cs
@@ -1,9 +1,9 @@
-using System;
+using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
-using AssetRipper.VersionUtilities;
+using AssetRipper.Primitives;
using MonoMod.Utils;
[assembly: InternalsVisibleTo("BepInEx.Unity.Mono.Preloader")]
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
index 79a5ee4..d902167 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
@@ -1,4 +1,4 @@
-
+
BepInEx support library for Il2Cpp games
net6.0
@@ -14,15 +14,15 @@
-
-
-
+
+
+
-
+
-
+
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
index 063f951..cc43fac 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
@@ -316,7 +316,7 @@ internal static partial class Il2CppInteropManager
cpp2IlProcessingLayer.Process(Cpp2IlApi.CurrentAppContext);
}
- var assemblies = new AsmResolverDummyDllOutputFormat().BuildAssemblies(Cpp2IlApi.CurrentAppContext);
+ var assemblies = new AsmResolverDllOutputFormatDefault().BuildAssemblies(Cpp2IlApi.CurrentAppContext);
LibCpp2IlMain.Reset();
Cpp2IlApi.CurrentAppContext = null;
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/EntrypointPatcher.cs b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/EntrypointPatcher.cs
index bfb52f0..fb2f834 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/EntrypointPatcher.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/EntrypointPatcher.cs
@@ -31,9 +31,9 @@ internal class EntrypointPatcher : BasePatcher
"The name of the method in the specified entrypoint assembly and type to hook and load Chainloader from.");
private static string DefaultEntrypointAssembly =>
- UnityInfo.Version.IsLess(2017) ? "UnityEngine.dll" : "UnityEngine.CoreModule.dll";
+ UnityInfo.Version.LessThan(2017) ? "UnityEngine.dll" : "UnityEngine.CoreModule.dll";
- private static string DefaultEntrypointType => UnityInfo.Version.IsLess(5) ? "MonoBehaviour" : "Application";
+ private static string DefaultEntrypointType => UnityInfo.Version.LessThan(5) ? "MonoBehaviour" : "Application";
private bool HasLoaded { get; set; }
From 36d130f00db2022b52177a0ffeb25b79cd3d6c7a Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Thu, 16 May 2024 16:42:10 -0400
Subject: [PATCH 02/65] Upgrade doorstop to version 4.2.0 (#865)
---
README.md | 2 +-
Runtimes/Unity/Doorstop/doorstop_config_mono.ini | 3 ++-
Runtimes/Unity/Doorstop/run_bepinex_mono.sh | 3 ++-
build/Program.cs | 2 +-
4 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 89637a7..eff40f7 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ A more comprehensive comparison list of features and compatibility is available
## Used libraries
-- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.0.0
+- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.2.0
- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.1
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.5.1.1
- [jbevain/cecil](https://github.com/jbevain/cecil) - v0.10.4
diff --git a/Runtimes/Unity/Doorstop/doorstop_config_mono.ini b/Runtimes/Unity/Doorstop/doorstop_config_mono.ini
index 743f430..7926ef0 100644
--- a/Runtimes/Unity/Doorstop/doorstop_config_mono.ini
+++ b/Runtimes/Unity/Doorstop/doorstop_config_mono.ini
@@ -23,7 +23,8 @@ ignore_disable_switch = false
# (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 =
+# To specify multiple paths, separate them with semicolons (;)
+dll_search_path_override = "BepInEx\core"
# If true, Mono debugger server will be enabled
debug_enabled = false
diff --git a/Runtimes/Unity/Doorstop/run_bepinex_mono.sh b/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
index 0163209..ed5496a 100644
--- a/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
+++ b/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
@@ -36,7 +36,8 @@ ignore_disable_switch="0"
# (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=""
+# To specify multiple paths, separate them with colons (:)
+dll_search_path_override="BepInEx/core"
# If 1, Mono debugger server will be enabled
debug_enable="0"
diff --git a/build/Program.cs b/build/Program.cs
index 1189597..e3012a8 100644
--- a/build/Program.cs
+++ b/build/Program.cs
@@ -32,7 +32,7 @@ public class BuildContext : FrostingContext
BleedingEdge
}
- public const string DoorstopVersion = "4.0.0";
+ public const string DoorstopVersion = "4.2.0";
public const string DotnetRuntimeVersion = "6.0.7";
public const string DobbyVersion = "1.0.5";
From 3ba398f269961fe6721f918423df399d0b481d7e Mon Sep 17 00:00:00 2001
From: deca <62450933+decaprime@users.noreply.github.com>
Date: Tue, 21 May 2024 05:29:34 -0700
Subject: [PATCH 03/65] Upgrade AssetRipper.Primitives to avoid implying Json
8.0.1 (#873)
---
Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
index 77791ba..2ff6c0c 100644
--- a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
@@ -6,7 +6,7 @@
-
+
From 851521cc126e4f9d841d2d9bfe857558f0395939 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 11 Jun 2024 12:00:31 -0400
Subject: [PATCH 04/65] Upgrade Doorstop to 4.3.0 (#901)
---
README.md | 2 +-
Runtimes/Unity/Doorstop/doorstop_config_il2cpp.ini | 3 +++
Runtimes/Unity/Doorstop/doorstop_config_mono.ini | 3 +++
Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh | 7 +++++++
Runtimes/Unity/Doorstop/run_bepinex_mono.sh | 7 +++++++
build/Program.cs | 2 +-
6 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index eff40f7..8e6dbe9 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ A more comprehensive comparison list of features and compatibility is available
## Used libraries
-- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.2.0
+- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.3.0
- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.1
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.5.1.1
- [jbevain/cecil](https://github.com/jbevain/cecil) - v0.10.4
diff --git a/Runtimes/Unity/Doorstop/doorstop_config_il2cpp.ini b/Runtimes/Unity/Doorstop/doorstop_config_il2cpp.ini
index ed1f29a..8af1026 100644
--- a/Runtimes/Unity/Doorstop/doorstop_config_il2cpp.ini
+++ b/Runtimes/Unity/Doorstop/doorstop_config_il2cpp.ini
@@ -11,6 +11,9 @@ target_assembly = BepInEx\core\BepInEx.Unity.IL2CPP.dll
# If true, Unity's output log is redirected to \output_log.txt
redirect_output_log = false
+# Overrides the default boot.config file path
+boot_config_override =
+
# 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
diff --git a/Runtimes/Unity/Doorstop/doorstop_config_mono.ini b/Runtimes/Unity/Doorstop/doorstop_config_mono.ini
index 7926ef0..342d479 100644
--- a/Runtimes/Unity/Doorstop/doorstop_config_mono.ini
+++ b/Runtimes/Unity/Doorstop/doorstop_config_mono.ini
@@ -11,6 +11,9 @@ target_assembly = BepInEx\core\BepInEx.Unity.Mono.Preloader.dll
# If true, Unity's output log is redirected to \output_log.txt
redirect_output_log = false
+# Overrides the default boot.config file path
+boot_config_override =
+
# 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
diff --git a/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh b/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh
index d751dcc..e947ee4 100644
--- a/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh
+++ b/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh
@@ -25,6 +25,9 @@ enabled="1"
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
target_assembly="BepInEx/core/BepInEx.Unity.IL2CPP.dll"
+# Overrides the default boot.config file path
+boot_config_override=
+
# If enabled, DOORSTOP_DISABLE env var value is ignored
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignore_disable_switch="0"
@@ -218,6 +221,10 @@ while :; do
target_assembly="$2"
shift
;;
+ --doorstop-boot-config-override)
+ boot_config_override="$2"
+ shift
+ ;;
--doorstop-mono-dll-search-path-override)
dll_search_path_override="$2"
shift
diff --git a/Runtimes/Unity/Doorstop/run_bepinex_mono.sh b/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
index ed5496a..aaa7496 100644
--- a/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
+++ b/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
@@ -25,6 +25,9 @@ enabled="1"
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
target_assembly="BepInEx/core/BepInEx.Unity.Mono.Preloader.dll"
+# Overrides the default boot.config file path
+boot_config_override=
+
# If enabled, DOORSTOP_DISABLE env var value is ignored
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignore_disable_switch="0"
@@ -219,6 +222,10 @@ while :; do
target_assembly="$2"
shift
;;
+ --doorstop-boot-config-override)
+ boot_config_override="$2"
+ shift
+ ;;
--doorstop-mono-dll-search-path-override)
dll_search_path_override="$2"
shift
diff --git a/build/Program.cs b/build/Program.cs
index e3012a8..0ba5f0a 100644
--- a/build/Program.cs
+++ b/build/Program.cs
@@ -32,7 +32,7 @@ public class BuildContext : FrostingContext
BleedingEdge
}
- public const string DoorstopVersion = "4.2.0";
+ public const string DoorstopVersion = "4.3.0";
public const string DotnetRuntimeVersion = "6.0.7";
public const string DobbyVersion = "1.0.5";
From 53625800b86f6c68751445248260edf0b27a71c2 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Mon, 12 Aug 2024 15:54:02 -0400
Subject: [PATCH 05/65] Upgrade Harmonyx, Iced and AssetRipper.Primitives
packages (#953)
* Upgrade Harmonyx to 2.10.2
* Upgrade Iced to 1.21.0
* Upgrade AssetRipper.Primitives to 3.1.3
---
BepInEx.Core/BepInEx.Core.csproj | 2 +-
BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj | 2 +-
README.md | 2 +-
Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj | 2 +-
.../BepInEx.NET.Framework.Launcher.csproj | 2 +-
.../Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj | 2 +-
.../Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj | 4 ++--
.../BepInEx.Unity.Mono.Preloader.csproj | 2 +-
Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj | 2 +-
9 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/BepInEx.Core/BepInEx.Core.csproj b/BepInEx.Core/BepInEx.Core.csproj
index 4b208a9..30a9e20 100644
--- a/BepInEx.Core/BepInEx.Core.csproj
+++ b/BepInEx.Core/BepInEx.Core.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj b/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
index 06e3b9f..b75a844 100644
--- a/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
+++ b/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/README.md b/README.md
index 8e6dbe9..95a4de4 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ A more comprehensive comparison list of features and compatibility is available
## Used libraries
- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.3.0
-- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.1
+- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.2
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.5.1.1
- [jbevain/cecil](https://github.com/jbevain/cecil) - v0.10.4
diff --git a/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj b/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
index a93ecf2..6884e40 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
+++ b/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/Runtimes/NET/BepInEx.NET.Framework.Launcher/BepInEx.NET.Framework.Launcher.csproj b/Runtimes/NET/BepInEx.NET.Framework.Launcher/BepInEx.NET.Framework.Launcher.csproj
index 274dc40..90a37b8 100644
--- a/Runtimes/NET/BepInEx.NET.Framework.Launcher/BepInEx.NET.Framework.Launcher.csproj
+++ b/Runtimes/NET/BepInEx.NET.Framework.Launcher/BepInEx.NET.Framework.Launcher.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
index 2ff6c0c..78ebd3c 100644
--- a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
index d902167..32eb297 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
@@ -13,8 +13,8 @@
-
-
+
+
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/BepInEx.Unity.Mono.Preloader.csproj b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/BepInEx.Unity.Mono.Preloader.csproj
index 58887aa..7a33d1d 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/BepInEx.Unity.Mono.Preloader.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/BepInEx.Unity.Mono.Preloader.csproj
@@ -16,6 +16,6 @@
-
+
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
index 26086e1..bf4385f 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
@@ -16,7 +16,7 @@
-
+
From 952c74f6fef9ad5b5d7237c9e08d53c7d3148e6a Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Wed, 14 Aug 2024 19:31:22 -0400
Subject: [PATCH 06/65] Implement Patcher and plugin providers
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 238 ++++++++++--------
BepInEx.Core/Bootstrap/TypeLoader.cs | 97 +++++--
BepInEx.Core/Contract/Attributes.cs | 52 +++-
BepInEx.Core/Contract/BasePluginProvider.cs | 71 ++++++
BepInEx.Core/Contract/IPluginLoader.cs | 26 ++
BepInEx.Core/Contract/PluginInfo.cs | 30 +--
BepInEx.Core/Paths.cs | 12 +
.../BepInEx.PatcherProvider.csproj | 16 ++
.../BepInExPatcherLoader.cs | 19 ++
.../BepInExPatcherProvider.cs | 48 ++++
.../BepInEx.PluginProvider.csproj | 16 ++
BepInEx.PluginProvider/BepInExPluginLoader.cs | 19 ++
.../BepInExPluginProvider.cs | 47 ++++
.../Patching/AssemblyPatcher.cs | 231 ++++++++++++-----
BepInEx.Preloader.Core/Patching/Attributes.cs | 25 ++
.../Patching/BasePatcherProvider.cs | 55 ++++
.../Patching/PatcherPluginMetadata.cs | 5 +
BepInEx.sln | 20 ++
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 2 +-
.../BepInEx.NET.Common.csproj | 12 +
.../BepInEx.NET.CoreCLR/NetCorePreloader.cs | 2 +-
.../NetPreloader.cs | 2 +-
.../Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 2 +-
.../BepInEx.Unity.IL2CPP.csproj | 12 +
.../Unity/BepInEx.Unity.IL2CPP/Preloader.cs | 2 +-
.../UnityPreloader.cs | 2 +-
.../BepInEx.Unity.Mono/BaseUnityPlugin.cs | 3 +-
.../BepInEx.Unity.Mono.csproj | 12 +
build/Program.cs | 2 +
29 files changed, 864 insertions(+), 216 deletions(-)
create mode 100644 BepInEx.Core/Contract/BasePluginProvider.cs
create mode 100644 BepInEx.Core/Contract/IPluginLoader.cs
create mode 100644 BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
create mode 100644 BepInEx.PatcherProvider/BepInExPatcherLoader.cs
create mode 100644 BepInEx.PatcherProvider/BepInExPatcherProvider.cs
create mode 100644 BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
create mode 100644 BepInEx.PluginProvider/BepInExPluginLoader.cs
create mode 100644 BepInEx.PluginProvider/BepInExPluginProvider.cs
create mode 100644 BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index c6fe182..9489e93 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -25,14 +25,14 @@ public abstract class BaseChainloader
/// Type definition to analyze.
/// The filepath of the assembly, to keep as metadata.
/// If the type represent a valid plugin, returns a instance. Otherwise, return null.
- public static PluginInfo ToPluginInfo(TypeDefinition type, string assemblyLocation)
+ public static PluginInfo ToPluginInfo(TypeDefinition type, IPluginLoader loader, string assemblyLocation)
{
if (type.IsInterface || type.IsAbstract)
return null;
try
{
- if (!type.IsSubtypeOf(typeof(TPlugin)))
+ if (!type.IsSubtypeOf(typeof(T)))
return null;
}
catch (AssemblyResolutionException)
@@ -41,8 +41,13 @@ public abstract class BaseChainloader
return null;
}
- var metadata = BepInPlugin.FromCecilType(type);
+ if (type.IsSubtypeOf(typeof(BasePluginProvider)))
+ return GetPluginInfo(type, BepInPluginProvider.FromCecilType(type), loader, assemblyLocation);
+ return GetPluginInfo(type, BepInPlugin.FromCecilType(type), loader, assemblyLocation);
+ }
+ private static PluginInfo GetPluginInfo(TypeDefinition type, BepInPlugin metadata, IPluginLoader loader, string assemblyLocation)
+ {
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
{
@@ -85,15 +90,16 @@ public abstract class BaseChainloader
Incompatibilities = incompatibilities,
TypeName = type.FullName,
TargettedBepInExVersion = bepinVersion,
- Location = assemblyLocation
+ Location = assemblyLocation,
+ Loader = loader
};
}
- protected static bool HasBepinPlugins(AssemblyDefinition ass)
+ protected static bool HasBepinPluginType(AssemblyDefinition ass)
{
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName))
return false;
- if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BepInPlugin).FullName))
+ if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).FullName))
return false;
return true;
@@ -116,10 +122,20 @@ public abstract class BaseChainloader
private bool _initialized;
///
- /// List of all instances loaded via the chainloader.
+ /// List of all instances of all plugins loaded via the chainloader.
///
public Dictionary Plugins { get; } = new();
+ ///
+ /// List of all instances of all providers loaded via the chainloader.
+ ///
+ public Dictionary Providers { get; } = new();
+
+ ///
+ /// List of all discovered from all the providers
+ ///
+ public List Loaders { get; } = new();
+
///
/// Collection of error chainloader messages that occured during plugin loading.
/// Contains information about what certain plugins were not loaded.
@@ -194,38 +210,30 @@ public abstract class BaseChainloader
/// Path from which to search the plugins.
/// Cache name to use. If null, results are not cached.
/// List of discovered plugins and their metadata.
- protected IList DiscoverPluginsFrom(string path, string cacheName = "chainloader")
+ protected List DiscoverPluginProvidersFrom(string path, string cacheName = "chainloader_meta")
{
var pluginsToLoad =
- TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPlugins, cacheName);
+ TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPluginType, cacheName);
return pluginsToLoad.SelectMany(p => p.Value).ToList();
}
- ///
- /// Discovers plugins to load.
- ///
- /// List of plugins to be loaded.
- protected virtual IList DiscoverPlugins()
- {
- return DiscoverPluginsFrom(Paths.PluginPath);
- }
-
///
/// Preprocess the plugins and modify the load order.
///
/// Some plugins may be skipped if they cannot be loaded (wrong metadata, etc).
/// Plugins to process.
/// List of plugins to load in the correct load order.
- protected virtual IList ModifyLoadOrder(IList plugins)
+ protected virtual List ModifyLoadOrder(IList plugins, Dictionary existingPlugins)
+ where T : PluginInfo
{
// We use a sorted dictionary to ensure consistent load order
var dependencyDict =
new SortedDictionary>(StringComparer.InvariantCultureIgnoreCase);
- var pluginsByGuid = new Dictionary();
+ var pluginsByGuid = new Dictionary();
foreach (var pluginInfoGroup in plugins.GroupBy(info => info.Metadata.GUID))
{
- if (Plugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
+ if (existingPlugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
{
Logger.Log(LogLevel.Warning,
$"Skipping [{pluginInfoGroup.Key}] because a plugin with a similar GUID ([{loadedPlugin}]) has been already loaded.");
@@ -266,7 +274,7 @@ public abstract class BaseChainloader
foreach (var pluginInfo in pluginsByGuid.Values.ToList())
if (pluginInfo.Incompatibilities.Any(incompatibility =>
pluginsByGuid.ContainsKey(incompatibility.IncompatibilityGUID)
- || Plugins.ContainsKey(incompatibility.IncompatibilityGUID))
+ || existingPlugins.ContainsKey(incompatibility.IncompatibilityGUID))
)
{
pluginsByGuid.Remove(pluginInfo.Metadata.GUID);
@@ -275,7 +283,7 @@ public abstract class BaseChainloader
var incompatiblePluginsNew = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID)
.Where(x => pluginsByGuid.ContainsKey(x));
var incompatiblePluginsExisting = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID)
- .Where(x => Plugins.ContainsKey(x));
+ .Where(x => existingPlugins.ContainsKey(x));
var incompatiblePlugins = incompatiblePluginsNew.Concat(incompatiblePluginsExisting).ToArray();
var message =
$@"Could not load [{pluginInfo}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}";
@@ -306,15 +314,23 @@ public abstract class BaseChainloader
}
///
- /// Run the chainloader and load all plugins from the plugins folder.
+ /// Run the chainloader and load all plugins from all providers.
///
public virtual void Execute()
{
try
{
- var plugins = DiscoverPlugins();
+ var builtinProviders = DiscoverPluginProvidersFrom(Paths.BepInExAssemblyDirectory);
+ var providers = DiscoverPluginProvidersFrom(Paths.PluginProviderPath);
+ Logger.Log(LogLevel.Info, $"{providers.Count + builtinProviders.Count} " +
+ $"meta plugin{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
+
+ LoadPlugins(builtinProviders, provider: true);
+ LoadPlugins(providers, provider: true);
+
+ var plugins = TypeLoader.GetPluginsFromLoaders(Loaders, ToPluginInfo, HasBepinPluginType, "chainloader");
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
- LoadPlugins(plugins);
+ LoadPlugins(plugins, provider: false);
Finished?.Invoke();
}
catch (Exception ex)
@@ -331,71 +347,19 @@ public abstract class BaseChainloader
Logger.Log(LogLevel.Message, "Chainloader startup complete");
}
- private IList LoadPlugins(IList plugins)
+ private void LoadPlugins(List plugins, bool provider)
{
- var sortedPlugins = ModifyLoadOrder(plugins);
-
+ var existingPlugins = provider ? Providers : Plugins;
+ var sortedPlugins = ModifyLoadOrder(plugins, existingPlugins);
+
var invalidPlugins = new HashSet();
var processedPlugins = new Dictionary();
var loadedAssemblies = new Dictionary();
- var loadedPlugins = new List();
foreach (var plugin in sortedPlugins)
{
- var dependsOnInvalidPlugin = false;
- var missingDependencies = new List();
- foreach (var dependency in plugin.Dependencies)
+ if (!CheckDependencies(plugin, processedPlugins, invalidPlugins, existingPlugins))
{
- static bool IsHardDependency(BepInDependency dep) =>
- (dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0;
-
- // If the dependency wasn't already processed, it's missing altogether
- var dependencyExists =
- processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
- // Alternatively, if the dependency hasn't been loaded before, it's missing too
- if (!dependencyExists)
- {
- dependencyExists = Plugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
- pluginVersion = pluginInfo?.Metadata.Version;
- }
-
- if (!dependencyExists || dependency.VersionRange != null &&
- !dependency.VersionRange.IsSatisfied(pluginVersion))
- {
- // If the dependency is hard, collect it into a list to show
- if (IsHardDependency(dependency))
- missingDependencies.Add(dependency);
- continue;
- }
-
- // If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
- if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
- {
- dependsOnInvalidPlugin = true;
- break;
- }
- }
-
- processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
-
- if (dependsOnInvalidPlugin)
- {
- var message =
- $"Skipping [{plugin}] because it has a dependency that was not loaded. See previous errors for details.";
- DependencyErrors.Add(message);
- Logger.Log(LogLevel.Warning, message);
- continue;
- }
-
- if (missingDependencies.Count != 0)
- {
- var message = $@"Could not load [{plugin}] because it has missing dependencies: {
- string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
- }";
- DependencyErrors.Add(message);
- Logger.Log(LogLevel.Error, message);
-
- invalidPlugins.Add(plugin.Metadata.GUID);
continue;
}
@@ -403,45 +367,107 @@ public abstract class BaseChainloader
{
Logger.Log(LogLevel.Info, $"Loading [{plugin}]");
- if (!loadedAssemblies.TryGetValue(plugin.Location, out var ass))
- loadedAssemblies[plugin.Location] = ass = Assembly.LoadFrom(plugin.Location);
+ Assembly ass = null;
+ if (provider && !loadedAssemblies.TryGetValue(plugin.Location, out ass))
+ {
+ ass = Assembly.LoadFrom(plugin.Location);
+ loadedAssemblies[plugin.Location] = ass;
+ }
+ else if (!provider && !loadedAssemblies.TryGetValue(plugin.Loader.AssemblyIdentifier, out ass))
+ {
+ ass = Assembly.Load(plugin.Loader.GetAssemblyData());
+ loadedAssemblies[plugin.Loader.AssemblyIdentifier] = ass;
+ }
- Plugins[plugin.Metadata.GUID] = plugin;
+ existingPlugins[plugin.Metadata.GUID] = plugin;
TryRunModuleCtor(plugin, ass);
- plugin.Instance = LoadPlugin(plugin, ass);
- loadedPlugins.Add(plugin);
+ if (provider)
+ {
+ var type = ass.GetType(plugin.TypeName);
+ plugin.Instance = Activator.CreateInstance(type);
+ AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)plugin.Instance).ResolveAssembly(args.Name);
+ var requestedPluginsStream = ((BasePluginProvider)plugin.Instance).GetPlugins();
+ Loaders.AddRange(requestedPluginsStream);
+ }
+ else
+ {
+ plugin.Instance = LoadPlugin(plugin, ass);
+ }
PluginLoaded?.Invoke(plugin);
}
catch (Exception ex)
{
invalidPlugins.Add(plugin.Metadata.GUID);
- Plugins.Remove(plugin.Metadata.GUID);
+ existingPlugins.Remove(plugin.Metadata.GUID);
Logger.Log(LogLevel.Error,
$"Error loading [{plugin}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
}
}
-
- return loadedPlugins;
}
- ///
- /// Detects and loads all plugins in the specified directories.
- ///
- ///
- /// It is better to collect all paths at once and use a single call to LoadPlugins than multiple calls.
- /// This allows to run proper dependency resolving and to load all plugins in one go.
- ///
- /// Directories to search the plugins from.
- /// List of loaded plugin infos.
- public IList LoadPlugins(params string[] pluginsPaths)
+ private bool CheckDependencies(T plugin, Dictionary processedPlugins, HashSet invalidPlugins, Dictionary existingPlugins)
+ where T : PluginInfo
{
- // TODO: This is a temporary solution for 3rd party loaders. Instead, this should be done via metaplugins.
- var plugins = new List();
- foreach (var pluginsPath in pluginsPaths)
- plugins.AddRange(DiscoverPluginsFrom(pluginsPath));
- return LoadPlugins(plugins);
+ var dependsOnInvalidPlugin = false;
+ var missingDependencies = new List();
+ foreach (var dependency in plugin.Dependencies)
+ {
+ static bool IsHardDependency(BepInDependency dep) =>
+ (dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0;
+
+ // If the dependency wasn't already processed, it's missing altogether
+ var dependencyExists =
+ processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
+ // Alternatively, if the dependency hasn't been loaded before, it's missing too
+ if (!dependencyExists)
+ {
+ dependencyExists = existingPlugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
+ pluginVersion = pluginInfo?.Metadata.Version;
+ }
+
+ if (!dependencyExists || dependency.VersionRange != null &&
+ !dependency.VersionRange.IsSatisfied(pluginVersion))
+ {
+ // If the dependency is hard, collect it into a list to show
+ if (IsHardDependency(dependency))
+ missingDependencies.Add(dependency);
+ continue;
+ }
+
+ // If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
+ if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
+ {
+ dependsOnInvalidPlugin = true;
+ break;
+ }
+ }
+
+ processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
+
+ if (dependsOnInvalidPlugin)
+ {
+ var message =
+ $"Skipping [{plugin}] because it has a dependency that was not loaded. See previous errors for details.";
+ DependencyErrors.Add(message);
+ Logger.Log(LogLevel.Warning, message);
+ return false;
+ }
+
+ if (missingDependencies.Count != 0)
+ {
+ var message = $@"Could not load [{plugin}] because it has missing dependencies: {
+ string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
+ }";
+ DependencyErrors.Add(message);
+ Logger.Log(LogLevel.Error, message);
+
+ invalidPlugins.Add(plugin.Metadata.GUID);
+ return false;
+ }
+
+ return true;
}
private static void TryRunModuleCtor(PluginInfo plugin, Assembly assembly)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index 69a7775..111ac38 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -89,7 +89,9 @@ public static class TypeLoader
{
Paths.BepInExAssemblyDirectory,
Paths.PluginPath,
+ Paths.PluginProviderPath,
Paths.PatcherPluginPath,
+ Paths.PatcherProviderPath,
Paths.ManagedPath
}.Concat(SearchDirectories);
@@ -126,9 +128,8 @@ public static class TypeLoader
/// selector.
///
public static Dictionary> FindPluginTypes(string directory,
- Func typeSelector,
- Func assemblyFilter =
- null,
+ Func typeSelector,
+ Func assemblyFilter = null,
string cacheName = null)
where T : ICacheable, new()
{
@@ -153,19 +154,7 @@ public static class TypeLoader
continue;
}
- using var ass = AssemblyDefinition.ReadAssembly(dllMs, ReaderParameters);
- Logger.Log(LogLevel.Debug, $"Examining '{dll}'");
-
- if (!assemblyFilter?.Invoke(ass) ?? false)
- {
- result[dll] = new List();
- continue;
- }
-
- var matches = ass.MainModule.Types
- .Select(t => typeSelector(t, dll))
- .Where(t => t != null).ToList();
- result[dll] = matches;
+ result[dll] = ExamineStream(typeSelector, assemblyFilter, dllMs, null, dll);
}
catch (BadImageFormatException e)
{
@@ -183,6 +172,82 @@ public static class TypeLoader
return result;
}
+ ///
+ /// Looks up assemblies using the given loaders and locates all types that can be loaded and collects their metadata.
+ ///
+ /// The specific base type to search for.
+ /// The loaders to obtain the assemblies from.
+ /// A function to check if a type should be selected and to build the type metadata.
+ /// A filter function to quickly determine if the assembly can be loaded.
+ /// The name of the cache to get cached types from.
+ ///
+ /// A dictionary of all assemblies in the directory and the list of type metadatas of types that match the
+ /// selector.
+ ///
+ public static List GetPluginsFromLoaders(IList loaders,
+ Func typeSelector,
+ Func assemblyFilter = null,
+ string cacheName = null)
+ where T : ICacheable, new()
+ {
+ var result = new Dictionary>();
+ var hashes = new Dictionary();
+ Dictionary> cache = null;
+
+ if (cacheName != null)
+ cache = LoadAssemblyCache(cacheName);
+
+ foreach (IPluginLoader pluginLoader in loaders)
+ {
+ IList plugins;
+ if (cache != null && pluginLoader.AssemblyHash != null &&
+ cache.TryGetValue(pluginLoader.AssemblyIdentifier, out var cacheEntry) &&
+ pluginLoader.AssemblyHash == cacheEntry.Hash)
+ {
+ plugins = cacheEntry.CacheItems;
+ }
+ else
+ {
+ var assemblyData = pluginLoader.GetAssemblyData();
+ using var memory = new MemoryStream(assemblyData);
+ plugins = ExamineStream(typeSelector, assemblyFilter, memory, pluginLoader, null);
+ }
+
+ foreach (T pluginInfo in plugins)
+ {
+ if (!result.ContainsKey(pluginLoader.AssemblyIdentifier))
+ result[pluginLoader.AssemblyIdentifier] = new();
+ result[pluginLoader.AssemblyIdentifier].Add(pluginInfo);
+ }
+ }
+
+ if (cache != null)
+ SaveAssemblyCache(cacheName, result, hashes);
+
+ return result.SelectMany(x => x.Value).ToList();
+ }
+
+ private static List ExamineStream(Func typeSelector,
+ Func assemblyFilter,
+ MemoryStream dllMs,
+ IPluginLoader loader,
+ string location)
+ where T : ICacheable, new()
+ {
+ using var ass = AssemblyDefinition.ReadAssembly(dllMs, ReaderParameters);
+ Logger.Log(LogLevel.Debug, $"Examining '{ass.Name}'");
+
+ if (!assemblyFilter?.Invoke(ass) ?? false)
+ {
+ return new List();
+ }
+
+ var matches = ass.MainModule.Types
+ .Select(t => typeSelector(t, loader, location))
+ .Where(t => t != null).ToList();
+ return matches;
+ }
+
///
/// Loads an index of type metadatas from a cache.
///
diff --git a/BepInEx.Core/Contract/Attributes.cs b/BepInEx.Core/Contract/Attributes.cs
index 0de9e93..978c170 100644
--- a/BepInEx.Core/Contract/Attributes.cs
+++ b/BepInEx.Core/Contract/Attributes.cs
@@ -76,6 +76,32 @@ public class BepInPlugin : Attribute
}
}
+///
+/// This attribute denotes that a class is a plugin provider, and specifies the required metadata.
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class BepInPluginProvider : BepInPlugin
+{
+ /// The unique identifier of the plugin. Should not change between plugin versions.
+ /// The user friendly name of the plugin. Is able to be changed between versions.
+ /// The specific version of the plugin.
+ public BepInPluginProvider(string GUID, string Name, string Version) : base(GUID, Name, Version)
+ {
+ }
+
+ internal static BepInPluginProvider FromCecilType(TypeDefinition td)
+ {
+ var attr = MetadataHelper.GetCustomAttributes(td, false).FirstOrDefault();
+
+ if (attr == null)
+ return null;
+
+ return new BepInPluginProvider((string) attr.ConstructorArguments[0].Value,
+ (string) attr.ConstructorArguments[1].Value,
+ (string) attr.ConstructorArguments[2].Value);
+ }
+}
+
///
/// This attribute specifies any dependencies that this plugin has on other plugins.
///
@@ -264,7 +290,7 @@ public static class MetadataHelper
///
/// The plugin type.
/// The BepInPlugin metadata of the plugin type.
- public static BepInPlugin GetMetadata(Type pluginType)
+ public static BepInPlugin GetPluginMetadata(Type pluginType)
{
var attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false);
@@ -279,7 +305,29 @@ public static class MetadataHelper
///
/// The plugin instance.
/// The BepInPlugin metadata of the plugin instance.
- public static BepInPlugin GetMetadata(object plugin) => GetMetadata(plugin.GetType());
+ public static BepInPlugin GetPluginMetadata(object plugin) => GetPluginMetadata(plugin.GetType());
+
+ ///
+ /// Retrieves the BepInPluginProvider metadata from a plugin type.
+ ///
+ /// The plugin type.
+ /// The BepInPluginProvider metadata of the plugin type.
+ public static BepInPlugin GetPluginProviderMetadata(Type pluginType)
+ {
+ var attributes = pluginType.GetCustomAttributes(typeof(BepInPluginProvider), false);
+
+ if (attributes.Length == 0)
+ return null;
+
+ return (BepInPlugin) attributes[0];
+ }
+
+ ///
+ /// Retrieves the BepInPluginProvider metadata from a plugin instance.
+ ///
+ /// The plugin instance.
+ /// The BepInPluginProvider metadata of the plugin instance.
+ public static BepInPlugin GetPluginProviderMetadata(object plugin) => GetPluginProviderMetadata(plugin.GetType());
///
/// Gets the specified attributes of a type, if they exist.
diff --git a/BepInEx.Core/Contract/BasePluginProvider.cs b/BepInEx.Core/Contract/BasePluginProvider.cs
new file mode 100644
index 0000000..202bd3a
--- /dev/null
+++ b/BepInEx.Core/Contract/BasePluginProvider.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using BepInEx.Configuration;
+using BepInEx.Logging;
+
+namespace BepInEx;
+
+///
+/// The base plugin provider type that is used by the BepInEx plugin loader.
+///
+public abstract class BasePluginProvider
+{
+ ///
+ /// Create a new instance of a plugin provider and all of its tied in objects.
+ ///
+ /// BepInPluginProvider attribute is missing.
+ protected BasePluginProvider()
+ {
+ var metadata = MetadataHelper.GetPluginProviderMetadata(this);
+ if (metadata == null)
+ throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
+ " because it inherits from BasePluginProvider and the BepInPluginProvider attribute is missing.");
+
+ Info = new PluginInfo
+ {
+ Metadata = metadata,
+ Instance = this,
+ Dependencies = MetadataHelper.GetDependencies(GetType()),
+ Processes = MetadataHelper.GetAttributes(GetType()),
+ };
+
+ Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
+
+ Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
+ }
+
+ ///
+ /// Information about this plugin provider as it was loaded.
+ ///
+ public PluginInfo Info { get; }
+
+ ///
+ /// Logger instance tied to this plugin provider.
+ ///
+ protected ManualLogSource Logger { get; }
+
+ ///
+ /// Default config file tied to this plugin provider. The config file will not be created until
+ /// any settings are added and changed, or is called.
+ ///
+ public ConfigFile Config { get; }
+
+ ///
+ /// Obtains a list of assemblies containing plugins to load
+ ///
+ /// A list of loaders, one per assembly
+ public abstract IList GetPlugins();
+
+ ///
+ /// A custom assembly resolver that can be used by this provider to resolve assemblies
+ /// that have failed to resolve
+ ///
+ /// The assembly's name
+ /// The resolved assembly or null if the assembly couldn't be resolved
+ public virtual Assembly ResolveAssembly(string name)
+ {
+ return null;
+ }
+}
diff --git a/BepInEx.Core/Contract/IPluginLoader.cs b/BepInEx.Core/Contract/IPluginLoader.cs
new file mode 100644
index 0000000..f98c937
--- /dev/null
+++ b/BepInEx.Core/Contract/IPluginLoader.cs
@@ -0,0 +1,26 @@
+namespace BepInEx;
+
+///
+/// An interface that allows to dynamically load and track an assembly
+/// which can be obtained from a provided
+///
+public interface IPluginLoader
+{
+ ///
+ /// An identifier that uniquely identifies an assembly from a provider no matter its revision
+ ///
+ public string AssemblyIdentifier { get; }
+
+ ///
+ /// An optional hash that changes each time the assembly changes which can be tracked for cache
+ /// invalidation purposes. If this is null, no caching occurs for this assembly load
+ ///
+ public string AssemblyHash { get; }
+
+ ///
+ /// Obtains the assembly's raw data without loading it into the appdomain.
+ /// This may be called multiple times by the chainloader
+ ///
+ /// The assembly's raw data in bytes
+ public byte[] GetAssemblyData();
+}
diff --git a/BepInEx.Core/Contract/PluginInfo.cs b/BepInEx.Core/Contract/PluginInfo.cs
index 0aa7585..f4d33ca 100644
--- a/BepInEx.Core/Contract/PluginInfo.cs
+++ b/BepInEx.Core/Contract/PluginInfo.cs
@@ -16,6 +16,11 @@ public class PluginInfo : ICacheable
/// General metadata about a plugin.
///
public BepInPlugin Metadata { get; internal set; }
+
+ ///
+ /// The loader used to load this plugin, null if it is a provider
+ ///
+ public IPluginLoader Loader { get; internal set; }
///
/// Collection of attributes that describe what processes the plugin can run on.
@@ -33,24 +38,23 @@ public class PluginInfo : ICacheable
///
public IEnumerable Incompatibilities { get; internal set; }
- ///
- /// File path to the plugin DLL
- ///
- public string Location { get; internal set; }
-
+ public string TypeName { get; internal set; }
+
///
/// Instance of the plugin that represents this info. NULL if no plugin is instantiated from info (yet)
///
public object Instance { get; internal set; }
-
- public string TypeName { get; internal set; }
-
+
internal Version TargettedBepInExVersion { get; set; }
+
+ internal string Location { get; set; }
+
+ ///
+ public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
- void ICacheable.Save(BinaryWriter bw)
+ public virtual void Save(BinaryWriter bw)
{
bw.Write(TypeName);
- bw.Write(Location);
bw.Write(Metadata.GUID);
bw.Write(Metadata.Name);
@@ -74,10 +78,9 @@ public class PluginInfo : ICacheable
bw.Write(TargettedBepInExVersion.ToString(4));
}
- void ICacheable.Load(BinaryReader br)
+ public virtual void Load(BinaryReader br)
{
TypeName = br.ReadString();
- Location = br.ReadString();
Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString());
@@ -111,7 +114,4 @@ public class PluginInfo : ICacheable
TargettedBepInExVersion = new Version(br.ReadString());
}
-
- ///
- public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
}
diff --git a/BepInEx.Core/Paths.cs b/BepInEx.Core/Paths.cs
index 34d3e98..ca5a446 100644
--- a/BepInEx.Core/Paths.cs
+++ b/BepInEx.Core/Paths.cs
@@ -75,6 +75,16 @@ public static class Paths
///
public static string PatcherPluginPath { get; private set; }
+ ///
+ /// The path to the plugin provider folder which resides in the BepInEx folder.
+ ///
+ public static string PluginProviderPath { get; private set; }
+
+ ///
+ /// The path to the patcher plugin provider folder which resides in the BepInEx folder.
+ ///
+ public static string PatcherProviderPath { get; private set; }
+
///
/// The path to the plugin folder which resides in the BepInEx folder.
///
@@ -113,7 +123,9 @@ public static class Paths
BepInExRootPath = bepinRootPath ?? Path.Combine(GameRootPath, "BepInEx");
ConfigPath = Path.Combine(BepInExRootPath, "config");
BepInExConfigPath = Path.Combine(ConfigPath, "BepInEx.cfg");
+ PluginProviderPath = Path.Combine(BepInExRootPath, "plugins-providers");
PluginPath = Path.Combine(BepInExRootPath, "plugins");
+ PatcherProviderPath = Path.Combine(BepInExRootPath, "patchers-providers");
PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers");
BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core");
BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory,
diff --git a/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj b/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
new file mode 100644
index 0000000..a56204f
--- /dev/null
+++ b/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net35;netstandard2.0
+ true
+ BepInEx patcher provider module
+
+
+
+
+
+
+
+
+
+
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoader.cs b/BepInEx.PatcherProvider/BepInExPatcherLoader.cs
new file mode 100644
index 0000000..0221434
--- /dev/null
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoader.cs
@@ -0,0 +1,19 @@
+using System.IO;
+
+namespace BepInEx.PatcherProvider;
+
+public class BepInExPatcherLoader : IPluginLoader
+{
+ public string AssemblyIdentifier { get; internal set; }
+ public string AssemblyHash { get; internal set; }
+ private byte[] assemblyData;
+ public byte[] GetAssemblyData()
+ {
+ if (assemblyData == null)
+ {
+ assemblyData = File.ReadAllBytes(AssemblyIdentifier);
+ }
+
+ return assemblyData;
+ }
+}
diff --git a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
new file mode 100644
index 0000000..24ef3cc
--- /dev/null
+++ b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using BepInEx.Logging;
+using BepInEx.Preloader.Core.Patching;
+
+namespace BepInEx.PatcherProvider;
+
+[PatcherProviderPluginInfo("io.bepinex.bepinexpatcherprovider", "BepInExPatcherProvider", "1.0")]
+public class BepInExPatcherProvider : BasePatcherProvider
+{
+ private static readonly Dictionary AssemblyLocationsByFilename = new();
+
+ public override IList GetPatchers()
+ {
+ var loaders = new List();
+ foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PatcherPluginPath), "*.dll", SearchOption.AllDirectories))
+ {
+ try
+ {
+ AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
+ loaders.Add(new BepInExPatcherLoader
+ {
+ AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
+ AssemblyIdentifier = dll
+ });
+ }
+ catch (Exception e)
+ {
+ Log.Log(LogLevel.Error, e);
+ }
+ }
+
+ return loaders;
+ }
+
+ public override Assembly ResolveAssembly(string name)
+ {
+ if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
+ return null;
+
+ if (!Utility.TryResolveDllAssembly(new(name), location, out var ass))
+ return null;
+
+ return ass;
+ }
+}
diff --git a/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj b/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
new file mode 100644
index 0000000..ee9c085
--- /dev/null
+++ b/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net35;netstandard2.0
+ true
+ BepInEx plugin provider module
+
+
+
+
+
+
+
+
+
+
diff --git a/BepInEx.PluginProvider/BepInExPluginLoader.cs b/BepInEx.PluginProvider/BepInExPluginLoader.cs
new file mode 100644
index 0000000..a71f3d7
--- /dev/null
+++ b/BepInEx.PluginProvider/BepInExPluginLoader.cs
@@ -0,0 +1,19 @@
+using System.IO;
+
+namespace BepInEx.PluginProvider;
+
+public class BepInExPluginLoader : IPluginLoader
+{
+ public string AssemblyIdentifier { get; internal set; }
+ public string AssemblyHash { get; internal set; }
+ private byte[] assemblyData;
+ public byte[] GetAssemblyData()
+ {
+ if (assemblyData == null)
+ {
+ assemblyData = File.ReadAllBytes(AssemblyIdentifier);
+ }
+
+ return assemblyData;
+ }
+}
diff --git a/BepInEx.PluginProvider/BepInExPluginProvider.cs b/BepInEx.PluginProvider/BepInExPluginProvider.cs
new file mode 100644
index 0000000..569b304
--- /dev/null
+++ b/BepInEx.PluginProvider/BepInExPluginProvider.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using BepInEx.Logging;
+
+namespace BepInEx.PluginProvider;
+
+[BepInPluginProvider("io.bepinex.bepinexpluginprovider", "BepInExPluginProvider", "1.0")]
+public class BepInExPluginProvider : BasePluginProvider
+{
+ private static readonly Dictionary AssemblyLocationsByFilename = new();
+
+ public override IList GetPlugins()
+ {
+ var loaders = new List();
+ foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PluginPath), "*.dll", SearchOption.AllDirectories))
+ {
+ try
+ {
+ AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
+ loaders.Add(new BepInExPluginLoader
+ {
+ AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
+ AssemblyIdentifier = dll
+ });
+ }
+ catch (Exception e)
+ {
+ Logger.Log(LogLevel.Error, e);
+ }
+ }
+
+ return loaders;
+ }
+
+ public override Assembly ResolveAssembly(string name)
+ {
+ if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
+ return null;
+
+ if (!Utility.TryResolveDllAssembly(new(name), location, out var ass))
+ return null;
+
+ return ass;
+ }
+}
diff --git a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
index ad25d31..d24755b 100644
--- a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
@@ -11,6 +11,7 @@ using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
+using MonoMod.Utils;
namespace BepInEx.Preloader.Core.Patching;
@@ -24,6 +25,9 @@ public class AssemblyPatcher : IDisposable
private Func assemblyLoader;
+ private readonly List loaders = new();
+ private readonly List providers = new();
+
public AssemblyPatcher(Func assemblyLoader)
{
this.assemblyLoader = assemblyLoader;
@@ -61,16 +65,18 @@ public class AssemblyPatcher : IDisposable
// Clear to allow GC collection.
PatcherContext.PatcherPlugins.Clear();
+
+ AppDomain.CurrentDomain.AssemblyResolve -= PatcherProvidersAssemblyResolver;
}
- private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, string assemblyPath)
+ private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, IPluginLoader loader, string assemblyPath)
{
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
return null;
try
{
- if (!type.IsSubtypeOf(typeof(BasePatcher)))
+ if (!type.IsSubtypeOf(typeof(T)))
return null;
}
catch (AssemblyResolutionException)
@@ -79,8 +85,13 @@ public class AssemblyPatcher : IDisposable
return null;
}
- var metadata = PatcherPluginInfoAttribute.FromCecilType(type);
+ if (type.IsSubtypeOf(typeof(BasePatcherProvider)))
+ return GetPatcherInfo(type, loader, PatcherProviderPluginInfoAttribute.FromCecilType(type));
+ return GetPatcherInfo(type, loader, PatcherPluginInfoAttribute.FromCecilType(type));
+ }
+ private PatcherPluginMetadata GetPatcherInfo(TypeDefinition type, IPluginLoader loader, PatcherPluginInfoAttribute metadata)
+ {
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
{
@@ -109,20 +120,160 @@ public class AssemblyPatcher : IDisposable
return new PatcherPluginMetadata
{
- TypeName = type.FullName
+ TypeName = type.FullName,
+ Loader = loader
};
}
- private bool HasPatcherPlugins(AssemblyDefinition ass)
+ private bool HasPatcherType(AssemblyDefinition ass)
{
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName) &&
ass.Name.Name != CurrentAssemblyName)
return false;
- if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BasePatcher).FullName))
+ if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).FullName))
return false;
return true;
}
+
+ ///
+ /// Adds all patchers from all managed assemblies obtained from all patcher providers.
+ ///
+ public void AddPatchersFromProviders()
+ {
+ var allProviders = TypeLoader.FindPluginTypes(Paths.BepInExAssemblyDirectory, ToPatcherPlugin, HasPatcherType);
+ var foundProviders = TypeLoader.FindPluginTypes(Paths.PatcherProviderPath, ToPatcherPlugin, HasPatcherType);
+ allProviders.AddRange(foundProviders);
+ AppDomain.CurrentDomain.AssemblyResolve += PatcherProvidersAssemblyResolver;
+
+ foreach (var keyValuePair in allProviders)
+ {
+ var assemblyPath = keyValuePair.Key;
+ var patcherCollection = keyValuePair.Value;
+
+ if (patcherCollection.Count == 0)
+ continue;
+
+ var ass = Assembly.LoadFrom(assemblyPath);
+
+ foreach (var patcherPlugin in patcherCollection)
+ {
+ try
+ {
+ var type = ass.GetType(patcherPlugin.TypeName);
+
+ var instance = (BasePatcherProvider) Activator.CreateInstance(type);
+ providers.Add(instance);
+ loaders.AddRange(instance.GetPatchers());
+ }
+ catch (Exception e)
+ {
+ Logger.Log(LogLevel.Error,
+ $"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
+ }
+ }
+ }
+
+ var patchers = TypeLoader.GetPluginsFromLoaders(loaders, ToPatcherPlugin, HasPatcherType);
+
+ var sortedPatchers = new List();
+
+ // TODO: Add dependency ordering and process attribute filtering
+
+ foreach (var patcherPlugin in patchers)
+ {
+ var ass = Assembly.Load(patcherPlugin.Loader.GetAssemblyData());
+ var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
+ sortedPatchers.AddRange(patchDefinitions);
+
+ var assName = ass.GetName();
+ Logger.Log(LogLevel.Debug, $"Loaded patcher {patcherPlugin.TypeName} from [{assName.Name} {assName.Version}]");
+ }
+
+ PatcherContext.PatchDefinitions.AddRange(sortedPatchers);
+ Logger.Log(LogLevel.Info, $"Loaded {sortedPatchers.Count} patcher{(sortedPatchers.Count > 0 ? "s" : "")}");
+ }
+
+ private Assembly PatcherProvidersAssemblyResolver(object sender, ResolveEventArgs args)
+ {
+ foreach (BasePatcherProvider provider in providers)
+ {
+ var ass = provider.ResolveAssembly(args.Name);
+ if (ass != null)
+ return ass;
+ }
+
+ return null;
+ }
+
+ private List LoadPatcherPlugin(Assembly ass, PatcherPluginMetadata patcher)
+ {
+ var patchDefinitions = new List();
+ try
+ {
+ var type = ass.GetType(patcher.TypeName);
+
+ var instance = (BasePatcher) Activator.CreateInstance(type);
+ instance.Context = PatcherContext;
+
+ PatcherContext.PatcherPlugins.Add(instance);
+
+ var methods =
+ type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ foreach (var method in methods)
+ {
+ var targetAssemblies = MetadataHelper.GetAttributes(method);
+ var targetTypes = MetadataHelper.GetAttributes(method);
+
+ if (targetAssemblies.Length == 0 && targetTypes.Length == 0)
+ continue;
+
+ var parameters = method.GetParameters();
+
+ if (parameters.Length < 1 || parameters.Length > 2
+ // Next few lines ensure that the first parameter is AssemblyDefinition and does not have any
+ // target type attributes, and vice versa
+ || !(
+ parameters[0].ParameterType == typeof(AssemblyDefinition)
+ || parameters[0].ParameterType ==
+ typeof(AssemblyDefinition).MakeByRefType()
+ && targetTypes.Length == 0
+ || parameters[0].ParameterType == typeof(TypeDefinition)
+ && targetAssemblies.Length == 0
+ )
+ || parameters.Length == 2 &&
+ parameters[1].ParameterType != typeof(string)
+ || method.ReturnType != typeof(void) &&
+ method.ReturnType != typeof(bool)
+ )
+ {
+ Logger
+ .Log(LogLevel.Warning,
+ $"Skipping method [{method.FullDescription()}] as it is not a valid patcher method");
+ continue;
+ }
+
+ void AddDefinition(PatchDefinition definition)
+ {
+ Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
+ patchDefinitions.Add(definition);
+ }
+
+ foreach (var targetAssembly in targetAssemblies)
+ AddDefinition(new PatchDefinition(targetAssembly, instance, method));
+ foreach (var targetType in targetTypes)
+ AddDefinition(new PatchDefinition(targetType, instance, method));
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log(LogLevel.Error,
+ $"Failed to load patchers from type [{patcher.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
+ }
+
+ return patchDefinitions;
+ }
///
/// Adds all patchers from all managed assemblies specified in a directory.
@@ -135,7 +286,7 @@ public class AssemblyPatcher : IDisposable
var sortedPatchers = new List();
- var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherPlugins);
+ var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherType);
// TODO: Add dependency ordering and process attribute filtering
@@ -150,68 +301,10 @@ public class AssemblyPatcher : IDisposable
var ass = Assembly.LoadFrom(assemblyPath);
foreach (var patcherPlugin in patcherCollection)
- try
- {
- var type = ass.GetType(patcherPlugin.TypeName);
-
- var instance = (BasePatcher) Activator.CreateInstance(type);
- instance.Context = PatcherContext;
-
- PatcherContext.PatcherPlugins.Add(instance);
-
- var methods =
- type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
-
- foreach (var method in methods)
- {
- var targetAssemblies = MetadataHelper.GetAttributes(method);
- var targetTypes = MetadataHelper.GetAttributes(method);
-
- if (targetAssemblies.Length == 0 && targetTypes.Length == 0)
- continue;
-
- var parameters = method.GetParameters();
-
- if (parameters.Length < 1 || parameters.Length > 2
- // Next few lines ensure that the first parameter is AssemblyDefinition and does not have any
- // target type attributes, and vice versa
- || !(
- parameters[0].ParameterType == typeof(AssemblyDefinition)
- || parameters[0].ParameterType ==
- typeof(AssemblyDefinition).MakeByRefType()
- && targetTypes.Length == 0
- || parameters[0].ParameterType == typeof(TypeDefinition)
- && targetAssemblies.Length == 0
- )
- || parameters.Length == 2 &&
- parameters[1].ParameterType != typeof(string)
- || method.ReturnType != typeof(void) &&
- method.ReturnType != typeof(bool)
- )
- {
- Logger
- .Log(LogLevel.Warning,
- $"Skipping method [{method.FullDescription()}] as it is not a valid patcher method");
- continue;
- }
-
- void AddDefinition(PatchDefinition definition)
- {
- Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
- sortedPatchers.Add(definition);
- }
-
- foreach (var targetAssembly in targetAssemblies)
- AddDefinition(new PatchDefinition(targetAssembly, instance, method));
- foreach (var targetType in targetTypes)
- AddDefinition(new PatchDefinition(targetType, instance, method));
- }
- }
- catch (Exception e)
- {
- Logger.Log(LogLevel.Error,
- $"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
- }
+ {
+ var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
+ sortedPatchers.AddRange(patchDefinitions);
+ }
var assName = ass.GetName();
Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
diff --git a/BepInEx.Preloader.Core/Patching/Attributes.cs b/BepInEx.Preloader.Core/Patching/Attributes.cs
index 95b21c4..a6e5675 100644
--- a/BepInEx.Preloader.Core/Patching/Attributes.cs
+++ b/BepInEx.Preloader.Core/Patching/Attributes.cs
@@ -79,6 +79,31 @@ public class PatcherPluginInfoAttribute : Attribute
}
}
+///
+/// This attribute denotes that a class is a patcher plugin provider, and specifies the required metadata.
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class PatcherProviderPluginInfoAttribute : PatcherPluginInfoAttribute {
+ /// The unique identifier of the plugin. Should not change between plugin versions.
+ /// The user friendly name of the plugin. Is able to be changed between versions.
+ /// The specific version of the plugin.
+ public PatcherProviderPluginInfoAttribute(string GUID, string Name, string Version) : base(GUID, Name, Version)
+ {
+ }
+
+ internal static PatcherProviderPluginInfoAttribute FromCecilType(TypeDefinition td)
+ {
+ var attr = MetadataHelper.GetCustomAttributes(td, false).FirstOrDefault();
+
+ if (attr == null)
+ return null;
+
+ return new PatcherProviderPluginInfoAttribute((string) attr.ConstructorArguments[0].Value,
+ (string) attr.ConstructorArguments[1].Value,
+ (string) attr.ConstructorArguments[2].Value);
+ }
+}
+
///
/// Defines an assembly that a patch method will target.
///
diff --git a/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs b/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
new file mode 100644
index 0000000..c1ef817
--- /dev/null
+++ b/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Reflection;
+using BepInEx.Configuration;
+using BepInEx.Logging;
+
+namespace BepInEx.Preloader.Core.Patching;
+
+///
+/// A patcher provider that can discover and load assemblies containing patcher plugins
+///
+public abstract class BasePatcherProvider
+{
+ protected BasePatcherProvider()
+ {
+ Info = PatcherPluginInfoAttribute.FromType(GetType());
+
+ Log = Logger.CreateLogSource(Info.Name);
+
+ Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, Info.GUID + ".cfg"), false,
+ new BepInPlugin(Info.GUID, Info.Name, Info.Version.ToString()));
+ }
+
+ ///
+ /// A instance created for use by this patcher plugin provider.
+ ///
+ public ManualLogSource Log { get; }
+
+ ///
+ /// A configuration file binding created with the of this provider as the
+ /// filename.
+ ///
+ public ConfigFile Config { get; }
+
+ ///
+ /// Metadata associated with this patcher plugin provider.
+ ///
+ public PatcherPluginInfoAttribute Info { get; }
+
+ ///
+ /// Obtains a list of assemblies containing patchers to load
+ ///
+ /// A list of loaders, one per assembly
+ public abstract IList GetPatchers();
+
+ ///
+ /// A custom assembly resolver that can be used by this provider to resolve assemblies
+ /// that have failed to resolve
+ ///
+ /// The assembly's name
+ /// The resolved assembly or null if the assembly couldn't be resolved
+ public virtual Assembly ResolveAssembly(string name)
+ {
+ return null;
+ }
+}
diff --git a/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs b/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs
index 6994dea..9d8b963 100644
--- a/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs
+++ b/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs
@@ -13,6 +13,11 @@ internal class PatcherPluginMetadata : ICacheable
///
public string TypeName { get; set; } = string.Empty;
+ ///
+ /// The loader used to load this patcher, null if it is a provider
+ ///
+ public IPluginLoader Loader { get; set; }
+
///
public void Save(BinaryWriter bw) => bw.Write(TypeName);
diff --git a/BepInEx.sln b/BepInEx.sln
index 356b05d..e723eb1 100644
--- a/BepInEx.sln
+++ b/BepInEx.sln
@@ -51,6 +51,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Doorstop", "Doorstop", "{CE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "build\Build.csproj", "{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.PluginProvider", "BepInEx.PluginProvider\BepInEx.PluginProvider.csproj", "{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.PatcherProvider", "BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj", "{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems*{067bcc8c-fc25-4929-b4f4-c6a5a7496994}*SharedItemsImports = 13
@@ -147,6 +151,22 @@ Global
{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}.Release_Unity|Any CPU.ActiveCfg = Debug|Any CPU
{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}.Release_Unity|Any CPU.Build.0 = Debug|Any CPU
{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_NetFramework|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_NetFramework|Any CPU.Build.0 = Debug|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_Unity|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_Unity|Any CPU.Build.0 = Debug|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_NetFramework|Any CPU.ActiveCfg = Debug|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_NetFramework|Any CPU.Build.0 = Debug|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_Unity|Any CPU.ActiveCfg = Debug|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_Unity|Any CPU.Build.0 = Debug|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
index 5d40b10..dfc863b 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
@@ -8,7 +8,7 @@ namespace BepInEx.NET.Common
{
protected BasePlugin()
{
- var metadata = MetadataHelper.GetMetadata(this);
+ var metadata = MetadataHelper.GetPluginMetadata(this);
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
diff --git a/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj b/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
index 6884e40..168fc2b 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
+++ b/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
@@ -12,6 +12,18 @@
+
+ false
+ Content
+ PreserveNewest
+ Build;DocumentationProjectOutputGroup
+
+
+ false
+ Content
+ PreserveNewest
+ Build;DocumentationProjectOutputGroup
+
diff --git a/Runtimes/NET/BepInEx.NET.CoreCLR/NetCorePreloader.cs b/Runtimes/NET/BepInEx.NET.CoreCLR/NetCorePreloader.cs
index 90e7aa7..3448cdb 100644
--- a/Runtimes/NET/BepInEx.NET.CoreCLR/NetCorePreloader.cs
+++ b/Runtimes/NET/BepInEx.NET.CoreCLR/NetCorePreloader.cs
@@ -54,7 +54,7 @@ namespace BepInEx.NET.CoreCLR
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
{
- assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
+ assemblyPatcher.AddPatchersFromProviders();
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");
diff --git a/Runtimes/NET/BepInEx.NET.Framework.Launcher/NetPreloader.cs b/Runtimes/NET/BepInEx.NET.Framework.Launcher/NetPreloader.cs
index f7396bb..51fbe08 100644
--- a/Runtimes/NET/BepInEx.NET.Framework.Launcher/NetPreloader.cs
+++ b/Runtimes/NET/BepInEx.NET.Framework.Launcher/NetPreloader.cs
@@ -110,7 +110,7 @@ public static class NetPreloader
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
{
- assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
+ assemblyPatcher.AddPatchersFromProviders();
Log.Log(LogLevel.Info,
$"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index 3b51e33..3476bde 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -8,7 +8,7 @@ public abstract class BasePlugin
{
protected BasePlugin()
{
- var metadata = MetadataHelper.GetMetadata(this);
+ var metadata = MetadataHelper.GetPluginMetadata(this);
Log = Logger.CreateLogSource(metadata.Name);
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
index 32eb297..2698124 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
@@ -9,6 +9,18 @@
+
+ false
+ Content
+ PreserveNewest
+ Build;DocumentationProjectOutputGroup
+
+
+ false
+ Content
+ PreserveNewest
+ Build;DocumentationProjectOutputGroup
+
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Preloader.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Preloader.cs
index 690d4ef..0f4f52e 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Preloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Preloader.cs
@@ -67,7 +67,7 @@ public static class Preloader
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
{
- assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
+ assemblyPatcher.AddPatchersFromProviders();
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherContext.PatcherPlugins.Count == 1 ? "" : "s")} loaded");
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloader.cs b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloader.cs
index 6cc3b33..b6b2a42 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloader.cs
@@ -77,7 +77,7 @@ internal static class UnityPreloader
using (var assemblyPatcher = new AssemblyPatcher(MonoAssemblyHelper.LoadFromMemory))
{
assemblyPatcher.AddPatchersFromDirectory(Paths.BepInExAssemblyDirectory);
- assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
+ assemblyPatcher.AddPatchersFromProviders();
Log.Log(LogLevel.Info,
$"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherContext.PatcherPlugins.Count == 1 ? "" : "s")} loaded");
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
index 4043771..09e9c07 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
@@ -16,7 +16,7 @@ public abstract class BaseUnityPlugin : MonoBehaviour
/// BepInPlugin attribute is missing.
protected BaseUnityPlugin()
{
- var metadata = MetadataHelper.GetMetadata(this);
+ var metadata = MetadataHelper.GetPluginMetadata(this);
if (metadata == null)
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
@@ -27,7 +27,6 @@ public abstract class BaseUnityPlugin : MonoBehaviour
Instance = this,
Dependencies = MetadataHelper.GetDependencies(GetType()),
Processes = MetadataHelper.GetAttributes(GetType()),
- Location = GetType().Assembly.Location
};
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
index bf4385f..71af84f 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
@@ -11,6 +11,18 @@
+
+ false
+ Content
+ PreserveNewest
+ Build;DocumentationProjectOutputGroup
+
+
+ false
+ Content
+ PreserveNewest
+ Build;DocumentationProjectOutputGroup
+
diff --git a/build/Program.cs b/build/Program.cs
index 0ba5f0a..ab63c15 100644
--- a/build/Program.cs
+++ b/build/Program.cs
@@ -239,8 +239,10 @@ public sealed class MakeDistTask : FrostingTask
var bepInExCoreDir = bepInExDir.Combine("core");
ctx.CreateDirectory(bepInExDir);
ctx.CreateDirectory(bepInExCoreDir);
+ ctx.CreateDirectory(bepInExDir.Combine("plugins-providers"));
ctx.CreateDirectory(bepInExDir.Combine("plugins"));
ctx.CreateDirectory(bepInExDir.Combine("patchers"));
+ ctx.CreateDirectory(bepInExDir.Combine("patchers-providers"));
File.WriteAllText(targetDir.CombineWithFilePath("changelog.txt").FullPath, changelog);
From 4d47c3cd39d55adb8edf57410394b8e6ba475afa Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 20 Aug 2024 15:13:10 -0400
Subject: [PATCH 07/65] Address code review
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 45 +++++++++++++------
BepInEx.Core/Bootstrap/TypeLoader.cs | 36 +++++++--------
BepInEx.Core/Contract/Attributes.cs | 2 +-
BepInEx.Core/Contract/BasePluginProvider.cs | 4 +-
...IPluginLoader.cs => IPluginLoadContext.cs} | 4 +-
BepInEx.Core/Contract/PluginInfo.cs | 8 ++--
...Loader.cs => BepInExPatcherLoadContext.cs} | 2 +-
.../BepInExPatcherProvider.cs | 8 ++--
...nLoader.cs => BepInExPluginLoadContext.cs} | 2 +-
.../BepInExPluginProvider.cs | 8 ++--
.../Patching/AssemblyPatcher.cs | 37 +++++++++------
BepInEx.Preloader.Core/Patching/Attributes.cs | 2 +-
.../Patching/BasePatcherProvider.cs | 4 +-
.../Patching/PatcherPluginMetadata.cs | 4 +-
14 files changed, 97 insertions(+), 69 deletions(-)
rename BepInEx.Core/Contract/{IPluginLoader.cs => IPluginLoadContext.cs} (91%)
rename BepInEx.PatcherProvider/{BepInExPatcherLoader.cs => BepInExPatcherLoadContext.cs} (87%)
rename BepInEx.PluginProvider/{BepInExPluginLoader.cs => BepInExPluginLoadContext.cs} (87%)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index 9489e93..a025b21 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -23,9 +23,10 @@ public abstract class BaseChainloader
/// Analyzes the given type definition and attempts to convert it to a valid
///
/// Type definition to analyze.
+ /// The load context of the plugin
/// The filepath of the assembly, to keep as metadata.
/// If the type represent a valid plugin, returns a instance. Otherwise, return null.
- public static PluginInfo ToPluginInfo(TypeDefinition type, IPluginLoader loader, string assemblyLocation)
+ public static PluginInfo ToPluginInfo(TypeDefinition type, IPluginLoadContext loadContext, string assemblyLocation)
{
if (type.IsInterface || type.IsAbstract)
return null;
@@ -42,11 +43,11 @@ public abstract class BaseChainloader
}
if (type.IsSubtypeOf(typeof(BasePluginProvider)))
- return GetPluginInfo(type, BepInPluginProvider.FromCecilType(type), loader, assemblyLocation);
- return GetPluginInfo(type, BepInPlugin.FromCecilType(type), loader, assemblyLocation);
+ return GetPluginInfo(type, BepInPluginProvider.FromCecilType(type), loadContext, assemblyLocation);
+ return GetPluginInfo(type, BepInPlugin.FromCecilType(type), loadContext, assemblyLocation);
}
- private static PluginInfo GetPluginInfo(TypeDefinition type, BepInPlugin metadata, IPluginLoader loader, string assemblyLocation)
+ private static PluginInfo GetPluginInfo(TypeDefinition type, BepInPlugin metadata, IPluginLoadContext loadContext, string assemblyLocation)
{
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
@@ -91,7 +92,7 @@ public abstract class BaseChainloader
TypeName = type.FullName,
TargettedBepInExVersion = bepinVersion,
Location = assemblyLocation,
- Loader = loader
+ LoadContext = loadContext
};
}
@@ -123,18 +124,22 @@ public abstract class BaseChainloader
///
/// List of all instances of all plugins loaded via the chainloader.
+ /// If a plugin fails to load, it is removed. This list only contains the plugins that were actually
+ /// loaded and have their dependencies satisfied.
///
public Dictionary Plugins { get; } = new();
///
/// List of all instances of all providers loaded via the chainloader.
+ /// If a provider fails to load, it is removed. This list only contains the providers that were actually
+ /// loaded and have their dependencies satisfied.
///
public Dictionary Providers { get; } = new();
///
- /// List of all discovered from all the providers
+ /// List of all discovered from all the providers
///
- public List Loaders { get; } = new();
+ public List LoadContexts { get; } = new();
///
/// Collection of error chainloader messages that occured during plugin loading.
@@ -142,6 +147,16 @@ public abstract class BaseChainloader
///
public List DependencyErrors { get; } = new();
+ ///
+ /// Occurs after a plugin provider is loaded.
+ ///
+ public event Action ProviderLoaded;
+
+ ///
+ /// Occurs after all plugins providers are loaded.
+ ///
+ public event Action FinishedProviders;
+
///
/// Occurs after a plugin is loaded.
///
@@ -323,12 +338,13 @@ public abstract class BaseChainloader
var builtinProviders = DiscoverPluginProvidersFrom(Paths.BepInExAssemblyDirectory);
var providers = DiscoverPluginProvidersFrom(Paths.PluginProviderPath);
Logger.Log(LogLevel.Info, $"{providers.Count + builtinProviders.Count} " +
- $"meta plugin{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
+ $"plugins provider{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
LoadPlugins(builtinProviders, provider: true);
LoadPlugins(providers, provider: true);
+ FinishedProviders?.Invoke();
- var plugins = TypeLoader.GetPluginsFromLoaders(Loaders, ToPluginInfo, HasBepinPluginType, "chainloader");
+ var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts, ToPluginInfo, HasBepinPluginType, "chainloader");
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
LoadPlugins(plugins, provider: false);
Finished?.Invoke();
@@ -373,10 +389,10 @@ public abstract class BaseChainloader
ass = Assembly.LoadFrom(plugin.Location);
loadedAssemblies[plugin.Location] = ass;
}
- else if (!provider && !loadedAssemblies.TryGetValue(plugin.Loader.AssemblyIdentifier, out ass))
+ else if (!provider && !loadedAssemblies.TryGetValue(plugin.LoadContext.AssemblyIdentifier, out ass))
{
- ass = Assembly.Load(plugin.Loader.GetAssemblyData());
- loadedAssemblies[plugin.Loader.AssemblyIdentifier] = ass;
+ ass = Assembly.Load(plugin.LoadContext.GetAssemblyData());
+ loadedAssemblies[plugin.LoadContext.AssemblyIdentifier] = ass;
}
existingPlugins[plugin.Metadata.GUID] = plugin;
@@ -388,13 +404,14 @@ public abstract class BaseChainloader
plugin.Instance = Activator.CreateInstance(type);
AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)plugin.Instance).ResolveAssembly(args.Name);
var requestedPluginsStream = ((BasePluginProvider)plugin.Instance).GetPlugins();
- Loaders.AddRange(requestedPluginsStream);
+ LoadContexts.AddRange(requestedPluginsStream);
+ ProviderLoaded?.Invoke(plugin);
}
else
{
plugin.Instance = LoadPlugin(plugin, ass);
+ PluginLoaded?.Invoke(plugin);
}
- PluginLoaded?.Invoke(plugin);
}
catch (Exception ex)
{
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index 111ac38..4cecee8 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -128,7 +128,7 @@ public static class TypeLoader
/// selector.
///
public static Dictionary> FindPluginTypes(string directory,
- Func typeSelector,
+ Func typeSelector,
Func assemblyFilter = null,
string cacheName = null)
where T : ICacheable, new()
@@ -176,7 +176,7 @@ public static class TypeLoader
/// Looks up assemblies using the given loaders and locates all types that can be loaded and collects their metadata.
///
/// The specific base type to search for.
- /// The loaders to obtain the assemblies from.
+ /// The load contexts to obtain the assemblies from.
/// A function to check if a type should be selected and to build the type metadata.
/// A filter function to quickly determine if the assembly can be loaded.
/// The name of the cache to get cached types from.
@@ -184,10 +184,10 @@ public static class TypeLoader
/// A dictionary of all assemblies in the directory and the list of type metadatas of types that match the
/// selector.
///
- public static List GetPluginsFromLoaders(IList loaders,
- Func typeSelector,
- Func assemblyFilter = null,
- string cacheName = null)
+ public static List GetPluginsFromLoadContexts(IList loadContexts,
+ Func typeSelector,
+ Func assemblyFilter = null,
+ string cacheName = null)
where T : ICacheable, new()
{
var result = new Dictionary>();
@@ -197,27 +197,27 @@ public static class TypeLoader
if (cacheName != null)
cache = LoadAssemblyCache(cacheName);
- foreach (IPluginLoader pluginLoader in loaders)
+ foreach (IPluginLoadContext loadContext in loadContexts)
{
IList plugins;
- if (cache != null && pluginLoader.AssemblyHash != null &&
- cache.TryGetValue(pluginLoader.AssemblyIdentifier, out var cacheEntry) &&
- pluginLoader.AssemblyHash == cacheEntry.Hash)
+ if (cache != null && loadContext.AssemblyHash != null &&
+ cache.TryGetValue(loadContext.AssemblyIdentifier, out var cacheEntry) &&
+ loadContext.AssemblyHash == cacheEntry.Hash)
{
plugins = cacheEntry.CacheItems;
}
else
{
- var assemblyData = pluginLoader.GetAssemblyData();
+ var assemblyData = loadContext.GetAssemblyData();
using var memory = new MemoryStream(assemblyData);
- plugins = ExamineStream(typeSelector, assemblyFilter, memory, pluginLoader, null);
+ plugins = ExamineStream(typeSelector, assemblyFilter, memory, loadContext, null);
}
foreach (T pluginInfo in plugins)
{
- if (!result.ContainsKey(pluginLoader.AssemblyIdentifier))
- result[pluginLoader.AssemblyIdentifier] = new();
- result[pluginLoader.AssemblyIdentifier].Add(pluginInfo);
+ if (!result.ContainsKey(loadContext.AssemblyIdentifier))
+ result[loadContext.AssemblyIdentifier] = new();
+ result[loadContext.AssemblyIdentifier].Add(pluginInfo);
}
}
@@ -227,10 +227,10 @@ public static class TypeLoader
return result.SelectMany(x => x.Value).ToList();
}
- private static List ExamineStream(Func typeSelector,
+ private static List ExamineStream(Func typeSelector,
Func assemblyFilter,
MemoryStream dllMs,
- IPluginLoader loader,
+ IPluginLoadContext loadContext,
string location)
where T : ICacheable, new()
{
@@ -243,7 +243,7 @@ public static class TypeLoader
}
var matches = ass.MainModule.Types
- .Select(t => typeSelector(t, loader, location))
+ .Select(t => typeSelector(t, loadContext, location))
.Where(t => t != null).ToList();
return matches;
}
diff --git a/BepInEx.Core/Contract/Attributes.cs b/BepInEx.Core/Contract/Attributes.cs
index 978c170..f9d495c 100644
--- a/BepInEx.Core/Contract/Attributes.cs
+++ b/BepInEx.Core/Contract/Attributes.cs
@@ -89,7 +89,7 @@ public class BepInPluginProvider : BepInPlugin
{
}
- internal static BepInPluginProvider FromCecilType(TypeDefinition td)
+ internal new static BepInPluginProvider FromCecilType(TypeDefinition td)
{
var attr = MetadataHelper.GetCustomAttributes(td, false).FirstOrDefault();
diff --git a/BepInEx.Core/Contract/BasePluginProvider.cs b/BepInEx.Core/Contract/BasePluginProvider.cs
index 202bd3a..594044a 100644
--- a/BepInEx.Core/Contract/BasePluginProvider.cs
+++ b/BepInEx.Core/Contract/BasePluginProvider.cs
@@ -55,8 +55,8 @@ public abstract class BasePluginProvider
///
/// Obtains a list of assemblies containing plugins to load
///
- /// A list of loaders, one per assembly
- public abstract IList GetPlugins();
+ /// A list of load context, one per assembly
+ public abstract IList GetPlugins();
///
/// A custom assembly resolver that can be used by this provider to resolve assemblies
diff --git a/BepInEx.Core/Contract/IPluginLoader.cs b/BepInEx.Core/Contract/IPluginLoadContext.cs
similarity index 91%
rename from BepInEx.Core/Contract/IPluginLoader.cs
rename to BepInEx.Core/Contract/IPluginLoadContext.cs
index f98c937..c17fc41 100644
--- a/BepInEx.Core/Contract/IPluginLoader.cs
+++ b/BepInEx.Core/Contract/IPluginLoadContext.cs
@@ -2,9 +2,9 @@
///
/// An interface that allows to dynamically load and track an assembly
-/// which can be obtained from a provided
+/// which can be obtained from a provider
///
-public interface IPluginLoader
+public interface IPluginLoadContext
{
///
/// An identifier that uniquely identifies an assembly from a provider no matter its revision
diff --git a/BepInEx.Core/Contract/PluginInfo.cs b/BepInEx.Core/Contract/PluginInfo.cs
index f4d33ca..15e2729 100644
--- a/BepInEx.Core/Contract/PluginInfo.cs
+++ b/BepInEx.Core/Contract/PluginInfo.cs
@@ -18,9 +18,9 @@ public class PluginInfo : ICacheable
public BepInPlugin Metadata { get; internal set; }
///
- /// The loader used to load this plugin, null if it is a provider
+ /// The load context used to load this plugin, null if it is a provider
///
- public IPluginLoader Loader { get; internal set; }
+ public IPluginLoadContext LoadContext { get; internal set; }
///
/// Collection of attributes that describe what processes the plugin can run on.
@@ -52,7 +52,7 @@ public class PluginInfo : ICacheable
///
public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
- public virtual void Save(BinaryWriter bw)
+ void ICacheable.Save(BinaryWriter bw)
{
bw.Write(TypeName);
@@ -78,7 +78,7 @@ public class PluginInfo : ICacheable
bw.Write(TargettedBepInExVersion.ToString(4));
}
- public virtual void Load(BinaryReader br)
+ void ICacheable.Load(BinaryReader br)
{
TypeName = br.ReadString();
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoader.cs b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
similarity index 87%
rename from BepInEx.PatcherProvider/BepInExPatcherLoader.cs
rename to BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
index 0221434..590678c 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherLoader.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
@@ -2,7 +2,7 @@
namespace BepInEx.PatcherProvider;
-public class BepInExPatcherLoader : IPluginLoader
+public class BepInExPatcherLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
diff --git a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
index 24ef3cc..fb2ff8c 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
@@ -12,15 +12,15 @@ public class BepInExPatcherProvider : BasePatcherProvider
{
private static readonly Dictionary AssemblyLocationsByFilename = new();
- public override IList GetPatchers()
+ public override IList GetPatchers()
{
- var loaders = new List();
+ var loadContexts = new List();
foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PatcherPluginPath), "*.dll", SearchOption.AllDirectories))
{
try
{
AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
- loaders.Add(new BepInExPatcherLoader
+ loadContexts.Add(new BepInExPatcherLoadContext
{
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
AssemblyIdentifier = dll
@@ -32,7 +32,7 @@ public class BepInExPatcherProvider : BasePatcherProvider
}
}
- return loaders;
+ return loadContexts;
}
public override Assembly ResolveAssembly(string name)
diff --git a/BepInEx.PluginProvider/BepInExPluginLoader.cs b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
similarity index 87%
rename from BepInEx.PluginProvider/BepInExPluginLoader.cs
rename to BepInEx.PluginProvider/BepInExPluginLoadContext.cs
index a71f3d7..a39f649 100644
--- a/BepInEx.PluginProvider/BepInExPluginLoader.cs
+++ b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
@@ -2,7 +2,7 @@
namespace BepInEx.PluginProvider;
-public class BepInExPluginLoader : IPluginLoader
+public class BepInExPluginLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
diff --git a/BepInEx.PluginProvider/BepInExPluginProvider.cs b/BepInEx.PluginProvider/BepInExPluginProvider.cs
index 569b304..b2f151c 100644
--- a/BepInEx.PluginProvider/BepInExPluginProvider.cs
+++ b/BepInEx.PluginProvider/BepInExPluginProvider.cs
@@ -11,15 +11,15 @@ public class BepInExPluginProvider : BasePluginProvider
{
private static readonly Dictionary AssemblyLocationsByFilename = new();
- public override IList GetPlugins()
+ public override IList GetPlugins()
{
- var loaders = new List();
+ var loadContexts = new List();
foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PluginPath), "*.dll", SearchOption.AllDirectories))
{
try
{
AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
- loaders.Add(new BepInExPluginLoader
+ loadContexts.Add(new BepInExPluginLoadContext
{
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
AssemblyIdentifier = dll
@@ -31,7 +31,7 @@ public class BepInExPluginProvider : BasePluginProvider
}
}
- return loaders;
+ return loadContexts;
}
public override Assembly ResolveAssembly(string name)
diff --git a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
index d24755b..50cff04 100644
--- a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
@@ -25,7 +25,7 @@ public class AssemblyPatcher : IDisposable
private Func assemblyLoader;
- private readonly List loaders = new();
+ private readonly List loadContexts = new();
private readonly List providers = new();
public AssemblyPatcher(Func assemblyLoader)
@@ -69,7 +69,7 @@ public class AssemblyPatcher : IDisposable
AppDomain.CurrentDomain.AssemblyResolve -= PatcherProvidersAssemblyResolver;
}
- private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, IPluginLoader loader, string assemblyPath)
+ private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, IPluginLoadContext loadContext, string assemblyPath)
{
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
return null;
@@ -86,11 +86,11 @@ public class AssemblyPatcher : IDisposable
}
if (type.IsSubtypeOf(typeof(BasePatcherProvider)))
- return GetPatcherInfo(type, loader, PatcherProviderPluginInfoAttribute.FromCecilType(type));
- return GetPatcherInfo(type, loader, PatcherPluginInfoAttribute.FromCecilType(type));
+ return GetPatcherInfo(type, loadContext, PatcherProviderPluginInfoAttribute.FromCecilType(type));
+ return GetPatcherInfo(type, loadContext, PatcherPluginInfoAttribute.FromCecilType(type));
}
- private PatcherPluginMetadata GetPatcherInfo(TypeDefinition type, IPluginLoader loader, PatcherPluginInfoAttribute metadata)
+ private PatcherPluginMetadata GetPatcherInfo(TypeDefinition type, IPluginLoadContext loadContext, PatcherPluginInfoAttribute metadata)
{
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
@@ -121,7 +121,7 @@ public class AssemblyPatcher : IDisposable
return new PatcherPluginMetadata
{
TypeName = type.FullName,
- Loader = loader
+ LoadContext = loadContext
};
}
@@ -164,7 +164,7 @@ public class AssemblyPatcher : IDisposable
var instance = (BasePatcherProvider) Activator.CreateInstance(type);
providers.Add(instance);
- loaders.AddRange(instance.GetPatchers());
+ loadContexts.AddRange(instance.GetPatchers());
}
catch (Exception e)
{
@@ -174,7 +174,7 @@ public class AssemblyPatcher : IDisposable
}
}
- var patchers = TypeLoader.GetPluginsFromLoaders(loaders, ToPatcherPlugin, HasPatcherType);
+ var patchers = TypeLoader.GetPluginsFromLoadContexts(loadContexts, ToPatcherPlugin, HasPatcherType);
var sortedPatchers = new List();
@@ -182,7 +182,7 @@ public class AssemblyPatcher : IDisposable
foreach (var patcherPlugin in patchers)
{
- var ass = Assembly.Load(patcherPlugin.Loader.GetAssemblyData());
+ var ass = Assembly.Load(patcherPlugin.LoadContext.GetAssemblyData());
var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
sortedPatchers.AddRange(patchDefinitions);
@@ -196,11 +196,22 @@ public class AssemblyPatcher : IDisposable
private Assembly PatcherProvidersAssemblyResolver(object sender, ResolveEventArgs args)
{
- foreach (BasePatcherProvider provider in providers)
+ BasePatcherProvider currentProvider = null;
+ try
{
- var ass = provider.ResolveAssembly(args.Name);
- if (ass != null)
- return ass;
+ foreach (BasePatcherProvider provider in providers)
+ {
+ currentProvider = provider;
+ var ass = provider.ResolveAssembly(args.Name);
+ if (ass != null)
+ return ass;
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log(LogLevel.Error, $"The assembly resolver of the patcher provider [{currentProvider?.Info.GUID}] " +
+ $"threw an exception while resolving {args.Name}: {e}");
+ return null;
}
return null;
diff --git a/BepInEx.Preloader.Core/Patching/Attributes.cs b/BepInEx.Preloader.Core/Patching/Attributes.cs
index a6e5675..1b2ccab 100644
--- a/BepInEx.Preloader.Core/Patching/Attributes.cs
+++ b/BepInEx.Preloader.Core/Patching/Attributes.cs
@@ -91,7 +91,7 @@ public class PatcherProviderPluginInfoAttribute : PatcherPluginInfoAttribute {
{
}
- internal static PatcherProviderPluginInfoAttribute FromCecilType(TypeDefinition td)
+ internal new static PatcherProviderPluginInfoAttribute FromCecilType(TypeDefinition td)
{
var attr = MetadataHelper.GetCustomAttributes(td, false).FirstOrDefault();
diff --git a/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs b/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
index c1ef817..08c3503 100644
--- a/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
+++ b/BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
@@ -39,8 +39,8 @@ public abstract class BasePatcherProvider
///
/// Obtains a list of assemblies containing patchers to load
///
- /// A list of loaders, one per assembly
- public abstract IList GetPatchers();
+ /// A list of load context, one per assembly
+ public abstract IList GetPatchers();
///
/// A custom assembly resolver that can be used by this provider to resolve assemblies
diff --git a/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs b/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs
index 9d8b963..ceb88b7 100644
--- a/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs
+++ b/BepInEx.Preloader.Core/Patching/PatcherPluginMetadata.cs
@@ -14,9 +14,9 @@ internal class PatcherPluginMetadata : ICacheable
public string TypeName { get; set; } = string.Empty;
///
- /// The loader used to load this patcher, null if it is a provider
+ /// The load context used to load this patcher, null if it is a provider
///
- public IPluginLoader Loader { get; set; }
+ public IPluginLoadContext LoadContext { get; set; }
///
public void Save(BinaryWriter bw) => bw.Write(TypeName);
From d8dd404e52d4669f54b665cb525f9b7119713a9e Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Fri, 23 Aug 2024 15:40:22 -0400
Subject: [PATCH 08/65] Address code review
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 6 ++++--
BepInEx.Core/Contract/IPluginLoadContext.cs | 13 +++++++++++--
.../BepInExPatcherLoadContext.cs | 18 +++++++++++++++++-
.../BepInExPluginLoadContext.cs | 18 +++++++++++++++++-
.../Patching/AssemblyPatcher.cs | 1 +
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index a025b21..75328c7 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -403,20 +403,22 @@ public abstract class BaseChainloader
var type = ass.GetType(plugin.TypeName);
plugin.Instance = Activator.CreateInstance(type);
AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)plugin.Instance).ResolveAssembly(args.Name);
- var requestedPluginsStream = ((BasePluginProvider)plugin.Instance).GetPlugins();
- LoadContexts.AddRange(requestedPluginsStream);
+ var pluginLoadContexts = ((BasePluginProvider)plugin.Instance).GetPlugins();
+ LoadContexts.AddRange(pluginLoadContexts);
ProviderLoaded?.Invoke(plugin);
}
else
{
plugin.Instance = LoadPlugin(plugin, ass);
PluginLoaded?.Invoke(plugin);
+ plugin.LoadContext.Dispose();
}
}
catch (Exception ex)
{
invalidPlugins.Add(plugin.Metadata.GUID);
existingPlugins.Remove(plugin.Metadata.GUID);
+ plugin.LoadContext.Dispose();
Logger.Log(LogLevel.Error,
$"Error loading [{plugin}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
diff --git a/BepInEx.Core/Contract/IPluginLoadContext.cs b/BepInEx.Core/Contract/IPluginLoadContext.cs
index c17fc41..715f09c 100644
--- a/BepInEx.Core/Contract/IPluginLoadContext.cs
+++ b/BepInEx.Core/Contract/IPluginLoadContext.cs
@@ -1,10 +1,12 @@
-namespace BepInEx;
+using System;
+
+namespace BepInEx;
///
/// An interface that allows to dynamically load and track an assembly
/// which can be obtained from a provider
///
-public interface IPluginLoadContext
+public interface IPluginLoadContext : IDisposable
{
///
/// An identifier that uniquely identifies an assembly from a provider no matter its revision
@@ -23,4 +25,11 @@ public interface IPluginLoadContext
///
/// The assembly's raw data in bytes
public byte[] GetAssemblyData();
+
+ ///
+ /// Obtains a file's raw data using a relative path from the plugin assembly
+ ///
+ /// The relative path from the plugin assembly used to locate the file
+ /// The file's raw data if resolved successfully, null if the file couldn't be loaded
+ public byte[] GetFile(string relativePath);
}
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
index 590678c..9be40a5 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
namespace BepInEx.PatcherProvider;
@@ -16,4 +17,19 @@ public class BepInExPatcherLoadContext : IPluginLoadContext
return assemblyData;
}
+
+ public byte[] GetFile(string relativePath)
+ {
+ if (relativePath == null)
+ throw new ArgumentNullException(nameof(relativePath));
+
+ string assemblyFolder = Path.GetDirectoryName(AssemblyIdentifier);
+ string filePath = Path.Combine(assemblyFolder, relativePath);
+ return File.ReadAllBytes(filePath);
+ }
+
+ public void Dispose()
+ {
+ assemblyData = null;
+ }
}
diff --git a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
index a39f649..9a7cb38 100644
--- a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
+++ b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
namespace BepInEx.PluginProvider;
@@ -16,4 +17,19 @@ public class BepInExPluginLoadContext : IPluginLoadContext
return assemblyData;
}
+
+ public byte[] GetFile(string relativePath)
+ {
+ if (relativePath == null)
+ throw new ArgumentNullException(nameof(relativePath));
+
+ string assemblyFolder = Path.GetDirectoryName(AssemblyIdentifier);
+ string filePath = Path.Combine(assemblyFolder, relativePath);
+ return File.ReadAllBytes(filePath);
+ }
+
+ public void Dispose()
+ {
+ assemblyData = null;
+ }
}
diff --git a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
index 50cff04..a9822d9 100644
--- a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
@@ -188,6 +188,7 @@ public class AssemblyPatcher : IDisposable
var assName = ass.GetName();
Logger.Log(LogLevel.Debug, $"Loaded patcher {patcherPlugin.TypeName} from [{assName.Name} {assName.Version}]");
+ patcherPlugin.LoadContext.Dispose();
}
PatcherContext.PatchDefinitions.AddRange(sortedPatchers);
From b688bfa97898187939dfefc7eb071dfae5188e47 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Fri, 23 Aug 2024 23:06:54 -0400
Subject: [PATCH 09/65] Add a Source property to PluginInfo
Also abstract an interface among the base plugins classes to make this easier
---
BepInEx.Core/BepInEx.Core.csproj | 3 --
BepInEx.Core/Bootstrap/BaseChainloader.cs | 23 +++++++++--
BepInEx.Core/Bootstrap/TypeLoader.cs | 2 +-
BepInEx.Core/Contract/IPlugin.cs | 39 ++++++++-----------
BepInEx.Core/Contract/PluginInfo.cs | 6 +++
BepInEx.Core/Properties/AssemblyInfo.cs | 1 +
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 18 +++++++--
.../Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 15 +++++--
.../BepInEx.Unity.Mono/BaseUnityPlugin.cs | 15 +++----
9 files changed, 75 insertions(+), 47 deletions(-)
diff --git a/BepInEx.Core/BepInEx.Core.csproj b/BepInEx.Core/BepInEx.Core.csproj
index 30a9e20..c4f5cd0 100644
--- a/BepInEx.Core/BepInEx.Core.csproj
+++ b/BepInEx.Core/BepInEx.Core.csproj
@@ -12,9 +12,6 @@
-
-
-
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index 75328c7..dab5eb0 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -116,6 +116,16 @@ public abstract class BaseChainloader
return pluginTarget.Build > CurrentAssemblyVersion.Build;
}
+ ///
+ /// Obtains the corresponding to its GUID
+ ///
+ /// The GUID of the plugin to get the info from
+ /// The info of the plugin, null if the plugin wasn't loaded by the chainloader
+ public static PluginInfo GetPluginInfoFromGuid(string pluginGuid)
+ {
+ return Plugins.TryGetValue(pluginGuid, out var pluginInfo) ? pluginInfo : null;
+ }
+
#region Contract
protected virtual string ConsoleTitle => $"BepInEx {Paths.BepInExVersion} - {Paths.ProcessName}";
@@ -127,7 +137,7 @@ public abstract class BaseChainloader
/// If a plugin fails to load, it is removed. This list only contains the plugins that were actually
/// loaded and have their dependencies satisfied.
///
- public Dictionary Plugins { get; } = new();
+ public static Dictionary Plugins { get; } = new();
///
/// List of all instances of all providers loaded via the chainloader.
@@ -138,8 +148,9 @@ public abstract class BaseChainloader
///
/// List of all discovered from all the providers
+ /// with each context associated with its provider info
///
- public List LoadContexts { get; } = new();
+ public Dictionary LoadContexts { get; } = new();
///
/// Collection of error chainloader messages that occured during plugin loading.
@@ -344,7 +355,7 @@ public abstract class BaseChainloader
LoadPlugins(providers, provider: true);
FinishedProviders?.Invoke();
- var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts, ToPluginInfo, HasBepinPluginType, "chainloader");
+ var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts.Keys, ToPluginInfo, HasBepinPluginType, "chainloader");
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
LoadPlugins(plugins, provider: false);
Finished?.Invoke();
@@ -404,11 +415,15 @@ public abstract class BaseChainloader
plugin.Instance = Activator.CreateInstance(type);
AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)plugin.Instance).ResolveAssembly(args.Name);
var pluginLoadContexts = ((BasePluginProvider)plugin.Instance).GetPlugins();
- LoadContexts.AddRange(pluginLoadContexts);
+ foreach (IPluginLoadContext context in pluginLoadContexts)
+ {
+ LoadContexts[context] = plugin;
+ }
ProviderLoaded?.Invoke(plugin);
}
else
{
+ plugin.Source = LoadContexts[plugin.LoadContext];
plugin.Instance = LoadPlugin(plugin, ass);
PluginLoaded?.Invoke(plugin);
plugin.LoadContext.Dispose();
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index 4cecee8..b847c4e 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -184,7 +184,7 @@ public static class TypeLoader
/// A dictionary of all assemblies in the directory and the list of type metadatas of types that match the
/// selector.
///
- public static List GetPluginsFromLoadContexts(IList loadContexts,
+ public static List GetPluginsFromLoadContexts(IEnumerable loadContexts,
Func typeSelector,
Func assemblyFilter = null,
string cacheName = null)
diff --git a/BepInEx.Core/Contract/IPlugin.cs b/BepInEx.Core/Contract/IPlugin.cs
index 6043fb0..84cc6b5 100644
--- a/BepInEx.Core/Contract/IPlugin.cs
+++ b/BepInEx.Core/Contract/IPlugin.cs
@@ -1,28 +1,23 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using BepInEx.Configuration;
+using BepInEx.Configuration;
using BepInEx.Logging;
-namespace BepInEx.Contract
+namespace BepInEx.Contract;
+
+internal interface IPlugin
{
- public interface IPlugin
- {
- ///
- /// Information about this plugin as it was loaded.
- ///
- PluginInfo Info { get; }
+ ///
+ /// Information about this plugin as it was loaded.
+ ///
+ PluginInfo Info { get; }
- ///
- /// Logger instance tied to this plugin.
- ///
- ManualLogSource Logger { get; }
+ ///
+ /// Logger instance tied to this plugin.
+ ///
+ ManualLogSource Logger { get; }
- ///
- /// Default config file tied to this plugin. The config file will not be created until
- /// any settings are added and changed, or is called.
- ///
- ConfigFile Config { get; }
- }
+ ///
+ /// Default config file tied to this plugin. The config file will not be created until
+ /// any settings are added and changed, or is called.
+ ///
+ ConfigFile Config { get; }
}
diff --git a/BepInEx.Core/Contract/PluginInfo.cs b/BepInEx.Core/Contract/PluginInfo.cs
index 15e2729..26e5cae 100644
--- a/BepInEx.Core/Contract/PluginInfo.cs
+++ b/BepInEx.Core/Contract/PluginInfo.cs
@@ -17,6 +17,12 @@ public class PluginInfo : ICacheable
///
public BepInPlugin Metadata { get; internal set; }
+ ///
+ /// The of the plugin provider that loaded this plugin.
+ /// This is null if this plugin is a provider
+ ///
+ public PluginInfo Source { get; internal set; }
+
///
/// The load context used to load this plugin, null if it is a provider
///
diff --git a/BepInEx.Core/Properties/AssemblyInfo.cs b/BepInEx.Core/Properties/AssemblyInfo.cs
index 749dcc2..96d56c1 100644
--- a/BepInEx.Core/Properties/AssemblyInfo.cs
+++ b/BepInEx.Core/Properties/AssemblyInfo.cs
@@ -2,6 +2,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("BepInEx.Preloader.Core")]
[assembly: InternalsVisibleTo("BepInEx.Unity.Mono")]
+[assembly: InternalsVisibleTo("BepInEx.NET.Common")]
[assembly: InternalsVisibleTo("BepInEx.NET.Framework.Launcher")]
[assembly: InternalsVisibleTo("BepInEx.NET.CoreCLR")]
[assembly: InternalsVisibleTo("BepInEx.Unity.IL2CPP")]
diff --git a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
index dfc863b..f417838 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
@@ -1,10 +1,12 @@
+using BepInEx.Bootstrap;
using BepInEx.Configuration;
+using BepInEx.Contract;
using BepInEx.Logging;
using HarmonyLib;
namespace BepInEx.NET.Common
{
- public abstract class BasePlugin
+ public abstract class BasePlugin : IPlugin
{
protected BasePlugin()
{
@@ -12,13 +14,21 @@ namespace BepInEx.NET.Common
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
- Log = Logger.CreateLogSource(metadata.Name);
+ Info = BaseChainloader.GetPluginInfoFromGuid(metadata.GUID);
+ Info.Instance = this;
+
+ Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
+
+ ///
+ public PluginInfo Info { get; }
+
+ ///
+ public ManualLogSource Logger { get; }
- public ManualLogSource Log { get; }
-
+ ///
public ConfigFile Config { get; }
public Harmony HarmonyInstance { get; set; }
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index 3476bde..581a6b4 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -1,21 +1,28 @@
-using BepInEx.Configuration;
+using BepInEx.Bootstrap;
+using BepInEx.Configuration;
+using BepInEx.Contract;
using BepInEx.Logging;
using Il2CppInterop.Runtime.InteropTypes;
namespace BepInEx.Unity.IL2CPP;
-public abstract class BasePlugin
+public abstract class BasePlugin : IPlugin
{
protected BasePlugin()
{
var metadata = MetadataHelper.GetPluginMetadata(this);
- Log = Logger.CreateLogSource(metadata.Name);
+ Info = BaseChainloader.GetPluginInfoFromGuid(metadata.GUID);
+ Info.Instance = this;
+
+ Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
- public ManualLogSource Log { get; }
+ public PluginInfo Info { get; }
+
+ public ManualLogSource Logger { get; }
public ConfigFile Config { get; }
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
index 09e9c07..cdf73c2 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
@@ -1,5 +1,7 @@
using System;
+using BepInEx.Bootstrap;
using BepInEx.Configuration;
+using BepInEx.Contract;
using BepInEx.Logging;
using UnityEngine;
@@ -8,7 +10,7 @@ namespace BepInEx.Unity.Mono;
///
/// The base plugin type that is used by the BepInEx plugin loader.
///
-public abstract class BaseUnityPlugin : MonoBehaviour
+public abstract class BaseUnityPlugin : MonoBehaviour, IPlugin
{
///
/// Create a new instance of a plugin and all of its tied in objects.
@@ -21,13 +23,8 @@ public abstract class BaseUnityPlugin : MonoBehaviour
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
- Info = new PluginInfo
- {
- Metadata = metadata,
- Instance = this,
- Dependencies = MetadataHelper.GetDependencies(GetType()),
- Processes = MetadataHelper.GetAttributes(GetType()),
- };
+ Info = BaseChainloader.GetPluginInfoFromGuid(metadata.GUID);
+ Info.Instance = this;
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
@@ -42,7 +39,7 @@ public abstract class BaseUnityPlugin : MonoBehaviour
///
/// Logger instance tied to this plugin.
///
- protected ManualLogSource Logger { get; }
+ public ManualLogSource Logger { get; }
///
/// Default config file tied to this plugin. The config file will not be created until
From bfc539bd74699d21eaea84140671ef86a92a0618 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 25 Aug 2024 12:03:33 -0400
Subject: [PATCH 10/65] Address code review round 2
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 76 ++++++++++---------
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 12 ++-
.../NET/BepInEx.NET.Common/NetChainloader.cs | 3 -
.../Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 14 +++-
.../BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs | 2 -
.../BepInEx.Unity.Mono/BaseUnityPlugin.cs | 11 ++-
.../Bootstrap/UnityChainloader.cs | 9 +--
7 files changed, 75 insertions(+), 52 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index dab5eb0..15509b7 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -120,10 +120,12 @@ public abstract class BaseChainloader
/// Obtains the corresponding to its GUID
///
/// The GUID of the plugin to get the info from
- /// The info of the plugin, null if the plugin wasn't loaded by the chainloader
- public static PluginInfo GetPluginInfoFromGuid(string pluginGuid)
+ /// When this method returns, contains the
+ /// associated with the specified GUID, if the plugin was loaded; otherwise, null. This parameter is passed uninitialized.
+ /// true if the plugin with the GUID was loaded by the chainloader; otherwise, false.
+ public bool TryGetPluginInfoFromGuid(string pluginGuid, out PluginInfo foundPluginInfo)
{
- return Plugins.TryGetValue(pluginGuid, out var pluginInfo) ? pluginInfo : null;
+ return Plugins.TryGetValue(pluginGuid, out foundPluginInfo);
}
#region Contract
@@ -132,12 +134,17 @@ public abstract class BaseChainloader
private bool _initialized;
+ ///
+ /// The current chainloader instance
+ ///
+ public static BaseChainloader Instance { get; protected set; }
+
///
/// List of all instances of all plugins loaded via the chainloader.
/// If a plugin fails to load, it is removed. This list only contains the plugins that were actually
/// loaded and have their dependencies satisfied.
///
- public static Dictionary Plugins { get; } = new();
+ public Dictionary Plugins { get; } = new();
///
/// List of all instances of all providers loaded via the chainloader.
@@ -147,8 +154,9 @@ public abstract class BaseChainloader
public Dictionary Providers { get; } = new();
///
- /// List of all discovered from all the providers
- /// with each context associated with its provider info
+ /// List of all objects received from plugin providers.
+ /// Value holds info of the originating provider. These contexts are used by chainloader to
+ /// load plugins. Plugins that are loaded successfully are added to .
///
public Dictionary LoadContexts { get; } = new();
@@ -349,7 +357,7 @@ public abstract class BaseChainloader
var builtinProviders = DiscoverPluginProvidersFrom(Paths.BepInExAssemblyDirectory);
var providers = DiscoverPluginProvidersFrom(Paths.PluginProviderPath);
Logger.Log(LogLevel.Info, $"{providers.Count + builtinProviders.Count} " +
- $"plugins provider{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
+ $"plugin provider{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
LoadPlugins(builtinProviders, provider: true);
LoadPlugins(providers, provider: true);
@@ -376,67 +384,67 @@ public abstract class BaseChainloader
private void LoadPlugins(List plugins, bool provider)
{
- var existingPlugins = provider ? Providers : Plugins;
- var sortedPlugins = ModifyLoadOrder(plugins, existingPlugins);
+ var existingPluginInfos = provider ? Providers : Plugins;
+ var sortedPluginInfos = ModifyLoadOrder(plugins, existingPluginInfos);
var invalidPlugins = new HashSet();
var processedPlugins = new Dictionary();
var loadedAssemblies = new Dictionary();
- foreach (var plugin in sortedPlugins)
+ foreach (var pluginInfo in sortedPluginInfos)
{
- if (!CheckDependencies(plugin, processedPlugins, invalidPlugins, existingPlugins))
+ if (!CheckDependencies(pluginInfo, processedPlugins, invalidPlugins, existingPluginInfos))
{
continue;
}
try
{
- Logger.Log(LogLevel.Info, $"Loading [{plugin}]");
+ Logger.Log(LogLevel.Info, $"Loading [{pluginInfo}]");
Assembly ass = null;
- if (provider && !loadedAssemblies.TryGetValue(plugin.Location, out ass))
+ if (provider && !loadedAssemblies.TryGetValue(pluginInfo.Location, out ass))
{
- ass = Assembly.LoadFrom(plugin.Location);
- loadedAssemblies[plugin.Location] = ass;
+ ass = Assembly.LoadFrom(pluginInfo.Location);
+ loadedAssemblies[pluginInfo.Location] = ass;
}
- else if (!provider && !loadedAssemblies.TryGetValue(plugin.LoadContext.AssemblyIdentifier, out ass))
+ else if (!provider && !loadedAssemblies.TryGetValue(pluginInfo.LoadContext.AssemblyIdentifier, out ass))
{
- ass = Assembly.Load(plugin.LoadContext.GetAssemblyData());
- loadedAssemblies[plugin.LoadContext.AssemblyIdentifier] = ass;
+ ass = Assembly.Load(pluginInfo.LoadContext.GetAssemblyData());
+ loadedAssemblies[pluginInfo.LoadContext.AssemblyIdentifier] = ass;
}
- existingPlugins[plugin.Metadata.GUID] = plugin;
- TryRunModuleCtor(plugin, ass);
+ existingPluginInfos[pluginInfo.Metadata.GUID] = pluginInfo;
+ TryRunModuleCtor(pluginInfo, ass);
if (provider)
{
- var type = ass.GetType(plugin.TypeName);
- plugin.Instance = Activator.CreateInstance(type);
- AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)plugin.Instance).ResolveAssembly(args.Name);
- var pluginLoadContexts = ((BasePluginProvider)plugin.Instance).GetPlugins();
+ var type = ass.GetType(pluginInfo.TypeName);
+ pluginInfo.Instance = Activator.CreateInstance(type);
+ AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)pluginInfo.Instance).ResolveAssembly(args.Name);
+ var pluginLoadContexts = ((BasePluginProvider)pluginInfo.Instance).GetPlugins();
foreach (IPluginLoadContext context in pluginLoadContexts)
{
- LoadContexts[context] = plugin;
+ LoadContexts[context] = pluginInfo;
}
- ProviderLoaded?.Invoke(plugin);
+ ProviderLoaded?.Invoke(pluginInfo);
}
else
{
- plugin.Source = LoadContexts[plugin.LoadContext];
- plugin.Instance = LoadPlugin(plugin, ass);
- PluginLoaded?.Invoke(plugin);
- plugin.LoadContext.Dispose();
+ pluginInfo.Source = LoadContexts[pluginInfo.LoadContext];
+ pluginInfo.Instance = LoadPlugin(pluginInfo, ass);
+ PluginLoaded?.Invoke(pluginInfo);
+ pluginInfo.LoadContext.Dispose();
}
}
catch (Exception ex)
{
- invalidPlugins.Add(plugin.Metadata.GUID);
- existingPlugins.Remove(plugin.Metadata.GUID);
- plugin.LoadContext.Dispose();
+ invalidPlugins.Add(pluginInfo.Metadata.GUID);
+ existingPluginInfos.Remove(pluginInfo.Metadata.GUID);
+ pluginInfo.LoadContext.Dispose();
Logger.Log(LogLevel.Error,
- $"Error loading [{plugin}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
+ $"Error loading [{pluginInfo}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
}
}
}
diff --git a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
index f417838..810dd19 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
@@ -1,3 +1,4 @@
+using System;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Contract;
@@ -14,8 +15,15 @@ namespace BepInEx.NET.Common
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
- Info = BaseChainloader.GetPluginInfoFromGuid(metadata.GUID);
- Info.Instance = this;
+ if (BaseChainloader.Instance.TryGetPluginInfoFromGuid(metadata.GUID, out var pluginInfo))
+ {
+ Info = pluginInfo;
+ Info.Instance = this;
+ }
+ else
+ {
+ throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
+ }
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
diff --git a/Runtimes/NET/BepInEx.NET.Common/NetChainloader.cs b/Runtimes/NET/BepInEx.NET.Common/NetChainloader.cs
index 5e4af66..9f9717c 100644
--- a/Runtimes/NET/BepInEx.NET.Common/NetChainloader.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/NetChainloader.cs
@@ -7,9 +7,6 @@ namespace BepInEx.NET.Common
{
public class NetChainloader : BaseChainloader
{
- // TODO: Remove once proper instance handling exists
- public static NetChainloader Instance { get; set; }
-
public override void Initialize(string gameExePath = null)
{
Instance = this;
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index 581a6b4..3ffa16d 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -1,4 +1,5 @@
-using BepInEx.Bootstrap;
+using System;
+using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Contract;
using BepInEx.Logging;
@@ -12,8 +13,15 @@ public abstract class BasePlugin : IPlugin
{
var metadata = MetadataHelper.GetPluginMetadata(this);
- Info = BaseChainloader.GetPluginInfoFromGuid(metadata.GUID);
- Info.Instance = this;
+ if (BaseChainloader.Instance.TryGetPluginInfoFromGuid(metadata.GUID, out var pluginInfo))
+ {
+ Info = pluginInfo;
+ Info.Instance = this;
+ }
+ else
+ {
+ throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
+ }
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
index 5f857cd..b493789 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
@@ -32,8 +32,6 @@ public class IL2CPPChainloader : BaseChainloader
private static INativeDetour RuntimeInvokeDetour { get; set; }
- public static IL2CPPChainloader Instance { get; set; }
-
///
/// Register and add a Unity Component (for example MonoBehaviour) into BepInEx global manager.
/// Automatically registers the type with Il2Cpp type system if it isn't initialised already.
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
index cdf73c2..4c0552b 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
@@ -23,8 +23,15 @@ public abstract class BaseUnityPlugin : MonoBehaviour, IPlugin
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
- Info = BaseChainloader.GetPluginInfoFromGuid(metadata.GUID);
- Info.Instance = this;
+ if (BaseChainloader.Instance.TryGetPluginInfoFromGuid(metadata.GUID, out var pluginInfo))
+ {
+ Info = pluginInfo;
+ Info.Instance = this;
+ }
+ else
+ {
+ throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
+ }
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs b/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs
index c08a8de..1c163b9 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs
@@ -37,9 +37,6 @@ public class UnityChainloader : BaseChainloader
private string _consoleTitle;
- // TODO: Remove once proper instance handling exists
- public static UnityChainloader Instance { get; set; }
-
///
/// The GameObject that all plugins are attached to as components.
///
@@ -70,9 +67,9 @@ public class UnityChainloader : BaseChainloader
Logger.Log(LogLevel.Debug, "Entering chainloader StaticStart");
- var instance = new UnityChainloader();
- instance.Initialize(gameExePath);
- instance.Execute();
+ Instance = new UnityChainloader();
+ Instance.Initialize(gameExePath);
+ Instance.Execute();
Logger.Log(LogLevel.Debug, "Exiting chainloader StaticStart");
}
From b729aee545aee871f17029f2c0e2c56d4c567dd7 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Wed, 28 Aug 2024 01:14:00 -0400
Subject: [PATCH 11/65] Address code review round 3 (first part)
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 7 +++----
BepInEx.PatcherProvider/BepInExPatcherProvider.cs | 2 +-
BepInEx.PluginProvider/BepInExPluginProvider.cs | 2 +-
Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj | 4 ++--
.../Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj | 4 ++--
.../Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj | 4 ++--
6 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index 15509b7..665535f 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -257,13 +257,12 @@ public abstract class BaseChainloader
/// Some plugins may be skipped if they cannot be loaded (wrong metadata, etc).
/// Plugins to process.
/// List of plugins to load in the correct load order.
- protected virtual List ModifyLoadOrder(IList plugins, Dictionary existingPlugins)
- where T : PluginInfo
+ protected virtual List ModifyLoadOrder(IList plugins, Dictionary existingPlugins)
{
// We use a sorted dictionary to ensure consistent load order
var dependencyDict =
new SortedDictionary>(StringComparer.InvariantCultureIgnoreCase);
- var pluginsByGuid = new Dictionary();
+ var pluginsByGuid = new Dictionary();
foreach (var pluginInfoGroup in plugins.GroupBy(info => info.Metadata.GUID))
{
@@ -441,7 +440,7 @@ public abstract class BaseChainloader
{
invalidPlugins.Add(pluginInfo.Metadata.GUID);
existingPluginInfos.Remove(pluginInfo.Metadata.GUID);
- pluginInfo.LoadContext.Dispose();
+ pluginInfo.LoadContext?.Dispose();
Logger.Log(LogLevel.Error,
$"Error loading [{pluginInfo}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
diff --git a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
index fb2ff8c..9221ddb 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
@@ -7,7 +7,7 @@ using BepInEx.Preloader.Core.Patching;
namespace BepInEx.PatcherProvider;
-[PatcherProviderPluginInfo("io.bepinex.bepinexpatcherprovider", "BepInExPatcherProvider", "1.0")]
+[PatcherProviderPluginInfo("BepInExPatcherProvider", "BepInExPatcherProvider", "1.0")]
public class BepInExPatcherProvider : BasePatcherProvider
{
private static readonly Dictionary AssemblyLocationsByFilename = new();
diff --git a/BepInEx.PluginProvider/BepInExPluginProvider.cs b/BepInEx.PluginProvider/BepInExPluginProvider.cs
index b2f151c..783342a 100644
--- a/BepInEx.PluginProvider/BepInExPluginProvider.cs
+++ b/BepInEx.PluginProvider/BepInExPluginProvider.cs
@@ -6,7 +6,7 @@ using BepInEx.Logging;
namespace BepInEx.PluginProvider;
-[BepInPluginProvider("io.bepinex.bepinexpluginprovider", "BepInExPluginProvider", "1.0")]
+[BepInPluginProvider("BepInExPluginProvider", "BepInExPluginProvider", "1.0")]
public class BepInExPluginProvider : BasePluginProvider
{
private static readonly Dictionary AssemblyLocationsByFilename = new();
diff --git a/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj b/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
index 168fc2b..f715bf1 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
+++ b/Runtimes/NET/BepInEx.NET.Common/BepInEx.NET.Common.csproj
@@ -14,13 +14,13 @@
false
- Content
+ None
PreserveNewest
Build;DocumentationProjectOutputGroup
false
- Content
+ None
PreserveNewest
Build;DocumentationProjectOutputGroup
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
index 2698124..86a2e3e 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
@@ -11,13 +11,13 @@
false
- Content
+ None
PreserveNewest
Build;DocumentationProjectOutputGroup
false
- Content
+ None
PreserveNewest
Build;DocumentationProjectOutputGroup
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
index 71af84f..7f49885 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
@@ -13,13 +13,13 @@
false
- Content
+ None
PreserveNewest
Build;DocumentationProjectOutputGroup
false
- Content
+ None
PreserveNewest
Build;DocumentationProjectOutputGroup
From e2396d202b82cea5c162fdf09a046b3001d3b0e0 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Thu, 29 Aug 2024 10:48:50 -0400
Subject: [PATCH 12/65] address code review round 3
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 16 +----
BepInEx.Core/Contract/IPluginLoadContext.cs | 6 ++
BepInEx.Core/Utility.cs | 59 +++++++++++++++++++
.../BepInExPatcherLoadContext.cs | 12 ++++
.../BepInExPatcherProvider.cs | 2 +-
.../BepInExPluginLoadContext.cs | 12 ++++
.../BepInExPluginProvider.cs | 2 +-
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 2 +-
.../Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 2 +-
.../BepInEx.Unity.Mono/BaseUnityPlugin.cs | 2 +-
10 files changed, 97 insertions(+), 18 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index 665535f..c2e7c7e 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -116,18 +116,6 @@ public abstract class BaseChainloader
return pluginTarget.Build > CurrentAssemblyVersion.Build;
}
- ///
- /// Obtains the corresponding to its GUID
- ///
- /// The GUID of the plugin to get the info from
- /// When this method returns, contains the
- /// associated with the specified GUID, if the plugin was loaded; otherwise, null. This parameter is passed uninitialized.
- /// true if the plugin with the GUID was loaded by the chainloader; otherwise, false.
- public bool TryGetPluginInfoFromGuid(string pluginGuid, out PluginInfo foundPluginInfo)
- {
- return Plugins.TryGetValue(pluginGuid, out foundPluginInfo);
- }
-
#region Contract
protected virtual string ConsoleTitle => $"BepInEx {Paths.BepInExVersion} - {Paths.ProcessName}";
@@ -409,7 +397,9 @@ public abstract class BaseChainloader
}
else if (!provider && !loadedAssemblies.TryGetValue(pluginInfo.LoadContext.AssemblyIdentifier, out ass))
{
- ass = Assembly.Load(pluginInfo.LoadContext.GetAssemblyData());
+ var symbols = pluginInfo.LoadContext.GetAssemblySymbolsData();
+ ass = symbols != null ? Assembly.Load(pluginInfo.LoadContext.GetAssemblyData(), symbols)
+ : Assembly.Load(pluginInfo.LoadContext.GetAssemblyData());
loadedAssemblies[pluginInfo.LoadContext.AssemblyIdentifier] = ass;
}
diff --git a/BepInEx.Core/Contract/IPluginLoadContext.cs b/BepInEx.Core/Contract/IPluginLoadContext.cs
index 715f09c..013b973 100644
--- a/BepInEx.Core/Contract/IPluginLoadContext.cs
+++ b/BepInEx.Core/Contract/IPluginLoadContext.cs
@@ -26,6 +26,12 @@ public interface IPluginLoadContext : IDisposable
/// The assembly's raw data in bytes
public byte[] GetAssemblyData();
+ ///
+ /// Obtains the assembly's symbols data which will be loaded by the chainloader alongside the assembly
+ ///
+ /// The assembly's symbols data in bytes (either in portable pdb or mdb format). Null if no symbols exists for this assembly
+ public byte[] GetAssemblySymbolsData();
+
///
/// Obtains a file's raw data using a relative path from the plugin assembly
///
diff --git a/BepInEx.Core/Utility.cs b/BepInEx.Core/Utility.cs
index a6b93bd..b8cd30c 100644
--- a/BepInEx.Core/Utility.cs
+++ b/BepInEx.Core/Utility.cs
@@ -221,6 +221,55 @@ public static class Utility
return false;
}
+ ///
+ /// Try to resolve the symbols data from an assembly DLL.
+ ///
+ /// File path of the DLL assembly.
+ /// The symbols of the DLL assembly.
+ /// True, if the symbols were found. Otherwise, false.
+ public static bool TryResolveDllSymbols(string dllPath, out byte[] symbolsData)
+ {
+ symbolsData = null;
+
+ var subDirectory = Path.GetDirectoryName(dllPath);
+ var filename = Path.GetFileNameWithoutExtension(dllPath);
+
+ var potentialSymbolsFileNames = new[]
+ {
+ $"{filename}.mdb",
+ $"{filename}.pdb"
+ };
+
+ foreach (var symbolFileName in potentialSymbolsFileNames)
+ {
+ var symbolPath = Path.Combine(subDirectory, symbolFileName);
+ if (!File.Exists(symbolPath))
+ continue;
+
+ try
+ {
+ symbolsData = File.ReadAllBytes(symbolPath);
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static Assembly LoadAssemblyWithSymbols(string assemblyFilePath)
+ {
+ var assemblyData = File.ReadAllBytes(assemblyFilePath);
+
+ return TryResolveDllSymbols(assemblyFilePath, out byte[] symbolsData)
+ ? Assembly.Load(assemblyData, symbolsData)
+ : Assembly.Load(assemblyData);
+ }
+
///
/// Checks whether a given cecil type definition is a subtype of a provided type.
///
@@ -259,6 +308,16 @@ public static class Utility
TryResolveDllAssembly(assemblyName, directory,
s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly);
+ ///
+ /// Try to resolve and load the given assembly DLL including their symbols file.
+ ///
+ /// Name of the assembly, of the type .
+ /// Directory to search the assembly from.
+ /// The loaded assembly.
+ /// True, if the assembly was found and loaded. Otherwise, false.
+ public static bool TryResolveDllAssemblyWithSymbols(AssemblyName assemblyName, string directory, out Assembly assembly) =>
+ TryResolveDllAssembly(assemblyName, directory, LoadAssemblyWithSymbols, out assembly);
+
///
/// Tries to create a file with the given name
///
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
index 9be40a5..63fc627 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
@@ -8,6 +8,7 @@ public class BepInExPatcherLoadContext : IPluginLoadContext
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
private byte[] assemblyData;
+ private byte[] assemblySymbolsData;
public byte[] GetAssemblyData()
{
if (assemblyData == null)
@@ -17,6 +18,17 @@ public class BepInExPatcherLoadContext : IPluginLoadContext
return assemblyData;
}
+
+ public byte[] GetAssemblySymbolsData()
+ {
+ if (assemblySymbolsData == null)
+ {
+ if (!Utility.TryResolveDllSymbols(AssemblyIdentifier, out assemblySymbolsData))
+ assemblySymbolsData = null;
+ }
+
+ return assemblySymbolsData;
+ }
public byte[] GetFile(string relativePath)
{
diff --git a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
index 9221ddb..78fa699 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
@@ -40,7 +40,7 @@ public class BepInExPatcherProvider : BasePatcherProvider
if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
return null;
- if (!Utility.TryResolveDllAssembly(new(name), location, out var ass))
+ if (!Utility.TryResolveDllAssemblyWithSymbols(new(name), location, out var ass))
return null;
return ass;
diff --git a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
index 9a7cb38..3271a24 100644
--- a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
+++ b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
@@ -8,6 +8,7 @@ public class BepInExPluginLoadContext : IPluginLoadContext
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
private byte[] assemblyData;
+ private byte[] assemblySymbolsData;
public byte[] GetAssemblyData()
{
if (assemblyData == null)
@@ -18,6 +19,17 @@ public class BepInExPluginLoadContext : IPluginLoadContext
return assemblyData;
}
+ public byte[] GetAssemblySymbolsData()
+ {
+ if (assemblySymbolsData == null)
+ {
+ if (!Utility.TryResolveDllSymbols(AssemblyIdentifier, out assemblySymbolsData))
+ assemblySymbolsData = null;
+ }
+
+ return assemblySymbolsData;
+ }
+
public byte[] GetFile(string relativePath)
{
if (relativePath == null)
diff --git a/BepInEx.PluginProvider/BepInExPluginProvider.cs b/BepInEx.PluginProvider/BepInExPluginProvider.cs
index 783342a..bae7689 100644
--- a/BepInEx.PluginProvider/BepInExPluginProvider.cs
+++ b/BepInEx.PluginProvider/BepInExPluginProvider.cs
@@ -39,7 +39,7 @@ public class BepInExPluginProvider : BasePluginProvider
if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
return null;
- if (!Utility.TryResolveDllAssembly(new(name), location, out var ass))
+ if (!Utility.TryResolveDllAssemblyWithSymbols(new(name), location, out var ass))
return null;
return ass;
diff --git a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
index 810dd19..bbd52eb 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
@@ -15,7 +15,7 @@ namespace BepInEx.NET.Common
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
- if (BaseChainloader.Instance.TryGetPluginInfoFromGuid(metadata.GUID, out var pluginInfo))
+ if (BaseChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
Info = pluginInfo;
Info.Instance = this;
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index 3ffa16d..e6889da 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -13,7 +13,7 @@ public abstract class BasePlugin : IPlugin
{
var metadata = MetadataHelper.GetPluginMetadata(this);
- if (BaseChainloader.Instance.TryGetPluginInfoFromGuid(metadata.GUID, out var pluginInfo))
+ if (BaseChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
Info = pluginInfo;
Info.Instance = this;
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
index 4c0552b..f7ea012 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
@@ -23,7 +23,7 @@ public abstract class BaseUnityPlugin : MonoBehaviour, IPlugin
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
- if (BaseChainloader.Instance.TryGetPluginInfoFromGuid(metadata.GUID, out var pluginInfo))
+ if (BaseChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
Info = pluginInfo;
Info.Instance = this;
From ee5918bd76ff547925d97e800a9f856b33fcbbed Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Thu, 29 Aug 2024 14:44:30 -0400
Subject: [PATCH 13/65] Address minor code review
---
BepInEx.Core/Utility.cs | 2 +-
BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs | 1 +
BepInEx.PluginProvider/BepInExPluginLoadContext.cs | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/BepInEx.Core/Utility.cs b/BepInEx.Core/Utility.cs
index b8cd30c..1a35841 100644
--- a/BepInEx.Core/Utility.cs
+++ b/BepInEx.Core/Utility.cs
@@ -236,7 +236,7 @@ public static class Utility
var potentialSymbolsFileNames = new[]
{
- $"{filename}.mdb",
+ $"{filename}.dll.mdb",
$"{filename}.pdb"
};
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
index 63fc627..3aca66f 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
@@ -43,5 +43,6 @@ public class BepInExPatcherLoadContext : IPluginLoadContext
public void Dispose()
{
assemblyData = null;
+ assemblySymbolsData = null;
}
}
diff --git a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
index 3271a24..389e181 100644
--- a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
+++ b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
@@ -43,5 +43,6 @@ public class BepInExPluginLoadContext : IPluginLoadContext
public void Dispose()
{
assemblyData = null;
+ assemblySymbolsData = null;
}
}
From 1f182c7770c91e716464785e2cf3ca4e50cccf4c Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Thu, 29 Aug 2024 21:11:36 -0400
Subject: [PATCH 14/65] Correct the symbols resolution and rename the method
---
BepInEx.Core/Utility.cs | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/BepInEx.Core/Utility.cs b/BepInEx.Core/Utility.cs
index 1a35841..3ba6f1f 100644
--- a/BepInEx.Core/Utility.cs
+++ b/BepInEx.Core/Utility.cs
@@ -227,17 +227,16 @@ public static class Utility
/// File path of the DLL assembly.
/// The symbols of the DLL assembly.
/// True, if the symbols were found. Otherwise, false.
- public static bool TryResolveDllSymbols(string dllPath, out byte[] symbolsData)
+ public static bool TryResolveAssemblySymbols(string dllPath, out byte[] symbolsData)
{
symbolsData = null;
var subDirectory = Path.GetDirectoryName(dllPath);
- var filename = Path.GetFileNameWithoutExtension(dllPath);
var potentialSymbolsFileNames = new[]
{
- $"{filename}.dll.mdb",
- $"{filename}.pdb"
+ $"{dllPath}.mdb",
+ Path.ChangeExtension(dllPath, ".pdb")
};
foreach (var symbolFileName in potentialSymbolsFileNames)
@@ -265,7 +264,7 @@ public static class Utility
{
var assemblyData = File.ReadAllBytes(assemblyFilePath);
- return TryResolveDllSymbols(assemblyFilePath, out byte[] symbolsData)
+ return TryResolveAssemblySymbols(assemblyFilePath, out byte[] symbolsData)
? Assembly.Load(assemblyData, symbolsData)
: Assembly.Load(assemblyData);
}
From 40643ffdefe2cb5e1bd36ee4a7a1a92a396ca0e5 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Thu, 29 Aug 2024 21:12:07 -0400
Subject: [PATCH 15/65] Offload the load context caching to bepinex
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 13 +++---
BepInEx.Core/Bootstrap/TypeLoader.cs | 41 +++++++++++++++++++
BepInEx.Core/Contract/IPluginLoadContext.cs | 2 +-
.../BepInExPatcherLoadContext.cs | 22 ++--------
.../BepInExPluginLoadContext.cs | 24 ++---------
.../Patching/AssemblyPatcher.cs | 12 ++++--
6 files changed, 64 insertions(+), 50 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index c2e7c7e..c233d7e 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -146,7 +146,7 @@ public abstract class BaseChainloader
/// Value holds info of the originating provider. These contexts are used by chainloader to
/// load plugins. Plugins that are loaded successfully are added to .
///
- public Dictionary LoadContexts { get; } = new();
+ internal Dictionary LoadContexts { get; } = new();
///
/// Collection of error chainloader messages that occured during plugin loading.
@@ -350,7 +350,7 @@ public abstract class BaseChainloader
LoadPlugins(providers, provider: true);
FinishedProviders?.Invoke();
- var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts.Keys, ToPluginInfo, HasBepinPluginType, "chainloader");
+ var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts.Keys.Cast(), ToPluginInfo, HasBepinPluginType, "chainloader");
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
LoadPlugins(plugins, provider: false);
Finished?.Invoke();
@@ -414,23 +414,24 @@ public abstract class BaseChainloader
var pluginLoadContexts = ((BasePluginProvider)pluginInfo.Instance).GetPlugins();
foreach (IPluginLoadContext context in pluginLoadContexts)
{
- LoadContexts[context] = pluginInfo;
+ var cachedContext = new CachedPluginLoadContext(context);
+ LoadContexts[cachedContext] = pluginInfo;
}
ProviderLoaded?.Invoke(pluginInfo);
}
else
{
- pluginInfo.Source = LoadContexts[pluginInfo.LoadContext];
+ pluginInfo.Source = LoadContexts[(CachedPluginLoadContext)pluginInfo.LoadContext];
pluginInfo.Instance = LoadPlugin(pluginInfo, ass);
PluginLoaded?.Invoke(pluginInfo);
- pluginInfo.LoadContext.Dispose();
+ foreach (CachedPluginLoadContext context in LoadContexts.Keys)
+ context.Dispose();
}
}
catch (Exception ex)
{
invalidPlugins.Add(pluginInfo.Metadata.GUID);
existingPluginInfos.Remove(pluginInfo.Metadata.GUID);
- pluginInfo.LoadContext?.Dispose();
Logger.Log(LogLevel.Error,
$"Error loading [{pluginInfo}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index b847c4e..d83e6d8 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -10,6 +10,47 @@ using Mono.Cecil;
namespace BepInEx.Bootstrap;
+internal class CachedPluginLoadContext : IPluginLoadContext, IDisposable
+{
+ public IPluginLoadContext PluginLoadContext { get; }
+ private byte[] assemblyData;
+ private byte[] assemblySymbolsData;
+ private readonly Dictionary files = new();
+
+ public CachedPluginLoadContext(IPluginLoadContext pluginLoadContext)
+ {
+ PluginLoadContext = pluginLoadContext;
+ }
+
+ public string AssemblyIdentifier => PluginLoadContext.AssemblyIdentifier;
+ public string AssemblyHash => PluginLoadContext.AssemblyHash;
+ public byte[] GetAssemblyData()
+ {
+ return assemblyData ??= PluginLoadContext.GetAssemblyData();
+ }
+
+ public byte[] GetAssemblySymbolsData()
+ {
+ return assemblySymbolsData ??= PluginLoadContext.GetAssemblySymbolsData();
+ }
+
+ public byte[] GetFile(string relativePath)
+ {
+ if (!files.ContainsKey(relativePath))
+ {
+ files[relativePath] = PluginLoadContext.GetFile(relativePath);
+ }
+ return files[relativePath];
+ }
+
+ public void Dispose()
+ {
+ assemblyData = null;
+ assemblySymbolsData = null;
+ files.Clear();
+ }
+}
+
///
/// A cacheable metadata item. Can be used with and
/// to cache plugin metadata.
diff --git a/BepInEx.Core/Contract/IPluginLoadContext.cs b/BepInEx.Core/Contract/IPluginLoadContext.cs
index 013b973..1812ea8 100644
--- a/BepInEx.Core/Contract/IPluginLoadContext.cs
+++ b/BepInEx.Core/Contract/IPluginLoadContext.cs
@@ -6,7 +6,7 @@ namespace BepInEx;
/// An interface that allows to dynamically load and track an assembly
/// which can be obtained from a provider
///
-public interface IPluginLoadContext : IDisposable
+public interface IPluginLoadContext
{
///
/// An identifier that uniquely identifies an assembly from a provider no matter its revision
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
index 3aca66f..be1ca0a 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
@@ -7,25 +7,15 @@ public class BepInExPatcherLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
- private byte[] assemblyData;
- private byte[] assemblySymbolsData;
public byte[] GetAssemblyData()
{
- if (assemblyData == null)
- {
- assemblyData = File.ReadAllBytes(AssemblyIdentifier);
- }
-
- return assemblyData;
+ return File.ReadAllBytes(AssemblyIdentifier);
}
public byte[] GetAssemblySymbolsData()
{
- if (assemblySymbolsData == null)
- {
- if (!Utility.TryResolveDllSymbols(AssemblyIdentifier, out assemblySymbolsData))
- assemblySymbolsData = null;
- }
+ if (Utility.TryResolveAssemblySymbols(AssemblyIdentifier, out var assemblySymbolsData))
+ return assemblySymbolsData;
return assemblySymbolsData;
}
@@ -39,10 +29,4 @@ public class BepInExPatcherLoadContext : IPluginLoadContext
string filePath = Path.Combine(assemblyFolder, relativePath);
return File.ReadAllBytes(filePath);
}
-
- public void Dispose()
- {
- assemblyData = null;
- assemblySymbolsData = null;
- }
}
diff --git a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
index 389e181..d7b0136 100644
--- a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
+++ b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
@@ -7,25 +7,15 @@ public class BepInExPluginLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
- private byte[] assemblyData;
- private byte[] assemblySymbolsData;
public byte[] GetAssemblyData()
{
- if (assemblyData == null)
- {
- assemblyData = File.ReadAllBytes(AssemblyIdentifier);
- }
-
- return assemblyData;
+ return File.ReadAllBytes(AssemblyIdentifier);
}
-
+
public byte[] GetAssemblySymbolsData()
{
- if (assemblySymbolsData == null)
- {
- if (!Utility.TryResolveDllSymbols(AssemblyIdentifier, out assemblySymbolsData))
- assemblySymbolsData = null;
- }
+ if (Utility.TryResolveAssemblySymbols(AssemblyIdentifier, out var assemblySymbolsData))
+ return assemblySymbolsData;
return assemblySymbolsData;
}
@@ -39,10 +29,4 @@ public class BepInExPluginLoadContext : IPluginLoadContext
string filePath = Path.Combine(assemblyFolder, relativePath);
return File.ReadAllBytes(filePath);
}
-
- public void Dispose()
- {
- assemblyData = null;
- assemblySymbolsData = null;
- }
}
diff --git a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
index a9822d9..64e04d8 100644
--- a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
@@ -25,7 +25,7 @@ public class AssemblyPatcher : IDisposable
private Func assemblyLoader;
- private readonly List loadContexts = new();
+ private readonly List loadContexts = new();
private readonly List providers = new();
public AssemblyPatcher(Func assemblyLoader)
@@ -164,7 +164,7 @@ public class AssemblyPatcher : IDisposable
var instance = (BasePatcherProvider) Activator.CreateInstance(type);
providers.Add(instance);
- loadContexts.AddRange(instance.GetPatchers());
+ loadContexts.AddRange(instance.GetPatchers().Select(x => new CachedPluginLoadContext(x)));
}
catch (Exception e)
{
@@ -174,7 +174,7 @@ public class AssemblyPatcher : IDisposable
}
}
- var patchers = TypeLoader.GetPluginsFromLoadContexts(loadContexts, ToPatcherPlugin, HasPatcherType);
+ var patchers = TypeLoader.GetPluginsFromLoadContexts(loadContexts.Cast(), ToPatcherPlugin, HasPatcherType);
var sortedPatchers = new List();
@@ -188,11 +188,15 @@ public class AssemblyPatcher : IDisposable
var assName = ass.GetName();
Logger.Log(LogLevel.Debug, $"Loaded patcher {patcherPlugin.TypeName} from [{assName.Name} {assName.Version}]");
- patcherPlugin.LoadContext.Dispose();
}
PatcherContext.PatchDefinitions.AddRange(sortedPatchers);
Logger.Log(LogLevel.Info, $"Loaded {sortedPatchers.Count} patcher{(sortedPatchers.Count > 0 ? "s" : "")}");
+
+ foreach (CachedPluginLoadContext loadContext in loadContexts)
+ {
+ loadContext.Dispose();
+ }
}
private Assembly PatcherProvidersAssemblyResolver(object sender, ResolveEventArgs args)
From ba3fac94dab3796f87649d3ae96712da16f01977 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sat, 31 Aug 2024 05:07:51 -0400
Subject: [PATCH 16/65] Upgrade MonoMod to 22.7.31.1 to fix old mono preloader
crashes (#973)
---
BepInEx.Core/BepInEx.Core.csproj | 2 +-
BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj | 4 ++--
README.md | 2 +-
.../Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj | 2 +-
.../Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/BepInEx.Core/BepInEx.Core.csproj b/BepInEx.Core/BepInEx.Core.csproj
index 30a9e20..c2873d3 100644
--- a/BepInEx.Core/BepInEx.Core.csproj
+++ b/BepInEx.Core/BepInEx.Core.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj b/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
index b75a844..6002a99 100644
--- a/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
+++ b/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
@@ -9,7 +9,7 @@
-
-
+
+
diff --git a/README.md b/README.md
index 95a4de4..18a1145 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ A more comprehensive comparison list of features and compatibility is available
- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.3.0
- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.2
-- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.5.1.1
+- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.7.31.1
- [jbevain/cecil](https://github.com/jbevain/cecil) - v0.10.4
#### IL2CPP libraries
diff --git a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
index 78ebd3c..240e981 100644
--- a/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Common/BepInEx.Unity.Common.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
index 32eb297..a8ebac2 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
@@ -21,7 +21,7 @@
-
+
From 4597be006e683ac6f4dc9165dd5ebd04c98cb32a Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sat, 31 Aug 2024 05:54:41 -0400
Subject: [PATCH 17/65] Remove files caching from CachedPluginLoadContext, make
it forward the call instead
---
BepInEx.Core/Bootstrap/TypeLoader.cs | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index d83e6d8..0c8e256 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -15,7 +15,6 @@ internal class CachedPluginLoadContext : IPluginLoadContext, IDisposable
public IPluginLoadContext PluginLoadContext { get; }
private byte[] assemblyData;
private byte[] assemblySymbolsData;
- private readonly Dictionary files = new();
public CachedPluginLoadContext(IPluginLoadContext pluginLoadContext)
{
@@ -36,18 +35,13 @@ internal class CachedPluginLoadContext : IPluginLoadContext, IDisposable
public byte[] GetFile(string relativePath)
{
- if (!files.ContainsKey(relativePath))
- {
- files[relativePath] = PluginLoadContext.GetFile(relativePath);
- }
- return files[relativePath];
+ return PluginLoadContext.GetFile(relativePath);
}
public void Dispose()
{
assemblyData = null;
assemblySymbolsData = null;
- files.Clear();
}
}
From 24cf635e6555004b04ab93b18ebcccf4dec9a3ca Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 1 Sep 2024 09:29:35 -0400
Subject: [PATCH 18/65] Ensure providers and plugins / patchers folders are
created if they don't exist
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 6 +++---
BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs | 6 ++++++
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index c233d7e..2d57639 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -187,12 +187,12 @@ public abstract class BaseChainloader
InitializeLoggers();
+ if (!Directory.Exists(Paths.PluginProviderPath))
+ Directory.CreateDirectory(Paths.PluginProviderPath);
+
if (!Directory.Exists(Paths.PluginPath))
Directory.CreateDirectory(Paths.PluginPath);
- if (!Directory.Exists(Paths.PatcherPluginPath))
- Directory.CreateDirectory(Paths.PatcherPluginPath);
-
_initialized = true;
Logger.Log(LogLevel.Message, "Chainloader initialized");
diff --git a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
index 64e04d8..3022e6a 100644
--- a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
@@ -31,6 +31,12 @@ public class AssemblyPatcher : IDisposable
public AssemblyPatcher(Func assemblyLoader)
{
this.assemblyLoader = assemblyLoader;
+
+ if (!Directory.Exists(Paths.PatcherProviderPath))
+ Directory.CreateDirectory(Paths.PatcherProviderPath);
+
+ if (!Directory.Exists(Paths.PatcherPluginPath))
+ Directory.CreateDirectory(Paths.PatcherPluginPath);
}
///
From 5a324246f16c47bdd38aeb3078c80d785c7b8e00 Mon Sep 17 00:00:00 2001
From: thegu5 <58223632+thegu5@users.noreply.github.com>
Date: Sun, 1 Sep 2024 13:09:38 -0400
Subject: [PATCH 19/65] Bump Cpp2IL (#967)
The currently used version doesn't have support for metadata version 29.2 (Unity 2023.2+) and 31 (2022.3.33+)
---
Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
index a8ebac2..bfc326a 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BepInEx.Unity.IL2CPP.csproj
@@ -22,7 +22,7 @@
-
+
From 0a4f1d6d7320fa4af991fa062fc20df2985f3dbd Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 1 Sep 2024 17:14:59 -0400
Subject: [PATCH 20/65] Accomodate breaking changes in preparation for api
refactors
---
BepInEx.Core/Contract/Attributes.cs | 4 ++--
BepInEx.Core/Contract/IPlugin.cs | 2 +-
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 6 +++---
Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 6 +++---
Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs | 4 ++--
5 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/BepInEx.Core/Contract/Attributes.cs b/BepInEx.Core/Contract/Attributes.cs
index f9d495c..793f456 100644
--- a/BepInEx.Core/Contract/Attributes.cs
+++ b/BepInEx.Core/Contract/Attributes.cs
@@ -290,7 +290,7 @@ public static class MetadataHelper
///
/// The plugin type.
/// The BepInPlugin metadata of the plugin type.
- public static BepInPlugin GetPluginMetadata(Type pluginType)
+ public static BepInPlugin GetMetadata(Type pluginType)
{
var attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false);
@@ -305,7 +305,7 @@ public static class MetadataHelper
///
/// The plugin instance.
/// The BepInPlugin metadata of the plugin instance.
- public static BepInPlugin GetPluginMetadata(object plugin) => GetPluginMetadata(plugin.GetType());
+ public static BepInPlugin GetMetadata(object plugin) => GetMetadata(plugin.GetType());
///
/// Retrieves the BepInPluginProvider metadata from a plugin type.
diff --git a/BepInEx.Core/Contract/IPlugin.cs b/BepInEx.Core/Contract/IPlugin.cs
index 84cc6b5..6963150 100644
--- a/BepInEx.Core/Contract/IPlugin.cs
+++ b/BepInEx.Core/Contract/IPlugin.cs
@@ -13,7 +13,7 @@ internal interface IPlugin
///
/// Logger instance tied to this plugin.
///
- ManualLogSource Logger { get; }
+ ManualLogSource Log { get; }
///
/// Default config file tied to this plugin. The config file will not be created until
diff --git a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
index bbd52eb..4356862 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
@@ -11,7 +11,7 @@ namespace BepInEx.NET.Common
{
protected BasePlugin()
{
- var metadata = MetadataHelper.GetPluginMetadata(this);
+ var metadata = MetadataHelper.GetMetadata(this);
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
@@ -25,7 +25,7 @@ namespace BepInEx.NET.Common
throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
}
- Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
+ Log = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
@@ -34,7 +34,7 @@ namespace BepInEx.NET.Common
public PluginInfo Info { get; }
///
- public ManualLogSource Logger { get; }
+ public ManualLogSource Log { get; }
///
public ConfigFile Config { get; }
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index e6889da..b9d90c0 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -11,7 +11,7 @@ public abstract class BasePlugin : IPlugin
{
protected BasePlugin()
{
- var metadata = MetadataHelper.GetPluginMetadata(this);
+ var metadata = MetadataHelper.GetMetadata(this);
if (BaseChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
@@ -23,14 +23,14 @@ public abstract class BasePlugin : IPlugin
throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
}
- Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
+ Log = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
public PluginInfo Info { get; }
- public ManualLogSource Logger { get; }
+ public ManualLogSource Log { get; }
public ConfigFile Config { get; }
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
index f7ea012..3076c2a 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
@@ -10,7 +10,7 @@ namespace BepInEx.Unity.Mono;
///
/// The base plugin type that is used by the BepInEx plugin loader.
///
-public abstract class BaseUnityPlugin : MonoBehaviour, IPlugin
+public abstract class BaseUnityPlugin : MonoBehaviour
{
///
/// Create a new instance of a plugin and all of its tied in objects.
@@ -18,7 +18,7 @@ public abstract class BaseUnityPlugin : MonoBehaviour, IPlugin
/// BepInPlugin attribute is missing.
protected BaseUnityPlugin()
{
- var metadata = MetadataHelper.GetPluginMetadata(this);
+ var metadata = MetadataHelper.GetMetadata(this);
if (metadata == null)
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
From e9337e9982df31d8ffea92466bfa5a37bc34c574 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 1 Sep 2024 17:35:53 -0400
Subject: [PATCH 21/65] Revert the removal of the il2cpp chainloader instance
---
Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
index b493789..e27c3cd 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
@@ -32,6 +32,8 @@ public class IL2CPPChainloader : BaseChainloader
private static INativeDetour RuntimeInvokeDetour { get; set; }
+ public static new IL2CPPChainloader Instance { get; set; }
+
///
/// Register and add a Unity Component (for example MonoBehaviour) into BepInEx global manager.
/// Automatically registers the type with Il2Cpp type system if it isn't initialised already.
From d44b93ba93ae44658a7992e6e44c878849c747f7 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 1 Sep 2024 22:56:30 -0400
Subject: [PATCH 22/65] Use the IL2CPPChainLoader instance for il2cpp to
preserve compatibility
---
Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 2 +-
Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs | 6 +++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index b9d90c0..825526b 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -13,7 +13,7 @@ public abstract class BasePlugin : IPlugin
{
var metadata = MetadataHelper.GetMetadata(this);
- if (BaseChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
+ if (IL2CPPChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
Info = pluginInfo;
Info.Instance = this;
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
index e27c3cd..390e845 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
@@ -32,7 +32,11 @@ public class IL2CPPChainloader : BaseChainloader
private static INativeDetour RuntimeInvokeDetour { get; set; }
- public static new IL2CPPChainloader Instance { get; set; }
+ public static new IL2CPPChainloader Instance
+ {
+ get => (IL2CPPChainloader)BaseChainloader.Instance;
+ set => BaseChainloader.Instance = value;
+ }
///
/// Register and add a Unity Component (for example MonoBehaviour) into BepInEx global manager.
From e0a81dfdbd18965e290ed69c4d1c48b5969bf98a Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Mon, 2 Sep 2024 07:16:13 -0400
Subject: [PATCH 23/65] Rename Location to prevent compilation with it
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 8 ++++----
BepInEx.Core/Contract/PluginInfo.cs | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index 2d57639..bf8646b 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -91,7 +91,7 @@ public abstract class BaseChainloader
Incompatibilities = incompatibilities,
TypeName = type.FullName,
TargettedBepInExVersion = bepinVersion,
- Location = assemblyLocation,
+ _location = assemblyLocation,
LoadContext = loadContext
};
}
@@ -390,10 +390,10 @@ public abstract class BaseChainloader
Logger.Log(LogLevel.Info, $"Loading [{pluginInfo}]");
Assembly ass = null;
- if (provider && !loadedAssemblies.TryGetValue(pluginInfo.Location, out ass))
+ if (provider && !loadedAssemblies.TryGetValue(pluginInfo._location, out ass))
{
- ass = Assembly.LoadFrom(pluginInfo.Location);
- loadedAssemblies[pluginInfo.Location] = ass;
+ ass = Assembly.LoadFrom(pluginInfo._location);
+ loadedAssemblies[pluginInfo._location] = ass;
}
else if (!provider && !loadedAssemblies.TryGetValue(pluginInfo.LoadContext.AssemblyIdentifier, out ass))
{
diff --git a/BepInEx.Core/Contract/PluginInfo.cs b/BepInEx.Core/Contract/PluginInfo.cs
index 26e5cae..02a8f59 100644
--- a/BepInEx.Core/Contract/PluginInfo.cs
+++ b/BepInEx.Core/Contract/PluginInfo.cs
@@ -53,7 +53,7 @@ public class PluginInfo : ICacheable
internal Version TargettedBepInExVersion { get; set; }
- internal string Location { get; set; }
+ internal string _location { get; set; }
///
public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
From c02ad9cd68ff4a08b53e145c7db99748e98bd935 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Mon, 2 Sep 2024 07:23:10 -0400
Subject: [PATCH 24/65] Add an obsolete property for compatibility
---
BepInEx.Core/Contract/PluginInfo.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/BepInEx.Core/Contract/PluginInfo.cs b/BepInEx.Core/Contract/PluginInfo.cs
index 02a8f59..3c5dfc8 100644
--- a/BepInEx.Core/Contract/PluginInfo.cs
+++ b/BepInEx.Core/Contract/PluginInfo.cs
@@ -53,6 +53,9 @@ public class PluginInfo : ICacheable
internal Version TargettedBepInExVersion { get; set; }
+ [Obsolete("Use LoadContext.AssemblyIdentifier instead", true)]
+ public string Location => LoadContext?.AssemblyIdentifier;
+
internal string _location { get; set; }
///
From 6b38cee1277f4e3da5f2f685aee8b503a14d4054 Mon Sep 17 00:00:00 2001
From: ManlyMarco <39247311+ManlyMarco@users.noreply.github.com>
Date: Mon, 2 Sep 2024 13:26:49 +0200
Subject: [PATCH 25/65] Fix failing to initialize if game's Data folder is just
'Data' (#975)
Any Unity game can have its _Data folder renamed to just Data and continue working just fine.
---
BepInEx.Core/Paths.cs | 22 +++++++++++++++----
.../Il2CppInteropManager.cs | 3 +--
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/BepInEx.Core/Paths.cs b/BepInEx.Core/Paths.cs
index 34d3e98..6a37342 100644
--- a/BepInEx.Core/Paths.cs
+++ b/BepInEx.Core/Paths.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using System.IO;
using System.Linq;
using System.Reflection;
using MonoMod.Utils;
@@ -106,9 +106,23 @@ public static class Paths
? Utility.ParentDirectory(executablePath, 4)
: Path.GetDirectoryName(executablePath);
- GameDataPath = managedPath != null && gameDataRelativeToManaged
- ? Path.GetDirectoryName(managedPath)
- : Path.Combine(GameRootPath, $"{ProcessName}_Data");
+ if (managedPath != null && gameDataRelativeToManaged)
+ {
+ GameDataPath = Path.GetDirectoryName(managedPath);
+ }
+ else
+ {
+ // According to some experiments, Unity checks whether globalgamemanagers/data.unity3d exists in the data folder before picking it.
+ // 'ProcessName_Data' folder is checked first, then if that fails 'Data' folder is checked. If neither is valid, the player crashes.
+ // A simple Directory.Exists check is accurate enough while being less likely to break in case these conditions change.
+ GameDataPath = Path.Combine(GameRootPath, $"{ProcessName}_Data");
+ if (!Directory.Exists(GameDataPath))
+ GameDataPath = Path.Combine(GameRootPath, "Data");
+ }
+
+ if (string.IsNullOrEmpty(GameDataPath) || !Directory.Exists(GameDataPath))
+ throw new DirectoryNotFoundException("Failed to extract valid GameDataPath from executablePath: " + executablePath);
+
ManagedPath = managedPath ?? Path.Combine(GameDataPath, "Managed");
BepInExRootPath = bepinRootPath ?? Path.Combine(GameRootPath, "BepInEx");
ConfigPath = Path.Combine(BepInExRootPath, "config");
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
index cc43fac..4e2761a 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
@@ -281,8 +281,7 @@ internal static partial class Il2CppInteropManager
{
Logger.LogMessage("Running Cpp2IL to generate dummy assemblies");
- var metadataPath = Path.Combine(Paths.GameRootPath,
- $"{Paths.ProcessName}_Data",
+ var metadataPath = Path.Combine(Paths.GameDataPath,
"il2cpp_data",
"Metadata",
"global-metadata.dat");
From 7315b7dd664f13e073f88c8cb41e3aab605c318b Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Mon, 2 Sep 2024 14:16:12 -0400
Subject: [PATCH 26/65] Make the default providers classes internal in their
assemblies
---
BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs | 2 +-
BepInEx.PatcherProvider/BepInExPatcherProvider.cs | 2 +-
BepInEx.PluginProvider/BepInExPluginLoadContext.cs | 2 +-
BepInEx.PluginProvider/BepInExPluginProvider.cs | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
index be1ca0a..9cb4cbc 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
@@ -3,7 +3,7 @@ using System.IO;
namespace BepInEx.PatcherProvider;
-public class BepInExPatcherLoadContext : IPluginLoadContext
+internal class BepInExPatcherLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
diff --git a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
index 78fa699..5ae4555 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
@@ -8,7 +8,7 @@ using BepInEx.Preloader.Core.Patching;
namespace BepInEx.PatcherProvider;
[PatcherProviderPluginInfo("BepInExPatcherProvider", "BepInExPatcherProvider", "1.0")]
-public class BepInExPatcherProvider : BasePatcherProvider
+internal class BepInExPatcherProvider : BasePatcherProvider
{
private static readonly Dictionary AssemblyLocationsByFilename = new();
diff --git a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
index d7b0136..354e972 100644
--- a/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
+++ b/BepInEx.PluginProvider/BepInExPluginLoadContext.cs
@@ -3,7 +3,7 @@ using System.IO;
namespace BepInEx.PluginProvider;
-public class BepInExPluginLoadContext : IPluginLoadContext
+internal class BepInExPluginLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
diff --git a/BepInEx.PluginProvider/BepInExPluginProvider.cs b/BepInEx.PluginProvider/BepInExPluginProvider.cs
index bae7689..6520974 100644
--- a/BepInEx.PluginProvider/BepInExPluginProvider.cs
+++ b/BepInEx.PluginProvider/BepInExPluginProvider.cs
@@ -7,7 +7,7 @@ using BepInEx.Logging;
namespace BepInEx.PluginProvider;
[BepInPluginProvider("BepInExPluginProvider", "BepInExPluginProvider", "1.0")]
-public class BepInExPluginProvider : BasePluginProvider
+internal class BepInExPluginProvider : BasePluginProvider
{
private static readonly Dictionary AssemblyLocationsByFilename = new();
From be1beb5e135b5754ea89e0ec5b2e94a4d1438e44 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 3 Sep 2024 20:34:17 -0400
Subject: [PATCH 27/65] Address code review round 5
---
BepInEx.Core/BepInEx.Core.csproj | 3 ++
BepInEx.Core/Bootstrap/BaseChainloader.cs | 8 ++++
BepInEx.Core/Contract/IPlugin.cs | 39 +++++++++++--------
BepInEx.Core/Utility.cs | 3 +-
.../BepInEx.PatcherProvider.csproj | 1 +
.../BepInEx.PluginProvider.csproj | 1 +
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 3 +-
.../Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 4 +-
.../BepInEx.Unity.Mono/BaseUnityPlugin.cs | 1 -
.../Bootstrap/UnityChainloader.cs | 6 +++
10 files changed, 44 insertions(+), 25 deletions(-)
diff --git a/BepInEx.Core/BepInEx.Core.csproj b/BepInEx.Core/BepInEx.Core.csproj
index c4f5cd0..30a9e20 100644
--- a/BepInEx.Core/BepInEx.Core.csproj
+++ b/BepInEx.Core/BepInEx.Core.csproj
@@ -12,6 +12,9 @@
+
+
+
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index bf8646b..6b62ffd 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -19,6 +19,8 @@ public abstract class BaseChainloader
private static Regex allowedGuidRegex { get; } = new(@"^[a-zA-Z0-9\._\-]+$");
+ internal BaseChainloader() { }
+
///
/// Analyzes the given type definition and attempts to convert it to a valid
///
@@ -369,6 +371,12 @@ public abstract class BaseChainloader
Logger.Log(LogLevel.Message, "Chainloader startup complete");
}
+ [Obsolete("Use a plugin provider instead", true)]
+ public IList LoadPlugins(params string[] pluginsPaths)
+ {
+ throw new NotSupportedException("Use a plugin provider instead");
+ }
+
private void LoadPlugins(List plugins, bool provider)
{
var existingPluginInfos = provider ? Providers : Plugins;
diff --git a/BepInEx.Core/Contract/IPlugin.cs b/BepInEx.Core/Contract/IPlugin.cs
index 6963150..150d151 100644
--- a/BepInEx.Core/Contract/IPlugin.cs
+++ b/BepInEx.Core/Contract/IPlugin.cs
@@ -1,23 +1,28 @@
-using BepInEx.Configuration;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using BepInEx.Configuration;
using BepInEx.Logging;
-namespace BepInEx.Contract;
-
-internal interface IPlugin
+namespace BepInEx.Contract
{
- ///
- /// Information about this plugin as it was loaded.
- ///
- PluginInfo Info { get; }
+ public interface IPlugin
+ {
+ ///
+ /// Information about this plugin as it was loaded.
+ ///
+ PluginInfo Info { get; }
- ///
- /// Logger instance tied to this plugin.
- ///
- ManualLogSource Log { get; }
+ ///
+ /// Logger instance tied to this plugin.
+ ///
+ ManualLogSource Logger { get; }
- ///
- /// Default config file tied to this plugin. The config file will not be created until
- /// any settings are added and changed, or is called.
- ///
- ConfigFile Config { get; }
+ ///
+ /// Default config file tied to this plugin. The config file will not be created until
+ /// any settings are added and changed, or is called.
+ ///
+ ConfigFile Config { get; }
+ }
}
diff --git a/BepInEx.Core/Utility.cs b/BepInEx.Core/Utility.cs
index 3ba6f1f..08e6c35 100644
--- a/BepInEx.Core/Utility.cs
+++ b/BepInEx.Core/Utility.cs
@@ -229,8 +229,6 @@ public static class Utility
/// True, if the symbols were found. Otherwise, false.
public static bool TryResolveAssemblySymbols(string dllPath, out byte[] symbolsData)
{
- symbolsData = null;
-
var subDirectory = Path.GetDirectoryName(dllPath);
var potentialSymbolsFileNames = new[]
@@ -257,6 +255,7 @@ public static class Utility
return true;
}
+ symbolsData = null;
return false;
}
diff --git a/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj b/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
index a56204f..c37d3e6 100644
--- a/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
+++ b/BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
@@ -4,6 +4,7 @@
net35;netstandard2.0
true
BepInEx patcher provider module
+ false
diff --git a/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj b/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
index ee9c085..e22ed74 100644
--- a/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
+++ b/BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
@@ -4,6 +4,7 @@
net35;netstandard2.0
true
BepInEx plugin provider module
+ false
diff --git a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
index 4356862..d9b375a 100644
--- a/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
+++ b/Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs
@@ -1,13 +1,12 @@
using System;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
-using BepInEx.Contract;
using BepInEx.Logging;
using HarmonyLib;
namespace BepInEx.NET.Common
{
- public abstract class BasePlugin : IPlugin
+ public abstract class BasePlugin
{
protected BasePlugin()
{
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
index 825526b..1a0fda3 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs
@@ -1,13 +1,11 @@
using System;
-using BepInEx.Bootstrap;
using BepInEx.Configuration;
-using BepInEx.Contract;
using BepInEx.Logging;
using Il2CppInterop.Runtime.InteropTypes;
namespace BepInEx.Unity.IL2CPP;
-public abstract class BasePlugin : IPlugin
+public abstract class BasePlugin
{
protected BasePlugin()
{
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
index 3076c2a..21fde4f 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BaseUnityPlugin.cs
@@ -1,7 +1,6 @@
using System;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
-using BepInEx.Contract;
using BepInEx.Logging;
using UnityEngine;
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs b/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs
index 1c163b9..19769e8 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/Bootstrap/UnityChainloader.cs
@@ -42,6 +42,12 @@ public class UnityChainloader : BaseChainloader
///
public static GameObject ManagerObject { get; private set; }
+ public static new UnityChainloader Instance
+ {
+ get => (UnityChainloader)BaseChainloader.Instance;
+ set => BaseChainloader.Instance = value;
+ }
+
protected override string ConsoleTitle => _consoleTitle;
From d389ed8067e72a7bbc6374801a832c3e84d7a2d2 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 3 Sep 2024 21:09:52 -0400
Subject: [PATCH 28/65] Add versioning to CachedAssembly
---
BepInEx.Core/Bootstrap/TypeLoader.cs | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index 0c8e256..eab5723 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -70,6 +70,11 @@ public interface ICacheable
///
public class CachedAssembly where T : ICacheable
{
+ ///
+ /// The version of the cache which increments on each format changes
+ ///
+ public static int Version => 0;
+
///
/// List of cached items inside the assembly.
///
@@ -305,7 +310,9 @@ public static class TypeLoader
if (!File.Exists(path))
return null;
- using (var br = new BinaryReader(File.OpenRead(path)))
+ using var br = new BinaryReader(File.OpenRead(path));
+ var version = br.ReadInt32();
+ if (version == CachedAssembly.Version)
{
var entriesCount = br.ReadInt32();
From ae4d7bb34cee7358da9128928d180e24528fa8e0 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Wed, 4 Sep 2024 11:46:18 -0400
Subject: [PATCH 29/65] Actually write the cache version in SaveAssemblyCache
---
BepInEx.Core/Bootstrap/TypeLoader.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index eab5723..ed428b8 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -366,6 +366,7 @@ public static class TypeLoader
var path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
using var bw = new BinaryWriter(File.OpenWrite(path));
+ bw.Write(CachedAssembly.Version);
bw.Write(entries.Count);
foreach (var kv in entries)
From ede28de4a5a09451733f86475625ff20ceb37f2c Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Wed, 4 Sep 2024 11:54:53 -0400
Subject: [PATCH 30/65] Make the cache version a const
---
BepInEx.Core/Bootstrap/TypeLoader.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index ed428b8..7fb173f 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -73,7 +73,7 @@ public class CachedAssembly where T : ICacheable
///
/// The version of the cache which increments on each format changes
///
- public static int Version => 0;
+ public const int VERSION = 0;
///
/// List of cached items inside the assembly.
@@ -312,7 +312,7 @@ public static class TypeLoader
using var br = new BinaryReader(File.OpenRead(path));
var version = br.ReadInt32();
- if (version == CachedAssembly.Version)
+ if (version == CachedAssembly.VERSION)
{
var entriesCount = br.ReadInt32();
@@ -366,7 +366,7 @@ public static class TypeLoader
var path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
using var bw = new BinaryWriter(File.OpenWrite(path));
- bw.Write(CachedAssembly.Version);
+ bw.Write(CachedAssembly.VERSION);
bw.Write(entries.Count);
foreach (var kv in entries)
From 2474dce1a13f018174941bbd812c0ee606c63a5e Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Wed, 4 Sep 2024 11:58:52 -0400
Subject: [PATCH 31/65] Use PascalCase for the cache versioning const
---
BepInEx.Core/Bootstrap/TypeLoader.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index 7fb173f..63c1295 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -73,7 +73,7 @@ public class CachedAssembly where T : ICacheable
///
/// The version of the cache which increments on each format changes
///
- public const int VERSION = 0;
+ public const int Version = 0;
///
/// List of cached items inside the assembly.
@@ -312,7 +312,7 @@ public static class TypeLoader
using var br = new BinaryReader(File.OpenRead(path));
var version = br.ReadInt32();
- if (version == CachedAssembly.VERSION)
+ if (version == CachedAssembly.Version)
{
var entriesCount = br.ReadInt32();
@@ -366,7 +366,7 @@ public static class TypeLoader
var path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
using var bw = new BinaryWriter(File.OpenWrite(path));
- bw.Write(CachedAssembly.VERSION);
+ bw.Write(CachedAssembly.Version);
bw.Write(entries.Count);
foreach (var kv in entries)
From cbd70eb2f085090751af32626f6c0f12c06efa4f Mon Sep 17 00:00:00 2001
From: ManlyMarco <39247311+ManlyMarco@users.noreply.github.com>
Date: Sun, 8 Sep 2024 15:45:19 +0200
Subject: [PATCH 32/65] Automatically load all interop assemblies before
loading plugins (#777)
Assemblies are often loaded on the il2cpp side but not on the mono side, causing confusion when using reflection.
This will load all interop assemblies right before plugins are loaded, which should mostly avoid these issues at a low time cost of around 40ms.
---
.../BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs | 2 +
.../Il2CppInteropManager.cs | 38 +++++++++++++++++++
2 files changed, 40 insertions(+)
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
index 390e845..bc130b6 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs
@@ -97,6 +97,8 @@ public class IL2CPPChainloader : BaseChainloader
unhook = true;
+ Il2CppInteropManager.PreloadInteropAssemblies();
+
Instance.Execute();
}
catch (Exception ex)
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
index 4e2761a..61aa583 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
@@ -8,6 +8,8 @@ using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.Common;
@@ -88,6 +90,14 @@ internal static partial class Il2CppInteropManager
.AppendLine("{BepInEx} - Path to the BepInEx folder.")
.AppendLine("{ProcessName} - Name of the current process")
.ToString());
+
+ private static readonly ConfigEntry PreloadIL2CPPInteropAssemblies = ConfigFile.CoreConfig.Bind(
+ "IL2CPP", "PreloadIL2CPPInteropAssemblies",
+ true,
+ new StringBuilder()
+ .AppendLine("Automatically load all interop assemblies right before loading plugins.")
+ .AppendLine("Some plugins may not work properly without this, but it may cause issues in some games.")
+ .ToString());
private static readonly ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("InteropManager");
@@ -357,4 +367,32 @@ internal static partial class Il2CppInteropManager
sourceAssemblies.Do(x => x.Dispose());
}
+
+ internal static void PreloadInteropAssemblies()
+ {
+ if (!PreloadIL2CPPInteropAssemblies.Value)
+ return;
+
+ var sw = Stopwatch.StartNew();
+
+ var files = Directory.EnumerateFiles(IL2CPPInteropAssemblyPath);
+ var loaded = 0;
+ Parallel.ForEach(files, file =>
+ {
+ if (!file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) return;
+ if (file.Equals("netstandard.dll", StringComparison.OrdinalIgnoreCase)) return;
+ if (file.Equals("Il2Cppnetstandard.dll", StringComparison.OrdinalIgnoreCase)) return;
+ try
+ {
+ Assembly.LoadFrom(file);
+ Interlocked.Increment(ref loaded);
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning($"Failed to preload {file} - {e}");
+ }
+ });
+
+ Logger.LogDebug($"Preloaded {loaded} interop assemblies in {sw.ElapsedMilliseconds}ms");
+ }
}
From f800cbcc43f774d3adb0b1a9c264f0326a5d9f38 Mon Sep 17 00:00:00 2001
From: ManlyMarco <39247311+ManlyMarco@users.noreply.github.com>
Date: Sun, 8 Sep 2024 16:09:44 +0200
Subject: [PATCH 33/65] Add GlobalMetadataPath setting (#978)
---
.../Il2CppInteropManager.cs | 24 ++++++++++++++-----
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
index 61aa583..1ebf4c9 100644
--- a/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
+++ b/Runtimes/Unity/BepInEx.Unity.IL2CPP/Il2CppInteropManager.cs
@@ -99,6 +99,17 @@ internal static partial class Il2CppInteropManager
.AppendLine("Some plugins may not work properly without this, but it may cause issues in some games.")
.ToString());
+ private static readonly ConfigEntry GlobalMetadataPath = ConfigFile.CoreConfig.Bind(
+ "IL2CPP", "GlobalMetadataPath",
+ "{GameDataPath}/il2cpp_data/Metadata/global-metadata.dat",
+ new StringBuilder()
+ .AppendLine("The path to the IL2CPP metadata file.")
+ .AppendLine("Supports the following placeholders:")
+ .AppendLine("{BepInEx} - Path to the BepInEx folder.")
+ .AppendLine("{ProcessName} - Name of the current process")
+ .AppendLine("{GameDataPath} - Path to the game's Data folder.")
+ .ToString());
+
private static readonly ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("InteropManager");
private static string il2cppInteropBasePath;
@@ -289,12 +300,13 @@ internal static partial class Il2CppInteropManager
private static List RunCpp2Il()
{
- Logger.LogMessage("Running Cpp2IL to generate dummy assemblies");
-
- var metadataPath = Path.Combine(Paths.GameDataPath,
- "il2cpp_data",
- "Metadata",
- "global-metadata.dat");
+ var metadataPath = Path.Combine(Paths.GameRootPath,
+ GlobalMetadataPath.Value
+ .Replace("{BepInEx}", Paths.BepInExRootPath)
+ .Replace("{ProcessName}", Paths.ProcessName)
+ .Replace("{GameDataPath}", Paths.GameDataPath));
+
+ Logger.LogMessage("Running Cpp2IL to generate dummy assemblies from " + metadataPath);
var stopwatch = new Stopwatch();
stopwatch.Start();
From cdeaad7868599c074894dfacfe50ec307b453244 Mon Sep 17 00:00:00 2001
From: ManlyMarco <39247311+ManlyMarco@users.noreply.github.com>
Date: Tue, 10 Sep 2024 14:47:22 +0200
Subject: [PATCH 34/65] Use upload-artifact@v4 (#981)
---
.github/workflows/build.yml | 2 +-
.github/workflows/build_pr.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3c4d5c4..63d2f62 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -72,7 +72,7 @@ jobs:
]
}
- name: Upload Artifacts
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
path: "./bin/dist/*.zip"
name: "BepInEx_CI_${{ steps.info.outputs.build_type }}_${{ steps.info.outputs.sha_short }}_${{ steps.info.outputs.build_id || 0 }}"
diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml
index e0ed1d6..25a749b 100644
--- a/.github/workflows/build_pr.yml
+++ b/.github/workflows/build_pr.yml
@@ -17,7 +17,7 @@ jobs:
run: |
./build.sh --target Publish
- name: Upload Artifacts
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
path: "./bin/dist/*.zip"
name: "BepInEx_CI"
From 726b68e52b13981fd8a23011ec21df3ef9b8d530 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 10 Sep 2024 08:48:48 -0400
Subject: [PATCH 35/65] Prioritize the shallowest path for the same assembly
name in the default providers (#980)
Change the behaviors of the default providers so if multiple assemblies with the same name exists, but in different directories, ignore any that are located deeper.
---
.../BepInExPatcherProvider.cs | 24 ++++++++++++++++++-
.../BepInExPluginProvider.cs | 24 ++++++++++++++++++-
2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
index 5ae4555..7685874 100644
--- a/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
+++ b/BepInEx.PatcherProvider/BepInExPatcherProvider.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Reflection;
using BepInEx.Logging;
using BepInEx.Preloader.Core.Patching;
@@ -19,7 +20,28 @@ internal class BepInExPatcherProvider : BasePatcherProvider
{
try
{
- AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
+ var filename = Path.GetFileNameWithoutExtension(dll);
+ var foundDirectory = Path.GetDirectoryName(dll);
+
+ // Prioritize the shallowest path of each assembly name
+ if (AssemblyLocationsByFilename.TryGetValue(filename, out var existingDirectory))
+ {
+ int levelExistingDirectory = existingDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
+ int levelFoundDirectory = foundDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
+
+ bool shallowerPathFound = levelExistingDirectory > levelFoundDirectory;
+ Log.LogWarning($"Found duplicate assemblies filenames: {filename} was found at {foundDirectory} " +
+ $"while it exists already at {AssemblyLocationsByFilename[filename]}. " +
+ $"Only the {(shallowerPathFound ? "first" : "second")} will be examined and resolved");
+
+ if (levelExistingDirectory > levelFoundDirectory)
+ AssemblyLocationsByFilename[filename] = foundDirectory;
+ }
+ else
+ {
+ AssemblyLocationsByFilename.Add(filename, foundDirectory);
+ }
+
loadContexts.Add(new BepInExPatcherLoadContext
{
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
diff --git a/BepInEx.PluginProvider/BepInExPluginProvider.cs b/BepInEx.PluginProvider/BepInExPluginProvider.cs
index 6520974..6f134ab 100644
--- a/BepInEx.PluginProvider/BepInExPluginProvider.cs
+++ b/BepInEx.PluginProvider/BepInExPluginProvider.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Reflection;
using BepInEx.Logging;
@@ -18,7 +19,28 @@ internal class BepInExPluginProvider : BasePluginProvider
{
try
{
- AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
+ var filename = Path.GetFileNameWithoutExtension(dll);
+ var foundDirectory = Path.GetDirectoryName(dll);
+
+ // Prioritize the shallowest path of each assembly name
+ if (AssemblyLocationsByFilename.TryGetValue(filename, out var existingDirectory))
+ {
+ int levelExistingDirectory = existingDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
+ int levelFoundDirectory = foundDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
+
+ bool shallowerPathFound = levelExistingDirectory > levelFoundDirectory;
+ Logger.LogWarning($"Found duplicate assemblies filenames: {filename} was found at {foundDirectory} " +
+ $"while it exists already at {AssemblyLocationsByFilename[filename]}. " +
+ $"Only the {(shallowerPathFound ? "first" : "second")} will be examined and resolved");
+
+ if (levelExistingDirectory > levelFoundDirectory)
+ AssemblyLocationsByFilename[filename] = foundDirectory;
+ }
+ else
+ {
+ AssemblyLocationsByFilename.Add(filename, foundDirectory);
+ }
+
loadContexts.Add(new BepInExPluginLoadContext
{
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
From b891cecab969ee6180b62eef74a9783bf0ddf1ae Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 17 Sep 2024 12:18:48 -0400
Subject: [PATCH 36/65] Upgrade the github workflows sdk to 8.0.x (#992)
---
.github/workflows/build.yml | 4 ++--
.github/workflows/build_pr.yml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 63d2f62..5533275 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -34,9 +34,9 @@ jobs:
branch: master
workflow_id: build.yml
github_token: ${{ secrets.GITHUB_TOKEN }}
- - uses: actions/setup-dotnet@v1
+ - uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Build
run: |
./build.sh --target Publish --build-type ${{ steps.info.outputs.build_type }} --build-id ${{ steps.info.outputs.build_id }} --last-build-commit \"${{ steps.last_successful_commit.outputs.commit_hash }}\" --nuget-api-key \"${{ secrets.BEPIN_DEV_NUGET_KEY }}\"
diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml
index 25a749b..fae513f 100644
--- a/.github/workflows/build_pr.yml
+++ b/.github/workflows/build_pr.yml
@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.5
- - uses: actions/setup-dotnet@v1
+ - uses: actions/setup-dotnet@v4
with:
- dotnet-version: "6.0.x"
+ dotnet-version: "8.0.x"
- name: Build
run: |
./build.sh --target Publish
From a5b7dabc1d8d18489d2835cbc25d888ac421268b Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 15 Sep 2024 18:31:17 -0400
Subject: [PATCH 37/65] Add error when using *nix build with Windows executable
Originally commit 8c7dcf265c5bf52800fa356fd3403a4dcadb82f3
Co-authored-by: ghorsington
---
Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh | 6 ++++++
Runtimes/Unity/Doorstop/run_bepinex_mono.sh | 6 ++++++
2 files changed, 12 insertions(+)
diff --git a/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh b/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh
index e947ee4..86ef48c 100644
--- a/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh
+++ b/Runtimes/Unity/Doorstop/run_bepinex_il2cpp.sh
@@ -183,6 +183,12 @@ echo "${executable_path}"
# Figure out the arch of the executable with file
file_out="$(LD_PRELOAD="" file -b "${executable_path}")"
case "${file_out}" in
+ *PE32*)
+ echo "The executable is a Windows executable file. You must use Wine/Proton and BepInEx for Windows with this executable."
+ echo "Uninstall BepInEx for *nix and install BepInEx for Windows instead."
+ echo "More info: https://docs.bepinex.dev/articles/advanced/steam_interop.html#protonwine"
+ exit 1
+ ;;
*64-bit*)
arch="x64"
;;
diff --git a/Runtimes/Unity/Doorstop/run_bepinex_mono.sh b/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
index aaa7496..2f743c1 100644
--- a/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
+++ b/Runtimes/Unity/Doorstop/run_bepinex_mono.sh
@@ -184,6 +184,12 @@ echo "${executable_path}"
# Figure out the arch of the executable with file
file_out="$(LD_PRELOAD="" file -b "${executable_path}")"
case "${file_out}" in
+ *PE32*)
+ echo "The executable is a Windows executable file. You must use Wine/Proton and BepInEx for Windows with this executable."
+ echo "Uninstall BepInEx for *nix and install BepInEx for Windows instead."
+ echo "More info: https://docs.bepinex.dev/articles/advanced/steam_interop.html#protonwine"
+ exit 1
+ ;;
*64-bit*)
arch="x64"
;;
From 92f7d25698c4bb75484b935569dd3052f40b00f7 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 15 Sep 2024 18:51:56 -0400
Subject: [PATCH 38/65] Print file write data in console
Originally commit 6c5766d5abc230a4c9427ebb14acfca05255efb8
Co-authored-by: ghorsington
---
BepInEx.Preloader.Core/Logging/ChainloaderLogHelper.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/BepInEx.Preloader.Core/Logging/ChainloaderLogHelper.cs b/BepInEx.Preloader.Core/Logging/ChainloaderLogHelper.cs
index c4a167d..43e48e0 100644
--- a/BepInEx.Preloader.Core/Logging/ChainloaderLogHelper.cs
+++ b/BepInEx.Preloader.Core/Logging/ChainloaderLogHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using BepInEx.Logging;
@@ -38,7 +39,7 @@ public static class ChainloaderLogHelper
var versionMini = new SemanticVersioning.Version(bepinVersion.Major, bepinVersion.Minor, bepinVersion.Patch,
bepinVersion.PreRelease);
var consoleTitle = $"BepInEx {versionMini} - {Paths.ProcessName}";
- log.Log(LogLevel.Message, consoleTitle);
+ log.Log(LogLevel.Message, $"{consoleTitle} ({File.GetLastWriteTime(Paths.ExecutablePath)})");
if (ConsoleManager.ConsoleActive)
ConsoleManager.SetConsoleTitle(consoleTitle);
From 6e846fc70a6b3eb9865455f48570abb36e049728 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 15 Sep 2024 18:55:23 -0400
Subject: [PATCH 39/65] Preload Harmony and MonoMod
Originally commit a1bb117c86a15b5144eaad5575a3cd73d09e3283
Co-authored-by: ghorsington
---
.../BepInEx.Unity.Mono.Preloader/UnityPreloaderRunner.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloaderRunner.cs b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloaderRunner.cs
index 0bacd38..0f063da 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloaderRunner.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono.Preloader/UnityPreloaderRunner.cs
@@ -22,7 +22,10 @@ internal static class UnityPreloaderRunner
"Mono.Cecil.dll",
"Mono.Cecil.Mdb.dll",
"Mono.Cecil.Pdb.dll",
- "Mono.Cecil.Rocks.dll"
+ "Mono.Cecil.Rocks.dll",
+ "MonoMod.Utils.dll",
+ "MonoMod.RuntimeDetour.dll",
+ "0Harmony.dll"
};
private static void LoadCriticalAssemblies()
From 8597ce7ebf9f18b024b6ef60419d3846f228c86e Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 15 Sep 2024 19:03:19 -0400
Subject: [PATCH 40/65] Add TomlTypeConverter for UnityEngine.Rect
Originally pull request #510
Co-authored-by: ManlyMarco <39247311+manlymarco@users.noreply.github.com>
---
.../UnityTomlTypeConverters.cs | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/UnityTomlTypeConverters.cs b/Runtimes/Unity/BepInEx.Unity.Mono/UnityTomlTypeConverters.cs
index 3f4da2e..56f51d3 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/UnityTomlTypeConverters.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/UnityTomlTypeConverters.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Runtime.CompilerServices;
using BepInEx.Configuration;
using UnityEngine;
@@ -36,5 +37,47 @@ internal static class UnityTomlTypeConverters
TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
+ TomlTypeConverter.AddConverter(typeof(Rect), new TypeConverter
+ {
+ ConvertToObject = StringToRect,
+ ConvertToString = RectToString
+ });
+ }
+
+ private static object StringToRect(string s, Type type)
+ {
+ // JsonUtility doesn't work with Rect on all Unity versions, so parse it manually
+ Rect result = default(Rect);
+
+ if (s == null)
+ return result;
+
+ string cleaned = s.Trim('{', '}').Replace(" ", "");
+ foreach (string part in cleaned.Split(','))
+ {
+ string[] parts = part.Split(':');
+ if (parts.Length != 2 || !float.TryParse(parts[1], out float value))
+ continue;
+
+ string id = parts[0].Trim('"');
+ if (id == "x")
+ result.x = value;
+ else if (id == "y")
+ result.y = value;
+ else if (id == "width" || id == "z")
+ result.width = value;
+ else if (id == "height" || id == "w")
+ result.height = value;
+ }
+
+ return result;
+ }
+
+ private static string RectToString(object o, Type type)
+ {
+ Rect rect = (Rect)o;
+ return string.Format(CultureInfo.InvariantCulture,
+ "{{ \"x\":{0}, \"y\":{1}, \"width\":{2}, \"height\":{3} }}",
+ rect.x, rect.y, rect.width, rect.height);
}
}
From af5d93917cfa0568ff40153009b2287ab564482f Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 15 Sep 2024 19:49:52 -0400
Subject: [PATCH 41/65] Make Logger.Listeners and Logger.Sources thread safe
Originally pull request #656
Co-authored-by: ManlyMarco <39247311+manlymarco@users.noreply.github.com>
---
BepInEx.Core/Logging/Logger.cs | 183 +++++++++++++++++++++++++++------
1 file changed, 151 insertions(+), 32 deletions(-)
diff --git a/BepInEx.Core/Logging/Logger.cs b/BepInEx.Core/Logging/Logger.cs
index e78842e..6590059 100644
--- a/BepInEx.Core/Logging/Logger.cs
+++ b/BepInEx.Core/Logging/Logger.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BepInEx.Core.Logging.Interpolation;
@@ -25,7 +26,7 @@ public static class Logger
///
/// Log levels that are currently listened to by at least one listener.
///
- public static LogLevel ListenedLogLevels => listeners.activeLogLevels;
+ public static LogLevel ListenedLogLevels => listeners.ActiveLogLevels;
///
/// Collection of all log listeners that receive log events.
@@ -39,10 +40,7 @@ public static class Logger
internal static void InternalLogEvent(object sender, LogEventArgs eventArgs)
{
- // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator Prevent extra allocations
- foreach (var listener in listeners)
- if ((eventArgs.Level & listener.LogLevelFilter) != LogLevel.None)
- listener?.LogEvent(sender, eventArgs);
+ listeners.SendLogEvent(sender, eventArgs);
}
///
@@ -74,69 +72,190 @@ public static class Logger
return source;
}
- private class LogListenerCollection : List, ICollection
+ private class LogListenerCollection : ThreadSafeCollection
{
- public LogLevel activeLogLevels = LogLevel.None;
+ internal LogLevel ActiveLogLevels = LogLevel.None;
- void ICollection.Add(ILogListener item)
+ internal void SendLogEvent(object sender, LogEventArgs eventArgs)
+ {
+ // Do this instead of foreach to avoid boxing, also very slightly faster
+ var aListInTime = BaseList;
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (int i = 0; i < aListInTime.Count; i++)
+ {
+ if ((eventArgs.Level & aListInTime[i].LogLevelFilter) != LogLevel.None)
+ aListInTime[i].LogEvent(sender, eventArgs);
+ }
+ }
+
+ public override void Add(ILogListener item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
- activeLogLevels |= item.LogLevelFilter;
-
- base.Add(item);
+ lock (SpinLock)
+ {
+ ActiveLogLevels |= item.LogLevelFilter;
+ base.Add(item);
+ }
}
- void ICollection.Clear()
+ public override void Clear()
{
- activeLogLevels = LogLevel.None;
- base.Clear();
+ lock (SpinLock)
+ {
+ ActiveLogLevels = LogLevel.None;
+ base.Clear();
+ }
}
- bool ICollection.Remove(ILogListener item)
+ public override bool Remove(ILogListener item)
{
if (item == null || !base.Remove(item))
return false;
- activeLogLevels = LogLevel.None;
+ lock (SpinLock)
+ {
+ ActiveLogLevels = LogLevel.None;
- foreach (var i in this)
- activeLogLevels |= i.LogLevelFilter;
+ foreach (var i in this)
+ ActiveLogLevels |= i.LogLevelFilter;
- return true;
+ return true;
+ }
}
}
- private class LogSourceCollection : List, ICollection
+ private class LogSourceCollection : ThreadSafeCollection
{
- void ICollection.Add(ILogSource item)
+ public override void Add(ILogSource item)
{
if (item == null)
throw new ArgumentNullException(nameof(item),
"Log sources cannot be null when added to the source list.");
- item.LogEvent += InternalLogEvent;
-
- base.Add(item);
+ lock (SpinLock)
+ {
+ item.LogEvent += InternalLogEvent;
+ base.Add(item);
+ var copy = new List(BaseList.Count + 1);
+ copy.AddRange(BaseList);
+ copy.Add(item);
+ BaseList = copy;
+ }
}
- void ICollection.Clear()
+ public override void Clear()
{
- foreach (var item in this)
- item.LogEvent -= InternalLogEvent;
+ if (Count == 0)
+ return;
- base.Clear();
+ lock (SpinLock)
+ {
+ for (var i = 0; i < BaseList.Count; i++)
+ BaseList[i].LogEvent -= InternalLogEvent;
+
+ BaseList = [];
+ }
}
- bool ICollection.Remove(ILogSource item)
+ public override bool Remove(ILogSource item)
{
- if (item == null || !Remove(item))
+ if (item == null)
return false;
- item.LogEvent -= InternalLogEvent;
- return true;
+ lock (SpinLock)
+ {
+ var wasPresent = base.Remove(item);
+ if (wasPresent)
+ item.LogEvent -= InternalLogEvent;
+ return wasPresent;
+ }
+ }
+ }
+
+ ///
+ /// Simple thread safe list that prioritizes read speed over write speed.
+ /// Read is the same as a normal list, while write locks and allocates a copy of the list.
+ /// Logger lists are rarely updated so this tradeoff should be fine.
+ ///
+ ///
+ private class ThreadSafeCollection : ICollection where T : class
+ {
+ protected readonly object SpinLock = new();
+ protected List BaseList = [];
+
+ public int Count => BaseList.Count;
+
+ public bool IsReadOnly => false;
+
+ public IEnumerator GetEnumerator()
+ {
+ // Can't avoid boxing
+ return BaseList.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ // Can't avoid boxing
+ return BaseList.GetEnumerator();
+ }
+
+ public virtual void Add(T item)
+ {
+ if (item == null)
+ throw new ArgumentNullException(nameof(item), "item can't be null");
+
+ lock (SpinLock)
+ {
+ var copy = new List(BaseList.Count + 1);
+ copy.AddRange(BaseList);
+ copy.Add(item);
+ BaseList = copy;
+ }
+ }
+
+ public virtual void Clear()
+ {
+ if (Count == 0)
+ return;
+
+ lock (SpinLock)
+ BaseList = [];
+ }
+
+ public bool Contains(T item)
+ {
+ return BaseList.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ BaseList.CopyTo(array, arrayIndex);
+ }
+
+ public virtual bool Remove(T item)
+ {
+ if (item == null)
+ return false;
+
+ lock (SpinLock)
+ {
+ var copy = new List(BaseList.Count);
+ var any = false;
+ for (int i = 0; i < BaseList.Count; i++)
+ {
+ var existingItem = BaseList[i];
+ if (existingItem.Equals(item))
+ any = true;
+ else
+ copy.Add(existingItem);
+ }
+
+ BaseList = copy;
+ return any;
+ }
}
}
}
From d9b95a0ac76e53c4f0f2be93939adb02cfde16d4 Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Sun, 15 Sep 2024 22:03:32 -0400
Subject: [PATCH 42/65] Add input abstraction layer to support games with the
Input class disabled
Originally pull request #444
Co-authored-by: ManlyMarco <39247311+manlymarco@users.noreply.github.com>
---
.../BepInEx.Unity.Mono.csproj | 1 +
.../Configuration/KeyboardShortcut.cs | 26 +-
.../Unity/BepInEx.Unity.Mono/UnityInput.cs | 854 ++++++++++++++++++
3 files changed, 866 insertions(+), 15 deletions(-)
create mode 100644 Runtimes/Unity/BepInEx.Unity.Mono/UnityInput.cs
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
index 7f49885..558ed33 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/BepInEx.Unity.Mono.csproj
@@ -30,5 +30,6 @@
+
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/Configuration/KeyboardShortcut.cs b/Runtimes/Unity/BepInEx.Unity.Mono/Configuration/KeyboardShortcut.cs
index 5e7f37b..9867f0b 100644
--- a/Runtimes/Unity/BepInEx.Unity.Mono/Configuration/KeyboardShortcut.cs
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/Configuration/KeyboardShortcut.cs
@@ -35,17 +35,8 @@ public struct KeyboardShortcut
///
public static readonly KeyboardShortcut Empty = new();
- ///
- /// All KeyCode values that can be used in a keyboard shortcut.
- ///
- public static readonly IEnumerable AllKeyCodes = (KeyCode[]) Enum.GetValues(typeof(KeyCode));
-
// Don't block hotkeys if mouse is being pressed, e.g. when shooting and trying to strafe
- private static readonly KeyCode[] _modifierBlockKeyCodes = AllKeyCodes.Except(new[]
- {
- KeyCode.Mouse0, KeyCode.Mouse1, KeyCode.Mouse2, KeyCode.Mouse3, KeyCode.Mouse4, KeyCode.Mouse5,
- KeyCode.Mouse6, KeyCode.None
- }).ToArray();
+ private static KeyCode[] modifierBlockKeyCodes;
private readonly KeyCode[] _allKeys;
@@ -122,7 +113,7 @@ public struct KeyboardShortcut
var mainKey = MainKey;
if (mainKey == KeyCode.None) return false;
- return Input.GetKeyDown(mainKey) && ModifierKeyTest();
+ return UnityInput.Current.GetKeyDown(mainKey) && ModifierKeyTest();
}
///
@@ -133,7 +124,7 @@ public struct KeyboardShortcut
var mainKey = MainKey;
if (mainKey == KeyCode.None) return false;
- return Input.GetKey(mainKey) && ModifierKeyTest();
+ return UnityInput.Current.GetKey(mainKey) && ModifierKeyTest();
}
///
@@ -144,7 +135,7 @@ public struct KeyboardShortcut
var mainKey = MainKey;
if (mainKey == KeyCode.None) return false;
- return Input.GetKeyUp(mainKey) && ModifierKeyTest();
+ return UnityInput.Current.GetKeyUp(mainKey) && ModifierKeyTest();
}
private bool ModifierKeyTest()
@@ -152,10 +143,15 @@ public struct KeyboardShortcut
var allKeys = _allKeys;
var mainKey = MainKey;
- var allModifiersPressed = allKeys.All(c => c == mainKey || Input.GetKey(c));
+ var allModifiersPressed = allKeys.All(c => c == mainKey || UnityInput.Current.GetKey(c));
if (!allModifiersPressed) return false;
- var noOtherModifiersPressed = _modifierBlockKeyCodes.All(c => !Input.GetKey(c) || allKeys.Contains(c));
+ // Lazy init to make sure the game is initialized, and we're in main thread before calling UnityInput.Current
+ modifierBlockKeyCodes ??= UnityInput.Current.SupportedKeyCodes.Except([KeyCode.Mouse0, KeyCode.Mouse1,
+ KeyCode.Mouse2, KeyCode.Mouse3, KeyCode.Mouse4, KeyCode.Mouse5, KeyCode.Mouse6, KeyCode.None
+ ]).ToArray();
+
+ bool noOtherModifiersPressed = modifierBlockKeyCodes.All(c => !UnityInput.Current.GetKey(c) || allKeys.Contains(c));
return noOtherModifiersPressed;
}
diff --git a/Runtimes/Unity/BepInEx.Unity.Mono/UnityInput.cs b/Runtimes/Unity/BepInEx.Unity.Mono/UnityInput.cs
new file mode 100644
index 0000000..4d72455
--- /dev/null
+++ b/Runtimes/Unity/BepInEx.Unity.Mono/UnityInput.cs
@@ -0,0 +1,854 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BepInEx.Logging;
+using UnityEngine;
+using UnityEngine.InputSystem;
+using UnityEngine.InputSystem.Controls;
+
+// ReSharper disable InconsistentNaming
+
+namespace BepInEx
+{
+ ///
+ /// Abstraction layer over Unity's input systems for use in universal plugins that need to use hotkeys.
+ /// It can use either Input or Unity.InputSystem, depending on what's available. Input is preferred.
+ /// WARNING: Use only inside of Unity's main thread!
+ ///
+ public class UnityInput
+ {
+ private static IInputSystem current;
+ ///
+ /// Best currently supported input system.
+ ///
+ public static IInputSystem Current
+ {
+ get
+ {
+ if (current == null)
+ {
+ try
+ {
+ Input.GetKeyDown(KeyCode.A);
+ current = new LegacyInputSystem();
+ Logging.Logger.Log(LogLevel.Debug, "[UnityInput] Using LegacyInputSystem");
+ }
+ catch (InvalidOperationException)
+ {
+ current = new NewInputSystem();
+ Logging.Logger.Log(LogLevel.Debug, "[UnityInput] Using NewInputSystem");
+ }
+ catch (Exception ex)
+ {
+ Logging.Logger.Log(LogLevel.Warning, "[UnityInput] Failed to detect available input systems - " + ex);
+ }
+ }
+ return current;
+ }
+ }
+
+ ///
+ /// True if the Input class is not disabled.
+ ///
+ public bool LegacyInputSystemAvailable => Current is LegacyInputSystem;
+ }
+
+ ///
+ /// Generic input system interface. Just barely good enough for hotkeys.
+ ///
+ public interface IInputSystem
+ {
+ // No easy way to use these with the new input system
+ //bool GetButton(string buttonName);
+ //bool GetButtonDown(string buttonName);
+ //bool GetButtonUp(string buttonName);
+
+ ///
+ bool GetKey(string name);
+ ///
+ bool GetKey(KeyCode key);
+
+ ///
+ bool GetKeyDown(string name);
+ ///
+ bool GetKeyDown(KeyCode key);
+
+ ///
+ bool GetKeyUp(string name);
+ ///
+ bool GetKeyUp(KeyCode key);
+
+
+ ///
+ bool GetMouseButton(int button);
+ ///
+ bool GetMouseButtonDown(int button);
+ ///
+ bool GetMouseButtonUp(int button);
+
+ ///
+ void ResetInputAxes();
+
+ ///
+ Vector3 mousePosition { get; }
+ ///
+ Vector2 mouseScrollDelta { get; }
+
+ ///
+ bool mousePresent { get; }
+
+ ///
+ bool anyKey { get; }
+ ///
+ bool anyKeyDown { get; }
+
+ ///
+ /// All KeyCodes supported by the current input system.
+ ///
+ IEnumerable SupportedKeyCodes { get; }
+ }
+
+ internal class NewInputSystem : IInputSystem
+ {
+ public bool GetKey(string name) => GetControl(name)?.isPressed ?? false;
+
+ public bool GetKey(KeyCode key) => GetControl(key)?.isPressed ?? false;
+
+ public bool GetKeyDown(string name) => GetControl(name)?.wasPressedThisFrame ?? false;
+
+ public bool GetKeyDown(KeyCode key) => GetControl(key)?.wasPressedThisFrame ?? false;
+
+ public bool GetKeyUp(string name) => GetControl(name)?.wasReleasedThisFrame ?? false;
+
+ public bool GetKeyUp(KeyCode key) => GetControl(key)?.wasReleasedThisFrame ?? false;
+
+ public bool GetMouseButton(int button) => GetControl(KeyCode.Mouse0 + button)?.isPressed ?? false;
+
+ public bool GetMouseButtonDown(int button) => GetControl(KeyCode.Mouse0 + button)?.wasPressedThisFrame ?? false;
+
+ public bool GetMouseButtonUp(int button) => GetControl(KeyCode.Mouse0 + button)?.wasReleasedThisFrame ?? false;
+
+ public void ResetInputAxes() { /*Not supported*/ }
+
+ public Vector3 mousePosition => Mouse.current.position.ReadValue();
+ public Vector2 mouseScrollDelta => Mouse.current.scroll.ReadValue();
+ public bool mousePresent => Mouse.current.enabled;
+
+ public bool anyKey
+ {
+ get
+ {
+ if (Keyboard.current.anyKey.isPressed)
+ return true;
+ var current = Mouse.current;
+ return current.leftButton.isPressed ||
+ current.rightButton.isPressed ||
+ current.forwardButton.isPressed ||
+ current.backButton.isPressed ||
+ current.middleButton.isPressed;
+ }
+ }
+
+ public bool anyKeyDown
+ {
+ get
+ {
+ if (Keyboard.current.anyKey.wasPressedThisFrame)
+ return true;
+ var current = Mouse.current;
+ return current.leftButton.wasPressedThisFrame ||
+ current.rightButton.wasPressedThisFrame ||
+ current.forwardButton.wasPressedThisFrame ||
+ current.backButton.wasPressedThisFrame ||
+ current.middleButton.wasPressedThisFrame;
+ }
+ }
+
+ public IEnumerable SupportedKeyCodes { get; } = Enum.GetValues(typeof(KeyCode)).Cast().Where(x => GetControl(x, true) != null).ToList();
+
+ private static ButtonControl GetControl(string name) => GetControl((KeyCode)Enum.Parse(typeof(KeyCode), name, true));
+
+ private static ButtonControl GetControl(KeyCode key, bool silent = false)
+ {
+ switch (key)
+ {
+ case KeyCode.None:
+ break;
+ case KeyCode.Backspace:
+ return Keyboard.current.backspaceKey;
+ case KeyCode.Delete:
+ return Keyboard.current.deleteKey;
+ case KeyCode.Tab:
+ return Keyboard.current.tabKey;
+ case KeyCode.Clear:
+ break;
+ case KeyCode.Return:
+ return Keyboard.current.enterKey;
+ case KeyCode.Pause:
+ return Keyboard.current.pauseKey;
+ case KeyCode.Escape:
+ return Keyboard.current.escapeKey;
+ case KeyCode.Space:
+ return Keyboard.current.spaceKey;
+ case KeyCode.Keypad0:
+ return Keyboard.current.numpad0Key;
+ case KeyCode.Keypad1:
+ return Keyboard.current.numpad1Key;
+ case KeyCode.Keypad2:
+ return Keyboard.current.numpad2Key;
+ case KeyCode.Keypad3:
+ return Keyboard.current.numpad3Key;
+ case KeyCode.Keypad4:
+ return Keyboard.current.numpad4Key;
+ case KeyCode.Keypad5:
+ return Keyboard.current.numpad5Key;
+ case KeyCode.Keypad6:
+ return Keyboard.current.numpad6Key;
+ case KeyCode.Keypad7:
+ return Keyboard.current.numpad7Key;
+ case KeyCode.Keypad8:
+ return Keyboard.current.numpad8Key;
+ case KeyCode.Keypad9:
+ return Keyboard.current.numpad9Key;
+ case KeyCode.KeypadPeriod:
+ return Keyboard.current.numpadPeriodKey;
+ case KeyCode.KeypadDivide:
+ return Keyboard.current.numpadDivideKey;
+ case KeyCode.KeypadMultiply:
+ return Keyboard.current.numpadMultiplyKey;
+ case KeyCode.KeypadMinus:
+ return Keyboard.current.numpadMinusKey;
+ case KeyCode.KeypadPlus:
+ return Keyboard.current.numpadPlusKey;
+ case KeyCode.KeypadEnter:
+ return Keyboard.current.numpadEnterKey;
+ case KeyCode.KeypadEquals:
+ return Keyboard.current.numpadEqualsKey;
+ case KeyCode.UpArrow:
+ return Keyboard.current.upArrowKey;
+ case KeyCode.DownArrow:
+ return Keyboard.current.downArrowKey;
+ case KeyCode.RightArrow:
+ return Keyboard.current.rightArrowKey;
+ case KeyCode.LeftArrow:
+ return Keyboard.current.leftArrowKey;
+ case KeyCode.Insert:
+ return Keyboard.current.insertKey;
+ case KeyCode.Home:
+ return Keyboard.current.homeKey;
+ case KeyCode.End:
+ return Keyboard.current.endKey;
+ case KeyCode.PageUp:
+ return Keyboard.current.pageUpKey;
+ case KeyCode.PageDown:
+ return Keyboard.current.pageDownKey;
+ case KeyCode.F1:
+ return Keyboard.current.f1Key;
+ case KeyCode.F2:
+ return Keyboard.current.f2Key;
+ case KeyCode.F3:
+ return Keyboard.current.f3Key;
+ case KeyCode.F4:
+ return Keyboard.current.f4Key;
+ case KeyCode.F5:
+ return Keyboard.current.f5Key;
+ case KeyCode.F6:
+ return Keyboard.current.f6Key;
+ case KeyCode.F7:
+ return Keyboard.current.f7Key;
+ case KeyCode.F8:
+ return Keyboard.current.f8Key;
+ case KeyCode.F9:
+ return Keyboard.current.f9Key;
+ case KeyCode.F10:
+ return Keyboard.current.f10Key;
+ case KeyCode.F11:
+ return Keyboard.current.f11Key;
+ case KeyCode.F12:
+ return Keyboard.current.f12Key;
+ case KeyCode.F13:
+ break;
+ case KeyCode.F14:
+ break;
+ case KeyCode.F15:
+ break;
+ case KeyCode.Alpha0:
+ return Keyboard.current.digit0Key;
+ case KeyCode.Alpha1:
+ return Keyboard.current.digit1Key;
+ case KeyCode.Alpha2:
+ return Keyboard.current.digit2Key;
+ case KeyCode.Alpha3:
+ return Keyboard.current.digit3Key;
+ case KeyCode.Alpha4:
+ return Keyboard.current.digit4Key;
+ case KeyCode.Alpha5:
+ return Keyboard.current.digit5Key;
+ case KeyCode.Alpha6:
+ return Keyboard.current.digit6Key;
+ case KeyCode.Alpha7:
+ return Keyboard.current.digit7Key;
+ case KeyCode.Alpha8:
+ return Keyboard.current.digit8Key;
+ case KeyCode.Alpha9:
+ return Keyboard.current.digit9Key;
+ case KeyCode.Exclaim:
+ break;
+ case KeyCode.DoubleQuote:
+ break;
+ case KeyCode.Hash:
+ break;
+ case KeyCode.Dollar:
+ break;
+ case KeyCode.Ampersand:
+ break;
+ case KeyCode.Quote:
+ return Keyboard.current.quoteKey;
+ case KeyCode.LeftParen:
+ break;
+ case KeyCode.RightParen:
+ break;
+ case KeyCode.Asterisk:
+ break;
+ case KeyCode.Plus:
+ return Keyboard.current.numpadPlusKey;
+ case KeyCode.Comma:
+ return Keyboard.current.commaKey;
+ case KeyCode.Minus:
+ return Keyboard.current.minusKey;
+ case KeyCode.Period:
+ return Keyboard.current.periodKey;
+ case KeyCode.Slash:
+ return Keyboard.current.slashKey;
+ case KeyCode.Colon:
+ break;
+ case KeyCode.Semicolon:
+ return Keyboard.current.semicolonKey;
+ case KeyCode.Less:
+ break;
+ case KeyCode.Equals:
+ return Keyboard.current.equalsKey;
+ case KeyCode.Greater:
+ break;
+ case KeyCode.Question:
+ break;
+ case KeyCode.At:
+ break;
+ case KeyCode.LeftBracket:
+ return Keyboard.current.leftBracketKey;
+ case KeyCode.Backslash:
+ return Keyboard.current.backslashKey;
+ case KeyCode.RightBracket:
+ return Keyboard.current.rightBracketKey;
+ case KeyCode.Caret:
+ break;
+ case KeyCode.Underscore:
+ break;
+ case KeyCode.BackQuote:
+ return Keyboard.current.backquoteKey;
+ case KeyCode.A:
+ return Keyboard.current.aKey;
+ case KeyCode.B:
+ return Keyboard.current.bKey;
+ case KeyCode.C:
+ return Keyboard.current.cKey;
+ case KeyCode.D:
+ return Keyboard.current.dKey;
+ case KeyCode.E:
+ return Keyboard.current.eKey;
+ case KeyCode.F:
+ return Keyboard.current.fKey;
+ case KeyCode.G:
+ return Keyboard.current.gKey;
+ case KeyCode.H:
+ return Keyboard.current.hKey;
+ case KeyCode.I:
+ return Keyboard.current.iKey;
+ case KeyCode.J:
+ return Keyboard.current.jKey;
+ case KeyCode.K:
+ return Keyboard.current.kKey;
+ case KeyCode.L:
+ return Keyboard.current.lKey;
+ case KeyCode.M:
+ return Keyboard.current.mKey;
+ case KeyCode.N:
+ return Keyboard.current.nKey;
+ case KeyCode.O:
+ return Keyboard.current.oKey;
+ case KeyCode.P:
+ return Keyboard.current.pKey;
+ case KeyCode.Q:
+ return Keyboard.current.qKey;
+ case KeyCode.R:
+ return Keyboard.current.rKey;
+ case KeyCode.S:
+ return Keyboard.current.sKey;
+ case KeyCode.T:
+ return Keyboard.current.tKey;
+ case KeyCode.U:
+ return Keyboard.current.uKey;
+ case KeyCode.V:
+ return Keyboard.current.vKey;
+ case KeyCode.W:
+ return Keyboard.current.wKey;
+ case KeyCode.X:
+ return Keyboard.current.xKey;
+ case KeyCode.Y:
+ return Keyboard.current.yKey;
+ case KeyCode.Z:
+ return Keyboard.current.zKey;
+ case KeyCode.Numlock:
+ return Keyboard.current.numLockKey;
+ case KeyCode.CapsLock:
+ return Keyboard.current.capsLockKey;
+ case KeyCode.ScrollLock:
+ return Keyboard.current.scrollLockKey;
+ case KeyCode.RightShift:
+ return Keyboard.current.rightShiftKey;
+ case KeyCode.LeftShift:
+ return Keyboard.current.leftShiftKey;
+ case KeyCode.RightControl:
+ return Keyboard.current.rightCtrlKey;
+ case KeyCode.LeftControl:
+ return Keyboard.current.leftCtrlKey;
+ case KeyCode.RightAlt:
+ return Keyboard.current.rightAltKey;
+ case KeyCode.LeftAlt:
+ return Keyboard.current.leftAltKey;
+ case KeyCode.LeftCommand:
+ return Keyboard.current.leftCommandKey;
+ case KeyCode.LeftWindows:
+ return Keyboard.current.leftWindowsKey;
+ case KeyCode.RightCommand:
+ return Keyboard.current.rightCommandKey;
+ case KeyCode.RightWindows:
+ return Keyboard.current.rightWindowsKey;
+ case KeyCode.AltGr:
+ break;
+ case KeyCode.Help:
+ break;
+ case KeyCode.Print:
+ return Keyboard.current.printScreenKey;
+ case KeyCode.SysReq:
+ break;
+ case KeyCode.Break:
+ break;
+ case KeyCode.Menu:
+ return Keyboard.current.contextMenuKey;
+ case KeyCode.Mouse0:
+ return Mouse.current.leftButton;
+ case KeyCode.Mouse1:
+ return Mouse.current.rightButton;
+ case KeyCode.Mouse2:
+ return Mouse.current.middleButton;
+ case KeyCode.Mouse3:
+ return Mouse.current.backButton;
+ case KeyCode.Mouse4:
+ return Mouse.current.forwardButton;
+ case KeyCode.Mouse5:
+ break;
+ case KeyCode.Mouse6:
+ break;
+ case KeyCode.JoystickButton0:
+ break;
+ case KeyCode.JoystickButton1:
+ break;
+ case KeyCode.JoystickButton2:
+ break;
+ case KeyCode.JoystickButton3:
+ break;
+ case KeyCode.JoystickButton4:
+ break;
+ case KeyCode.JoystickButton5:
+ break;
+ case KeyCode.JoystickButton6:
+ break;
+ case KeyCode.JoystickButton7:
+ break;
+ case KeyCode.JoystickButton8:
+ break;
+ case KeyCode.JoystickButton9:
+ break;
+ case KeyCode.JoystickButton10:
+ break;
+ case KeyCode.JoystickButton11:
+ break;
+ case KeyCode.JoystickButton12:
+ break;
+ case KeyCode.JoystickButton13:
+ break;
+ case KeyCode.JoystickButton14:
+ break;
+ case KeyCode.JoystickButton15:
+ break;
+ case KeyCode.JoystickButton16:
+ break;
+ case KeyCode.JoystickButton17:
+ break;
+ case KeyCode.JoystickButton18:
+ break;
+ case KeyCode.JoystickButton19:
+ break;
+ case KeyCode.Joystick1Button0:
+ break;
+ case KeyCode.Joystick1Button1:
+ break;
+ case KeyCode.Joystick1Button2:
+ break;
+ case KeyCode.Joystick1Button3:
+ break;
+ case KeyCode.Joystick1Button4:
+ break;
+ case KeyCode.Joystick1Button5:
+ break;
+ case KeyCode.Joystick1Button6:
+ break;
+ case KeyCode.Joystick1Button7:
+ break;
+ case KeyCode.Joystick1Button8:
+ break;
+ case KeyCode.Joystick1Button9:
+ break;
+ case KeyCode.Joystick1Button10:
+ break;
+ case KeyCode.Joystick1Button11:
+ break;
+ case KeyCode.Joystick1Button12:
+ break;
+ case KeyCode.Joystick1Button13:
+ break;
+ case KeyCode.Joystick1Button14:
+ break;
+ case KeyCode.Joystick1Button15:
+ break;
+ case KeyCode.Joystick1Button16:
+ break;
+ case KeyCode.Joystick1Button17:
+ break;
+ case KeyCode.Joystick1Button18:
+ break;
+ case KeyCode.Joystick1Button19:
+ break;
+ case KeyCode.Joystick2Button0:
+ break;
+ case KeyCode.Joystick2Button1:
+ break;
+ case KeyCode.Joystick2Button2:
+ break;
+ case KeyCode.Joystick2Button3:
+ break;
+ case KeyCode.Joystick2Button4:
+ break;
+ case KeyCode.Joystick2Button5:
+ break;
+ case KeyCode.Joystick2Button6:
+ break;
+ case KeyCode.Joystick2Button7:
+ break;
+ case KeyCode.Joystick2Button8:
+ break;
+ case KeyCode.Joystick2Button9:
+ break;
+ case KeyCode.Joystick2Button10:
+ break;
+ case KeyCode.Joystick2Button11:
+ break;
+ case KeyCode.Joystick2Button12:
+ break;
+ case KeyCode.Joystick2Button13:
+ break;
+ case KeyCode.Joystick2Button14:
+ break;
+ case KeyCode.Joystick2Button15:
+ break;
+ case KeyCode.Joystick2Button16:
+ break;
+ case KeyCode.Joystick2Button17:
+ break;
+ case KeyCode.Joystick2Button18:
+ break;
+ case KeyCode.Joystick2Button19:
+ break;
+ case KeyCode.Joystick3Button0:
+ break;
+ case KeyCode.Joystick3Button1:
+ break;
+ case KeyCode.Joystick3Button2:
+ break;
+ case KeyCode.Joystick3Button3:
+ break;
+ case KeyCode.Joystick3Button4:
+ break;
+ case KeyCode.Joystick3Button5:
+ break;
+ case KeyCode.Joystick3Button6:
+ break;
+ case KeyCode.Joystick3Button7:
+ break;
+ case KeyCode.Joystick3Button8:
+ break;
+ case KeyCode.Joystick3Button9:
+ break;
+ case KeyCode.Joystick3Button10:
+ break;
+ case KeyCode.Joystick3Button11:
+ break;
+ case KeyCode.Joystick3Button12:
+ break;
+ case KeyCode.Joystick3Button13:
+ break;
+ case KeyCode.Joystick3Button14:
+ break;
+ case KeyCode.Joystick3Button15:
+ break;
+ case KeyCode.Joystick3Button16:
+ break;
+ case KeyCode.Joystick3Button17:
+ break;
+ case KeyCode.Joystick3Button18:
+ break;
+ case KeyCode.Joystick3Button19:
+ break;
+ case KeyCode.Joystick4Button0:
+ break;
+ case KeyCode.Joystick4Button1:
+ break;
+ case KeyCode.Joystick4Button2:
+ break;
+ case KeyCode.Joystick4Button3:
+ break;
+ case KeyCode.Joystick4Button4:
+ break;
+ case KeyCode.Joystick4Button5:
+ break;
+ case KeyCode.Joystick4Button6:
+ break;
+ case KeyCode.Joystick4Button7:
+ break;
+ case KeyCode.Joystick4Button8:
+ break;
+ case KeyCode.Joystick4Button9:
+ break;
+ case KeyCode.Joystick4Button10:
+ break;
+ case KeyCode.Joystick4Button11:
+ break;
+ case KeyCode.Joystick4Button12:
+ break;
+ case KeyCode.Joystick4Button13:
+ break;
+ case KeyCode.Joystick4Button14:
+ break;
+ case KeyCode.Joystick4Button15:
+ break;
+ case KeyCode.Joystick4Button16:
+ break;
+ case KeyCode.Joystick4Button17:
+ break;
+ case KeyCode.Joystick4Button18:
+ break;
+ case KeyCode.Joystick4Button19:
+ break;
+ case KeyCode.Joystick5Button0:
+ break;
+ case KeyCode.Joystick5Button1:
+ break;
+ case KeyCode.Joystick5Button2:
+ break;
+ case KeyCode.Joystick5Button3:
+ break;
+ case KeyCode.Joystick5Button4:
+ break;
+ case KeyCode.Joystick5Button5:
+ break;
+ case KeyCode.Joystick5Button6:
+ break;
+ case KeyCode.Joystick5Button7:
+ break;
+ case KeyCode.Joystick5Button8:
+ break;
+ case KeyCode.Joystick5Button9:
+ break;
+ case KeyCode.Joystick5Button10:
+ break;
+ case KeyCode.Joystick5Button11:
+ break;
+ case KeyCode.Joystick5Button12:
+ break;
+ case KeyCode.Joystick5Button13:
+ break;
+ case KeyCode.Joystick5Button14:
+ break;
+ case KeyCode.Joystick5Button15:
+ break;
+ case KeyCode.Joystick5Button16:
+ break;
+ case KeyCode.Joystick5Button17:
+ break;
+ case KeyCode.Joystick5Button18:
+ break;
+ case KeyCode.Joystick5Button19:
+ break;
+ case KeyCode.Joystick6Button0:
+ break;
+ case KeyCode.Joystick6Button1:
+ break;
+ case KeyCode.Joystick6Button2:
+ break;
+ case KeyCode.Joystick6Button3:
+ break;
+ case KeyCode.Joystick6Button4:
+ break;
+ case KeyCode.Joystick6Button5:
+ break;
+ case KeyCode.Joystick6Button6:
+ break;
+ case KeyCode.Joystick6Button7:
+ break;
+ case KeyCode.Joystick6Button8:
+ break;
+ case KeyCode.Joystick6Button9:
+ break;
+ case KeyCode.Joystick6Button10:
+ break;
+ case KeyCode.Joystick6Button11:
+ break;
+ case KeyCode.Joystick6Button12:
+ break;
+ case KeyCode.Joystick6Button13:
+ break;
+ case KeyCode.Joystick6Button14:
+ break;
+ case KeyCode.Joystick6Button15:
+ break;
+ case KeyCode.Joystick6Button16:
+ break;
+ case KeyCode.Joystick6Button17:
+ break;
+ case KeyCode.Joystick6Button18:
+ break;
+ case KeyCode.Joystick6Button19:
+ break;
+ case KeyCode.Joystick7Button0:
+ break;
+ case KeyCode.Joystick7Button1:
+ break;
+ case KeyCode.Joystick7Button2:
+ break;
+ case KeyCode.Joystick7Button3:
+ break;
+ case KeyCode.Joystick7Button4:
+ break;
+ case KeyCode.Joystick7Button5:
+ break;
+ case KeyCode.Joystick7Button6:
+ break;
+ case KeyCode.Joystick7Button7:
+ break;
+ case KeyCode.Joystick7Button8:
+ break;
+ case KeyCode.Joystick7Button9:
+ break;
+ case KeyCode.Joystick7Button10:
+ break;
+ case KeyCode.Joystick7Button11:
+ break;
+ case KeyCode.Joystick7Button12:
+ break;
+ case KeyCode.Joystick7Button13:
+ break;
+ case KeyCode.Joystick7Button14:
+ break;
+ case KeyCode.Joystick7Button15:
+ break;
+ case KeyCode.Joystick7Button16:
+ break;
+ case KeyCode.Joystick7Button17:
+ break;
+ case KeyCode.Joystick7Button18:
+ break;
+ case KeyCode.Joystick7Button19:
+ break;
+ case KeyCode.Joystick8Button0:
+ break;
+ case KeyCode.Joystick8Button1:
+ break;
+ case KeyCode.Joystick8Button2:
+ break;
+ case KeyCode.Joystick8Button3:
+ break;
+ case KeyCode.Joystick8Button4:
+ break;
+ case KeyCode.Joystick8Button5:
+ break;
+ case KeyCode.Joystick8Button6:
+ break;
+ case KeyCode.Joystick8Button7:
+ break;
+ case KeyCode.Joystick8Button8:
+ break;
+ case KeyCode.Joystick8Button9:
+ break;
+ case KeyCode.Joystick8Button10:
+ break;
+ case KeyCode.Joystick8Button11:
+ break;
+ case KeyCode.Joystick8Button12:
+ break;
+ case KeyCode.Joystick8Button13:
+ break;
+ case KeyCode.Joystick8Button14:
+ break;
+ case KeyCode.Joystick8Button15:
+ break;
+ case KeyCode.Joystick8Button16:
+ break;
+ case KeyCode.Joystick8Button17:
+ break;
+ case KeyCode.Joystick8Button18:
+ break;
+ case KeyCode.Joystick8Button19:
+ break;
+ //default:
+ // throw new ArgumentOutOfRangeException(nameof(key), key, null);
+ }
+
+ if (!silent)
+ Logging.Logger.Log(LogLevel.Debug, $"[{nameof(NewInputSystem)}] Unsupported key: {key}");
+
+ return null;
+ }
+ }
+
+ internal class LegacyInputSystem : IInputSystem
+ {
+ public bool GetKey(string name) => Input.GetKey(name);
+
+ public bool GetKey(KeyCode key) => Input.GetKey(key);
+
+ public bool GetKeyDown(string name) => Input.GetKeyDown(name);
+
+ public bool GetKeyDown(KeyCode key) => Input.GetKeyDown(key);
+
+ public bool GetKeyUp(string name) => Input.GetKeyUp(name);
+
+ public bool GetKeyUp(KeyCode key) => Input.GetKeyUp(key);
+
+ public bool GetMouseButton(int button) => Input.GetMouseButton(button);
+
+ public bool GetMouseButtonDown(int button) => Input.GetMouseButtonDown(button);
+
+ public bool GetMouseButtonUp(int button) => Input.GetMouseButtonUp(button);
+
+ public void ResetInputAxes() => Input.ResetInputAxes();
+
+ public Vector3 mousePosition => Input.mousePosition;
+ public Vector2 mouseScrollDelta => Input.mouseScrollDelta;
+ public bool mousePresent => Input.mousePresent;
+ public bool anyKey => Input.anyKey;
+ public bool anyKeyDown => Input.anyKeyDown;
+
+ public IEnumerable SupportedKeyCodes { get; } = (KeyCode[])Enum.GetValues(typeof(KeyCode));
+ }
+}
From 544f909a80e19d42c85b7169031a58baf65cb9fc Mon Sep 17 00:00:00 2001
From: aldelaro5
Date: Tue, 24 Sep 2024 14:25:56 -0400
Subject: [PATCH 43/65] Revert "Merge pull request #960 from
aldelaro5/metaplugins" (#995)
This reverts commit 2a61b614978b1f3388c41f7784c43178ba98b58e, reversing
changes made to 6b38cee1277f4e3da5f2f685aee8b503a14d4054.
---
BepInEx.Core/Bootstrap/BaseChainloader.cs | 294 +++++++-----------
BepInEx.Core/Bootstrap/TypeLoader.cs | 142 +--------
BepInEx.Core/Contract/Attributes.cs | 48 ---
BepInEx.Core/Contract/BasePluginProvider.cs | 71 -----
BepInEx.Core/Contract/IPlugin.cs | 32 +-
BepInEx.Core/Contract/IPluginLoadContext.cs | 41 ---
BepInEx.Core/Contract/PluginInfo.cs | 35 +--
BepInEx.Core/Paths.cs | 12 -
BepInEx.Core/Properties/AssemblyInfo.cs | 1 -
BepInEx.Core/Utility.cs | 57 ----
.../BepInEx.PatcherProvider.csproj | 17 -
.../BepInExPatcherLoadContext.cs | 32 --
.../BepInExPatcherProvider.cs | 71 +----
.../BepInEx.PluginProvider.csproj | 17 -
.../BepInExPluginLoadContext.cs | 32 --
.../BepInExPluginProvider.cs | 70 +----
.../Patching/AssemblyPatcher.cs | 253 ++++-----------
BepInEx.Preloader.Core/Patching/Attributes.cs | 25 --
.../Patching/BasePatcherProvider.cs | 55 ----
.../Patching/PatcherPluginMetadata.cs | 5 -
BepInEx.sln | 20 --
Runtimes/NET/BepInEx.NET.Common/BasePlugin.cs | 21 +-
.../BepInEx.NET.Common.csproj | 12 -
.../NET/BepInEx.NET.Common/NetChainloader.cs | 3 +
.../BepInEx.NET.CoreCLR/NetCorePreloader.cs | 2 +-
.../NetPreloader.cs | 2 +-
.../Unity/BepInEx.Unity.IL2CPP/BasePlugin.cs | 17 +-
.../BepInEx.Unity.IL2CPP.csproj | 12 -
.../BepInEx.Unity.IL2CPP/IL2CPPChainloader.cs | 6 +-
.../Unity/BepInEx.Unity.IL2CPP/Preloader.cs | 2 +-
.../UnityPreloader.cs | 2 +-
.../BepInEx.Unity.Mono/BaseUnityPlugin.cs | 18 +-
.../BepInEx.Unity.Mono.csproj | 12 -
.../Bootstrap/UnityChainloader.cs | 15 +-
build/Program.cs | 2 -
35 files changed, 257 insertions(+), 1199 deletions(-)
delete mode 100644 BepInEx.Core/Contract/BasePluginProvider.cs
delete mode 100644 BepInEx.Core/Contract/IPluginLoadContext.cs
delete mode 100644 BepInEx.PatcherProvider/BepInEx.PatcherProvider.csproj
delete mode 100644 BepInEx.PatcherProvider/BepInExPatcherLoadContext.cs
delete mode 100644 BepInEx.PluginProvider/BepInEx.PluginProvider.csproj
delete mode 100644 BepInEx.PluginProvider/BepInExPluginLoadContext.cs
delete mode 100644 BepInEx.Preloader.Core/Patching/BasePatcherProvider.cs
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
index 6b62ffd..c6fe182 100644
--- a/BepInEx.Core/Bootstrap/BaseChainloader.cs
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -19,23 +19,20 @@ public abstract class BaseChainloader
private static Regex allowedGuidRegex { get; } = new(@"^[a-zA-Z0-9\._\-]+$");
- internal BaseChainloader() { }
-
///
/// Analyzes the given type definition and attempts to convert it to a valid
///
/// Type definition to analyze.
- /// The load context of the plugin
/// The filepath of the assembly, to keep as metadata.
/// If the type represent a valid plugin, returns a instance. Otherwise, return null.
- public static PluginInfo ToPluginInfo(TypeDefinition type, IPluginLoadContext loadContext, string assemblyLocation)
+ public static PluginInfo ToPluginInfo(TypeDefinition type, string assemblyLocation)
{
if (type.IsInterface || type.IsAbstract)
return null;
try
{
- if (!type.IsSubtypeOf(typeof(T)))
+ if (!type.IsSubtypeOf(typeof(TPlugin)))
return null;
}
catch (AssemblyResolutionException)
@@ -44,13 +41,8 @@ public abstract class BaseChainloader
return null;
}
- if (type.IsSubtypeOf(typeof(BasePluginProvider)))
- return GetPluginInfo(type, BepInPluginProvider.FromCecilType(type), loadContext, assemblyLocation);
- return GetPluginInfo(type, BepInPlugin.FromCecilType(type), loadContext, assemblyLocation);
- }
+ var metadata = BepInPlugin.FromCecilType(type);
- private static PluginInfo GetPluginInfo(TypeDefinition type, BepInPlugin metadata, IPluginLoadContext loadContext, string assemblyLocation)
- {
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
{
@@ -93,16 +85,15 @@ public abstract class BaseChainloader
Incompatibilities = incompatibilities,
TypeName = type.FullName,
TargettedBepInExVersion = bepinVersion,
- _location = assemblyLocation,
- LoadContext = loadContext
+ Location = assemblyLocation
};
}
- protected static bool HasBepinPluginType(AssemblyDefinition ass)
+ protected 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(T).FullName))
+ if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BepInPlugin).FullName))
return false;
return true;
@@ -125,47 +116,16 @@ public abstract class BaseChainloader
private bool _initialized;
///
- /// The current chainloader instance
- ///
- public static BaseChainloader Instance { get; protected set; }
-
- ///
- /// List of all instances of all plugins loaded via the chainloader.
- /// If a plugin fails to load, it is removed. This list only contains the plugins that were actually
- /// loaded and have their dependencies satisfied.
+ /// List of all instances loaded via the chainloader.
///
public Dictionary Plugins { get; } = new();
- ///
- /// List of all instances of all providers loaded via the chainloader.
- /// If a provider fails to load, it is removed. This list only contains the providers that were actually
- /// loaded and have their dependencies satisfied.
- ///
- public Dictionary Providers { get; } = new();
-
- ///
- /// List of all objects received from plugin providers.
- /// Value holds info of the originating provider. These contexts are used by chainloader to
- /// load plugins. Plugins that are loaded successfully are added to .
- ///
- internal Dictionary LoadContexts { get; } = new();
-
///
/// Collection of error chainloader messages that occured during plugin loading.
/// Contains information about what certain plugins were not loaded.
///
public List DependencyErrors { get; } = new();
- ///
- /// Occurs after a plugin provider is loaded.
- ///
- public event Action ProviderLoaded;
-
- ///
- /// Occurs after all plugins providers are loaded.
- ///
- public event Action FinishedProviders;
-
///
/// Occurs after a plugin is loaded.
///
@@ -189,12 +149,12 @@ public abstract class BaseChainloader
InitializeLoggers();
- if (!Directory.Exists(Paths.PluginProviderPath))
- Directory.CreateDirectory(Paths.PluginProviderPath);
-
if (!Directory.Exists(Paths.PluginPath))
Directory.CreateDirectory(Paths.PluginPath);
+ if (!Directory.Exists(Paths.PatcherPluginPath))
+ Directory.CreateDirectory(Paths.PatcherPluginPath);
+
_initialized = true;
Logger.Log(LogLevel.Message, "Chainloader initialized");
@@ -234,20 +194,29 @@ public abstract class BaseChainloader
/// Path from which to search the plugins.
/// Cache name to use. If null, results are not cached.
/// List of discovered plugins and their metadata.
- protected List DiscoverPluginProvidersFrom(string path, string cacheName = "chainloader_meta")
+ protected IList DiscoverPluginsFrom(string path, string cacheName = "chainloader")
{
var pluginsToLoad =
- TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPluginType, cacheName);
+ TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPlugins, cacheName);
return pluginsToLoad.SelectMany(p => p.Value).ToList();
}
+ ///
+ /// Discovers plugins to load.
+ ///
+ /// List of plugins to be loaded.
+ protected virtual IList DiscoverPlugins()
+ {
+ return DiscoverPluginsFrom(Paths.PluginPath);
+ }
+
///
/// Preprocess the plugins and modify the load order.
///
/// Some plugins may be skipped if they cannot be loaded (wrong metadata, etc).
/// Plugins to process.
/// List of plugins to load in the correct load order.
- protected virtual List ModifyLoadOrder(IList plugins, Dictionary existingPlugins)
+ protected virtual IList ModifyLoadOrder(IList plugins)
{
// We use a sorted dictionary to ensure consistent load order
var dependencyDict =
@@ -256,7 +225,7 @@ public abstract class BaseChainloader
foreach (var pluginInfoGroup in plugins.GroupBy(info => info.Metadata.GUID))
{
- if (existingPlugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
+ if (Plugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
{
Logger.Log(LogLevel.Warning,
$"Skipping [{pluginInfoGroup.Key}] because a plugin with a similar GUID ([{loadedPlugin}]) has been already loaded.");
@@ -297,7 +266,7 @@ public abstract class BaseChainloader
foreach (var pluginInfo in pluginsByGuid.Values.ToList())
if (pluginInfo.Incompatibilities.Any(incompatibility =>
pluginsByGuid.ContainsKey(incompatibility.IncompatibilityGUID)
- || existingPlugins.ContainsKey(incompatibility.IncompatibilityGUID))
+ || Plugins.ContainsKey(incompatibility.IncompatibilityGUID))
)
{
pluginsByGuid.Remove(pluginInfo.Metadata.GUID);
@@ -306,7 +275,7 @@ public abstract class BaseChainloader
var incompatiblePluginsNew = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID)
.Where(x => pluginsByGuid.ContainsKey(x));
var incompatiblePluginsExisting = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID)
- .Where(x => existingPlugins.ContainsKey(x));
+ .Where(x => Plugins.ContainsKey(x));
var incompatiblePlugins = incompatiblePluginsNew.Concat(incompatiblePluginsExisting).ToArray();
var message =
$@"Could not load [{pluginInfo}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}";
@@ -337,24 +306,15 @@ public abstract class BaseChainloader
}
///
- /// Run the chainloader and load all plugins from all providers.
+ /// Run the chainloader and load all plugins from the plugins folder.
///
public virtual void Execute()
{
try
{
- var builtinProviders = DiscoverPluginProvidersFrom(Paths.BepInExAssemblyDirectory);
- var providers = DiscoverPluginProvidersFrom(Paths.PluginProviderPath);
- Logger.Log(LogLevel.Info, $"{providers.Count + builtinProviders.Count} " +
- $"plugin provider{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
-
- LoadPlugins(builtinProviders, provider: true);
- LoadPlugins(providers, provider: true);
- FinishedProviders?.Invoke();
-
- var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts.Keys.Cast(), ToPluginInfo, HasBepinPluginType, "chainloader");
+ var plugins = DiscoverPlugins();
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
- LoadPlugins(plugins, provider: false);
+ LoadPlugins(plugins);
Finished?.Invoke();
}
catch (Exception ex)
@@ -371,143 +331,117 @@ public abstract class BaseChainloader
Logger.Log(LogLevel.Message, "Chainloader startup complete");
}
- [Obsolete("Use a plugin provider instead", true)]
- public IList LoadPlugins(params string[] pluginsPaths)
+ private IList LoadPlugins(IList plugins)
{
- throw new NotSupportedException("Use a plugin provider instead");
- }
-
- private void LoadPlugins(List plugins, bool provider)
- {
- var existingPluginInfos = provider ? Providers : Plugins;
- var sortedPluginInfos = ModifyLoadOrder(plugins, existingPluginInfos);
-
+ var sortedPlugins = ModifyLoadOrder(plugins);
+
var invalidPlugins = new HashSet();
var processedPlugins = new Dictionary();
var loadedAssemblies = new Dictionary();
+ var loadedPlugins = new List();
- foreach (var pluginInfo in sortedPluginInfos)
+ foreach (var plugin in sortedPlugins)
{
- if (!CheckDependencies(pluginInfo, processedPlugins, invalidPlugins, existingPluginInfos))
+ var dependsOnInvalidPlugin = false;
+ var missingDependencies = new List();
+ foreach (var dependency in plugin.Dependencies)
{
+ static bool IsHardDependency(BepInDependency dep) =>
+ (dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0;
+
+ // If the dependency wasn't already processed, it's missing altogether
+ var dependencyExists =
+ processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
+ // Alternatively, if the dependency hasn't been loaded before, it's missing too
+ if (!dependencyExists)
+ {
+ dependencyExists = Plugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
+ pluginVersion = pluginInfo?.Metadata.Version;
+ }
+
+ if (!dependencyExists || dependency.VersionRange != null &&
+ !dependency.VersionRange.IsSatisfied(pluginVersion))
+ {
+ // If the dependency is hard, collect it into a list to show
+ if (IsHardDependency(dependency))
+ missingDependencies.Add(dependency);
+ continue;
+ }
+
+ // If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
+ if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
+ {
+ dependsOnInvalidPlugin = true;
+ break;
+ }
+ }
+
+ processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
+
+ if (dependsOnInvalidPlugin)
+ {
+ var message =
+ $"Skipping [{plugin}] because it has a dependency that was not loaded. See previous errors for details.";
+ DependencyErrors.Add(message);
+ Logger.Log(LogLevel.Warning, message);
+ continue;
+ }
+
+ if (missingDependencies.Count != 0)
+ {
+ var message = $@"Could not load [{plugin}] because it has missing dependencies: {
+ string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
+ }";
+ DependencyErrors.Add(message);
+ Logger.Log(LogLevel.Error, message);
+
+ invalidPlugins.Add(plugin.Metadata.GUID);
continue;
}
try
{
- Logger.Log(LogLevel.Info, $"Loading [{pluginInfo}]");
+ Logger.Log(LogLevel.Info, $"Loading [{plugin}]");
- Assembly ass = null;
- if (provider && !loadedAssemblies.TryGetValue(pluginInfo._location, out ass))
- {
- ass = Assembly.LoadFrom(pluginInfo._location);
- loadedAssemblies[pluginInfo._location] = ass;
- }
- else if (!provider && !loadedAssemblies.TryGetValue(pluginInfo.LoadContext.AssemblyIdentifier, out ass))
- {
- var symbols = pluginInfo.LoadContext.GetAssemblySymbolsData();
- ass = symbols != null ? Assembly.Load(pluginInfo.LoadContext.GetAssemblyData(), symbols)
- : Assembly.Load(pluginInfo.LoadContext.GetAssemblyData());
- loadedAssemblies[pluginInfo.LoadContext.AssemblyIdentifier] = ass;
- }
+ if (!loadedAssemblies.TryGetValue(plugin.Location, out var ass))
+ loadedAssemblies[plugin.Location] = ass = Assembly.LoadFrom(plugin.Location);
- existingPluginInfos[pluginInfo.Metadata.GUID] = pluginInfo;
- TryRunModuleCtor(pluginInfo, ass);
+ Plugins[plugin.Metadata.GUID] = plugin;
+ TryRunModuleCtor(plugin, ass);
+ plugin.Instance = LoadPlugin(plugin, ass);
+ loadedPlugins.Add(plugin);
- if (provider)
- {
- var type = ass.GetType(pluginInfo.TypeName);
- pluginInfo.Instance = Activator.CreateInstance(type);
- AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)pluginInfo.Instance).ResolveAssembly(args.Name);
- var pluginLoadContexts = ((BasePluginProvider)pluginInfo.Instance).GetPlugins();
- foreach (IPluginLoadContext context in pluginLoadContexts)
- {
- var cachedContext = new CachedPluginLoadContext(context);
- LoadContexts[cachedContext] = pluginInfo;
- }
- ProviderLoaded?.Invoke(pluginInfo);
- }
- else
- {
- pluginInfo.Source = LoadContexts[(CachedPluginLoadContext)pluginInfo.LoadContext];
- pluginInfo.Instance = LoadPlugin(pluginInfo, ass);
- PluginLoaded?.Invoke(pluginInfo);
- foreach (CachedPluginLoadContext context in LoadContexts.Keys)
- context.Dispose();
- }
+ PluginLoaded?.Invoke(plugin);
}
catch (Exception ex)
{
- invalidPlugins.Add(pluginInfo.Metadata.GUID);
- existingPluginInfos.Remove(pluginInfo.Metadata.GUID);
+ invalidPlugins.Add(plugin.Metadata.GUID);
+ Plugins.Remove(plugin.Metadata.GUID);
Logger.Log(LogLevel.Error,
- $"Error loading [{pluginInfo}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
+ $"Error loading [{plugin}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
}
}
+
+ return loadedPlugins;
}
- private bool CheckDependencies(T plugin, Dictionary processedPlugins, HashSet invalidPlugins, Dictionary existingPlugins)
- where T : PluginInfo
+ ///
+ /// Detects and loads all plugins in the specified directories.
+ ///
+ ///
+ /// It is better to collect all paths at once and use a single call to LoadPlugins than multiple calls.
+ /// This allows to run proper dependency resolving and to load all plugins in one go.
+ ///
+ /// Directories to search the plugins from.
+ /// List of loaded plugin infos.
+ public IList LoadPlugins(params string[] pluginsPaths)
{
- var dependsOnInvalidPlugin = false;
- var missingDependencies = new List();
- foreach (var dependency in plugin.Dependencies)
- {
- static bool IsHardDependency(BepInDependency dep) =>
- (dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0;
-
- // If the dependency wasn't already processed, it's missing altogether
- var dependencyExists =
- processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
- // Alternatively, if the dependency hasn't been loaded before, it's missing too
- if (!dependencyExists)
- {
- dependencyExists = existingPlugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
- pluginVersion = pluginInfo?.Metadata.Version;
- }
-
- if (!dependencyExists || dependency.VersionRange != null &&
- !dependency.VersionRange.IsSatisfied(pluginVersion))
- {
- // If the dependency is hard, collect it into a list to show
- if (IsHardDependency(dependency))
- missingDependencies.Add(dependency);
- continue;
- }
-
- // If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
- if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
- {
- dependsOnInvalidPlugin = true;
- break;
- }
- }
-
- processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
-
- if (dependsOnInvalidPlugin)
- {
- var message =
- $"Skipping [{plugin}] because it has a dependency that was not loaded. See previous errors for details.";
- DependencyErrors.Add(message);
- Logger.Log(LogLevel.Warning, message);
- return false;
- }
-
- if (missingDependencies.Count != 0)
- {
- var message = $@"Could not load [{plugin}] because it has missing dependencies: {
- string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
- }";
- DependencyErrors.Add(message);
- Logger.Log(LogLevel.Error, message);
-
- invalidPlugins.Add(plugin.Metadata.GUID);
- return false;
- }
-
- return true;
+ // TODO: This is a temporary solution for 3rd party loaders. Instead, this should be done via metaplugins.
+ var plugins = new List();
+ foreach (var pluginsPath in pluginsPaths)
+ plugins.AddRange(DiscoverPluginsFrom(pluginsPath));
+ return LoadPlugins(plugins);
}
private static void TryRunModuleCtor(PluginInfo plugin, Assembly assembly)
diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
index 63c1295..69a7775 100644
--- a/BepInEx.Core/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -10,41 +10,6 @@ using Mono.Cecil;
namespace BepInEx.Bootstrap;
-internal class CachedPluginLoadContext : IPluginLoadContext, IDisposable
-{
- public IPluginLoadContext PluginLoadContext { get; }
- private byte[] assemblyData;
- private byte[] assemblySymbolsData;
-
- public CachedPluginLoadContext(IPluginLoadContext pluginLoadContext)
- {
- PluginLoadContext = pluginLoadContext;
- }
-
- public string AssemblyIdentifier => PluginLoadContext.AssemblyIdentifier;
- public string AssemblyHash => PluginLoadContext.AssemblyHash;
- public byte[] GetAssemblyData()
- {
- return assemblyData ??= PluginLoadContext.GetAssemblyData();
- }
-
- public byte[] GetAssemblySymbolsData()
- {
- return assemblySymbolsData ??= PluginLoadContext.GetAssemblySymbolsData();
- }
-
- public byte[] GetFile(string relativePath)
- {
- return PluginLoadContext.GetFile(relativePath);
- }
-
- public void Dispose()
- {
- assemblyData = null;
- assemblySymbolsData = null;
- }
-}
-
///
/// A cacheable metadata item. Can be used with and
/// to cache plugin metadata.
@@ -70,11 +35,6 @@ public interface ICacheable
///
public class CachedAssembly where T : ICacheable
{
- ///
- /// The version of the cache which increments on each format changes
- ///
- public const int Version = 0;
-
///
/// List of cached items inside the assembly.
///
@@ -129,9 +89,7 @@ public static class TypeLoader
{
Paths.BepInExAssemblyDirectory,
Paths.PluginPath,
- Paths.PluginProviderPath,
Paths.PatcherPluginPath,
- Paths.PatcherProviderPath,
Paths.ManagedPath
}.Concat(SearchDirectories);
@@ -168,8 +126,9 @@ public static class TypeLoader
/// selector.
///
public static Dictionary> FindPluginTypes(string directory,
- Func typeSelector,
- Func assemblyFilter = null,
+ Func typeSelector,
+ Func assemblyFilter =
+ null,
string cacheName = null)
where T : ICacheable, new()
{
@@ -194,7 +153,19 @@ public static class TypeLoader
continue;
}
- result[dll] = ExamineStream(typeSelector, assemblyFilter, dllMs, null, dll);
+ using var ass = AssemblyDefinition.ReadAssembly(dllMs, ReaderParameters);
+ Logger.Log(LogLevel.Debug, $"Examining '{dll}'");
+
+ if (!assemblyFilter?.Invoke(ass) ?? false)
+ {
+ result[dll] = new List();
+ continue;
+ }
+
+ var matches = ass.MainModule.Types
+ .Select(t => typeSelector(t, dll))
+ .Where(t => t != null).ToList();
+ result[dll] = matches;
}
catch (BadImageFormatException e)
{
@@ -212,82 +183,6 @@ public static class TypeLoader
return result;
}
- ///
- /// Looks up assemblies using the given loaders and locates all types that can be loaded and collects their metadata.
- ///
- /// The specific base type to search for.
- /// The load contexts to obtain the assemblies from.
- /// A function to check if a type should be selected and to build the type metadata.
- /// A filter function to quickly determine if the assembly can be loaded.
- /// The name of the cache to get cached types from.
- ///
- /// A dictionary of all assemblies in the directory and the list of type metadatas of types that match the
- /// selector.
- ///
- public static List GetPluginsFromLoadContexts(IEnumerable loadContexts,
- Func typeSelector,
- Func assemblyFilter = null,
- string cacheName = null)
- where T : ICacheable, new()
- {
- var result = new Dictionary>();
- var hashes = new Dictionary();
- Dictionary> cache = null;
-
- if (cacheName != null)
- cache = LoadAssemblyCache(cacheName);
-
- foreach (IPluginLoadContext loadContext in loadContexts)
- {
- IList plugins;
- if (cache != null && loadContext.AssemblyHash != null &&
- cache.TryGetValue(loadContext.AssemblyIdentifier, out var cacheEntry) &&
- loadContext.AssemblyHash == cacheEntry.Hash)
- {
- plugins = cacheEntry.CacheItems;
- }
- else
- {
- var assemblyData = loadContext.GetAssemblyData();
- using var memory = new MemoryStream(assemblyData);
- plugins = ExamineStream(typeSelector, assemblyFilter, memory, loadContext, null);
- }
-
- foreach (T pluginInfo in plugins)
- {
- if (!result.ContainsKey(loadContext.AssemblyIdentifier))
- result[loadContext.AssemblyIdentifier] = new();
- result[loadContext.AssemblyIdentifier].Add(pluginInfo);
- }
- }
-
- if (cache != null)
- SaveAssemblyCache(cacheName, result, hashes);
-
- return result.SelectMany(x => x.Value).ToList();
- }
-
- private static List ExamineStream(Func typeSelector,
- Func assemblyFilter,
- MemoryStream dllMs,
- IPluginLoadContext loadContext,
- string location)
- where T : ICacheable, new()
- {
- using var ass = AssemblyDefinition.ReadAssembly(dllMs, ReaderParameters);
- Logger.Log(LogLevel.Debug, $"Examining '{ass.Name}'");
-
- if (!assemblyFilter?.Invoke(ass) ?? false)
- {
- return new List