Fix mod assembly loading on Windows.

This commit is contained in:
Paul Chote
2016-02-07 18:14:06 +00:00
parent e0d74455f5
commit 6c6826c3f3
6 changed files with 55 additions and 39 deletions

View File

@@ -21,8 +21,6 @@ namespace OpenRA.FileSystem
{
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
static readonly Dictionary<string, Assembly> AssemblyCache = new Dictionary<string, Assembly>();
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
public IReadWritePackage CreatePackage(string filename, Dictionary<string, byte[]> content)
@@ -217,30 +215,5 @@ namespace OpenRA.FileSystem
else
return mountedPackages.Keys.Any(f => f.Contains(name));
}
public static Assembly ResolveAssembly(object sender, ResolveEventArgs e)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
if (assembly.FullName == e.Name)
return assembly;
var frags = e.Name.Split(',');
var filename = frags[0] + ".dll";
Assembly a;
if (AssemblyCache.TryGetValue(filename, out a))
return a;
if (Game.ModData.ModFiles.Exists(filename))
using (var s = Game.ModData.ModFiles.Open(filename))
{
var buf = s.ReadBytes((int)s.Length);
a = Assembly.Load(buf);
AssemblyCache.Add(filename, a);
return a;
}
return null;
}
}
}

View File

@@ -235,8 +235,6 @@ namespace OpenRA
{
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
AppDomain.CurrentDomain.AssemblyResolve += FileSystem.FileSystem.ResolveAssembly;
InitializeSettings(args);
Log.AddChannel("perf", "perf.log");

View File

@@ -43,11 +43,7 @@ namespace OpenRA
Manifest = new Manifest(mod);
ModFiles.LoadFromManifest(Manifest);
// Allow mods to load types from the core Game assembly, and any additional assemblies they specify.
var assemblies = new[] { typeof(Game).Assembly }
.Concat(Manifest.Assemblies.Select(path => Assembly.Load(ModFiles.Open(path).ReadAllBytes())))
.ToList();
ObjectCreator = new ObjectCreator(assemblies);
ObjectCreator = new ObjectCreator(Manifest, ModFiles);
Manifest.LoadCustomData(ObjectCreator);
if (useLoadScreen)

View File

@@ -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 =

View File

@@ -108,7 +108,7 @@ namespace OpenRA.Test
var yaml = MiniYaml.Merge(sources.Select(s => MiniYaml.FromString(s)));
var allUnits = yaml.ToDictionary(node => node.Key, node => node.Value);
var unit = allUnits[name];
var creator = new ObjectCreator(new[] { typeof(ActorInfoTest).Assembly });
var creator = new ObjectCreator(typeof(ActorInfoTest).Assembly);
return new ActorInfo(creator, name, unit);
}
}

View File

@@ -42,8 +42,6 @@ namespace OpenRA.Utility
return;
}
AppDomain.CurrentDomain.AssemblyResolve += FileSystem.FileSystem.ResolveAssembly;
Log.AddChannel("perf", null);
Log.AddChannel("debug", null);