diff --git a/BepInEx.Core/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs index 8c4b9b8a8..175ebaba1 100644 --- a/BepInEx.Core/Bootstrap/TypeLoader.cs +++ b/BepInEx.Core/Bootstrap/TypeLoader.cs @@ -63,6 +63,38 @@ public static class TypeLoader public static HashSet SearchDirectories = new(); + private static readonly Dictionary AssemblyPathByName = new(StringComparer.InvariantCultureIgnoreCase); + + /// + /// Maps every managed assembly file in a directory to its assembly name, so files whose + /// filename does not match the assembly name (e.g. renamed by the user) still resolve. + /// First file wins when two files share an assembly name. + /// + public static void RegisterAssemblyPaths(string directory) + { + if (!Directory.Exists(directory)) + return; + + foreach (var dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories)) + { + AssemblyName name; + try + { + name = AssemblyName.GetAssemblyName(dll); + } + catch (Exception) + { + continue; + } + + if (!AssemblyPathByName.ContainsKey(name.Name)) + AssemblyPathByName[name.Name] = dll; + } + } + + public static bool TryGetRegisteredAssemblyPath(string name, out string path) => + AssemblyPathByName.TryGetValue(name, out path) && File.Exists(path); + #region Config private static readonly ConfigEntry EnableAssemblyCache = ConfigFile.CoreConfig.Bind( @@ -104,6 +136,15 @@ public static AssemblyDefinition CecilResolveOnFailure(object sender, AssemblyNa return assembly; } + if (TryGetRegisteredAssemblyPath(name.Name, out var mappedPath)) + { + try + { + return AssemblyDefinition.ReadAssembly(mappedPath, ReaderParameters); + } + catch (BadImageFormatException) { } + } + return AssemblyResolve?.Invoke(sender, reference); } diff --git a/BepInEx.Core/Contract/Attributes.cs b/BepInEx.Core/Contract/Attributes.cs index f8f813bc6..647e87e42 100644 --- a/BepInEx.Core/Contract/Attributes.cs +++ b/BepInEx.Core/Contract/Attributes.cs @@ -254,12 +254,27 @@ public static class MetadataHelper { internal static bool TypeInheretsFrom(TypeReference derived, Type type) { - var td = derived.Resolve(); + TypeDefinition td; + try + { + td = derived.Resolve(); + } + catch (AssemblyResolutionException) + { + return false; + } while (td != null) { if (td.FullName == type.FullName) return true; - td = td.BaseType?.Resolve(); + try + { + td = td.BaseType?.Resolve(); + } + catch (AssemblyResolutionException) + { + return false; + } } return false; } diff --git a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs index 4ba5396c0..a6fc099cf 100644 --- a/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs +++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs @@ -20,8 +20,6 @@ namespace BepInEx.Preloader.Core.Patching; /// public class AssemblyPatcher : IDisposable { - private static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name; - private Func assemblyLoader; public AssemblyPatcher(Func assemblyLoader) @@ -115,12 +113,11 @@ private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, string assemb private bool HasPatcherPlugins(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)) - return false; - + var typeReferences = ass.MainModule.GetTypeReferences().ToList(); + if (typeReferences.All(r => r.FullName != typeof(BasePatcher).FullName)) + { + return typeReferences.Any(r => MetadataHelper.TypeInheretsFrom(r, typeof(BasePatcher))); + } return true; } @@ -135,6 +132,8 @@ public void AddPatchersFromDirectory(string directory) var sortedPatchers = new List(); + TypeLoader.RegisterAssemblyPaths(directory); + var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherPlugins); // TODO: Add dependency ordering and process attribute filtering diff --git a/BepInEx.Preloader.Core/Patching/Attributes.cs b/BepInEx.Preloader.Core/Patching/Attributes.cs index c8c76c138..d8e31108b 100644 --- a/BepInEx.Preloader.Core/Patching/Attributes.cs +++ b/BepInEx.Preloader.Core/Patching/Attributes.cs @@ -58,7 +58,7 @@ private static Version TryParseLongVersion(string version) internal static PatcherPluginInfoAttribute FromCecilType(TypeDefinition td) { - var attr = MetadataHelper.GetCustomAttributes(td, false).FirstOrDefault(); + var attr = MetadataHelper.GetCustomAttributes(td, false, true).FirstOrDefault(); if (attr == null) return null; diff --git a/Runtimes/NET/BepInEx.NET.Shared/SharedEntrypoint.cs b/Runtimes/NET/BepInEx.NET.Shared/SharedEntrypoint.cs index 638ba9dab..09a163515 100644 --- a/Runtimes/NET/BepInEx.NET.Shared/SharedEntrypoint.cs +++ b/Runtimes/NET/BepInEx.NET.Shared/SharedEntrypoint.cs @@ -78,6 +78,15 @@ public static Assembly LocalResolve(object sender, ResolveEventArgs args) || LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly)) return foundAssembly; + if (Bootstrap.TypeLoader.TryGetRegisteredAssemblyPath(assemblyName.Name, out var mappedPath)) + { + try + { + return Utility.LoadContext.LoadFromAssemblyPath(mappedPath); + } + catch (Exception) { } + } + return null; } }