Files
crtr/Assets/Cryville/Crtr/Browsing/ExtensionManager.cs

139 lines
4.4 KiB
C#

using Cryville.Crtr.Extension;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Cryville.Crtr.Browsing {
internal static class ExtensionManager {
static bool _init;
static readonly Dictionary<string, List<ResourceConverter>> _converters
= new();
public static ISet<string> GetSupportedFormats() {
return new HashSet<string>(_converters.Keys);
}
public static bool TryGetConverters(string extension, out IEnumerable<ResourceConverter> converters) {
bool result = _converters.TryGetValue(extension, out List<ResourceConverter> outResult);
converters = outResult;
return result;
}
static readonly Dictionary<string, string> _localRes
= new();
public static IReadOnlyDictionary<string, string> GetLocalResourcePaths() {
return _localRes;
}
public static void Init(string rootPath) {
if (_init) return;
_init = true;
LoadExtension(typeof(Extensions.Extension));
var asms = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToHashSet();
var modules = new Queue<ModuleItem>();
var extensionDir = new DirectoryInfo(Path.Combine(rootPath, "extensions"));
if (extensionDir.Exists) {
foreach (var extension in extensionDir.EnumerateFiles("*.dll")) {
try {
modules.Enqueue(new ModuleItem(extension.OpenRead()));
}
catch (Exception ex) {
Game.MainLogger.Log(4, "Extension", "Failed to load DLL {0}: {1}", extension, ex);
}
}
}
int refCounter = 0;
while (modules.Count > 0 && refCounter < modules.Count) {
var module = modules.Dequeue();
bool flag = false;
foreach (var reference in module.Definition.AssemblyReferences) {
if (!asms.Contains(reference.Name)) {
flag = true;
break;
}
}
if (flag) {
modules.Enqueue(module);
refCounter++;
}
else {
try {
var stream = module.Stream;
stream.Seek(0, SeekOrigin.Begin);
var buf = new byte[stream.Length];
stream.Read(buf, 0, buf.Length);
var asm = Assembly.Load(buf) ?? throw new TypeLoadException("Failed to load the module");
asms.Add(asm.GetName().Name);
foreach (var type in asm.GetTypes()) {
if (typeof(ExtensionInterface).IsAssignableFrom(type)) {
LoadExtension(type);
}
}
Game.MainLogger.Log(1, "Extension", "Loaded module {0}", module.Definition.Name);
}
catch (Exception ex) {
Game.MainLogger.Log(4, "Extension", "An error occurred while trying to load module {0}: {1}", module.Definition.Name, ex);
}
finally {
module.Definition.Dispose();
module.Stream.Dispose();
refCounter = 0;
}
}
}
var missingList = new List<string>();
while (modules.Count > 0) {
missingList.Clear();
var module = modules.Dequeue();
foreach (var reference in module.Definition.AssemblyReferences) {
if (!asms.Contains(reference.Name)) {
missingList.Add(reference.Name);
}
}
Game.MainLogger.Log(4, "Extension", "Could not load the module {0} because the following dependencies were missing: {1}", module.Definition.Name, missingList.Aggregate((current, next) => current + ", " + next));
module.Definition.Dispose();
module.Stream.Dispose();
}
}
struct ModuleItem {
public ModuleDefinition Definition { get; set; }
public FileStream Stream { get; set; }
public ModuleItem(FileStream stream) {
Stream = stream;
Definition = ModuleDefinition.ReadModule(stream);
}
}
static void LoadExtension(Type type) {
try {
var extension = (ExtensionInterface)Activator.CreateInstance(type);
var l1 = extension.GetResourceConverters();
if (l1 != null) {
foreach (var c in l1) {
var fs = c.GetSupportedFormats();
if (fs == null) continue;
foreach (var f in fs) {
if (f == null) continue;
if (!_converters.ContainsKey(f))
_converters.Add(f, new List<ResourceConverter> { c });
else _converters[f].Add(c);
}
}
}
var l2 = extension.GetResourceFinders();
if (l2 != null) {
foreach (var f in l2) {
var name = f.Name;
var path = f.GetRootPath();
if (name != null && path != null) _localRes.Add(name, path);
}
}
Game.MainLogger.Log(1, "Extension", "Loaded extension {0}", type);
}
catch (Exception ex) {
Game.MainLogger.Log(4, "Extension", "Failed to load extension {0}: {1}", type, ex);
}
}
}
}