Fix mod assembly loading on Windows.
This commit is contained in:
@@ -10,23 +10,74 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class ObjectCreator
|
||||
{
|
||||
// .NET does not support unloading assemblies, so mod libraries will leak across mod changes.
|
||||
// This tracks the assemblies that have been loaded since game start so that we don't load multiple copies
|
||||
static readonly Dictionary<string, Assembly> ResolvedAssemblies = new Dictionary<string, Assembly>();
|
||||
|
||||
readonly Cache<string, Type> typeCache;
|
||||
readonly Cache<Type, ConstructorInfo> ctorCache;
|
||||
readonly Pair<Assembly, string>[] assemblies;
|
||||
|
||||
public ObjectCreator(IEnumerable<Assembly> sourceAssemblies)
|
||||
public ObjectCreator(Assembly a)
|
||||
{
|
||||
typeCache = new Cache<string, Type>(FindType);
|
||||
ctorCache = new Cache<Type, ConstructorInfo>(GetCtor);
|
||||
assemblies = sourceAssemblies.SelectMany(asm => asm.GetNamespaces().Select(ns => Pair.New(asm, ns))).ToArray();
|
||||
assemblies = a.GetNamespaces().Select(ns => Pair.New(a, ns)).ToArray();
|
||||
}
|
||||
|
||||
public ObjectCreator(Manifest manifest, FileSystem.FileSystem modFiles)
|
||||
{
|
||||
typeCache = new Cache<string, Type>(FindType);
|
||||
ctorCache = new Cache<Type, ConstructorInfo>(GetCtor);
|
||||
|
||||
// Allow mods to load types from the core Game assembly, and any additional assemblies they specify.
|
||||
var assemblyList = new List<Assembly>() { typeof(Game).Assembly };
|
||||
foreach (var path in manifest.Assemblies)
|
||||
{
|
||||
var data = modFiles.Open(path).ReadAllBytes();
|
||||
|
||||
// .NET doesn't provide any way of querying the metadata of an assembly without either:
|
||||
// (a) loading duplicate data into the application domain, breaking the world.
|
||||
// (b) crashing if the assembly has already been loaded.
|
||||
// We can't check the internal name of the assembly, so we'll work off the data instead
|
||||
string hash;
|
||||
using (var ms = new MemoryStream(data))
|
||||
using (var csp = SHA1.Create())
|
||||
hash = new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
|
||||
Assembly assembly;
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out assembly))
|
||||
{
|
||||
assembly = Assembly.Load(data);
|
||||
ResolvedAssemblies.Add(hash, assembly);
|
||||
}
|
||||
|
||||
assemblyList.Add(assembly);
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
|
||||
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => Pair.New(asm, ns))).ToArray();
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;
|
||||
}
|
||||
|
||||
Assembly ResolveAssembly(object sender, ResolveEventArgs e)
|
||||
{
|
||||
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
|
||||
if (a.FullName == e.Name)
|
||||
return a;
|
||||
|
||||
return assemblies.Select(a => a.First).FirstOrDefault(a => a.FullName == e.Name);
|
||||
}
|
||||
|
||||
public static Action<string> MissingTypeAction =
|
||||
|
||||
Reference in New Issue
Block a user