using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using BepInEx.Contract;
using BepInEx.Logging;
using Mono.Cecil;
namespace BepInEx.Bootstrap
{
///
/// Provides methods for loading specified types from an assembly.
///
public static class TypeLoader
{
private static bool Is(this TypeDefinition self, Type td)
{
if (self.FullName == td.FullName)
return true;
return self.FullName != "System.Object" && (self.BaseType?.Resolve().Is(td) ?? false);
}
private static DefaultAssemblyResolver resolver;
private static ReaderParameters readerParameters;
static TypeLoader()
{
resolver = new DefaultAssemblyResolver();
readerParameters = new ReaderParameters { AssemblyResolver = resolver };
resolver.ResolveFailure += (sender, reference) =>
{
var name = new AssemblyName(reference.FullName);
if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out AssemblyDefinition assembly) || Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly) || Utility.TryResolveDllAssembly(name, Paths.ManagedPath, readerParameters, out assembly))
return assembly;
return null;
};
}
///
/// Loads a list of types from a directory containing assemblies, that derive from a base type.
///
/// The specific base type to search for.
/// The directory to search for assemblies.
/// Returns a list of found derivative types.
public static Dictionary> FindPluginTypes(string directory)
{
var result = new Dictionary>();
var pluginType = typeof(BaseUnityPlugin);
string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
{
try
{
var ass = AssemblyDefinition.ReadAssembly(dll, readerParameters);
var matchingTypes = ass.MainModule.Types.Where(t => !t.IsInterface && !t.IsAbstract && t.Is(pluginType)).ToList();
if (matchingTypes.Count == 0)
continue;
var pluginInfos = new List();
foreach (var pluginTypeDefinition in matchingTypes)
{
var metadata = BepInPlugin.FromCecilType(pluginTypeDefinition);
if (metadata == null)
{
Logger.LogWarning($"Skipping over type [{pluginTypeDefinition.Name}] as no metadata attribute is specified");
continue;
}
//Perform a filter for currently running process
var filters = BepInProcess.FromCecilType(pluginTypeDefinition);
bool invalidProcessName = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
if (invalidProcessName)
{
Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
continue;
}
var dependencies = BepInDependency.FromCecilType(pluginTypeDefinition);
pluginInfos.Add(new PluginInfo
{
Metadata = metadata,
Processes = filters,
Dependencies = dependencies,
CecilType = pluginTypeDefinition,
Location = dll
});
}
result[ass] = pluginInfos;
}
catch (Exception e)
{
Logger.LogError(e.ToString());
}
}
return result;
}
private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
sb.AppendLine(exSub.Message);
if (exSub is FileNotFoundException exFileNotFound)
{
if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
{
sb.AppendLine("Fusion Log:");
sb.AppendLine(exFileNotFound.FusionLog);
}
}
else if (exSub is FileLoadException exLoad)
{
if (!string.IsNullOrEmpty(exLoad.FusionLog))
{
sb.AppendLine("Fusion Log:");
sb.AppendLine(exLoad.FusionLog);
}
}
sb.AppendLine();
}
return sb.ToString();
}
}
}