Fix mod assembly loading on Windows.
This commit is contained in:
@@ -21,8 +21,6 @@ namespace OpenRA.FileSystem
|
|||||||
{
|
{
|
||||||
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
||||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
|
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>());
|
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||||
|
|
||||||
public IReadWritePackage CreatePackage(string filename, Dictionary<string, byte[]> content)
|
public IReadWritePackage CreatePackage(string filename, Dictionary<string, byte[]> content)
|
||||||
@@ -217,30 +215,5 @@ namespace OpenRA.FileSystem
|
|||||||
else
|
else
|
||||||
return mountedPackages.Keys.Any(f => f.Contains(name));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,8 +235,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
||||||
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += FileSystem.FileSystem.ResolveAssembly;
|
|
||||||
|
|
||||||
InitializeSettings(args);
|
InitializeSettings(args);
|
||||||
|
|
||||||
Log.AddChannel("perf", "perf.log");
|
Log.AddChannel("perf", "perf.log");
|
||||||
|
|||||||
@@ -43,11 +43,7 @@ namespace OpenRA
|
|||||||
Manifest = new Manifest(mod);
|
Manifest = new Manifest(mod);
|
||||||
ModFiles.LoadFromManifest(Manifest);
|
ModFiles.LoadFromManifest(Manifest);
|
||||||
|
|
||||||
// Allow mods to load types from the core Game assembly, and any additional assemblies they specify.
|
ObjectCreator = new ObjectCreator(Manifest, ModFiles);
|
||||||
var assemblies = new[] { typeof(Game).Assembly }
|
|
||||||
.Concat(Manifest.Assemblies.Select(path => Assembly.Load(ModFiles.Open(path).ReadAllBytes())))
|
|
||||||
.ToList();
|
|
||||||
ObjectCreator = new ObjectCreator(assemblies);
|
|
||||||
Manifest.LoadCustomData(ObjectCreator);
|
Manifest.LoadCustomData(ObjectCreator);
|
||||||
|
|
||||||
if (useLoadScreen)
|
if (useLoadScreen)
|
||||||
|
|||||||
@@ -10,23 +10,74 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public class ObjectCreator
|
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<string, Type> typeCache;
|
||||||
readonly Cache<Type, ConstructorInfo> ctorCache;
|
readonly Cache<Type, ConstructorInfo> ctorCache;
|
||||||
readonly Pair<Assembly, string>[] assemblies;
|
readonly Pair<Assembly, string>[] assemblies;
|
||||||
|
|
||||||
public ObjectCreator(IEnumerable<Assembly> sourceAssemblies)
|
public ObjectCreator(Assembly a)
|
||||||
{
|
{
|
||||||
typeCache = new Cache<string, Type>(FindType);
|
typeCache = new Cache<string, Type>(FindType);
|
||||||
ctorCache = new Cache<Type, ConstructorInfo>(GetCtor);
|
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 =
|
public static Action<string> MissingTypeAction =
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ namespace OpenRA.Test
|
|||||||
var yaml = MiniYaml.Merge(sources.Select(s => MiniYaml.FromString(s)));
|
var yaml = MiniYaml.Merge(sources.Select(s => MiniYaml.FromString(s)));
|
||||||
var allUnits = yaml.ToDictionary(node => node.Key, node => node.Value);
|
var allUnits = yaml.ToDictionary(node => node.Key, node => node.Value);
|
||||||
var unit = allUnits[name];
|
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);
|
return new ActorInfo(creator, name, unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ namespace OpenRA.Utility
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += FileSystem.FileSystem.ResolveAssembly;
|
|
||||||
|
|
||||||
Log.AddChannel("perf", null);
|
Log.AddChannel("perf", null);
|
||||||
Log.AddChannel("debug", null);
|
Log.AddChannel("debug", null);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user