diff --git a/Torch/Plugins/PluginManager.cs b/Torch/Plugins/PluginManager.cs
index 560e476e..5421c9a4 100644
--- a/Torch/Plugins/PluginManager.cs
+++ b/Torch/Plugins/PluginManager.cs
@@ -1,23 +1,17 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
-using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows;
-using System.Xml.Serialization;
-using Havok;
using NLog;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Plugins;
using Torch.API.Session;
-using Torch.API.WebAPI;
using Torch.Collections;
using Torch.Commands;
using Torch.Utils;
@@ -28,9 +22,9 @@ namespace Torch.Managers
///
public class PluginManager : Manager, IPluginManager
{
-
//event for when the plugins are reloaded
public event Action PluginsReloaded;
+
private class PluginItem
{
public string Filename { get; set; }
@@ -39,35 +33,34 @@ private class PluginItem
public bool IsZip { get; set; }
public List ResolvedDependencies { get; set; }
}
-
+
private static Logger _log = LogManager.GetCurrentClassLogger();
-
+
private const string MANIFEST_NAME = "manifest.xml";
-
+
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
private readonly MtObservableSortedDictionary _plugins = new MtObservableSortedDictionary();
private readonly List _pluginItems = new List();
private readonly List _reloadList = new List();
private CommandManager _mgr;
-
+
#pragma warning disable 649
[Dependency]
private ITorchSessionManager _sessionManager;
#pragma warning restore 649
-
+
///
public IReadOnlyDictionary Plugins => _plugins.AsReadOnlyObservable();
public event Action> PluginsLoaded;
-
+
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
{
Task.Run(async () => await TestApiConnection()).Wait();
- if (!Directory.Exists(PluginDir))
- Directory.CreateDirectory(PluginDir);
+ Directory.CreateDirectory(PluginDir);
}
-
+
///
/// Updates loaded plugins in parallel.
///
@@ -85,7 +78,7 @@ public void UpdatePlugins()
}
}
}
-
+
///
public override void Attach()
{
@@ -96,8 +89,10 @@ public override void Attach()
private void SessionManagerOnSessionStateChanged(ITorchSession session, TorchSessionState newState)
{
_mgr = session.Managers.GetManager();
+
if (_mgr == null)
return;
+
switch (newState)
{
case TorchSessionState.Loaded:
@@ -114,13 +109,14 @@ private void SessionManagerOnSessionStateChanged(ITorchSession session, TorchSes
return;
}
}
-
+
///
/// Unloads all plugins.
///
public override void Detach()
{
_sessionManager.SessionStateChanged -= SessionManagerOnSessionStateChanged;
+
foreach (var plugin in _plugins.Values)
plugin.Dispose();
@@ -145,6 +141,7 @@ public void LoadPlugins()
{
plugin.Init(Torch);
}
+
_log.Info($"Loaded {_plugins.Count} plugins.");
PluginsLoaded?.Invoke(_plugins.Values.AsReadOnly());
return;
@@ -152,26 +149,29 @@ public void LoadPlugins()
var pluginItems = GetLocalPlugins(PluginDir);
var pluginsToLoad = new List();
+
foreach (var item in pluginItems)
{
var pluginItem = item;
+
if (!TryValidatePluginDependencies(pluginItems, ref pluginItem, out var missingPlugins))
{
// We have some missing dependencies.
// Future fix would be to download them, but instead for now let's
// just warn the user it's missing
- foreach(var missingPlugin in missingPlugins)
+ foreach (var missingPlugin in missingPlugins)
_log.Warn($"{item.Manifest.Name} is missing dependency {missingPlugin}. Skipping plugin.");
+
continue;
}
-
+
pluginsToLoad.Add(pluginItem);
}
-
+
_log.Info($"Is plugin API reachable: {IsApiReachable}");
+
if (IsApiReachable)
{
-
if (Torch.Config.ShouldUpdatePlugins)
{
if (DownloadPluginUpdates(pluginsToLoad))
@@ -179,14 +179,16 @@ public void LoadPlugins()
// Resort the plugins just in case updates changed load hints.
pluginItems = GetLocalPlugins(PluginDir);
pluginsToLoad.Clear();
+
foreach (var item in pluginItems)
{
var pluginItem = item;
+
if (!TryValidatePluginDependencies(pluginItems, ref pluginItem, out var missingPlugins))
{
foreach (var missingPlugin in missingPlugins)
- _log.Warn(
- $"{item.Manifest.Name} is missing dependency {missingPlugin}. Skipping plugin.");
+ _log.Warn($"{item.Manifest.Name} is missing dependency {missingPlugin}. Skipping plugin.");
+
continue;
}
@@ -227,16 +229,15 @@ public void LoadPlugins()
_pluginItems.Add(plugin);
LoadPlugin(plugin);
}
-
+
foreach (var plugin in _plugins.Values)
{
plugin.Init(Torch);
}
}
-
+
_reloadList.Clear();
-
-
+
_log.Info($"Loaded {_plugins.Count} plugins.");
PluginsLoaded?.Invoke(_plugins.Values.AsReadOnly());
}
@@ -246,11 +247,13 @@ public void LoadPlugins()
private List GetLocalPlugins(string pluginDir, bool debug = false)
{
var firstLoad = Torch.Config.Plugins.Count == 0;
-
+
var pluginItems = Directory.EnumerateFiles(pluginDir, "*.zip")
.Union(Directory.EnumerateDirectories(pluginDir));
+
if (debug)
- pluginItems = pluginItems.Union(new List {pluginDir});
+ pluginItems = pluginItems.Union(new List { pluginDir });
+
var results = new List();
foreach (var item in pluginItems)
@@ -266,22 +269,22 @@ private List GetLocalPlugins(string pluginDir, bool debug = false)
_log.Warn($"Item '{item}' is missing a manifest, skipping.");
continue;
}
- manifest = new PluginManifest()
- {
- Guid = new Guid(),
- Version = "0",
- Name = "TEST"
- };
+
+ manifest = new PluginManifest() {
+ Guid = new Guid(),
+ Version = "0",
+ Name = "TEST"
+ };
}
var duplicatePlugin = results.FirstOrDefault(r => r.Manifest.Guid == manifest.Guid);
+
if (duplicatePlugin != null)
{
- _log.Warn(
- $"The GUID provided by {manifest.Name} ({item}) is already in use by {duplicatePlugin.Manifest.Name}.");
+ _log.Warn($"The GUID provided by {manifest.Name} ({item}) is already in use by {duplicatePlugin.Manifest.Name}.");
continue;
}
-
+
if (!Torch.Config.LocalPlugins && !debug)
{
if (isZip && !Torch.Config.Plugins.Contains(manifest.Guid))
@@ -291,13 +294,13 @@ private List GetLocalPlugins(string pluginDir, bool debug = false)
_log.Warn($"Plugin {manifest.Name} ({item}) exists in the plugin directory, but is not listed in torch.cfg. Skipping load!");
continue;
}
+
_log.Info($"First-time load: Plugin {manifest.Name} added to torch.cfg.");
Torch.Config.Plugins.Add(manifest.Guid);
}
}
-
- results.Add(new PluginItem
- {
+
+ results.Add(new PluginItem {
Filename = item,
IsZip = isZip,
Manifest = manifest,
@@ -307,29 +310,33 @@ private List GetLocalPlugins(string pluginDir, bool debug = false)
if (!Torch.Config.LocalPlugins && firstLoad)
Torch.Config.Save();
-
+
return results;
- }
-
+ }
+
private bool DownloadPluginUpdates(List plugins)
{
_log.Info("Checking for plugin updates...");
- var count = 0;
+
+ int count = 0;
+
Task.WaitAll(plugins.Select(async item =>
{
try
{
if (!item.IsZip)
{
- _log.Warn($"Unzipped plugins cannot be auto-updated. Skipping plugin {item}");
+ _log.Warn($"Unzipped plugins cannot be auto-updated. Skipping plugin '{item.Manifest.Name}'");
return;
}
+
item.Manifest.Version.TryExtractVersion(out Version currentVersion);
+
var latest = await Instance.QueryOne(item.Manifest.Guid);
if (latest?.LatestVersion == null)
{
- _log.Warn($"Plugin {item.Manifest.Name} does not have any releases on torchapi.com. Cannot update.");
+ _log.Warn($"Plugin '{item.Manifest.Name}' does not have any releases on torchapi.com. Cannot update.");
return;
}
@@ -348,26 +355,27 @@ private bool DownloadPluginUpdates(List plugins)
}
_log.Info($"Updating plugin '{item.Manifest.Name}' from {currentVersion} to {newVersion}.");
+
await Instance.DownloadPlugin(latest, item.Path);
Interlocked.Increment(ref count);
}
catch (Exception e)
{
- _log.Warn($"An error occurred updating the plugin {item.Manifest.Name}.");
+ _log.Warn($"An error occurred updating the plugin '{item.Manifest.Name}'.");
_log.Warn(e);
}
}).ToArray());
_log.Info($"Updated {count} plugins.");
+
return count > 0;
}
-
+
private void LoadPlugin(PluginItem item)
{
var assemblies = new List();
+ //var loaded = AppDomain.CurrentDomain.GetAssemblies();
- var loaded = AppDomain.CurrentDomain.GetAssemblies();
-
if (item.IsZip)
{
using (var zipFile = ZipFile.OpenRead(item.Path))
@@ -380,24 +388,25 @@ private void LoadPlugin(PluginItem item)
//if (loaded.Any(a => entry.Name.Contains(a.GetName().Name)))
// continue;
-
using (var stream = entry.Open())
{
- var data = stream.ReadToEnd((int) entry.Length);
+ var data = stream.ReadToEnd((int)entry.Length);
byte[] symbol = null;
- var symbolEntryName =
- entry.FullName.Substring(0, entry.FullName.Length - "dll".Length) + "pdb";
+ var symbolEntryName = entry.FullName.Substring(0, entry.FullName.Length - "dll".Length) + "pdb";
var symbolEntry = zipFile.GetEntry(symbolEntryName);
+
if (symbolEntry != null)
+ {
try
{
using (var symbolStream = symbolEntry.Open())
- symbol = symbolStream.ReadToEnd((int) symbolEntry.Length);
+ symbol = symbolStream.ReadToEnd((int)symbolEntry.Length);
}
catch (Exception e)
{
_log.Warn(e, $"Failed to read debugging symbols from {item.Filename}:{symbolEntryName}");
}
+ }
assemblies.Add(symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data));
}
@@ -409,7 +418,7 @@ private void LoadPlugin(PluginItem item)
var files = Directory
.EnumerateFiles(item.Path, "*.*", SearchOption.AllDirectories)
.ToList();
-
+
foreach (var file in files)
{
if (!file.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase))
@@ -422,9 +431,12 @@ private void LoadPlugin(PluginItem item)
{
var data = stream.ReadToEnd();
byte[] symbol = null;
+
var symbolPath = Path.Combine(Path.GetDirectoryName(file) ?? ".",
Path.GetFileNameWithoutExtension(file) + ".pdb");
+
if (File.Exists(symbolPath))
+ {
try
{
using (var symbolStream = File.OpenRead(symbolPath))
@@ -434,46 +446,52 @@ private void LoadPlugin(PluginItem item)
{
_log.Warn(e, $"Failed to read debugging symbols from {symbolPath}");
}
-
+ }
+
assemblies.Add(symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data));
}
}
-
-
}
-
+
RegisterAllAssemblies(assemblies);
InstantiatePlugin(item.Manifest, assemblies);
}
-
+
private void RegisterAllAssemblies(IReadOnlyCollection assemblies)
{
Assembly ResolveDependentAssembly(object sender, ResolveEventArgs args)
{
var requiredAssemblyName = new AssemblyName(args.Name);
+
foreach (Assembly asm in assemblies)
{
if (IsAssemblyCompatible(requiredAssemblyName, asm.GetName()))
return asm;
}
+
if (requiredAssemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
return null;
+
foreach (var asm in assemblies)
+ {
if (asm == args.RequestingAssembly)
{
_log.Warn($"Couldn't find dependency! {args.RequestingAssembly} depends on {requiredAssemblyName}.");
break;
}
+ }
+
return null;
}
AppDomain.CurrentDomain.AssemblyResolve += ResolveDependentAssembly;
+
foreach (Assembly asm in assemblies)
{
TorchBase.RegisterAuxAssembly(asm);
}
}
-
+
private static bool IsAssemblyCompatible(AssemblyName a, AssemblyName b)
{
return a.Name == b.Name && a.Version.Major == b.Version.Major && a.Version.Minor == b.Version.Minor;
@@ -482,19 +500,19 @@ private static bool IsAssemblyCompatible(AssemblyName a, AssemblyName b)
public void ReloadPlugins()
{
_log.Info("Reloading plugins.");
-
+
var plugins = _plugins.ToList();
if (!Torch.Config.BypassIsReloadableFlag)
plugins = plugins.Where(p => p.Value.IsReloadable).ToList();
-
+
foreach (var plugin in plugins)
{
_reloadList.Add(plugin.Key);
plugin.Value?.Dispose();
_plugins.Remove(plugin.Key);
}
-
+
LoadPlugins();
PluginsReloaded?.Invoke();
}
@@ -502,7 +520,7 @@ public void ReloadPlugins()
public void ReloadPlugin(Guid guid)
{
var plugin = _plugins[guid];
-
+
plugin.Dispose();
_plugins.Remove(guid);
_log.Info($"{plugin.Name} {plugin.Version} has been unloaded.");
@@ -510,11 +528,12 @@ public void ReloadPlugin(Guid guid)
LoadPlugin(_pluginItems.First(p => p.Manifest.Guid == guid));
_log.Info($"{plugin.Name} {plugin.Version} has been reloaded.");
}
-
+
private void InstantiatePlugin(PluginManifest manifest, IEnumerable assemblies)
{
Type pluginType = null;
bool mult = false;
+
foreach (var asm in assemblies)
{
foreach (var type in asm.GetExportedTypes())
@@ -553,11 +572,14 @@ private void InstantiatePlugin(PluginManifest manifest, IEnumerable as
// Backwards compatibility for PluginAttribute.
var pluginAttr = pluginType.GetCustomAttribute();
+
if (pluginAttr != null)
{
_log.Warn($"Plugin '{manifest.Name}' is using the obsolete {nameof(PluginAttribute)}, using info from attribute if necessary.");
+
manifest.Version = manifest.Version ?? pluginAttr.Version.ToString();
manifest.Name = manifest.Name ?? pluginAttr.Name;
+
if (manifest.Guid == default(Guid))
manifest.Guid = pluginAttr.Guid;
}
@@ -565,6 +587,7 @@ private void InstantiatePlugin(PluginManifest manifest, IEnumerable as
_log.Info($"Loading plugin '{manifest.Name}' ({manifest.Version})");
TorchPluginBase plugin;
+
try
{
plugin = (TorchPluginBase)Activator.CreateInstance(pluginType);
@@ -574,12 +597,13 @@ private void InstantiatePlugin(PluginManifest manifest, IEnumerable as
_log.Error(ex, $"Plugin {manifest.Name} threw an exception during instantiation! Not loading!");
return;
}
+
plugin.Manifest = manifest;
plugin.StoragePath = Torch.Config.InstancePath;
plugin.Torch = Torch;
_plugins.Add(manifest.Guid, plugin);
}
-
+
private PluginManifest GetManifestFromZip(string path)
{
try
@@ -611,11 +635,11 @@ private bool TryValidatePluginDependencies(List items, ref PluginIte
{
var dependencies = new List();
missingDependencies = new List();
-
+
foreach (var pluginDependency in item.Manifest.Dependencies)
{
- var dependency = items
- .FirstOrDefault(pi => pi?.Manifest.Guid == pluginDependency.Plugin);
+ var dependency = items.FirstOrDefault(pi => pi?.Manifest.Guid == pluginDependency.Plugin);
+
if (dependency == null)
{
missingDependencies.Add(pluginDependency.Plugin);
@@ -631,7 +655,7 @@ private bool TryValidatePluginDependencies(List items, ref PluginIte
{
// If dependency version is too low, we can try to update. Otherwise
// it's a missing dependency.
-
+
// For now let's just warn the user. bitMuse is lazy.
_log.Warn($"{dependency.Manifest.Name} is below the requested version for {item.Manifest.Name}."
+ Environment.NewLine
@@ -645,8 +669,10 @@ private bool TryValidatePluginDependencies(List items, ref PluginIte
}
item.ResolvedDependencies = dependencies;
+
if (missingDependencies.Count > 0)
return false;
+
return true;
}
diff --git a/Torch/Session/TorchSessionManager.cs b/Torch/Session/TorchSessionManager.cs
index 14053cfd..0c6f71b3 100644
--- a/Torch/Session/TorchSessionManager.cs
+++ b/Torch/Session/TorchSessionManager.cs
@@ -1,18 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using NLog;
-using Sandbox.Engine.Networking;
using Sandbox.Game.World;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers;
using Torch.Mod;
-using Torch.Session;
using Torch.Utils;
using VRage.Game;
@@ -46,8 +41,9 @@ public class TorchSessionManager : Manager, ITorchSessionManager
public TorchSessionManager(ITorchBase torchInstance) : base(torchInstance)
{
_overrideMods = new Dictionary();
+
if (Torch.Config.UgcServiceType == UGCServiceType.Steam)
- _overrideMods.Add(TorchModCore.MOD_ID, ModItemUtils.Create(TorchModCore.MOD_ID));
+ _overrideMods.Add(TorchModCore.MOD_ID, ModItemUtils.Create(TorchModCore.MOD_ID, "Steam"));
}
///
@@ -81,7 +77,7 @@ public bool AddOverrideMod(ulong modId)
///
public bool RemoveOverrideMod(ulong modId)
{
- if(_overrideMods.TryGetValue(modId, out var item))
+ if (_overrideMods.TryGetValue(modId, out var item))
OverrideModsChanged?.Invoke(new CollectionChangeEventArgs(CollectionChangeAction.Remove, item));
return _overrideMods.Remove(modId);
diff --git a/Torch/Utils/MiscExtensions.cs b/Torch/Utils/MiscExtensions.cs
index 46078dc6..1dca31dd 100644
--- a/Torch/Utils/MiscExtensions.cs
+++ b/Torch/Utils/MiscExtensions.cs
@@ -1,8 +1,6 @@
using System;
using System.IO;
-using System.Linq;
using System.Net;
-using System.Threading;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Entities;
using Sandbox.Game.World;
@@ -12,52 +10,56 @@ namespace Torch.Utils
{
public static class MiscExtensions
{
- private static readonly ThreadLocal> _streamBuffer = new ThreadLocal>(() => new WeakReference(null));
-
- private static long LengthSafe(this Stream stream)
+ public static byte[] ReadToEnd(this Stream stream, int optionalDataLength = -1)
{
+ long streamLength = optionalDataLength;
+
try
{
- return stream.Length;
+ if (stream.CanSeek)
+ streamLength = stream.Length;
}
catch
{
- return 512;
}
- }
- public static byte[] ReadToEnd(this Stream stream, int optionalDataLength = -1)
- {
- byte[] buffer;
- if (!_streamBuffer.Value.TryGetTarget(out buffer))
- buffer = new byte[stream.LengthSafe()];
- var initialBufferSize = optionalDataLength > 0 ? optionalDataLength : stream.LengthSafe();
- if (buffer.Length < initialBufferSize)
- buffer = new byte[initialBufferSize];
- if (buffer.Length < 1024)
- buffer = new byte[1024];
- var streamPosition = 0;
- while (true)
+ int bufferSize = streamLength > 0 ? (int)streamLength : 32768;
+ var buffer = new byte[bufferSize];
+ int totalRead = 0;
+ int numRead;
+
+ do
{
- if (buffer.Length == streamPosition)
- Array.Resize(ref buffer, Math.Max((int)stream.LengthSafe(), buffer.Length * 2));
- int count = stream.Read(buffer, streamPosition, buffer.Length - streamPosition);
- if (count == 0)
- break;
+ int toRead = buffer.Length - totalRead;
- streamPosition += count;
+ numRead = stream.Read(buffer, totalRead, toRead);
+
+ totalRead += numRead;
+
+ if (numRead > 0 && totalRead == buffer.Length)
+ {
+ if (streamLength > 0)
+ break;
+
+ Array.Resize(ref buffer, buffer.Length * 2);
+ }
}
+ while (numRead > 0);
+
+ var result = new byte[totalRead];
- var result = new byte[streamPosition];
Array.Copy(buffer, 0, result, 0, result.Length);
- _streamBuffer.Value.SetTarget(buffer);
+
return result;
}
public static IPAddress GetRemoteIP(this P2PSessionState_t state)
{
- // What is endianness anyway?
- return new IPAddress(BitConverter.GetBytes(state.m_nRemoteIP).Reverse().ToArray());
+ // Reverse endianness
+ var bytes = BitConverter.GetBytes(state.m_nRemoteIP);
+ Array.Reverse(bytes);
+
+ return new IPAddress(bytes);
}
public static string GetGridOwnerName(this MyCubeGrid grid)