Merge pull request #161 from TorchAPI/plugin-wpf-controls
Pluggable WPF Controls for the Entity Manager UI
This commit is contained in:
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@@ -39,8 +39,8 @@ node {
|
|||||||
} else {
|
} else {
|
||||||
buildMode = "Debug"
|
buildMode = "Debug"
|
||||||
}
|
}
|
||||||
bat "rmdir /Q /S \"bin\""
|
bat "IF EXIST \"bin\" rmdir /Q /S \"bin\""
|
||||||
bat "rmdir /Q /S \"bin-test\""
|
bat "IF EXIST \"bin-test\" rmdir /Q /S \"bin-test\""
|
||||||
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64 /t:Clean"
|
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64 /t:Clean"
|
||||||
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64"
|
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64"
|
||||||
}
|
}
|
||||||
|
265
Torch.Server/Managers/EntityControlManager.cs
Normal file
265
Torch.Server/Managers/EntityControlManager.cs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Server.ViewModels.Entities;
|
||||||
|
using Torch.Utils;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manager that lets users bind random view models to entities in Torch's Entity Manager
|
||||||
|
/// </summary>
|
||||||
|
public class EntityControlManager : Manager
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an entity control manager for the given instance of torch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="torchInstance">Torch instance</param>
|
||||||
|
internal EntityControlManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class ModelFactory
|
||||||
|
{
|
||||||
|
private readonly ConditionalWeakTable<EntityViewModel, EntityControlViewModel> _models = new ConditionalWeakTable<EntityViewModel, EntityControlViewModel>();
|
||||||
|
|
||||||
|
public abstract Delegate Delegate { get; }
|
||||||
|
|
||||||
|
protected abstract EntityControlViewModel Create(EntityViewModel evm);
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedGetter(Name = "Keys")]
|
||||||
|
private static readonly Func<ConditionalWeakTable<EntityViewModel, EntityControlViewModel>, ICollection<EntityViewModel>> _weakTableKeys;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Warning: Creates a giant list, avoid if possible.
|
||||||
|
/// </summary>
|
||||||
|
internal ICollection<EntityViewModel> Keys => _weakTableKeys(_models);
|
||||||
|
|
||||||
|
internal EntityControlViewModel GetOrCreate(EntityViewModel evm)
|
||||||
|
{
|
||||||
|
return _models.GetValue(evm, Create);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGet(EntityViewModel evm, out EntityControlViewModel res)
|
||||||
|
{
|
||||||
|
return _models.TryGetValue(evm, out res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelFactory<T> : ModelFactory where T : EntityViewModel
|
||||||
|
{
|
||||||
|
private readonly Func<T, EntityControlViewModel> _factory;
|
||||||
|
public override Delegate Delegate => _factory;
|
||||||
|
|
||||||
|
internal ModelFactory(Func<T, EntityControlViewModel> factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override EntityControlViewModel Create(EntityViewModel evm)
|
||||||
|
{
|
||||||
|
if (evm is T m)
|
||||||
|
{
|
||||||
|
var result = _factory(m);
|
||||||
|
_log.Debug($"Model factory {_factory.Method} created {result} for {evm}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<ModelFactory> _modelFactories = new List<ModelFactory>();
|
||||||
|
private readonly List<Delegate> _controlFactories = new List<Delegate>();
|
||||||
|
|
||||||
|
private readonly List<WeakReference<EntityViewModel>> _boundEntityViewModels = new List<WeakReference<EntityViewModel>>();
|
||||||
|
private readonly ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>> _boundViewModels = new ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This factory will be used to create component models for matching entity models.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
|
||||||
|
/// <param name="modelFactory">Method to create component model from entity model.</param>
|
||||||
|
public void RegisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
|
||||||
|
where TEntityBaseModel : EntityViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
var factory = new ModelFactory<TEntityBaseModel>(modelFactory);
|
||||||
|
_modelFactories.Add(factory);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < _boundEntityViewModels.Count)
|
||||||
|
{
|
||||||
|
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
|
||||||
|
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
|
||||||
|
{
|
||||||
|
if (target is TEntityBaseModel tent)
|
||||||
|
UpdateBinding(target, components);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_boundEntityViewModels.RemoveAtFast(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters a factory registered with <see cref="RegisterModelFactory{TEntityBaseModel}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
|
||||||
|
/// <param name="modelFactory">Method to create component model from entity model.</param>
|
||||||
|
public void UnregisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
|
||||||
|
where TEntityBaseModel : EntityViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _modelFactories.Count; i++)
|
||||||
|
{
|
||||||
|
if (_modelFactories[i].Delegate == (Delegate)modelFactory)
|
||||||
|
{
|
||||||
|
foreach (var entry in _modelFactories[i].Keys)
|
||||||
|
if (_modelFactories[i].TryGet(entry, out EntityControlViewModel ecvm) && ecvm != null
|
||||||
|
&& _boundViewModels.TryGetValue(entry, out var binding))
|
||||||
|
binding.Remove(ecvm);
|
||||||
|
_modelFactories.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This factory will be used to create controls for matching view models.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
|
||||||
|
/// <param name="controlFactory">Method to create control from component model</param>
|
||||||
|
public void RegisterControlFactory<TEntityComponentModel>(
|
||||||
|
Func<TEntityComponentModel, Control> controlFactory)
|
||||||
|
where TEntityComponentModel : EntityControlViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_controlFactories.Add(controlFactory);
|
||||||
|
RefreshControls<TEntityComponentModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Unregisters a factory registered with <see cref="RegisterControlFactory{TEntityComponentModel}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
|
||||||
|
/// <param name="controlFactory">Method to create control from component model</param>
|
||||||
|
public void UnregisterControlFactory<TEntityComponentModel>(
|
||||||
|
Func<TEntityComponentModel, Control> controlFactory)
|
||||||
|
where TEntityComponentModel : EntityControlViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_controlFactories.Remove(controlFactory);
|
||||||
|
RefreshControls<TEntityComponentModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshControls<TEntityComponentModel>() where TEntityComponentModel : EntityControlViewModel
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
while (i < _boundEntityViewModels.Count)
|
||||||
|
{
|
||||||
|
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
|
||||||
|
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
|
||||||
|
{
|
||||||
|
foreach (EntityControlViewModel component in components)
|
||||||
|
if (component is TEntityComponentModel)
|
||||||
|
component.InvalidateControl();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_boundEntityViewModels.RemoveAtFast(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the models bound to the given entity view model.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">view model to query</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MtObservableList<EntityControlViewModel> BoundModels(EntityViewModel entity)
|
||||||
|
{
|
||||||
|
return _boundViewModels.GetValue(entity, CreateFreshBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a control for the given view model type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">model to create a control for</param>
|
||||||
|
/// <returns>control, or null if none</returns>
|
||||||
|
public Control CreateControl(EntityControlViewModel model)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
foreach (Delegate factory in _controlFactories)
|
||||||
|
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
|
||||||
|
factory.DynamicInvoke(model) is Control result)
|
||||||
|
{
|
||||||
|
_log.Debug($"Control factory {factory.Method} created {result}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
_log.Warn($"No control created for {model}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MtObservableList<EntityControlViewModel> CreateFreshBinding(EntityViewModel key)
|
||||||
|
{
|
||||||
|
var binding = new MtObservableList<EntityControlViewModel>();
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_boundEntityViewModels.Add(new WeakReference<EntityViewModel>(key));
|
||||||
|
}
|
||||||
|
binding.PropertyChanged += (x, args) =>
|
||||||
|
{
|
||||||
|
if (nameof(binding.IsObserved).Equals(args.PropertyName))
|
||||||
|
UpdateBinding(key, binding);
|
||||||
|
};
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBinding(EntityViewModel key, MtObservableList<EntityControlViewModel> binding)
|
||||||
|
{
|
||||||
|
if (!binding.IsObserved)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
foreach (ModelFactory factory in _modelFactories)
|
||||||
|
{
|
||||||
|
var result = factory.GetOrCreate(key);
|
||||||
|
if (result != null && !binding.Contains(result))
|
||||||
|
binding.Add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -195,6 +195,7 @@
|
|||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="ListBoxExtensions.cs" />
|
<Compile Include="ListBoxExtensions.cs" />
|
||||||
|
<Compile Include="Managers\EntityControlManager.cs" />
|
||||||
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
|
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
|
||||||
<Compile Include="Managers\InstanceManager.cs" />
|
<Compile Include="Managers\InstanceManager.cs" />
|
||||||
<Compile Include="NativeMethods.cs" />
|
<Compile Include="NativeMethods.cs" />
|
||||||
@@ -214,6 +215,13 @@
|
|||||||
<Compile Include="ViewModels\Entities\Blocks\PropertyViewModel.cs" />
|
<Compile Include="ViewModels\Entities\Blocks\PropertyViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
||||||
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
|
||||||
|
<Compile Include="Views\Entities\EntityControlHost.xaml.cs">
|
||||||
|
<DependentUpon>EntityControlHost.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Views\Entities\EntityControlsView.xaml.cs">
|
||||||
|
<DependentUpon>EntityControlsView.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="ViewModels\EntityTreeViewModel.cs" />
|
<Compile Include="ViewModels\EntityTreeViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Entities\EntityViewModel.cs" />
|
<Compile Include="ViewModels\Entities\EntityViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Entities\FloatingObjectViewModel.cs" />
|
<Compile Include="ViewModels\Entities\FloatingObjectViewModel.cs" />
|
||||||
@@ -305,6 +313,14 @@
|
|||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Page Include="Views\Entities\EntityControlHost.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Views\Entities\EntityControlsView.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\AddWorkshopItemsDialog.xaml">
|
<Page Include="Views\AddWorkshopItemsDialog.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -333,6 +349,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\PluginsControl.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\Entities\VoxelMapView.xaml">
|
<Page Include="Views\Entities\VoxelMapView.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -345,10 +365,6 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
<Page Include="Views\PluginsControl.xaml">
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
<Page Include="Views\TorchUI.xaml">
|
<Page Include="Views\TorchUI.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
|
@@ -72,6 +72,7 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
DedicatedInstance = new InstanceManager(this);
|
DedicatedInstance = new InstanceManager(this);
|
||||||
AddManager(DedicatedInstance);
|
AddManager(DedicatedInstance);
|
||||||
|
AddManager(new EntityControlManager(this));
|
||||||
Config = config ?? new TorchConfig();
|
Config = config ?? new TorchConfig();
|
||||||
|
|
||||||
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||||
@@ -102,9 +103,19 @@ namespace Torch.Server
|
|||||||
MyPlugins.Load();
|
MyPlugins.Load();
|
||||||
MyGlobalTypeMetadata.Static.Init();
|
MyGlobalTypeMetadata.Static.Init();
|
||||||
|
|
||||||
|
Managers.GetManager<ITorchSessionManager>().SessionStateChanged += OnSessionStateChanged;
|
||||||
GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
|
GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
|
||||||
|
{
|
||||||
|
if (newState == TorchSessionState.Unloading || newState == TorchSessionState.Unloaded)
|
||||||
|
{
|
||||||
|
_watchdog?.Dispose();
|
||||||
|
_watchdog = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void InvokeBeforeRun()
|
private void InvokeBeforeRun()
|
||||||
{
|
{
|
||||||
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
|
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
|
||||||
@@ -202,11 +213,18 @@ namespace Torch.Server
|
|||||||
((TorchServer)state).Invoke(() => mre.Set());
|
((TorchServer)state).Invoke(() => mre.Set());
|
||||||
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Log.Error($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
|
||||||
|
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
|
||||||
|
#else
|
||||||
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
|
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
|
||||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
|
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Debug("Server watchdog responded");
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("Server watchdog responded");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000)
|
private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000)
|
||||||
|
38
Torch.Server/ViewModels/Entities/EntityControlViewModel.cs
Normal file
38
Torch.Server/ViewModels/Entities/EntityControlViewModel.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Torch.Server.ViewModels.Entities
|
||||||
|
{
|
||||||
|
public class EntityControlViewModel : ViewModel
|
||||||
|
{
|
||||||
|
internal const string SignalPropertyInvalidateControl =
|
||||||
|
"InvalidateControl-4124a476-704f-4762-8b5e-336a18e2f7e5";
|
||||||
|
|
||||||
|
internal void InvalidateControl()
|
||||||
|
{
|
||||||
|
// ReSharper disable once ExplicitCallerInfoArgument
|
||||||
|
OnPropertyChanged(SignalPropertyInvalidateControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _hide;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should this element be forced into the <see cref="Visibility.Collapsed"/>
|
||||||
|
/// </summary>
|
||||||
|
public bool Hide
|
||||||
|
{
|
||||||
|
get => _hide;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
if (_hide == value)
|
||||||
|
return;
|
||||||
|
_hide = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,8 @@
|
|||||||
using VRage.Game.ModAPI;
|
using System.Windows.Controls;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
using VRage.ModAPI;
|
using VRage.ModAPI;
|
||||||
using VRageMath;
|
using VRageMath;
|
||||||
|
|
||||||
@@ -7,9 +11,25 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
public class EntityViewModel : ViewModel
|
public class EntityViewModel : ViewModel
|
||||||
{
|
{
|
||||||
protected EntityTreeViewModel Tree { get; }
|
protected EntityTreeViewModel Tree { get; }
|
||||||
public IMyEntity Entity { get; }
|
|
||||||
|
private IMyEntity _backing;
|
||||||
|
public IMyEntity Entity
|
||||||
|
{
|
||||||
|
get => _backing;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
_backing = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
EntityControls = TorchBase.Instance?.Managers.GetManager<EntityControlManager>()?.BoundModels(this);
|
||||||
|
// ReSharper disable once ExplicitCallerInfoArgument
|
||||||
|
OnPropertyChanged(nameof(EntityControls));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long Id => Entity.EntityId;
|
public long Id => Entity.EntityId;
|
||||||
|
|
||||||
|
public MtObservableList<EntityControlViewModel> EntityControls { get; private set; }
|
||||||
|
|
||||||
public virtual string Name
|
public virtual string Name
|
||||||
{
|
{
|
||||||
get => Entity.DisplayName;
|
get => Entity.DisplayName;
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sandbox.Game.Entities;
|
using Sandbox.Game.Entities;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
|
using Torch.API.Managers;
|
||||||
using Torch.Collections;
|
using Torch.Collections;
|
||||||
using Torch.Server.ViewModels.Blocks;
|
using Torch.Server.ViewModels.Blocks;
|
||||||
|
|
||||||
|
@@ -17,6 +17,8 @@ namespace Torch.Server.ViewModels
|
|||||||
{
|
{
|
||||||
public class EntityTreeViewModel : ViewModel
|
public class EntityTreeViewModel : ViewModel
|
||||||
{
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
//TODO: these should be sorted sets for speed
|
//TODO: these should be sorted sets for speed
|
||||||
public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>();
|
public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>();
|
||||||
public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>();
|
public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>();
|
||||||
@@ -46,39 +48,55 @@ namespace Torch.Server.ViewModels
|
|||||||
|
|
||||||
private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj)
|
private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj)
|
||||||
{
|
{
|
||||||
switch (obj)
|
try
|
||||||
{
|
{
|
||||||
case MyCubeGrid grid:
|
switch (obj)
|
||||||
Grids.RemoveWhere(m => m.Id == grid.EntityId);
|
{
|
||||||
break;
|
case MyCubeGrid grid:
|
||||||
case MyCharacter character:
|
Grids.RemoveWhere(m => m.Id == grid.EntityId);
|
||||||
Characters.RemoveWhere(m => m.Id == character.EntityId);
|
break;
|
||||||
break;
|
case MyCharacter character:
|
||||||
case MyFloatingObject floating:
|
Characters.RemoveWhere(m => m.Id == character.EntityId);
|
||||||
FloatingObjects.RemoveWhere(m => m.Id == floating.EntityId);
|
break;
|
||||||
break;
|
case MyFloatingObject floating:
|
||||||
case MyVoxelBase voxel:
|
FloatingObjects.RemoveWhere(m => m.Id == floating.EntityId);
|
||||||
VoxelMaps.RemoveWhere(m => m.Id == voxel.EntityId);
|
break;
|
||||||
break;
|
case MyVoxelBase voxel:
|
||||||
|
VoxelMaps.RemoveWhere(m => m.Id == voxel.EntityId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
// ignore error "it's only UI"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj)
|
private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj)
|
||||||
{
|
{
|
||||||
switch (obj)
|
try
|
||||||
{
|
{
|
||||||
case MyCubeGrid grid:
|
switch (obj)
|
||||||
Grids.Add(new GridViewModel(grid, this));
|
{
|
||||||
break;
|
case MyCubeGrid grid:
|
||||||
case MyCharacter character:
|
Grids.Add(new GridViewModel(grid, this));
|
||||||
Characters.Add(new CharacterViewModel(character, this));
|
break;
|
||||||
break;
|
case MyCharacter character:
|
||||||
case MyFloatingObject floating:
|
Characters.Add(new CharacterViewModel(character, this));
|
||||||
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
|
break;
|
||||||
break;
|
case MyFloatingObject floating:
|
||||||
case MyVoxelBase voxel:
|
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
|
||||||
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
|
break;
|
||||||
break;
|
case MyVoxelBase voxel:
|
||||||
|
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
// ignore error "it's only UI"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,7 +44,7 @@ namespace Torch.Server
|
|||||||
public void BindServer(ITorchServer server)
|
public void BindServer(ITorchServer server)
|
||||||
{
|
{
|
||||||
_server = (TorchBase)server;
|
_server = (TorchBase)server;
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
ChatItems.Inlines.Clear();
|
ChatItems.Inlines.Clear();
|
||||||
});
|
});
|
||||||
@@ -59,7 +59,7 @@ namespace Torch.Server
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case TorchSessionState.Loading:
|
case TorchSessionState.Loading:
|
||||||
Dispatcher.Invoke(() => ChatItems.Inlines.Clear());
|
Dispatcher.InvokeAsync(() => ChatItems.Inlines.Clear());
|
||||||
break;
|
break;
|
||||||
case TorchSessionState.Loaded:
|
case TorchSessionState.Loaded:
|
||||||
{
|
{
|
||||||
@@ -112,7 +112,7 @@ namespace Torch.Server
|
|||||||
ChatScroller.ScrollToBottom();
|
ChatScroller.ScrollToBottom();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Dispatcher.Invoke(() => InsertMessage(msg));
|
Dispatcher.InvokeAsync(() => InsertMessage(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendButton_Click(object sender, RoutedEventArgs e)
|
private void SendButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Torch.Server.Views.Blocks"
|
xmlns:local="clr-namespace:Torch.Server.Views.Blocks"
|
||||||
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
|
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
|
||||||
|
xmlns:entities="clr-namespace:Torch.Server.Views.Entities"
|
||||||
|
xmlns:entities1="clr-namespace:Torch.Server.ViewModels.Entities"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<blocks:BlockViewModel />
|
<blocks:BlockViewModel />
|
||||||
@@ -12,6 +14,7 @@
|
|||||||
<Grid Margin="3">
|
<Grid Margin="3">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<StackPanel Grid.Row="0">
|
<StackPanel Grid.Row="0">
|
||||||
@@ -22,22 +25,27 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Label Content="Properties"/>
|
<Label Content="Properties"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ListView Grid.Row="1" ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True">
|
<Expander Grid.Row="1" Header="Block Properties">
|
||||||
<ListView.ItemTemplate>
|
<ListView ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True">
|
||||||
<DataTemplate>
|
<ListView.ItemTemplate>
|
||||||
<local:PropertyView />
|
<DataTemplate>
|
||||||
</DataTemplate>
|
<local:PropertyView />
|
||||||
</ListView.ItemTemplate>
|
</DataTemplate>
|
||||||
<ListView.ItemContainerStyle>
|
</ListView.ItemTemplate>
|
||||||
<Style TargetType="ListViewItem">
|
<ListView.ItemContainerStyle>
|
||||||
<Setter Property="HorizontalContentAlignment"
|
<Style TargetType="ListViewItem">
|
||||||
|
<Setter Property="HorizontalContentAlignment"
|
||||||
Value="Stretch" />
|
Value="Stretch" />
|
||||||
<Setter Property="VerticalContentAlignment"
|
<Setter Property="VerticalContentAlignment"
|
||||||
Value="Center" />
|
Value="Center" />
|
||||||
<Setter Property="Focusable"
|
<Setter Property="Focusable"
|
||||||
Value="false" />
|
Value="false" />
|
||||||
</Style>
|
</Style>
|
||||||
</ListView.ItemContainerStyle>
|
</ListView.ItemContainerStyle>
|
||||||
</ListView>
|
</ListView>
|
||||||
|
</Expander>
|
||||||
|
<ScrollViewer Grid.Row="2" Margin="3" VerticalScrollBarVisibility="Auto">
|
||||||
|
<entities:EntityControlsView DataContext="{Binding}"/>
|
||||||
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
8
Torch.Server/Views/Entities/EntityControlHost.xaml
Normal file
8
Torch.Server/Views/Entities/EntityControlHost.xaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<UserControl x:Class="Torch.Server.Views.Entities.EntityControlHost"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300" d:DesignWidth="300">
|
||||||
|
</UserControl>
|
72
Torch.Server/Views/Entities/EntityControlHost.xaml.cs
Normal file
72
Torch.Server/Views/Entities/EntityControlHost.xaml.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Server.ViewModels.Entities;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views.Entities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for EntityControlHost.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class EntityControlHost : UserControl
|
||||||
|
{
|
||||||
|
public EntityControlHost()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContextChanged += OnDataContextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.OldValue is ViewModel vmo)
|
||||||
|
{
|
||||||
|
vmo.PropertyChanged -= DataContext_OnPropertyChanged;
|
||||||
|
}
|
||||||
|
if (e.NewValue is ViewModel vmn)
|
||||||
|
{
|
||||||
|
vmn.PropertyChanged += DataContext_OnPropertyChanged;
|
||||||
|
}
|
||||||
|
RefreshControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DataContext_OnPropertyChanged(object sender, PropertyChangedEventArgs pa)
|
||||||
|
{
|
||||||
|
if (pa.PropertyName.Equals(EntityControlViewModel.SignalPropertyInvalidateControl))
|
||||||
|
RefreshControl();
|
||||||
|
else if (pa.PropertyName.Equals(nameof(EntityControlViewModel.Hide)))
|
||||||
|
RefreshVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control _currentControl;
|
||||||
|
|
||||||
|
private void RefreshControl()
|
||||||
|
{
|
||||||
|
if (Dispatcher.Thread != Thread.CurrentThread)
|
||||||
|
{
|
||||||
|
Dispatcher.InvokeAsync(RefreshControl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentControl = DataContext is EntityControlViewModel ecvm
|
||||||
|
? TorchBase.Instance?.Managers.GetManager<EntityControlManager>()?.CreateControl(ecvm)
|
||||||
|
: null;
|
||||||
|
Content = _currentControl;
|
||||||
|
RefreshVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshVisibility()
|
||||||
|
{
|
||||||
|
if (Dispatcher.Thread != Thread.CurrentThread)
|
||||||
|
{
|
||||||
|
Dispatcher.InvokeAsync(RefreshVisibility);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Visibility = (DataContext is EntityControlViewModel ecvm) && !ecvm.Hide && _currentControl != null
|
||||||
|
? Visibility.Visible
|
||||||
|
: Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Torch.Server/Views/Entities/EntityControlsView.xaml
Normal file
31
Torch.Server/Views/Entities/EntityControlsView.xaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<ItemsControl x:Class="Torch.Server.Views.Entities.EntityControlsView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:entities="clr-namespace:Torch.Server.Views.Entities"
|
||||||
|
xmlns:modelsEntities="clr-namespace:Torch.Server.ViewModels.Entities"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300" d:DesignWidth="300"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
ItemsSource="{Binding EntityControls}">
|
||||||
|
<ItemsControl.DataContext>
|
||||||
|
<modelsEntities:EntityViewModel/>
|
||||||
|
</ItemsControl.DataContext>
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Vertical" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<entities:EntityControlHost DataContext="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style>
|
||||||
|
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="Control.VerticalContentAlignment" Value="Stretch"/>
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
</ItemsControl>
|
15
Torch.Server/Views/Entities/EntityControlsView.xaml.cs
Normal file
15
Torch.Server/Views/Entities/EntityControlsView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views.Entities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for EntityControlsView.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class EntityControlsView : ItemsControl
|
||||||
|
{
|
||||||
|
public EntityControlsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,20 +3,28 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Torch.Server.Views.Entities"
|
|
||||||
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
|
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
|
||||||
|
xmlns:local="clr-namespace:Torch.Server.Views.Entities"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<entities:GridViewModel />
|
<entities:GridViewModel />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
<StackPanel>
|
<Grid>
|
||||||
<StackPanel Orientation="Horizontal">
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||||
<Label Content="Name" Width="100"/>
|
<Label Content="Name" Width="100"/>
|
||||||
<TextBox Text="{Binding Name}" Margin="3"/>
|
<TextBox Text="{Binding Name}" Margin="3"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Grid.Row="1" Orientation="Horizontal">
|
||||||
<Label Content="Position" Width="100"/>
|
<Label Content="Position" Width="100"/>
|
||||||
<TextBox Text="{Binding Position}" Margin="3" />
|
<TextBox Text="{Binding Position}" Margin="3" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
<ScrollViewer Grid.Row="2" Margin="3" VerticalScrollBarVisibility="Auto">
|
||||||
|
<local:EntityControlsView DataContext="{Binding}"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
@@ -9,14 +9,23 @@
|
|||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<entities:VoxelMapViewModel/>
|
<entities:VoxelMapViewModel/>
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
<StackPanel>
|
<Grid>
|
||||||
<Label Content="Attached Grids"></Label>
|
<Grid.RowDefinitions>
|
||||||
<ListView ItemsSource="{Binding AttachedGrids}" Margin="3">
|
<RowDefinition Height="Auto"/>
|
||||||
<ListView.ItemTemplate>
|
<RowDefinition Height="*"/>
|
||||||
<DataTemplate>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock Text="{Binding Name}"/>
|
<Expander Grid.Row="0" Header="Attached Grids">
|
||||||
</DataTemplate>
|
<ListView ItemsSource="{Binding AttachedGrids}" Margin="3">
|
||||||
</ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
</ListView>
|
<DataTemplate>
|
||||||
</StackPanel>
|
<TextBlock Text="{Binding Name}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</Expander>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" Margin="3" VerticalScrollBarVisibility="Auto">
|
||||||
|
<local:EntityControlsView DataContext="{Binding}"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -57,10 +57,10 @@ namespace Torch.Server
|
|||||||
switch (newState)
|
switch (newState)
|
||||||
{
|
{
|
||||||
case TorchSessionState.Loaded:
|
case TorchSessionState.Loaded:
|
||||||
Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>());
|
Dispatcher.InvokeAsync(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>());
|
||||||
break;
|
break;
|
||||||
case TorchSessionState.Unloading:
|
case TorchSessionState.Unloading:
|
||||||
Dispatcher.Invoke(() => DataContext = null);
|
Dispatcher.InvokeAsync(() => DataContext = null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,10 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
|
|
||||||
namespace Torch.Collections
|
namespace Torch.Collections
|
||||||
@@ -35,6 +32,11 @@ namespace Torch.Collections
|
|||||||
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
|
_threadViews = new ThreadLocal<ThreadView>(() => new ThreadView(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should this observable collection actually dispatch events.
|
||||||
|
/// </summary>
|
||||||
|
public bool NotificationsEnabled { get; protected set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Takes a snapshot of this collection. Note: This call is only done when a read lock is acquired.
|
/// Takes a snapshot of this collection. Note: This call is only done when a read lock is acquired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,7 +56,7 @@ namespace Torch.Collections
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(TV item)
|
public void Add(TV item)
|
||||||
{
|
{
|
||||||
using(Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
Backing.Add(item);
|
Backing.Add(item);
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
@@ -66,7 +68,7 @@ namespace Torch.Collections
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
using(Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
Backing.Clear();
|
Backing.Clear();
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
@@ -92,11 +94,13 @@ namespace Torch.Collections
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Remove(TV item)
|
public bool Remove(TV item)
|
||||||
{
|
{
|
||||||
using(Lock.UpgradableReadUsing()) {
|
using (Lock.UpgradableReadUsing())
|
||||||
|
{
|
||||||
int? oldIndex = (Backing as IList<TV>)?.IndexOf(item);
|
int? oldIndex = (Backing as IList<TV>)?.IndexOf(item);
|
||||||
if (oldIndex == -1)
|
if (oldIndex == -1)
|
||||||
return false;
|
return false;
|
||||||
using(Lock.WriteUsing()) {
|
using (Lock.WriteUsing())
|
||||||
|
{
|
||||||
if (!Backing.Remove(item))
|
if (!Backing.Remove(item))
|
||||||
return false;
|
return false;
|
||||||
MarkSnapshotsDirty();
|
MarkSnapshotsDirty();
|
||||||
@@ -125,6 +129,56 @@ namespace Torch.Collections
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Event Wrappers
|
#region Event Wrappers
|
||||||
|
private readonly WeakReference<DeferredUpdateToken> _deferredSnapshot = new WeakReference<DeferredUpdateToken>(null);
|
||||||
|
private bool _deferredSnapshotTaken = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Disposable that stops update signals and signals a full refresh when disposed.
|
||||||
|
/// </summary>
|
||||||
|
public IDisposable DeferredUpdate()
|
||||||
|
{
|
||||||
|
using (Lock.WriteUsing())
|
||||||
|
{
|
||||||
|
if (_deferredSnapshotTaken)
|
||||||
|
return new DummyToken();
|
||||||
|
DeferredUpdateToken token;
|
||||||
|
if (!_deferredSnapshot.TryGetTarget(out token))
|
||||||
|
_deferredSnapshot.SetTarget(token = new DeferredUpdateToken());
|
||||||
|
token.SetCollection(this);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct DummyToken : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DeferredUpdateToken : IDisposable
|
||||||
|
{
|
||||||
|
private MtObservableCollection<TC, TV> _collection;
|
||||||
|
|
||||||
|
internal void SetCollection(MtObservableCollection<TC, TV> c)
|
||||||
|
{
|
||||||
|
c._deferredSnapshotTaken = true;
|
||||||
|
_collection = c;
|
||||||
|
c.NotificationsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
using (_collection.Lock.WriteUsing())
|
||||||
|
{
|
||||||
|
_collection.NotificationsEnabled = true;
|
||||||
|
_collection.OnPropertyChanged(nameof(Count));
|
||||||
|
_collection.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
_collection._deferredSnapshotTaken = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void OnPropertyChanged(string propName)
|
protected void OnPropertyChanged(string propName)
|
||||||
{
|
{
|
||||||
NotifyEvent(this, new PropertyChangedEventArgs(propName));
|
NotifyEvent(this, new PropertyChangedEventArgs(propName));
|
||||||
@@ -133,109 +187,61 @@ namespace Torch.Collections
|
|||||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
NotifyEvent(this, e);
|
NotifyEvent(this, e);
|
||||||
|
OnPropertyChanged("Item[]");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void NotifyEvent(object sender, PropertyChangedEventArgs args)
|
protected void NotifyEvent(object sender, PropertyChangedEventArgs args)
|
||||||
{
|
{
|
||||||
_propertyChangedEvent.Raise(sender, args);
|
if (NotificationsEnabled)
|
||||||
|
_propertyChangedEvent.Raise(sender, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void NotifyEvent(object sender, NotifyCollectionChangedEventArgs args)
|
protected void NotifyEvent(object sender, NotifyCollectionChangedEventArgs args)
|
||||||
{
|
{
|
||||||
_collectionChangedEvent.Raise(sender, args);
|
if (NotificationsEnabled)
|
||||||
|
_collectionChangedEvent.Raise(sender, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly DispatcherEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent =
|
private readonly MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler> _propertyChangedEvent =
|
||||||
new DispatcherEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
|
new MtObservableEvent<PropertyChangedEventArgs, PropertyChangedEventHandler>();
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event PropertyChangedEventHandler PropertyChanged
|
public event PropertyChangedEventHandler PropertyChanged
|
||||||
{
|
{
|
||||||
add => _propertyChangedEvent.Add(value);
|
add
|
||||||
remove => _propertyChangedEvent.Remove(value);
|
{
|
||||||
|
_propertyChangedEvent.Add(value);
|
||||||
|
OnPropertyChanged(nameof(IsObserved));
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
_propertyChangedEvent.Remove(value);
|
||||||
|
OnPropertyChanged(nameof(IsObserved));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly DispatcherEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler> _collectionChangedEvent =
|
private readonly MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler> _collectionChangedEvent =
|
||||||
new DispatcherEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>();
|
new MtObservableEvent<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventHandler>();
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||||||
{
|
{
|
||||||
add => _collectionChangedEvent.Add(value);
|
add
|
||||||
remove => _collectionChangedEvent.Remove(value);
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Event that invokes handlers registered by dispatchers on dispatchers.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEvtArgs">Event argument type</typeparam>
|
|
||||||
/// <typeparam name="TEvtHandle">Event handler delegate type</typeparam>
|
|
||||||
private sealed class DispatcherEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs
|
|
||||||
{
|
|
||||||
private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args);
|
|
||||||
|
|
||||||
private static readonly DelInvokeHandler _invokeDirectly;
|
|
||||||
static DispatcherEvent()
|
|
||||||
{
|
{
|
||||||
MethodInfo invoke = typeof(TEvtHandle).GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
_collectionChangedEvent.Add(value);
|
||||||
Debug.Assert(invoke != null, "No invoke method on handler type");
|
OnPropertyChanged(nameof(IsObserved));
|
||||||
_invokeDirectly = (DelInvokeHandler)Delegate.CreateDelegate(typeof(DelInvokeHandler), invoke);
|
|
||||||
}
|
}
|
||||||
|
remove
|
||||||
private static Dispatcher CurrentDispatcher => Dispatcher.FromThread(Thread.CurrentThread);
|
|
||||||
|
|
||||||
|
|
||||||
private event EventHandler<TEvtArgs> _event;
|
|
||||||
|
|
||||||
internal void Raise(object sender, TEvtArgs args)
|
|
||||||
{
|
{
|
||||||
_event?.Invoke(sender, args);
|
_collectionChangedEvent.Remove(value);
|
||||||
}
|
OnPropertyChanged(nameof(IsObserved));
|
||||||
|
|
||||||
internal void Add(TEvtHandle evt)
|
|
||||||
{
|
|
||||||
if (evt == null)
|
|
||||||
return;
|
|
||||||
_event += new DispatcherDelegate(evt).Invoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Remove(TEvtHandle evt)
|
|
||||||
{
|
|
||||||
if (_event == null || evt == null)
|
|
||||||
return;
|
|
||||||
Delegate[] invokeList = _event.GetInvocationList();
|
|
||||||
for (int i = invokeList.Length - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var wrapper = (DispatcherDelegate)invokeList[i].Target;
|
|
||||||
if (wrapper._delegate.Equals(evt))
|
|
||||||
{
|
|
||||||
_event -= wrapper.Invoke;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct DispatcherDelegate
|
|
||||||
{
|
|
||||||
private readonly Dispatcher _dispatcher;
|
|
||||||
internal readonly TEvtHandle _delegate;
|
|
||||||
|
|
||||||
internal DispatcherDelegate(TEvtHandle del)
|
|
||||||
{
|
|
||||||
_dispatcher = CurrentDispatcher;
|
|
||||||
_delegate = del;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Invoke(object sender, TEvtArgs args)
|
|
||||||
{
|
|
||||||
if (_dispatcher == null || _dispatcher == CurrentDispatcher)
|
|
||||||
_invokeDirectly(_delegate, sender, args);
|
|
||||||
else
|
|
||||||
// (Delegate) (object) == dual cast so that the compiler likes it
|
|
||||||
_dispatcher.BeginInvoke((Delegate)(object)_delegate, DispatcherPriority.DataBind, sender, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this collection observed by any listeners.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsObserved => _collectionChangedEvent.IsObserved || _propertyChangedEvent.IsObserved;
|
||||||
|
|
||||||
#region Enumeration
|
#region Enumeration
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages a snapshot to a collection and dispatches enumerators from that snapshot.
|
/// Manages a snapshot to a collection and dispatches enumerators from that snapshot.
|
||||||
|
@@ -91,12 +91,6 @@ namespace Torch.Collections
|
|||||||
/// <inheritdoc cref="Keys"/>
|
/// <inheritdoc cref="Keys"/>
|
||||||
private ProxyCollection<TV> ObservableValues { get; }
|
private ProxyCollection<TV> ObservableValues { get; }
|
||||||
|
|
||||||
internal void RaiseFullReset()
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(Count));
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Proxy collection capable of raising notifications when the parent collection changes.
|
/// Proxy collection capable of raising notifications when the parent collection changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
102
Torch/Collections/MtObservableEvent.cs
Normal file
102
Torch/Collections/MtObservableEvent.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Torch.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event that invokes handlers registered by dispatchers on dispatchers.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEvtArgs">Event argument type</typeparam>
|
||||||
|
/// <typeparam name="TEvtHandle">Event handler delegate type</typeparam>
|
||||||
|
public sealed class MtObservableEvent<TEvtArgs, TEvtHandle> where TEvtArgs : EventArgs
|
||||||
|
{
|
||||||
|
private delegate void DelInvokeHandler(TEvtHandle handler, object sender, TEvtArgs args);
|
||||||
|
|
||||||
|
private static readonly DelInvokeHandler _invokeDirectly;
|
||||||
|
static MtObservableEvent()
|
||||||
|
{
|
||||||
|
MethodInfo invoke = typeof(TEvtHandle).GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
||||||
|
Debug.Assert(invoke != null, "No invoke method on handler type");
|
||||||
|
_invokeDirectly = (DelInvokeHandler)Delegate.CreateDelegate(typeof(DelInvokeHandler), invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dispatcher CurrentDispatcher => Dispatcher.FromThread(Thread.CurrentThread);
|
||||||
|
|
||||||
|
|
||||||
|
private event EventHandler<TEvtArgs> Event;
|
||||||
|
|
||||||
|
private int _observerCount = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if this event has an observers.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsObserved => _observerCount > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises this event for the given sender, with the given args
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">sender</param>
|
||||||
|
/// <param name="args">args</param>
|
||||||
|
public void Raise(object sender, TEvtArgs args)
|
||||||
|
{
|
||||||
|
Event?.Invoke(sender, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="evt"></param>
|
||||||
|
public void Add(TEvtHandle evt)
|
||||||
|
{
|
||||||
|
if (evt == null)
|
||||||
|
return;
|
||||||
|
_observerCount++;
|
||||||
|
Event += new DispatcherDelegate(evt).Invoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the given event handler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="evt"></param>
|
||||||
|
public void Remove(TEvtHandle evt)
|
||||||
|
{
|
||||||
|
if (Event == null || evt == null)
|
||||||
|
return;
|
||||||
|
Delegate[] invokeList = Event.GetInvocationList();
|
||||||
|
for (int i = invokeList.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var wrapper = (DispatcherDelegate)invokeList[i].Target;
|
||||||
|
if (wrapper._delegate.Equals(evt))
|
||||||
|
{
|
||||||
|
Event -= wrapper.Invoke;
|
||||||
|
_observerCount--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct DispatcherDelegate
|
||||||
|
{
|
||||||
|
private readonly Dispatcher _dispatcher;
|
||||||
|
internal readonly TEvtHandle _delegate;
|
||||||
|
|
||||||
|
internal DispatcherDelegate(TEvtHandle del)
|
||||||
|
{
|
||||||
|
_dispatcher = CurrentDispatcher;
|
||||||
|
_delegate = del;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke(object sender, TEvtArgs args)
|
||||||
|
{
|
||||||
|
if (_dispatcher == null || _dispatcher == CurrentDispatcher)
|
||||||
|
_invokeDirectly(_delegate, sender, args);
|
||||||
|
else
|
||||||
|
// (Delegate) (object) == dual cast so that the compiler likes it
|
||||||
|
_dispatcher.BeginInvoke((Delegate)(object)_delegate, DispatcherPriority.DataBind, sender, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -106,7 +106,8 @@ namespace Torch.Collections
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||||
{
|
{
|
||||||
using (Lock.ReadUsing())
|
using (DeferredUpdate())
|
||||||
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
comparer = comparer ?? Comparer<TKey>.Default;
|
comparer = comparer ?? Comparer<TKey>.Default;
|
||||||
if (Backing is List<T> lst)
|
if (Backing is List<T> lst)
|
||||||
@@ -118,8 +119,6 @@ namespace Torch.Collections
|
|||||||
foreach (T v in sortedItems)
|
foreach (T v in sortedItems)
|
||||||
Backing.Add(v);
|
Backing.Add(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,22 +27,38 @@ namespace Torch.Managers.PatchManager
|
|||||||
private byte[] _revertData = null;
|
private byte[] _revertData = null;
|
||||||
private GCHandle? _pinnedPatch;
|
private GCHandle? _pinnedPatch;
|
||||||
|
|
||||||
|
internal bool HasChanged()
|
||||||
|
{
|
||||||
|
return Prefixes.HasChanges() || Suffixes.HasChanges() || Transpilers.HasChanges() || PostTranspilers.HasChanges();
|
||||||
|
}
|
||||||
|
|
||||||
internal void Commit()
|
internal void Commit()
|
||||||
{
|
{
|
||||||
if (!Prefixes.HasChanges() && !Suffixes.HasChanges() && !Transpilers.HasChanges())
|
try
|
||||||
return;
|
{
|
||||||
Revert();
|
// non-greedy so they are all reset
|
||||||
|
if (!Prefixes.HasChanges(true) & !Suffixes.HasChanges(true) & !Transpilers.HasChanges(true) & !PostTranspilers.HasChanges(true))
|
||||||
|
return;
|
||||||
|
Revert();
|
||||||
|
|
||||||
if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0)
|
if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0 && PostTranspilers.Count == 0)
|
||||||
return;
|
return;
|
||||||
_log.Debug($"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
_log.Log(PrintMsil ? LogLevel.Info : LogLevel.Debug,
|
||||||
var patch = ComposePatchedMethod();
|
$"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
||||||
|
var patch = ComposePatchedMethod();
|
||||||
|
|
||||||
_revertAddress = AssemblyMemory.GetMethodBodyStart(_method);
|
_revertAddress = AssemblyMemory.GetMethodBodyStart(_method);
|
||||||
var newAddress = AssemblyMemory.GetMethodBodyStart(patch);
|
var newAddress = AssemblyMemory.GetMethodBodyStart(patch);
|
||||||
_revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress);
|
_revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress);
|
||||||
_pinnedPatch = GCHandle.Alloc(patch);
|
_pinnedPatch = GCHandle.Alloc(patch);
|
||||||
_log.Debug($"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
_log.Log(PrintMsil ? LogLevel.Info : LogLevel.Debug,
|
||||||
|
$"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_log.Fatal(exception, $"Error patching {_method.DeclaringType?.FullName}#{_method}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Revert()
|
internal void Revert()
|
||||||
@@ -95,100 +111,119 @@ namespace Torch.Managers.PatchManager
|
|||||||
public DynamicMethod ComposePatchedMethod()
|
public DynamicMethod ComposePatchedMethod()
|
||||||
{
|
{
|
||||||
DynamicMethod method = AllocatePatchMethod();
|
DynamicMethod method = AllocatePatchMethod();
|
||||||
var generator = new LoggingIlGenerator(method.GetILGenerator());
|
var generator = new LoggingIlGenerator(method.GetILGenerator(), PrintMsil ? LogLevel.Info : LogLevel.Trace);
|
||||||
EmitPatched(generator);
|
List<MsilInstruction> il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList();
|
||||||
|
if (PrintMsil)
|
||||||
|
{
|
||||||
|
lock (_log)
|
||||||
|
{
|
||||||
|
MethodTranspiler.IntegrityAnalysis(LogLevel.Info, il);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodTranspiler.EmitMethod(il, generator);
|
||||||
|
|
||||||
// Force it to compile
|
try
|
||||||
RuntimeMethodHandle handle = _getMethodHandle.Invoke(method);
|
{
|
||||||
object runtimeMethodInfo = _getMethodInfo.Invoke(handle);
|
// Force it to compile
|
||||||
_compileDynamicMethod.Invoke(runtimeMethodInfo);
|
RuntimeMethodHandle handle = _getMethodHandle.Invoke(method);
|
||||||
|
object runtimeMethodInfo = _getMethodInfo.Invoke(handle);
|
||||||
|
_compileDynamicMethod.Invoke(runtimeMethodInfo);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
lock (_log)
|
||||||
|
{
|
||||||
|
var ctx = new MethodContext(method);
|
||||||
|
ctx.Read();
|
||||||
|
MethodTranspiler.IntegrityAnalysis(LogLevel.Warn, ctx.Instructions);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Emit
|
#region Emit
|
||||||
private void EmitPatched(LoggingIlGenerator target)
|
private IEnumerable<MsilInstruction> EmitPatched(Func<Type, bool, MsilLocal> declareLocal)
|
||||||
{
|
{
|
||||||
var originalLocalVariables = _method.GetMethodBody().LocalVariables
|
var methodBody = _method.GetMethodBody();
|
||||||
.Select(x =>
|
Debug.Assert(methodBody != null, "Method body is null");
|
||||||
{
|
foreach (var localVar in methodBody.LocalVariables)
|
||||||
Debug.Assert(x.LocalType != null);
|
{
|
||||||
return target.DeclareLocal(x.LocalType, x.IsPinned);
|
Debug.Assert(localVar.LocalType != null);
|
||||||
}).ToArray();
|
declareLocal(localVar.LocalType, localVar.IsPinned);
|
||||||
|
}
|
||||||
|
var instructions = new List<MsilInstruction>();
|
||||||
|
var specialVariables = new Dictionary<string, MsilLocal>();
|
||||||
|
|
||||||
var specialVariables = new Dictionary<string, LocalBuilder>();
|
var labelAfterOriginalContent = new MsilLabel();
|
||||||
|
var labelSkipMethodContent = new MsilLabel();
|
||||||
Label labelAfterOriginalContent = target.DefineLabel();
|
|
||||||
Label labelSkipMethodContent = target.DefineLabel();
|
|
||||||
|
|
||||||
|
|
||||||
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
||||||
LocalBuilder resultVariable = null;
|
MsilLocal resultVariable = null;
|
||||||
if (returnType != typeof(void))
|
if (returnType != typeof(void))
|
||||||
{
|
{
|
||||||
if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER))
|
if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)
|
||||||
resultVariable = target.DeclareLocal(returnType);
|
|| Prefixes.Any(x => x.ReturnType == typeof(bool)))
|
||||||
else if (Prefixes.Any(x => x.ReturnType == typeof(bool)))
|
resultVariable = declareLocal(returnType, false);
|
||||||
resultVariable = target.DeclareLocal(returnType);
|
|
||||||
}
|
}
|
||||||
resultVariable?.SetToDefault(target);
|
if (resultVariable != null)
|
||||||
LocalBuilder prefixSkippedVariable = null;
|
instructions.AddRange(resultVariable.SetToDefault());
|
||||||
|
MsilLocal prefixSkippedVariable = null;
|
||||||
if (Prefixes.Count > 0 && Suffixes.Any(x => x.GetParameters()
|
if (Prefixes.Count > 0 && Suffixes.Any(x => x.GetParameters()
|
||||||
.Any(y => y.Name.Equals(PREFIX_SKIPPED_PARAMETER))))
|
.Any(y => y.Name.Equals(PREFIX_SKIPPED_PARAMETER))))
|
||||||
{
|
{
|
||||||
prefixSkippedVariable = target.DeclareLocal(typeof(bool));
|
prefixSkippedVariable = declareLocal(typeof(bool), false);
|
||||||
specialVariables.Add(PREFIX_SKIPPED_PARAMETER, prefixSkippedVariable);
|
specialVariables.Add(PREFIX_SKIPPED_PARAMETER, prefixSkippedVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultVariable != null)
|
if (resultVariable != null)
|
||||||
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
||||||
|
|
||||||
target.EmitComment("Prefixes Begin");
|
|
||||||
foreach (MethodInfo prefix in Prefixes)
|
foreach (MethodInfo prefix in Prefixes)
|
||||||
{
|
{
|
||||||
EmitMonkeyCall(target, prefix, specialVariables);
|
instructions.AddRange(EmitMonkeyCall(prefix, specialVariables));
|
||||||
if (prefix.ReturnType == typeof(bool))
|
if (prefix.ReturnType == typeof(bool))
|
||||||
target.Emit(OpCodes.Brfalse, labelSkipMethodContent);
|
instructions.Add(new MsilInstruction(OpCodes.Brfalse).InlineTarget(labelSkipMethodContent));
|
||||||
else if (prefix.ReturnType != typeof(void))
|
else if (prefix.ReturnType != typeof(void))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
|
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
|
||||||
}
|
}
|
||||||
target.EmitComment("Prefixes End");
|
instructions.AddRange(MethodTranspiler.Transpile(_method, (x) => declareLocal(x, false), Transpilers, labelAfterOriginalContent));
|
||||||
|
|
||||||
target.EmitComment("Original Begin");
|
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(labelAfterOriginalContent));
|
||||||
MethodTranspiler.Transpile(_method, (type) => new MsilLocal(target.DeclareLocal(type)), Transpilers, target, labelAfterOriginalContent);
|
|
||||||
target.EmitComment("Original End");
|
|
||||||
|
|
||||||
target.MarkLabel(labelAfterOriginalContent);
|
|
||||||
if (resultVariable != null)
|
if (resultVariable != null)
|
||||||
target.Emit(OpCodes.Stloc, resultVariable);
|
instructions.Add(new MsilInstruction(OpCodes.Stloc).InlineValue(resultVariable));
|
||||||
Label notSkip = target.DefineLabel();
|
var notSkip = new MsilLabel();
|
||||||
target.Emit(OpCodes.Br, notSkip);
|
instructions.Add(new MsilInstruction(OpCodes.Br).InlineTarget(notSkip));
|
||||||
target.MarkLabel(labelSkipMethodContent);
|
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(labelSkipMethodContent));
|
||||||
if (prefixSkippedVariable != null)
|
if (prefixSkippedVariable != null)
|
||||||
{
|
{
|
||||||
target.Emit(OpCodes.Ldc_I4_1);
|
instructions.Add(new MsilInstruction(OpCodes.Ldc_I4_1));
|
||||||
target.Emit(OpCodes.Stloc, prefixSkippedVariable);
|
instructions.Add(new MsilInstruction(OpCodes.Stloc).InlineValue(prefixSkippedVariable));
|
||||||
}
|
}
|
||||||
target.MarkLabel(notSkip);
|
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(notSkip));
|
||||||
|
|
||||||
target.EmitComment("Suffixes Begin");
|
|
||||||
foreach (MethodInfo suffix in Suffixes)
|
foreach (MethodInfo suffix in Suffixes)
|
||||||
{
|
{
|
||||||
EmitMonkeyCall(target, suffix, specialVariables);
|
instructions.AddRange(EmitMonkeyCall(suffix, specialVariables));
|
||||||
if (suffix.ReturnType != typeof(void))
|
if (suffix.ReturnType != typeof(void))
|
||||||
throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}");
|
throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}");
|
||||||
}
|
}
|
||||||
target.EmitComment("Suffixes End");
|
|
||||||
if (resultVariable != null)
|
if (resultVariable != null)
|
||||||
target.Emit(OpCodes.Ldloc, resultVariable);
|
instructions.Add(new MsilInstruction(OpCodes.Ldloc).InlineValue(resultVariable));
|
||||||
target.Emit(OpCodes.Ret);
|
instructions.Add(new MsilInstruction(OpCodes.Ret));
|
||||||
|
|
||||||
|
var result = MethodTranspiler.Transpile(_method, instructions, (x) => declareLocal(x, false), PostTranspilers, null).ToList();
|
||||||
|
if (result.Last().OpCode != OpCodes.Ret)
|
||||||
|
result.Add(new MsilInstruction(OpCodes.Ret));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch,
|
private IEnumerable<MsilInstruction> EmitMonkeyCall(MethodInfo patch,
|
||||||
IReadOnlyDictionary<string, LocalBuilder> specialVariables)
|
IReadOnlyDictionary<string, MsilLocal> specialVariables)
|
||||||
{
|
{
|
||||||
target.EmitComment($"Call {patch.DeclaringType?.FullName}#{patch.Name}");
|
|
||||||
foreach (var param in patch.GetParameters())
|
foreach (var param in patch.GetParameters())
|
||||||
{
|
{
|
||||||
switch (param.Name)
|
switch (param.Name)
|
||||||
@@ -196,25 +231,26 @@ namespace Torch.Managers.PatchManager
|
|||||||
case INSTANCE_PARAMETER:
|
case INSTANCE_PARAMETER:
|
||||||
if (_method.IsStatic)
|
if (_method.IsStatic)
|
||||||
throw new Exception("Can't use an instance parameter for a static method");
|
throw new Exception("Can't use an instance parameter for a static method");
|
||||||
target.Emit(OpCodes.Ldarg_0);
|
yield return new MsilInstruction(OpCodes.Ldarg_0);
|
||||||
break;
|
break;
|
||||||
case PREFIX_SKIPPED_PARAMETER:
|
case PREFIX_SKIPPED_PARAMETER:
|
||||||
if (param.ParameterType != typeof(bool))
|
if (param.ParameterType != typeof(bool))
|
||||||
throw new Exception($"Prefix skipped parameter {param.ParameterType} must be of type bool");
|
throw new Exception($"Prefix skipped parameter {param.ParameterType} must be of type bool");
|
||||||
if (param.ParameterType.IsByRef || param.IsOut)
|
if (param.ParameterType.IsByRef || param.IsOut)
|
||||||
throw new Exception($"Prefix skipped parameter {param.ParameterType} can't be a reference type");
|
throw new Exception($"Prefix skipped parameter {param.ParameterType} can't be a reference type");
|
||||||
if (specialVariables.TryGetValue(PREFIX_SKIPPED_PARAMETER, out LocalBuilder prefixSkip))
|
if (specialVariables.TryGetValue(PREFIX_SKIPPED_PARAMETER, out MsilLocal prefixSkip))
|
||||||
target.Emit(OpCodes.Ldloc, prefixSkip);
|
yield return new MsilInstruction(OpCodes.Ldloc).InlineValue(prefixSkip);
|
||||||
else
|
else
|
||||||
target.Emit(OpCodes.Ldc_I4_0);
|
yield return new MsilInstruction(OpCodes.Ldc_I4_0);
|
||||||
break;
|
break;
|
||||||
case RESULT_PARAMETER:
|
case RESULT_PARAMETER:
|
||||||
Type retType = param.ParameterType.IsByRef
|
Type retType = param.ParameterType.IsByRef
|
||||||
? param.ParameterType.GetElementType()
|
? param.ParameterType.GetElementType()
|
||||||
: param.ParameterType;
|
: param.ParameterType;
|
||||||
if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].LocalType))
|
if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].Type))
|
||||||
throw new Exception($"Return type {specialVariables[RESULT_PARAMETER].LocalType} can't be assigned to result parameter type {retType}");
|
throw new Exception($"Return type {specialVariables[RESULT_PARAMETER].Type} can't be assigned to result parameter type {retType}");
|
||||||
target.Emit(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, specialVariables[RESULT_PARAMETER]);
|
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc)
|
||||||
|
.InlineValue(specialVariables[RESULT_PARAMETER]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ParameterInfo declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
|
ParameterInfo declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
|
||||||
@@ -225,18 +261,18 @@ namespace Torch.Managers.PatchManager
|
|||||||
bool patchByRef = param.IsOut || param.ParameterType.IsByRef;
|
bool patchByRef = param.IsOut || param.ParameterType.IsByRef;
|
||||||
bool declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
|
bool declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
|
||||||
if (patchByRef == declByRef)
|
if (patchByRef == declByRef)
|
||||||
target.Emit(OpCodes.Ldarg, paramIdx);
|
yield return new MsilInstruction(OpCodes.Ldarg).InlineValue(new MsilArgument(paramIdx));
|
||||||
else if (patchByRef)
|
else if (patchByRef)
|
||||||
target.Emit(OpCodes.Ldarga, paramIdx);
|
yield return new MsilInstruction(OpCodes.Ldarga).InlineValue(new MsilArgument(paramIdx));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
target.Emit(OpCodes.Ldarg, paramIdx);
|
yield return new MsilInstruction(OpCodes.Ldarg).InlineValue(new MsilArgument(paramIdx));
|
||||||
target.EmitDereference(declParam.ParameterType);
|
yield return EmitExtensions.EmitDereference(declParam.ParameterType);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target.Emit(OpCodes.Call, patch);
|
yield return new MsilInstruction(OpCodes.Call).InlineValue(patch);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager
|
namespace Torch.Managers.PatchManager
|
||||||
@@ -11,65 +13,64 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// Sets the given local to its default value in the given IL generator.
|
/// Sets the given local to its default value in the given IL generator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="local">Local to set to default</param>
|
/// <param name="local">Local to set to default</param>
|
||||||
/// <param name="target">The IL generator</param>
|
/// <returns>Instructions</returns>
|
||||||
public static void SetToDefault(this LocalBuilder local, LoggingIlGenerator target)
|
public static IEnumerable<MsilInstruction> SetToDefault(this MsilLocal local)
|
||||||
{
|
{
|
||||||
Debug.Assert(local.LocalType != null);
|
Debug.Assert(local.Type != null);
|
||||||
if (local.LocalType.IsEnum || local.LocalType.IsPrimitive)
|
if (local.Type.IsEnum || local.Type.IsPrimitive)
|
||||||
{
|
{
|
||||||
if (local.LocalType == typeof(float))
|
if (local.Type == typeof(float))
|
||||||
target.Emit(OpCodes.Ldc_R4, 0f);
|
yield return new MsilInstruction(OpCodes.Ldc_R4).InlineValue(0f);
|
||||||
else if (local.LocalType == typeof(double))
|
else if (local.Type == typeof(double))
|
||||||
target.Emit(OpCodes.Ldc_R8, 0d);
|
yield return new MsilInstruction(OpCodes.Ldc_R8).InlineValue(0d);
|
||||||
else if (local.LocalType == typeof(long) || local.LocalType == typeof(ulong))
|
else if (local.Type == typeof(long) || local.Type == typeof(ulong))
|
||||||
target.Emit(OpCodes.Ldc_I8, 0L);
|
yield return new MsilInstruction(OpCodes.Ldc_I8).InlineValue(0L);
|
||||||
else
|
else
|
||||||
target.Emit(OpCodes.Ldc_I4, 0);
|
yield return new MsilInstruction(OpCodes.Ldc_I4).InlineValue(0);
|
||||||
target.Emit(OpCodes.Stloc, local);
|
yield return new MsilInstruction(OpCodes.Stloc).InlineValue(local);
|
||||||
}
|
}
|
||||||
else if (local.LocalType.IsValueType) // struct
|
else if (local.Type.IsValueType) // struct
|
||||||
{
|
{
|
||||||
target.Emit(OpCodes.Ldloca, local);
|
yield return new MsilInstruction(OpCodes.Ldloca).InlineValue(local);
|
||||||
target.Emit(OpCodes.Initobj, local.LocalType);
|
yield return new MsilInstruction(OpCodes.Initobj).InlineValue(local.Type);
|
||||||
}
|
}
|
||||||
else // class
|
else // class
|
||||||
{
|
{
|
||||||
target.Emit(OpCodes.Ldnull);
|
yield return new MsilInstruction(OpCodes.Ldnull);
|
||||||
target.Emit(OpCodes.Stloc, local);
|
yield return new MsilInstruction(OpCodes.Stloc).InlineValue(local);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Emits a dereference for the given type.
|
/// Emits a dereference for the given type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">IL Generator to emit on</param>
|
|
||||||
/// <param name="type">Type to dereference</param>
|
/// <param name="type">Type to dereference</param>
|
||||||
public static void EmitDereference(this LoggingIlGenerator target, Type type)
|
/// <returns>Derference instruction</returns>
|
||||||
|
public static MsilInstruction EmitDereference(Type type)
|
||||||
{
|
{
|
||||||
if (type.IsByRef)
|
if (type.IsByRef)
|
||||||
type = type.GetElementType();
|
type = type.GetElementType();
|
||||||
Debug.Assert(type != null);
|
Debug.Assert(type != null);
|
||||||
|
|
||||||
if (type == typeof(float))
|
if (type == typeof(float))
|
||||||
target.Emit(OpCodes.Ldind_R4);
|
return new MsilInstruction(OpCodes.Ldind_R4);
|
||||||
else if (type == typeof(double))
|
if (type == typeof(double))
|
||||||
target.Emit(OpCodes.Ldind_R8);
|
return new MsilInstruction(OpCodes.Ldind_R8);
|
||||||
else if (type == typeof(byte))
|
if (type == typeof(byte))
|
||||||
target.Emit(OpCodes.Ldind_U1);
|
return new MsilInstruction(OpCodes.Ldind_U1);
|
||||||
else if (type == typeof(ushort) || type == typeof(char))
|
if (type == typeof(ushort) || type == typeof(char))
|
||||||
target.Emit(OpCodes.Ldind_U2);
|
return new MsilInstruction(OpCodes.Ldind_U2);
|
||||||
else if (type == typeof(uint))
|
if (type == typeof(uint))
|
||||||
target.Emit(OpCodes.Ldind_U4);
|
return new MsilInstruction(OpCodes.Ldind_U4);
|
||||||
else if (type == typeof(sbyte))
|
if (type == typeof(sbyte))
|
||||||
target.Emit(OpCodes.Ldind_I1);
|
return new MsilInstruction(OpCodes.Ldind_I1);
|
||||||
else if (type == typeof(short))
|
if (type == typeof(short))
|
||||||
target.Emit(OpCodes.Ldind_I2);
|
return new MsilInstruction(OpCodes.Ldind_I2);
|
||||||
else if (type == typeof(int) || type.IsEnum)
|
if (type == typeof(int) || type.IsEnum)
|
||||||
target.Emit(OpCodes.Ldind_I4);
|
return new MsilInstruction(OpCodes.Ldind_I4);
|
||||||
else if (type == typeof(long) || type == typeof(ulong))
|
if (type == typeof(long) || type == typeof(ulong))
|
||||||
target.Emit(OpCodes.Ldind_I8);
|
return new MsilInstruction(OpCodes.Ldind_I8);
|
||||||
else
|
return new MsilInstruction(OpCodes.Ldind_Ref);
|
||||||
target.Emit(OpCodes.Ldind_Ref);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -62,7 +62,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
{
|
{
|
||||||
internal static readonly NullTokenResolver Instance = new NullTokenResolver();
|
internal static readonly NullTokenResolver Instance = new NullTokenResolver();
|
||||||
|
|
||||||
private NullTokenResolver()
|
internal NullTokenResolver()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,9 +28,20 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
internal MsilArgument(ParameterInfo local)
|
/// <summary>
|
||||||
|
/// Creates an argument from the given parameter info.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="local">parameter info to use</param>
|
||||||
|
public MsilArgument(ParameterInfo local)
|
||||||
{
|
{
|
||||||
Position = (((MethodBase)local.Member).IsStatic ? 0 : 1) + local.Position;
|
bool isStatic;
|
||||||
|
if (local.Member is FieldInfo fi)
|
||||||
|
isStatic = fi.IsStatic;
|
||||||
|
else if (local.Member is MethodBase mb)
|
||||||
|
isStatic = mb.IsStatic;
|
||||||
|
else
|
||||||
|
throw new ArgumentException("ParameterInfo.Member must be MethodBase or FieldInfo", nameof(local));
|
||||||
|
Position = (isStatic ? 0 : 1) + local.Position;
|
||||||
Type = local.ParameterType;
|
Type = local.ParameterType;
|
||||||
Name = local.Name;
|
Name = local.Name;
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
case OperandType.InlineField:
|
case OperandType.InlineField:
|
||||||
Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(this);
|
Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(this);
|
||||||
break;
|
break;
|
||||||
|
case OperandType.ShortInlineI:
|
||||||
case OperandType.InlineI:
|
case OperandType.InlineI:
|
||||||
Operand = new MsilOperandInline.MsilOperandInt32(this);
|
Operand = new MsilOperandInline.MsilOperandInt32(this);
|
||||||
break;
|
break;
|
||||||
@@ -63,16 +64,11 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
break;
|
break;
|
||||||
case OperandType.ShortInlineVar:
|
case OperandType.ShortInlineVar:
|
||||||
case OperandType.InlineVar:
|
case OperandType.InlineVar:
|
||||||
if (OpCode.Name.IndexOf("loc", StringComparison.OrdinalIgnoreCase) != -1)
|
if (OpCode.IsLocalStore() || OpCode.IsLocalLoad() || OpCode.IsLocalLoadByRef())
|
||||||
Operand = new MsilOperandInline.MsilOperandLocal(this);
|
Operand = new MsilOperandInline.MsilOperandLocal(this);
|
||||||
else
|
else
|
||||||
Operand = new MsilOperandInline.MsilOperandArgument(this);
|
Operand = new MsilOperandInline.MsilOperandArgument(this);
|
||||||
break;
|
break;
|
||||||
case OperandType.ShortInlineI:
|
|
||||||
Operand = OpCode == OpCodes.Ldc_I4_S
|
|
||||||
? (MsilOperand)new MsilOperandInline.MsilOperandInt8(this)
|
|
||||||
: new MsilOperandInline.MsilOperandUInt8(this);
|
|
||||||
break;
|
|
||||||
case OperandType.ShortInlineR:
|
case OperandType.ShortInlineR:
|
||||||
Operand = new MsilOperandInline.MsilOperandSingle(this);
|
Operand = new MsilOperandInline.MsilOperandSingle(this);
|
||||||
break;
|
break;
|
||||||
@@ -104,6 +100,11 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public HashSet<MsilLabel> Labels { get; } = new HashSet<MsilLabel>();
|
public HashSet<MsilLabel> Labels { get; } = new HashSet<MsilLabel>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The try catch operation that is performed here.
|
||||||
|
/// </summary>
|
||||||
|
public MsilTryCatchOperation TryCatchOperation { get; set; } = null;
|
||||||
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<Type, PropertyInfo> _setterInfoForInlines = new ConcurrentDictionary<Type, PropertyInfo>();
|
private static readonly ConcurrentDictionary<Type, PropertyInfo> _setterInfoForInlines = new ConcurrentDictionary<Type, PropertyInfo>();
|
||||||
|
|
||||||
@@ -147,6 +148,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
Operand?.CopyTo(result.Operand);
|
Operand?.CopyTo(result.Operand);
|
||||||
foreach (MsilLabel x in Labels)
|
foreach (MsilLabel x in Labels)
|
||||||
result.Labels.Add(x);
|
result.Labels.Add(x);
|
||||||
|
result.TryCatchOperation = TryCatchOperation;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,20 +174,6 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Emits this instruction to the given generator
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="target">Emit target</param>
|
|
||||||
public void Emit(LoggingIlGenerator target)
|
|
||||||
{
|
|
||||||
foreach (MsilLabel label in Labels)
|
|
||||||
target.MarkLabel(label.LabelFor(target));
|
|
||||||
if (Operand != null)
|
|
||||||
Operand.Emit(target);
|
|
||||||
else
|
|
||||||
target.Emit(OpCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
@@ -214,6 +202,8 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
Operand is MsilOperandInline<MethodBase> inline)
|
Operand is MsilOperandInline<MethodBase> inline)
|
||||||
{
|
{
|
||||||
MethodBase op = inline.Value;
|
MethodBase op = inline.Value;
|
||||||
|
if (op == null)
|
||||||
|
return num;
|
||||||
if (op is MethodInfo mi && mi.ReturnType != typeof(void))
|
if (op is MethodInfo mi && mi.ReturnType != typeof(void))
|
||||||
num++;
|
num++;
|
||||||
num -= op.GetParameters().Length;
|
num -= op.GetParameters().Length;
|
||||||
|
@@ -19,8 +19,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsLocalLoad(this MsilInstruction me)
|
public static bool IsLocalLoad(this MsilInstruction me)
|
||||||
{
|
{
|
||||||
return me.OpCode == OpCodes.Ldloc || me.OpCode == OpCodes.Ldloc_S || me.OpCode == OpCodes.Ldloc_0 ||
|
return me.OpCode.IsLocalLoad();
|
||||||
me.OpCode == OpCodes.Ldloc_1 || me.OpCode == OpCodes.Ldloc_2 || me.OpCode == OpCodes.Ldloc_3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,7 +27,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsLocalLoadByRef(this MsilInstruction me)
|
public static bool IsLocalLoadByRef(this MsilInstruction me)
|
||||||
{
|
{
|
||||||
return me.OpCode == OpCodes.Ldloca || me.OpCode == OpCodes.Ldloca_S;
|
return me.OpCode.IsLocalLoadByRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,8 +35,33 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsLocalStore(this MsilInstruction me)
|
public static bool IsLocalStore(this MsilInstruction me)
|
||||||
{
|
{
|
||||||
return me.OpCode == OpCodes.Stloc || me.OpCode == OpCodes.Stloc_S || me.OpCode == OpCodes.Stloc_0 ||
|
return me.OpCode.IsLocalStore();
|
||||||
me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Stloc_3;
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction a local load-by-value instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLocalLoad(this OpCode opcode)
|
||||||
|
{
|
||||||
|
return opcode == OpCodes.Ldloc || opcode == OpCodes.Ldloc_S || opcode == OpCodes.Ldloc_0 ||
|
||||||
|
opcode == OpCodes.Ldloc_1 || opcode == OpCodes.Ldloc_2 || opcode == OpCodes.Ldloc_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction a local load-by-reference instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLocalLoadByRef(this OpCode opcode)
|
||||||
|
{
|
||||||
|
return opcode == OpCodes.Ldloca || opcode == OpCodes.Ldloca_S;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction a local store instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLocalStore(this OpCode opcode)
|
||||||
|
{
|
||||||
|
return opcode == OpCodes.Stloc || opcode == OpCodes.Stloc_S || opcode == OpCodes.Stloc_0 ||
|
||||||
|
opcode == OpCodes.Stloc_1 || opcode == OpCodes.Stloc_2 || opcode == OpCodes.Stloc_3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
|
@@ -21,15 +21,40 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
|
|
||||||
? reader.ReadInt32()
|
long offset;
|
||||||
: reader.ReadSByte();
|
|
||||||
Target = context.LabelAt((int)reader.BaseStream.Position + val);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineBrTarget:
|
||||||
|
offset = reader.ReadSByte();
|
||||||
|
break;
|
||||||
|
case OperandType.InlineBrTarget:
|
||||||
|
offset = reader.ReadInt32();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Target = context.LabelAt((int)(reader.BaseStream.Position + offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineBrTarget:
|
||||||
|
case OperandType.InlineBrTarget:
|
||||||
|
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void CopyTo(MsilOperand operand)
|
internal override void CopyTo(MsilOperand operand)
|
||||||
|
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
|
using Torch.Utils;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager.MSIL
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
{
|
{
|
||||||
@@ -44,47 +45,6 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class MsilOperandInline
|
public static class MsilOperandInline
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Inline unsigned byte
|
|
||||||
/// </summary>
|
|
||||||
public class MsilOperandUInt8 : MsilOperandInline<byte>
|
|
||||||
{
|
|
||||||
internal MsilOperandUInt8(MsilInstruction instruction) : base(instruction)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
|
||||||
{
|
|
||||||
Value = reader.ReadByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
|
||||||
{
|
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inline signed byte
|
|
||||||
/// </summary>
|
|
||||||
public class MsilOperandInt8 : MsilOperandInline<sbyte>
|
|
||||||
{
|
|
||||||
internal MsilOperandInt8(MsilInstruction instruction) : base(instruction)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
|
||||||
{
|
|
||||||
Value =
|
|
||||||
(sbyte)reader.ReadByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
|
||||||
{
|
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inline integer
|
/// Inline integer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -96,12 +56,36 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value = reader.ReadInt32();
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineI:
|
||||||
|
Value = reader.ReadByte();
|
||||||
|
return;
|
||||||
|
case OperandType.InlineI:
|
||||||
|
Value = reader.ReadInt32();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineI:
|
||||||
|
generator.Emit(Instruction.OpCode, (byte)Value);
|
||||||
|
return;
|
||||||
|
case OperandType.InlineI:
|
||||||
|
generator.Emit(Instruction.OpCode, Value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +100,30 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value = reader.ReadSingle();
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineR:
|
||||||
|
Value = reader.ReadSingle();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineR:
|
||||||
|
generator.Emit(Instruction.OpCode, Value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,12 +138,30 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value = reader.ReadDouble();
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineR:
|
||||||
|
Value = reader.ReadDouble();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineR:
|
||||||
|
generator.Emit(Instruction.OpCode, Value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,12 +176,30 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value = reader.ReadInt64();
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineI8:
|
||||||
|
Value = reader.ReadInt64();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineI8:
|
||||||
|
generator.Emit(Instruction.OpCode, Value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +214,30 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value =
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
context.TokenResolver.ResolveString(reader.ReadInt32());
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineString:
|
||||||
|
Value = context.TokenResolver.ResolveString(reader.ReadInt32());
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineString:
|
||||||
|
generator.Emit(Instruction.OpCode, Value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,14 +252,28 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
byte[] sig = context.TokenResolver
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
.ResolveSignature(reader.ReadInt32());
|
switch (Instruction.OpCode.OperandType)
|
||||||
throw new ArgumentException("Can't figure out how to convert this.");
|
{
|
||||||
|
case OperandType.InlineSig:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineSig:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,18 +288,45 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
int paramID =
|
int id;
|
||||||
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
? reader.ReadByte()
|
switch (Instruction.OpCode.OperandType)
|
||||||
: reader.ReadUInt16();
|
{
|
||||||
if (paramID == 0 && !context.Method.IsStatic)
|
case OperandType.ShortInlineVar:
|
||||||
|
id = reader.ReadByte();
|
||||||
|
break;
|
||||||
|
case OperandType.InlineVar:
|
||||||
|
id = reader.ReadUInt16();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == 0 && !context.Method.IsStatic)
|
||||||
throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument");
|
throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument");
|
||||||
Value = new MsilArgument(context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]);
|
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||||
|
if (context.Method == null)
|
||||||
|
Value = new MsilArgument(id);
|
||||||
|
else
|
||||||
|
Value = new MsilArgument(context.Method.GetParameters()[id - (context.Method.IsStatic ? 0 : 1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value.Position);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineVar:
|
||||||
|
generator.Emit(Instruction.OpCode, (byte) Value.Position);
|
||||||
|
break;
|
||||||
|
case OperandType.InlineVar:
|
||||||
|
generator.Emit(Instruction.OpCode, (short)Value.Position);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,16 +341,42 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value =
|
int id;
|
||||||
new MsilLocal(context.Method.GetMethodBody().LocalVariables[
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
switch (Instruction.OpCode.OperandType)
|
||||||
? reader.ReadByte()
|
{
|
||||||
: reader.ReadUInt16()]);
|
case OperandType.ShortInlineVar:
|
||||||
|
id = reader.ReadByte();
|
||||||
|
break;
|
||||||
|
case OperandType.InlineVar:
|
||||||
|
id = reader.ReadUInt16();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
|
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||||
|
if (context.MethodBody == null)
|
||||||
|
Value = new MsilLocal(id);
|
||||||
|
else
|
||||||
|
Value = new MsilLocal(context.MethodBody.LocalVariables[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value.Index);
|
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.ShortInlineVar:
|
||||||
|
generator.Emit(Instruction.OpCode, (byte)Value.Index);
|
||||||
|
break;
|
||||||
|
case OperandType.InlineVar:
|
||||||
|
generator.Emit(Instruction.OpCode, (short)Value.Index);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,16 +408,40 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
value = context.TokenResolver.ResolveField(reader.ReadInt32());
|
value = context.TokenResolver.ResolveField(reader.ReadInt32());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException("Reflected operand only applies to inline reflected types");
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
}
|
}
|
||||||
if (value is TY vty)
|
if (value is TY vty)
|
||||||
Value = vty;
|
Value = vty;
|
||||||
|
else if (value == null)
|
||||||
|
Value = null;
|
||||||
else
|
else
|
||||||
throw new Exception($"Expected type {typeof(TY).Name} from operand {Instruction.OpCode.OperandType}, got {value.GetType()?.Name ?? "null"}");
|
throw new Exception($"Expected type {typeof(TY).Name} from operand {Instruction.OpCode.OperandType}, got {value.GetType()?.Name ?? "null"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
switch (Instruction.OpCode.OperandType)
|
||||||
|
{
|
||||||
|
case OperandType.InlineTok:
|
||||||
|
Debug.Assert(Value is MethodBase || Value is Type || Value is FieldInfo,
|
||||||
|
$"Value {Value?.GetType()} doesn't match operand type");
|
||||||
|
break;
|
||||||
|
case OperandType.InlineType:
|
||||||
|
Debug.Assert(Value is Type, $"Value {Value?.GetType()} doesn't match operand type");
|
||||||
|
break;
|
||||||
|
case OperandType.InlineMethod:
|
||||||
|
Debug.Assert(Value is MethodBase, $"Value {Value?.GetType()} doesn't match operand type");
|
||||||
|
break;
|
||||||
|
case OperandType.InlineField:
|
||||||
|
Debug.Assert(Value is FieldInfo, $"Value {Value?.GetType()} doesn't match operand type");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidBranchException(
|
||||||
|
$"OpCode {Instruction.OpCode}, operand type {Instruction.OpCode.OperandType} doesn't match {GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
if (Value is ConstructorInfo)
|
if (Value is ConstructorInfo)
|
||||||
generator.Emit(Instruction.OpCode, Value as ConstructorInfo);
|
generator.Emit(Instruction.OpCode, Value as ConstructorInfo);
|
||||||
else if (Value is FieldInfo)
|
else if (Value is FieldInfo)
|
||||||
|
54
Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs
Normal file
54
Torch/Managers/PatchManager/MSIL/MsilTryCatchOperation.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a try/catch block operation type
|
||||||
|
/// </summary>
|
||||||
|
public enum MsilTryCatchOperationType
|
||||||
|
{
|
||||||
|
// TryCatchBlockIL:
|
||||||
|
// var exBlock = ILGenerator.BeginExceptionBlock();
|
||||||
|
// try{
|
||||||
|
// ILGenerator.BeginCatchBlock(typeof(Exception));
|
||||||
|
// } catch(Exception e) {
|
||||||
|
// ILGenerator.BeginCatchBlock(null);
|
||||||
|
// } catch {
|
||||||
|
// ILGenerator.BeginFinallyBlock();
|
||||||
|
// }finally {
|
||||||
|
// ILGenerator.EndExceptionBlock();
|
||||||
|
// }
|
||||||
|
BeginExceptionBlock,
|
||||||
|
BeginCatchBlock,
|
||||||
|
BeginFinallyBlock,
|
||||||
|
EndExceptionBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a try catch operation.
|
||||||
|
/// </summary>
|
||||||
|
public class MsilTryCatchOperation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Operation type
|
||||||
|
/// </summary>
|
||||||
|
public readonly MsilTryCatchOperationType Type;
|
||||||
|
/// <summary>
|
||||||
|
/// Type caught by this operation, or null if none.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Type CatchType;
|
||||||
|
|
||||||
|
public MsilTryCatchOperation(MsilTryCatchOperationType op, Type caughtType = null)
|
||||||
|
{
|
||||||
|
Type = op;
|
||||||
|
if (caughtType != null && op != MsilTryCatchOperationType.BeginCatchBlock)
|
||||||
|
throw new ArgumentException($"Can't use caught type with operation type {op}", nameof(caughtType));
|
||||||
|
CatchType = caughtType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -36,9 +36,11 @@ namespace Torch.Managers.PatchManager
|
|||||||
|
|
||||||
private int _hasChanges = 0;
|
private int _hasChanges = 0;
|
||||||
|
|
||||||
internal bool HasChanges()
|
internal bool HasChanges(bool reset = false)
|
||||||
{
|
{
|
||||||
return Interlocked.Exchange(ref _hasChanges, 0) != 0;
|
if (reset)
|
||||||
|
return Interlocked.Exchange(ref _hasChanges, 0) != 0;
|
||||||
|
return _hasChanges != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -154,10 +156,33 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MethodRewriteSet Transpilers { get; }
|
public MethodRewriteSet Transpilers { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Methods capable of accepting one <see cref="IEnumerable{MsilInstruction}"/> and returing another, modified.
|
||||||
|
/// Runs after prefixes, suffixes, and normal transpilers are applied.
|
||||||
|
/// </summary>
|
||||||
|
public MethodRewriteSet PostTranspilers { get; }
|
||||||
|
/// <summary>
|
||||||
/// Methods run after the original method has run.
|
/// Methods run after the original method has run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MethodRewriteSet Suffixes { get; }
|
public MethodRewriteSet Suffixes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should the resulting MSIL of the transpile operation be printed.
|
||||||
|
/// </summary>
|
||||||
|
public bool PrintMsil
|
||||||
|
{
|
||||||
|
get => _parent?.PrintMsil ?? _printMsilBacking;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_parent != null)
|
||||||
|
_parent.PrintMsil = value;
|
||||||
|
else
|
||||||
|
_printMsilBacking = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool _printMsilBacking;
|
||||||
|
|
||||||
|
private readonly MethodRewritePattern _parent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -166,7 +191,9 @@ namespace Torch.Managers.PatchManager
|
|||||||
{
|
{
|
||||||
Prefixes = new MethodRewriteSet(parentPattern?.Prefixes);
|
Prefixes = new MethodRewriteSet(parentPattern?.Prefixes);
|
||||||
Transpilers = new MethodRewriteSet(parentPattern?.Transpilers);
|
Transpilers = new MethodRewriteSet(parentPattern?.Transpilers);
|
||||||
|
PostTranspilers = new MethodRewriteSet(parentPattern?.PostTranspilers);
|
||||||
Suffixes = new MethodRewriteSet(parentPattern?.Suffixes);
|
Suffixes = new MethodRewriteSet(parentPattern?.Suffixes);
|
||||||
|
_parent = parentPattern;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
@@ -148,12 +150,43 @@ namespace Torch.Managers.PatchManager
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static int _finishedPatchCount, _dirtyPatchCount;
|
||||||
|
|
||||||
|
private static void DoCommit(DecoratedMethod method)
|
||||||
|
{
|
||||||
|
if (!method.HasChanged())
|
||||||
|
return;
|
||||||
|
method.Commit();
|
||||||
|
int value = Interlocked.Increment(ref _finishedPatchCount);
|
||||||
|
var actualPercentage = (value * 100) / _dirtyPatchCount;
|
||||||
|
var currentPrintGroup = actualPercentage / 10;
|
||||||
|
var prevPrintGroup = (value - 1) * 10 / _dirtyPatchCount;
|
||||||
|
if (currentPrintGroup != prevPrintGroup && value >= 1)
|
||||||
|
{
|
||||||
|
_log.Info($"Patched {value}/{_dirtyPatchCount}. ({actualPercentage:D2}%)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Commit"/>
|
/// <inheritdoc cref="Commit"/>
|
||||||
internal static void CommitInternal()
|
internal static void CommitInternal()
|
||||||
{
|
{
|
||||||
lock (_rewritePatterns)
|
lock (_rewritePatterns)
|
||||||
|
{
|
||||||
|
_log.Info("Patching begins...");
|
||||||
|
_finishedPatchCount = 0;
|
||||||
|
_dirtyPatchCount = _rewritePatterns.Values.Sum(x => x.HasChanged() ? 1 : 0);
|
||||||
|
#if true
|
||||||
|
ParallelTasks.Parallel.ForEach(_rewritePatterns.Values.Where(x => !x.PrintMsil), DoCommit);
|
||||||
|
foreach (DecoratedMethod m in _rewritePatterns.Values.Where(x => x.PrintMsil))
|
||||||
|
DoCommit(m);
|
||||||
|
#else
|
||||||
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
||||||
m.Commit();
|
DoCommit(m);
|
||||||
|
#endif
|
||||||
|
_log.Info("Patching done");
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -164,12 +197,9 @@ namespace Torch.Managers.PatchManager
|
|||||||
CommitInternal();
|
CommitInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc cref="Manager.Attach"/>
|
||||||
/// Commits any existing patches.
|
|
||||||
/// </summary>
|
|
||||||
public override void Attach()
|
public override void Attach()
|
||||||
{
|
{
|
||||||
Commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
40
Torch/Managers/PatchManager/PatchUtilities.cs
Normal file
40
Torch/Managers/PatchManager/PatchUtilities.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
|
|
||||||
|
namespace Torch.Managers.PatchManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Functions that let you read and write MSIL to methods directly.
|
||||||
|
/// </summary>
|
||||||
|
public class PatchUtilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content of a method as an instruction stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">Method to examine</param>
|
||||||
|
/// <returns>instruction stream</returns>
|
||||||
|
public static IEnumerable<MsilInstruction> ReadInstructions(MethodBase method)
|
||||||
|
{
|
||||||
|
var context = new MethodContext(method);
|
||||||
|
context.Read();
|
||||||
|
return context.Instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the given instruction stream to the given IL generator, fixing short branch instructions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="insn">Instruction stream</param>
|
||||||
|
/// <param name="generator">Output</param>
|
||||||
|
public static void EmitInstructions(IEnumerable<MsilInstruction> insn, LoggingIlGenerator generator)
|
||||||
|
{
|
||||||
|
MethodTranspiler.EmitMethod(insn.ToList(), generator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -24,20 +24,23 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ILGenerator Backing { get; }
|
public ILGenerator Backing { get; }
|
||||||
|
|
||||||
|
private readonly LogLevel _level;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new logging IL generator backed by the given generator.
|
/// Creates a new logging IL generator backed by the given generator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="backing">Backing generator</param>
|
/// <param name="backing">Backing generator</param>
|
||||||
public LoggingIlGenerator(ILGenerator backing)
|
public LoggingIlGenerator(ILGenerator backing, LogLevel level)
|
||||||
{
|
{
|
||||||
Backing = backing;
|
Backing = backing;
|
||||||
|
_level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.DeclareLocal(Type, bool)"/>
|
/// <inheritdoc cref="ILGenerator.DeclareLocal(Type, bool)"/>
|
||||||
public LocalBuilder DeclareLocal(Type localType, bool isPinned = false)
|
public LocalBuilder DeclareLocal(Type localType, bool isPinned = false)
|
||||||
{
|
{
|
||||||
LocalBuilder res = Backing.DeclareLocal(localType, isPinned);
|
LocalBuilder res = Backing.DeclareLocal(localType, isPinned);
|
||||||
_log?.Trace($"DclLoc\t{res.LocalIndex}\t=> {res.LocalType} {res.IsPinned}");
|
_log?.Log(_level, $"DclLoc\t{res.LocalIndex}\t=> {res.LocalType} {res.IsPinned}");
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,111 +48,170 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode)"/>
|
||||||
public void Emit(OpCode op)
|
public void Emit(OpCode op)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding}");
|
||||||
Backing.Emit(op);
|
Backing.Emit(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
|
||||||
public void Emit(OpCode op, LocalBuilder arg)
|
public void Emit(OpCode op, LocalBuilder arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} Local:{arg.LocalIndex}/{arg.LocalType}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} Local:{arg.LocalIndex}/{arg.LocalType}");
|
||||||
|
Backing.Emit(op, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, byte)"/>
|
||||||
|
public void Emit(OpCode op, byte arg)
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
|
Backing.Emit(op, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/>
|
||||||
|
public void Emit(OpCode op, short arg)
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/>
|
||||||
public void Emit(OpCode op, int arg)
|
public void Emit(OpCode op, int arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, long)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, long)"/>
|
||||||
public void Emit(OpCode op, long arg)
|
public void Emit(OpCode op, long arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, float)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, float)"/>
|
||||||
public void Emit(OpCode op, float arg)
|
public void Emit(OpCode op, float arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, double)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, double)"/>
|
||||||
public void Emit(OpCode op, double arg)
|
public void Emit(OpCode op, double arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, string)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, string)"/>
|
||||||
public void Emit(OpCode op, string arg)
|
public void Emit(OpCode op, string arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Type)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Type)"/>
|
||||||
public void Emit(OpCode op, Type arg)
|
public void Emit(OpCode op, Type arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, FieldInfo)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, FieldInfo)"/>
|
||||||
public void Emit(OpCode op, FieldInfo arg)
|
public void Emit(OpCode op, FieldInfo arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, MethodInfo)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, MethodInfo)"/>
|
||||||
public void Emit(OpCode op, MethodInfo arg)
|
public void Emit(OpCode op, MethodInfo arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[ReflectedGetter(Name="m_label")]
|
[ReflectedGetter(Name = "m_label")]
|
||||||
private static Func<Label, int> _labelID;
|
private static Func<Label, int> _labelID;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label)"/>
|
||||||
public void Emit(OpCode op, Label arg)
|
public void Emit(OpCode op, Label arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding}\tL:{_labelID.Invoke(arg)}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding}\tL:{_labelID.Invoke(arg)}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label[])"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label[])"/>
|
||||||
public void Emit(OpCode op, Label[] arg)
|
public void Emit(OpCode op, Label[] arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding}\t{string.Join(", ", arg.Select(x => "L:" + _labelID.Invoke(x)))}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding}\t{string.Join(", ", arg.Select(x => "L:" + _labelID.Invoke(x)))}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, SignatureHelper)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, SignatureHelper)"/>
|
||||||
public void Emit(OpCode op, SignatureHelper arg)
|
public void Emit(OpCode op, SignatureHelper arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, ConstructorInfo)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, ConstructorInfo)"/>
|
||||||
public void Emit(OpCode op, ConstructorInfo arg)
|
public void Emit(OpCode op, ConstructorInfo arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
|
_log?.Log(_level, $"Emit\t{op,_opcodePadding} {arg}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Exceptions
|
||||||
|
/// <inheritdoc cref="ILGenerator.BeginExceptionBlock"/>
|
||||||
|
public Label BeginExceptionBlock()
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"BeginExceptionBlock");
|
||||||
|
return Backing.BeginExceptionBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.BeginCatchBlock"/>
|
||||||
|
public void BeginCatchBlock(Type caught)
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"BeginCatchBlock {caught}");
|
||||||
|
Backing.BeginCatchBlock(caught);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.BeginExceptFilterBlock"/>
|
||||||
|
public void BeginExceptFilterBlock()
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"BeginExceptFilterBlock");
|
||||||
|
Backing.BeginExceptFilterBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.BeginFaultBlock"/>
|
||||||
|
public void BeginFaultBlock()
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"BeginFaultBlock");
|
||||||
|
Backing.BeginFaultBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.BeginFinallyBlock"/>
|
||||||
|
public void BeginFinallyBlock()
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"BeginFinallyBlock");
|
||||||
|
Backing.BeginFinallyBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ILGenerator.EndExceptionBlock"/>
|
||||||
|
public void EndExceptionBlock()
|
||||||
|
{
|
||||||
|
_log?.Log(_level, $"EndExceptionBlock");
|
||||||
|
Backing.EndExceptionBlock();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/>
|
/// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/>
|
||||||
public void MarkLabel(Label label)
|
public void MarkLabel(Label label)
|
||||||
{
|
{
|
||||||
_log?.Trace($"MkLbl\tL:{_labelID.Invoke(label)}");
|
_log?.Log(_level, $"MkLbl\tL:{_labelID.Invoke(label)}");
|
||||||
Backing.MarkLabel(label);
|
Backing.MarkLabel(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +228,7 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
public void EmitComment(string comment)
|
public void EmitComment(string comment)
|
||||||
{
|
{
|
||||||
_log?.Trace($"// {comment}");
|
_log?.Log(_level, $"// {comment}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore 162
|
#pragma warning restore 162
|
||||||
|
@@ -16,6 +16,7 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public readonly MethodBase Method;
|
public readonly MethodBase Method;
|
||||||
|
public readonly MethodBody MethodBody;
|
||||||
private readonly byte[] _msilBytes;
|
private readonly byte[] _msilBytes;
|
||||||
|
|
||||||
internal Dictionary<int, MsilLabel> Labels { get; } = new Dictionary<int, MsilLabel>();
|
internal Dictionary<int, MsilLabel> Labels { get; } = new Dictionary<int, MsilLabel>();
|
||||||
@@ -35,14 +36,32 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
public MethodContext(MethodBase method)
|
public MethodContext(MethodBase method)
|
||||||
{
|
{
|
||||||
Method = method;
|
Method = method;
|
||||||
_msilBytes = Method.GetMethodBody().GetILAsByteArray();
|
MethodBody = method.GetMethodBody();
|
||||||
|
Debug.Assert(MethodBody != null, "Method body is null");
|
||||||
|
_msilBytes = MethodBody.GetILAsByteArray();
|
||||||
TokenResolver = new NormalTokenResolver(method);
|
TokenResolver = new NormalTokenResolver(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedMethod(Name = "BakeByteArray")]
|
||||||
|
private static Func<ILGenerator, byte[]> _ilGeneratorBakeByteArray;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public MethodContext(DynamicMethod method)
|
||||||
|
{
|
||||||
|
Method = null;
|
||||||
|
MethodBody = null;
|
||||||
|
_msilBytes = _ilGeneratorBakeByteArray(method.GetILGenerator());
|
||||||
|
TokenResolver = new DynamicMethodTokenResolver(method);
|
||||||
|
}
|
||||||
|
|
||||||
public void Read()
|
public void Read()
|
||||||
{
|
{
|
||||||
ReadInstructions();
|
ReadInstructions();
|
||||||
ResolveLabels();
|
ResolveLabels();
|
||||||
|
ResolveCatchClauses();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadInstructions()
|
private void ReadInstructions()
|
||||||
@@ -53,14 +72,19 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
using (var reader = new BinaryReader(memory))
|
using (var reader = new BinaryReader(memory))
|
||||||
while (memory.Length > memory.Position)
|
while (memory.Length > memory.Position)
|
||||||
{
|
{
|
||||||
var opcodeOffset = (int) memory.Position;
|
var opcodeOffset = (int)memory.Position;
|
||||||
var instructionValue = (short)memory.ReadByte();
|
var instructionValue = (short)memory.ReadByte();
|
||||||
if (Prefixes.Contains(instructionValue))
|
if (Prefixes.Contains(instructionValue))
|
||||||
{
|
{
|
||||||
instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
|
instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
|
||||||
}
|
}
|
||||||
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
|
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
|
||||||
throw new Exception($"Unknown opcode {instructionValue:X}");
|
{
|
||||||
|
var msg = $"Unknown opcode {instructionValue:X}";
|
||||||
|
_log.Error(msg);
|
||||||
|
Debug.Assert(false, msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (opcode.Size != memory.Position - opcodeOffset)
|
if (opcode.Size != memory.Position - opcodeOffset)
|
||||||
throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}");
|
throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}");
|
||||||
var instruction = new MsilInstruction(opcode)
|
var instruction = new MsilInstruction(opcode)
|
||||||
@@ -72,75 +96,49 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResolveCatchClauses()
|
||||||
|
{
|
||||||
|
if (MethodBody == null)
|
||||||
|
return;
|
||||||
|
foreach (ExceptionHandlingClause clause in MethodBody.ExceptionHandlingClauses)
|
||||||
|
{
|
||||||
|
var beginInstruction = FindInstruction(clause.TryOffset);
|
||||||
|
var catchInstruction = FindInstruction(clause.HandlerOffset);
|
||||||
|
var finalInstruction = FindInstruction(clause.HandlerOffset + clause.HandlerLength);
|
||||||
|
beginInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.BeginExceptionBlock);
|
||||||
|
if ((clause.Flags & ExceptionHandlingClauseOptions.Clause) != 0)
|
||||||
|
catchInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.BeginCatchBlock, clause.CatchType);
|
||||||
|
else if ((clause.Flags & ExceptionHandlingClauseOptions.Finally) != 0)
|
||||||
|
catchInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.BeginFinallyBlock);
|
||||||
|
finalInstruction.TryCatchOperation = new MsilTryCatchOperation(MsilTryCatchOperationType.EndExceptionBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MsilInstruction FindInstruction(int offset)
|
||||||
|
{
|
||||||
|
int min = 0, max = _instructions.Count;
|
||||||
|
while (min != max)
|
||||||
|
{
|
||||||
|
int mid = (min + max) / 2;
|
||||||
|
if (_instructions[mid].Offset < offset)
|
||||||
|
min = mid + 1;
|
||||||
|
else
|
||||||
|
max = mid;
|
||||||
|
}
|
||||||
|
return min >= 0 && min < _instructions.Count ? _instructions[min] : null;
|
||||||
|
}
|
||||||
|
|
||||||
private void ResolveLabels()
|
private void ResolveLabels()
|
||||||
{
|
{
|
||||||
foreach (var label in Labels)
|
foreach (var label in Labels)
|
||||||
{
|
{
|
||||||
int min = 0, max = _instructions.Count;
|
MsilInstruction target = FindInstruction(label.Key);
|
||||||
while (min != max)
|
Debug.Assert(target != null, $"No label for offset {label.Key}");
|
||||||
{
|
target?.Labels?.Add(label.Value);
|
||||||
int mid = (min + max) / 2;
|
|
||||||
if (_instructions[mid].Offset < label.Key)
|
|
||||||
min = mid + 1;
|
|
||||||
else
|
|
||||||
max = mid;
|
|
||||||
}
|
|
||||||
#if DEBUG
|
|
||||||
if (min >= _instructions.Count || min < 0)
|
|
||||||
{
|
|
||||||
_log.Trace(
|
|
||||||
$"Want offset {label.Key} for {label.Value}, instruction offsets at\n {string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4} {x}"))}");
|
|
||||||
}
|
|
||||||
MsilInstruction prevInsn = min > 0 ? _instructions[min - 1] : null;
|
|
||||||
if ((prevInsn == null || prevInsn.Offset >= label.Key) ||
|
|
||||||
_instructions[min].Offset < label.Key)
|
|
||||||
_log.Error($"Label {label.Value} wanted {label.Key} but instruction is at {_instructions[min].Offset}. Previous instruction is at {prevInsn?.Offset ?? -1}");
|
|
||||||
#endif
|
|
||||||
_instructions[min]?.Labels?.Add(label.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
|
||||||
public void CheckIntegrity()
|
|
||||||
{
|
|
||||||
var entryStackCount = new Dictionary<MsilLabel, Dictionary<MsilInstruction, int>>();
|
|
||||||
var currentStackSize = 0;
|
|
||||||
foreach (MsilInstruction insn in _instructions)
|
|
||||||
{
|
|
||||||
// I don't want to deal with this, so I won't
|
|
||||||
if (insn.OpCode == OpCodes.Br || insn.OpCode == OpCodes.Br_S || insn.OpCode == OpCodes.Jmp ||
|
|
||||||
insn.OpCode == OpCodes.Leave || insn.OpCode == OpCodes.Leave_S)
|
|
||||||
break;
|
|
||||||
foreach (MsilLabel label in insn.Labels)
|
|
||||||
if (entryStackCount.TryGetValue(label, out Dictionary<MsilInstruction, int> dict))
|
|
||||||
dict.Add(insn, currentStackSize);
|
|
||||||
else
|
|
||||||
(entryStackCount[label] = new Dictionary<MsilInstruction, int>()).Add(insn, currentStackSize);
|
|
||||||
|
|
||||||
currentStackSize += insn.StackChange();
|
|
||||||
|
|
||||||
if (insn.Operand is MsilOperandBrTarget br)
|
|
||||||
if (entryStackCount.TryGetValue(br.Target, out Dictionary<MsilInstruction, int> dict))
|
|
||||||
dict.Add(insn, currentStackSize);
|
|
||||||
else
|
|
||||||
(entryStackCount[br.Target] = new Dictionary<MsilInstruction, int>()).Add(insn, currentStackSize);
|
|
||||||
}
|
|
||||||
foreach (KeyValuePair<MsilLabel, Dictionary<MsilInstruction, int>> label in entryStackCount)
|
|
||||||
{
|
|
||||||
if (label.Value.Values.Aggregate(new HashSet<int>(), (a, b) =>
|
|
||||||
{
|
|
||||||
a.Add(b);
|
|
||||||
return a;
|
|
||||||
}).Count > 1)
|
|
||||||
{
|
|
||||||
_log.Warn($"Label {label.Key} has multiple entry stack counts");
|
|
||||||
foreach (KeyValuePair<MsilInstruction, int> kv in label.Value)
|
|
||||||
_log.Warn($"{kv.Key.Offset:X4} {kv.Key} => {kv.Value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ToHumanMsil()
|
public string ToHumanMsil()
|
||||||
{
|
{
|
||||||
return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange():+0;-#} {x}"));
|
return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange():+0;-#} {x}"));
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Documents;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Torch.Managers.PatchManager.MSIL;
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
|
|
||||||
@@ -11,15 +14,19 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
{
|
{
|
||||||
public static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
public static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
internal static void Transpile(MethodBase baseMethod, Func<Type, MsilLocal> localCreator, IEnumerable<MethodInfo> transpilers, LoggingIlGenerator output, Label? retLabel)
|
internal static IEnumerable<MsilInstruction> Transpile(MethodBase baseMethod, Func<Type, MsilLocal> localCreator,
|
||||||
|
IEnumerable<MethodInfo> transpilers, MsilLabel retLabel)
|
||||||
{
|
{
|
||||||
var context = new MethodContext(baseMethod);
|
var context = new MethodContext(baseMethod);
|
||||||
context.Read();
|
context.Read();
|
||||||
context.CheckIntegrity();
|
// IntegrityAnalysis(LogLevel.Trace, context.Instructions);
|
||||||
// _log.Trace("Input Method:");
|
return Transpile(baseMethod, context.Instructions, localCreator, transpilers, retLabel);
|
||||||
// _log.Trace(context.ToHumanMsil);
|
}
|
||||||
|
|
||||||
var methodContent = (IEnumerable<MsilInstruction>)context.Instructions;
|
internal static IEnumerable<MsilInstruction> Transpile(MethodBase baseMethod, IEnumerable<MsilInstruction> methodContent,
|
||||||
|
Func<Type, MsilLocal> localCreator,
|
||||||
|
IEnumerable<MethodInfo> transpilers, MsilLabel retLabel)
|
||||||
|
{
|
||||||
foreach (MethodInfo transpiler in transpilers)
|
foreach (MethodInfo transpiler in transpilers)
|
||||||
{
|
{
|
||||||
var paramList = new List<object>();
|
var paramList = new List<object>();
|
||||||
@@ -27,6 +34,8 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
{
|
{
|
||||||
if (parameter.Name.Equals("__methodBody"))
|
if (parameter.Name.Equals("__methodBody"))
|
||||||
paramList.Add(baseMethod.GetMethodBody());
|
paramList.Add(baseMethod.GetMethodBody());
|
||||||
|
else if (parameter.Name.Equals("__methodBase"))
|
||||||
|
paramList.Add(baseMethod);
|
||||||
else if (parameter.Name.Equals("__localCreator"))
|
else if (parameter.Name.Equals("__localCreator"))
|
||||||
paramList.Add(localCreator);
|
paramList.Add(localCreator);
|
||||||
else if (parameter.ParameterType == typeof(IEnumerable<MsilInstruction>))
|
else if (parameter.ParameterType == typeof(IEnumerable<MsilInstruction>))
|
||||||
@@ -37,20 +46,157 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
}
|
}
|
||||||
methodContent = (IEnumerable<MsilInstruction>)transpiler.Invoke(null, paramList.ToArray());
|
methodContent = (IEnumerable<MsilInstruction>)transpiler.Invoke(null, paramList.ToArray());
|
||||||
}
|
}
|
||||||
methodContent = FixBranchAndReturn(methodContent, retLabel);
|
return FixBranchAndReturn(methodContent, retLabel);
|
||||||
foreach (var k in methodContent)
|
|
||||||
k.Emit(output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, Label? retTarget)
|
internal static void EmitMethod(IReadOnlyList<MsilInstruction> instructions, LoggingIlGenerator target)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < instructions.Count; i++)
|
||||||
|
{
|
||||||
|
MsilInstruction il = instructions[i];
|
||||||
|
if (il.TryCatchOperation != null)
|
||||||
|
switch (il.TryCatchOperation.Type)
|
||||||
|
{
|
||||||
|
case MsilTryCatchOperationType.BeginExceptionBlock:
|
||||||
|
target.BeginExceptionBlock();
|
||||||
|
break;
|
||||||
|
case MsilTryCatchOperationType.BeginCatchBlock:
|
||||||
|
target.BeginCatchBlock(il.TryCatchOperation.CatchType);
|
||||||
|
break;
|
||||||
|
case MsilTryCatchOperationType.BeginFinallyBlock:
|
||||||
|
target.BeginFinallyBlock();
|
||||||
|
break;
|
||||||
|
case MsilTryCatchOperationType.EndExceptionBlock:
|
||||||
|
target.EndExceptionBlock();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (MsilLabel label in il.Labels)
|
||||||
|
target.MarkLabel(label.LabelFor(target));
|
||||||
|
|
||||||
|
MsilInstruction ilNext = i < instructions.Count - 1 ? instructions[i + 1] : null;
|
||||||
|
|
||||||
|
// Leave opcodes emitted by these:
|
||||||
|
if (il.OpCode == OpCodes.Endfilter && ilNext?.TryCatchOperation?.Type ==
|
||||||
|
MsilTryCatchOperationType.BeginCatchBlock)
|
||||||
|
continue;
|
||||||
|
if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S) &&
|
||||||
|
(ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.EndExceptionBlock ||
|
||||||
|
ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginCatchBlock ||
|
||||||
|
ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.BeginFinallyBlock))
|
||||||
|
continue;
|
||||||
|
if ((il.OpCode == OpCodes.Leave || il.OpCode == OpCodes.Leave_S || il.OpCode == OpCodes.Endfinally) &&
|
||||||
|
ilNext?.TryCatchOperation?.Type == MsilTryCatchOperationType.EndExceptionBlock)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (il.Operand != null)
|
||||||
|
il.Operand.Emit(target);
|
||||||
|
else
|
||||||
|
target.Emit(il.OpCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes the integrity of a set of instructions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">default logging level</param>
|
||||||
|
/// <param name="instructions">instructions</param>
|
||||||
|
public static void IntegrityAnalysis(LogLevel level, IReadOnlyList<MsilInstruction> instructions)
|
||||||
|
{
|
||||||
|
var targets = new Dictionary<MsilLabel, int>();
|
||||||
|
for (var i = 0; i < instructions.Count; i++)
|
||||||
|
foreach (var label in instructions[i].Labels)
|
||||||
|
{
|
||||||
|
if (targets.TryGetValue(label, out var other))
|
||||||
|
_log.Warn($"Label {label} is applied to ({i}: {instructions[i]}) and ({other}: {instructions[other]})");
|
||||||
|
targets[label] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reparsed = new HashSet<MsilLabel>();
|
||||||
|
var labelStackSize = new Dictionary<MsilLabel, Dictionary<int, int>>();
|
||||||
|
var stack = 0;
|
||||||
|
var unreachable = false;
|
||||||
|
var data = new StringBuilder[instructions.Count];
|
||||||
|
for (var i = 0; i < instructions.Count; i++)
|
||||||
|
{
|
||||||
|
var k = instructions[i];
|
||||||
|
var line = (data[i] ?? (data[i] = new StringBuilder())).Clear();
|
||||||
|
if (!unreachable)
|
||||||
|
{
|
||||||
|
foreach (var label in k.Labels)
|
||||||
|
{
|
||||||
|
if (!labelStackSize.TryGetValue(label, out Dictionary<int, int> otherStack))
|
||||||
|
labelStackSize[label] = otherStack = new Dictionary<int, int>();
|
||||||
|
|
||||||
|
otherStack[i - 1] = stack;
|
||||||
|
if (otherStack.Values.Distinct().Count() > 1 || (otherStack.Count == 1 && !otherStack.ContainsValue(stack)))
|
||||||
|
{
|
||||||
|
string otherDesc = string.Join(", ", otherStack.Select(x => $"{x.Key:X4}=>{x.Value}"));
|
||||||
|
line.AppendLine($"WARN// | Label {label} has multiple entry stack sizes ({otherDesc})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var label in k.Labels)
|
||||||
|
{
|
||||||
|
if (!labelStackSize.TryGetValue(label, out var entry))
|
||||||
|
continue;
|
||||||
|
string desc = string.Join(", ", entry.Select(x => $"{x.Key:X4}=>{x.Value}"));
|
||||||
|
line.AppendLine($"// \\/ Label {label} has stack sizes {desc}");
|
||||||
|
if (unreachable && entry.Any())
|
||||||
|
{
|
||||||
|
stack = entry.Values.First();
|
||||||
|
unreachable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack += k.StackChange();
|
||||||
|
if (k.TryCatchOperation != null)
|
||||||
|
line.AppendLine($"// .{k.TryCatchOperation.Type} ({k.TryCatchOperation.CatchType})");
|
||||||
|
line.AppendLine($"{i:X4} S:{stack:D2} dS:{k.StackChange():+0;-#}\t{k}" + (unreachable ? "\t// UNREACHABLE" : ""));
|
||||||
|
if (k.Operand is MsilOperandBrTarget br)
|
||||||
|
{
|
||||||
|
if (!targets.ContainsKey(br.Target))
|
||||||
|
line.AppendLine($"WARN// ^ Unknown target {br.Target}");
|
||||||
|
|
||||||
|
if (!labelStackSize.TryGetValue(br.Target, out Dictionary<int, int> otherStack))
|
||||||
|
labelStackSize[br.Target] = otherStack = new Dictionary<int, int>();
|
||||||
|
|
||||||
|
otherStack[i] = stack;
|
||||||
|
if (otherStack.Values.Distinct().Count() > 1 || (otherStack.Count == 1 && !otherStack.ContainsValue(stack)))
|
||||||
|
{
|
||||||
|
string otherDesc = string.Join(", ", otherStack.Select(x => $"{x.Key:X4}=>{x.Value}"));
|
||||||
|
line.AppendLine($"WARN// ^ Label {br.Target} has multiple entry stack sizes ({otherDesc})");
|
||||||
|
}
|
||||||
|
if (targets.TryGetValue(br.Target, out var target) && target < i && reparsed.Add(br.Target))
|
||||||
|
{
|
||||||
|
i = target - 1;
|
||||||
|
unreachable = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k.OpCode == OpCodes.Br || k.OpCode == OpCodes.Br_S)
|
||||||
|
unreachable = true;
|
||||||
|
}
|
||||||
|
foreach (var k in data)
|
||||||
|
foreach (var line in k.ToString().Split('\n'))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
|
continue;
|
||||||
|
if (line.StartsWith("WARN", StringComparison.OrdinalIgnoreCase))
|
||||||
|
_log.Warn(line.Substring(4).Trim());
|
||||||
|
else
|
||||||
|
_log.Log(level, line.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, MsilLabel retTarget)
|
||||||
{
|
{
|
||||||
foreach (MsilInstruction i in insn)
|
foreach (MsilInstruction i in insn)
|
||||||
{
|
{
|
||||||
if (retTarget.HasValue && i.OpCode == OpCodes.Ret)
|
if (retTarget != null && i.OpCode == OpCodes.Ret)
|
||||||
{
|
{
|
||||||
MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value));
|
var j = i.CopyWith(OpCodes.Br).InlineTarget(retTarget);
|
||||||
foreach (MsilLabel l in i.Labels)
|
|
||||||
j.Labels.Add(l);
|
|
||||||
_log.Trace($"Replacing {i} with {j}");
|
_log.Trace($"Replacing {i} with {j}");
|
||||||
yield return j;
|
yield return j;
|
||||||
}
|
}
|
||||||
|
@@ -1,196 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NLog;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.API.Managers;
|
|
||||||
using Torch.API.Plugins;
|
|
||||||
using Torch.API.Session;
|
|
||||||
using Torch.Commands;
|
|
||||||
using VRage.Collections;
|
|
||||||
|
|
||||||
namespace Torch.Managers
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class PluginManager : Manager, IPluginManager
|
|
||||||
{
|
|
||||||
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
|
||||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
|
||||||
[Dependency]
|
|
||||||
private UpdateManager _updateManager;
|
|
||||||
[Dependency(Optional = true)]
|
|
||||||
private ITorchSessionManager _sessionManager;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
|
|
||||||
|
|
||||||
public event Action<ITorchPlugin> PluginLoaded;
|
|
||||||
public event Action<IList<ITorchPlugin>> PluginsLoaded;
|
|
||||||
|
|
||||||
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(PluginDir))
|
|
||||||
Directory.CreateDirectory(PluginDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates loaded plugins in parallel.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdatePlugins()
|
|
||||||
{
|
|
||||||
foreach (var plugin in Plugins)
|
|
||||||
plugin.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action<ITorchPlugin> _attachCommandsHandler = null;
|
|
||||||
|
|
||||||
private void SessionStateChanged(ITorchSession session, TorchSessionState newState)
|
|
||||||
{
|
|
||||||
var cmdManager = session.Managers.GetManager<CommandManager>();
|
|
||||||
if (cmdManager == null)
|
|
||||||
return;
|
|
||||||
switch (newState)
|
|
||||||
{
|
|
||||||
case TorchSessionState.Loaded:
|
|
||||||
if (_attachCommandsHandler != null)
|
|
||||||
PluginLoaded -= _attachCommandsHandler;
|
|
||||||
_attachCommandsHandler = (x) => cmdManager.RegisterPluginCommands(x);
|
|
||||||
PluginLoaded += _attachCommandsHandler;
|
|
||||||
foreach (ITorchPlugin plugin in Plugins)
|
|
||||||
cmdManager.RegisterPluginCommands(plugin);
|
|
||||||
break;
|
|
||||||
case TorchSessionState.Unloading:
|
|
||||||
if (_attachCommandsHandler != null)
|
|
||||||
{
|
|
||||||
PluginLoaded -= _attachCommandsHandler;
|
|
||||||
_attachCommandsHandler = null;
|
|
||||||
}
|
|
||||||
foreach (ITorchPlugin plugin in Plugins)
|
|
||||||
{
|
|
||||||
// cmdMgr?.UnregisterPluginCommands(plugin);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TorchSessionState.Loading:
|
|
||||||
case TorchSessionState.Unloaded:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(newState), newState, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prepares the plugin manager for loading.
|
|
||||||
/// </summary>
|
|
||||||
public override void Attach()
|
|
||||||
{
|
|
||||||
if (_sessionManager != null)
|
|
||||||
_sessionManager.SessionStateChanged += SessionStateChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unloads all plugins.
|
|
||||||
/// </summary>
|
|
||||||
public override void Detach()
|
|
||||||
{
|
|
||||||
if (_sessionManager != null)
|
|
||||||
_sessionManager.SessionStateChanged -= SessionStateChanged;
|
|
||||||
foreach (var plugin in Plugins)
|
|
||||||
plugin.Dispose();
|
|
||||||
|
|
||||||
Plugins.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DownloadPlugins()
|
|
||||||
{
|
|
||||||
var folders = Directory.GetDirectories(PluginDir);
|
|
||||||
var taskList = new List<Task>();
|
|
||||||
|
|
||||||
//Copy list because we don't want to modify the config.
|
|
||||||
var toDownload = Torch.Config.Plugins.ToList();
|
|
||||||
|
|
||||||
foreach (var folder in folders)
|
|
||||||
{
|
|
||||||
var manifestPath = Path.Combine(folder, "manifest.xml");
|
|
||||||
if (!File.Exists(manifestPath))
|
|
||||||
{
|
|
||||||
_log.Debug($"No manifest in {folder}, skipping");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var manifest = PluginManifest.Load(manifestPath);
|
|
||||||
toDownload.RemoveAll(x => string.Compare(manifest.Repository, x, StringComparison.InvariantCultureIgnoreCase) == 0);
|
|
||||||
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var repository in toDownload)
|
|
||||||
{
|
|
||||||
var manifest = new PluginManifest { Repository = repository, Version = "0.0" };
|
|
||||||
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.WaitAll(taskList.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void LoadPlugins()
|
|
||||||
{
|
|
||||||
if (Torch.Config.ShouldUpdatePlugins)
|
|
||||||
DownloadPlugins();
|
|
||||||
else
|
|
||||||
_log.Warn("Automatic plugin updates are disabled.");
|
|
||||||
|
|
||||||
_log.Info("Loading plugins");
|
|
||||||
var dlls = Directory.GetFiles(PluginDir, "*.dll", SearchOption.AllDirectories);
|
|
||||||
foreach (var dllPath in dlls)
|
|
||||||
{
|
|
||||||
_log.Info($"Loading plugin {dllPath}");
|
|
||||||
var asm = Assembly.UnsafeLoadFrom(dllPath);
|
|
||||||
|
|
||||||
foreach (var type in asm.GetExportedTypes())
|
|
||||||
{
|
|
||||||
if (type.GetInterfaces().Contains(typeof(ITorchPlugin)))
|
|
||||||
{
|
|
||||||
if (type.GetCustomAttribute<PluginAttribute>() == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var plugin = (TorchPluginBase)Activator.CreateInstance(type);
|
|
||||||
if (plugin.Id == default(Guid))
|
|
||||||
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
|
|
||||||
|
|
||||||
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
|
|
||||||
plugin.StoragePath = Torch.Config.InstancePath;
|
|
||||||
Plugins.Add(plugin);
|
|
||||||
PluginLoaded?.Invoke(plugin);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_log.Error($"Error loading plugin '{type.FullName}'");
|
|
||||||
_log.Error(e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Plugins.ForEach(p => p.Init(Torch));
|
|
||||||
PluginsLoaded?.Invoke(Plugins.ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<ITorchPlugin> GetEnumerator()
|
|
||||||
{
|
|
||||||
return Plugins.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
125
Torch/Patches/ObjectFactoryInitPatch.cs
Normal file
125
Torch/Patches/ObjectFactoryInitPatch.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Game.Entities;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.Game.Common;
|
||||||
|
using VRage.Game.Components;
|
||||||
|
using VRage.Game.Entity;
|
||||||
|
using VRage.ObjectBuilders;
|
||||||
|
using VRage.Plugins;
|
||||||
|
using VRage.Utils;
|
||||||
|
|
||||||
|
namespace Torch.Patches
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// There are places in static ctors where the registered assembly depends on the <see cref="Assembly.GetCallingAssembly"/>
|
||||||
|
/// or <see cref="MyPlugins"/>. Here we force those registrations with the proper assemblies to ensure they work correctly.
|
||||||
|
/// </summary>
|
||||||
|
internal static class ObjectFactoryInitPatch
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedGetter(Name = "m_objectFactory", TypeName = "Sandbox.Game.Entities.MyEntityFactory, Sandbox.Game")]
|
||||||
|
private static readonly Func<MyObjectFactory<MyEntityTypeAttribute, MyEntity>> _entityFactoryObjectFactory;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
internal static void ForceRegisterAssemblies()
|
||||||
|
{
|
||||||
|
// static MyEntities() called by MySandboxGame.ForceStaticCtor
|
||||||
|
RuntimeHelpers.RunClassConstructor(typeof(MyEntities).TypeHandle);
|
||||||
|
{
|
||||||
|
MyObjectFactory<MyEntityTypeAttribute, MyEntity> factory = _entityFactoryObjectFactory();
|
||||||
|
ObjectFactory_RegisterFromAssemblySafe(factory, typeof(MySandboxGame).Assembly); // calling assembly
|
||||||
|
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.GameAssembly);
|
||||||
|
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.SandboxAssembly);
|
||||||
|
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.UserAssembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static MyGuiManager():
|
||||||
|
// MyGuiControlsFactory.RegisterDescriptorsFromAssembly();
|
||||||
|
|
||||||
|
// static MyComponentTypeFactory() called by MyComponentContainer.Add
|
||||||
|
RuntimeHelpers.RunClassConstructor(typeof(MyComponentTypeFactory).TypeHandle);
|
||||||
|
{
|
||||||
|
ComponentTypeFactory_RegisterFromAssemblySafe(typeof(MyComponentContainer).Assembly); // calling assembly
|
||||||
|
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.SandboxAssembly);
|
||||||
|
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.GameAssembly);
|
||||||
|
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.SandboxGameAssembly);
|
||||||
|
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.UserAssembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static MyObjectPoolManager()
|
||||||
|
// Render, so should be fine.
|
||||||
|
}
|
||||||
|
|
||||||
|
#region MyObjectFactory Adders
|
||||||
|
private static void ObjectFactory_RegisterDescriptorSafe<TAttribute, TCreatedObjectBase>(
|
||||||
|
MyObjectFactory<TAttribute, TCreatedObjectBase> factory, TAttribute descriptor, Type type) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
|
||||||
|
{
|
||||||
|
if (factory.Attributes.TryGetValue(type, out _))
|
||||||
|
return;
|
||||||
|
if (descriptor.ObjectBuilderType != null && factory.TryGetProducedType(descriptor.ObjectBuilderType) != null)
|
||||||
|
return;
|
||||||
|
if (typeof(MyObjectBuilder_Base).IsAssignableFrom(descriptor.ProducedType) &&
|
||||||
|
factory.TryGetProducedType(descriptor.ProducedType) != null)
|
||||||
|
return;
|
||||||
|
factory.RegisterDescriptor(descriptor, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ObjectFactory_RegisterFromAssemblySafe<TAttribute, TCreatedObjectBase>(MyObjectFactory<TAttribute, TCreatedObjectBase> factory, Assembly assembly) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (Type type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
foreach (TAttribute descriptor in type.GetCustomAttributes<TAttribute>())
|
||||||
|
{
|
||||||
|
ObjectFactory_RegisterDescriptorSafe(factory, descriptor, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region MyComponentTypeFactory Adders
|
||||||
|
|
||||||
|
[ReflectedGetter(Name = "m_idToType", Type = typeof(MyComponentTypeFactory))]
|
||||||
|
private static Func<Dictionary<MyStringId, Type>> _componentTypeFactoryIdToType;
|
||||||
|
[ReflectedGetter(Name = "m_typeToId", Type = typeof(MyComponentTypeFactory))]
|
||||||
|
private static Func<Dictionary<Type, MyStringId>> _componentTypeFactoryTypeToId;
|
||||||
|
[ReflectedGetter(Name = "m_typeToContainerComponentType", Type = typeof(MyComponentTypeFactory))]
|
||||||
|
private static Func<Dictionary<Type, Type>> _componentTypeFactoryContainerComponentType;
|
||||||
|
|
||||||
|
private static void ComponentTypeFactory_RegisterFromAssemblySafe(Assembly assembly)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
return;
|
||||||
|
foreach (Type type in assembly.GetTypes())
|
||||||
|
if (typeof(MyComponentBase).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
ComponentTypeFactory_AddIdSafe(type, MyStringId.GetOrCompute(type.Name));
|
||||||
|
ComponentTypeFactory_RegisterComponentTypeAttributeSafe(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComponentTypeFactory_RegisterComponentTypeAttributeSafe(Type type)
|
||||||
|
{
|
||||||
|
Type componentType = type.GetCustomAttribute<MyComponentTypeAttribute>(true)?.ComponentType;
|
||||||
|
if (componentType != null)
|
||||||
|
_componentTypeFactoryContainerComponentType()[type] = componentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComponentTypeFactory_AddIdSafe(Type type, MyStringId id)
|
||||||
|
{
|
||||||
|
_componentTypeFactoryIdToType()[id] = type;
|
||||||
|
_componentTypeFactoryTypeToId()[type] = id;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@@ -44,7 +44,7 @@ namespace Torch
|
|||||||
path = Path;
|
path = Path;
|
||||||
|
|
||||||
var ser = new XmlSerializer(typeof(T));
|
var ser = new XmlSerializer(typeof(T));
|
||||||
using (var f = File.Create(path))
|
using (var f = File.CreateText(path))
|
||||||
{
|
{
|
||||||
ser.Serialize(f, Data);
|
ser.Serialize(f, Data);
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ namespace Torch
|
|||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
var ser = new XmlSerializer(typeof(T));
|
var ser = new XmlSerializer(typeof(T));
|
||||||
using (var f = File.OpenRead(path))
|
using (var f = File.OpenText(path))
|
||||||
{
|
{
|
||||||
config.Data = (T)ser.Deserialize(f);
|
config.Data = (T)ser.Deserialize(f);
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ using Torch.API.Plugins;
|
|||||||
using Torch.API.Session;
|
using Torch.API.Session;
|
||||||
using Torch.Collections;
|
using Torch.Collections;
|
||||||
using Torch.Commands;
|
using Torch.Commands;
|
||||||
|
using Torch.Utils;
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
@@ -235,11 +236,35 @@ namespace Torch.Managers
|
|||||||
if (!file.Contains(".dll", StringComparison.CurrentCultureIgnoreCase))
|
if (!file.Contains(".dll", StringComparison.CurrentCultureIgnoreCase))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
var asm = Assembly.LoadFrom(file);
|
||||||
|
assemblies.Add(asm);
|
||||||
|
TorchBase.RegisterAuxAssembly(asm);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
using (var stream = File.OpenRead(file))
|
using (var stream = File.OpenRead(file))
|
||||||
{
|
{
|
||||||
var data = new byte[stream.Length];
|
var data = stream.ReadToEnd();
|
||||||
stream.Read(data, 0, data.Length);
|
#if DEBUG
|
||||||
|
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))
|
||||||
|
symbol = symbolStream.ReadToEnd();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Warn(e, $"Failed to read debugging symbols from {symbolPath}");
|
||||||
|
}
|
||||||
|
Assembly asm = symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data);
|
||||||
|
#else
|
||||||
Assembly asm = Assembly.Load(data);
|
Assembly asm = Assembly.Load(data);
|
||||||
|
#endif
|
||||||
assemblies.Add(asm);
|
assemblies.Add(asm);
|
||||||
TorchBase.RegisterAuxAssembly(asm);
|
TorchBase.RegisterAuxAssembly(asm);
|
||||||
}
|
}
|
||||||
@@ -266,11 +291,29 @@ namespace Torch.Managers
|
|||||||
if (!entry.Name.Contains(".dll", StringComparison.CurrentCultureIgnoreCase))
|
if (!entry.Name.Contains(".dll", StringComparison.CurrentCultureIgnoreCase))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
||||||
using (var stream = entry.Open())
|
using (var stream = entry.Open())
|
||||||
{
|
{
|
||||||
var data = new byte[entry.Length];
|
var data = stream.ReadToEnd((int)entry.Length);
|
||||||
stream.Read(data, 0, data.Length);
|
#if DEBUG
|
||||||
|
byte[] symbol = null;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Warn(e, $"Failed to read debugging symbols from {path}:{symbolEntryName}");
|
||||||
|
}
|
||||||
|
Assembly asm = symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data);
|
||||||
|
#else
|
||||||
Assembly asm = Assembly.Load(data);
|
Assembly asm = Assembly.Load(data);
|
||||||
|
#endif
|
||||||
|
assemblies.Add(asm);
|
||||||
TorchBase.RegisterAuxAssembly(asm);
|
TorchBase.RegisterAuxAssembly(asm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -156,6 +156,7 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Collections\MtObservableCollection.cs" />
|
<Compile Include="Collections\MtObservableCollection.cs" />
|
||||||
<Compile Include="Collections\MtObservableDictionary.cs" />
|
<Compile Include="Collections\MtObservableDictionary.cs" />
|
||||||
|
<Compile Include="Collections\MtObservableEvent.cs" />
|
||||||
<Compile Include="Collections\MtObservableList.cs" />
|
<Compile Include="Collections\MtObservableList.cs" />
|
||||||
<Compile Include="Collections\TransformComparer.cs" />
|
<Compile Include="Collections\TransformComparer.cs" />
|
||||||
<Compile Include="Collections\TransformEnumerator.cs" />
|
<Compile Include="Collections\TransformEnumerator.cs" />
|
||||||
@@ -183,15 +184,18 @@
|
|||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
|
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
|
||||||
|
<Compile Include="Managers\PatchManager\MSIL\MsilTryCatchOperation.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchShimAttribute.cs" />
|
<Compile Include="Managers\PatchManager\PatchShimAttribute.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchContext.cs" />
|
<Compile Include="Managers\PatchManager\PatchContext.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchManager.cs" />
|
<Compile Include="Managers\PatchManager\PatchManager.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
|
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
|
||||||
|
<Compile Include="Managers\PatchManager\PatchUtilities.cs" />
|
||||||
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
|
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
|
||||||
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
|
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
|
||||||
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
|
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
|
||||||
<Compile Include="Patches\GameAnalyticsPatch.cs" />
|
<Compile Include="Patches\GameAnalyticsPatch.cs" />
|
||||||
<Compile Include="Patches\GameStatePatchShim.cs" />
|
<Compile Include="Patches\GameStatePatchShim.cs" />
|
||||||
|
<Compile Include="Patches\ObjectFactoryInitPatch.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SaveGameStatus.cs" />
|
<Compile Include="SaveGameStatus.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
@@ -216,12 +220,24 @@
|
|||||||
<Compile Include="Managers\UpdateManager.cs" />
|
<Compile Include="Managers\UpdateManager.cs" />
|
||||||
<Compile Include="Persistent.cs" />
|
<Compile Include="Persistent.cs" />
|
||||||
<Compile Include="Plugins\PluginManifest.cs" />
|
<Compile Include="Plugins\PluginManifest.cs" />
|
||||||
|
<Compile Include="Utils\MiscExtensions.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedFieldInfoAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedGetterAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedLazyAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedMemberAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedMethodAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedMethodInfoAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedPropertyInfoAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedSetterAttribute.cs" />
|
||||||
|
<Compile Include="Utils\Reflected\ReflectedStaticMethodAttribute.cs" />
|
||||||
<Compile Include="Utils\Reflection.cs" />
|
<Compile Include="Utils\Reflection.cs" />
|
||||||
<Compile Include="Managers\ScriptingManager.cs" />
|
<Compile Include="Managers\ScriptingManager.cs" />
|
||||||
<Compile Include="Utils\StringUtils.cs" />
|
<Compile Include="Utils\StringUtils.cs" />
|
||||||
<Compile Include="Utils\SynchronizationExtensions.cs" />
|
<Compile Include="Utils\SynchronizationExtensions.cs" />
|
||||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||||
<Compile Include="Utils\ReflectedManager.cs" />
|
<Compile Include="Utils\Reflected\ReflectedManager.cs" />
|
||||||
<Compile Include="Session\TorchSessionManager.cs" />
|
<Compile Include="Session\TorchSessionManager.cs" />
|
||||||
<Compile Include="TorchBase.cs" />
|
<Compile Include="TorchBase.cs" />
|
||||||
<Compile Include="SteamService.cs" />
|
<Compile Include="SteamService.cs" />
|
||||||
|
@@ -15,6 +15,7 @@ using Sandbox.Game;
|
|||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.Game.Screens.Helpers;
|
using Sandbox.Game.Screens.Helpers;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
|
using Sandbox.Graphics.GUI;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using SpaceEngineers.Game;
|
using SpaceEngineers.Game;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
@@ -32,6 +33,8 @@ using Torch.Session;
|
|||||||
using VRage.Collections;
|
using VRage.Collections;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
|
using VRage.Game.Common;
|
||||||
|
using VRage.Game.Components;
|
||||||
using VRage.Game.ObjectBuilder;
|
using VRage.Game.ObjectBuilder;
|
||||||
using VRage.ObjectBuilders;
|
using VRage.ObjectBuilders;
|
||||||
using VRage.Plugins;
|
using VRage.Plugins;
|
||||||
@@ -251,6 +254,18 @@ namespace Torch
|
|||||||
Debug.Assert(!_init, "Torch instance is already initialized.");
|
Debug.Assert(!_init, "Torch instance is already initialized.");
|
||||||
SpaceEngineersGame.SetupBasicGameInfo();
|
SpaceEngineersGame.SetupBasicGameInfo();
|
||||||
SpaceEngineersGame.SetupPerGameSettings();
|
SpaceEngineersGame.SetupPerGameSettings();
|
||||||
|
// If the attached assemblies change (MySandboxGame.ctor => MySandboxGame.ParseArgs => MyPlugins.RegisterFromArgs)
|
||||||
|
// attach assemblies to object factories again.
|
||||||
|
ObjectFactoryInitPatch.ForceRegisterAssemblies();
|
||||||
|
GameStateChanged += (game, state) =>
|
||||||
|
{
|
||||||
|
if (state == TorchGameState.Created)
|
||||||
|
{
|
||||||
|
ObjectFactoryInitPatch.ForceRegisterAssemblies();
|
||||||
|
// safe to commit here; all important static ctors have run
|
||||||
|
PatchManager.CommitInternal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
|
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
|
||||||
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
||||||
@@ -281,6 +296,10 @@ namespace Torch
|
|||||||
Managers.GetManager<PluginManager>().LoadPlugins();
|
Managers.GetManager<PluginManager>().LoadPlugins();
|
||||||
Managers.Attach();
|
Managers.Attach();
|
||||||
_init = true;
|
_init = true;
|
||||||
|
|
||||||
|
if (GameState >= TorchGameState.Created && GameState < TorchGameState.Unloading)
|
||||||
|
// safe to commit here; all important static ctors have run
|
||||||
|
PatchManager.CommitInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionLoading()
|
private void OnSessionLoading()
|
||||||
|
53
Torch/Utils/MiscExtensions.cs
Normal file
53
Torch/Utils/MiscExtensions.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
public static class MiscExtensions
|
||||||
|
{
|
||||||
|
private static readonly ThreadLocal<WeakReference<byte[]>> _streamBuffer = new ThreadLocal<WeakReference<byte[]>>(() => new WeakReference<byte[]>(null));
|
||||||
|
|
||||||
|
private static long LengthSafe(this Stream stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return 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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
streamPosition += count;
|
||||||
|
}
|
||||||
|
var result = new byte[streamPosition];
|
||||||
|
Array.Copy(buffer, 0, result, 0, result.Length);
|
||||||
|
_streamBuffer.Value.SetTarget(buffer);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
Torch/Utils/Reflected/ReflectedEventReplaceAttribute.cs
Normal file
51
Torch/Utils/Reflected/ReflectedEventReplaceAttribute.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute used to indicate that the the given field, of type <![CDATA[Func<ReflectedEventReplacer>]]>, should be filled with
|
||||||
|
/// a function used to create a new event replacer.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedEventReplaceAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type that the event is declared in
|
||||||
|
/// </summary>
|
||||||
|
public Type EventDeclaringType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the event
|
||||||
|
/// </summary>
|
||||||
|
public string EventName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type that the method to replace is declared in
|
||||||
|
/// </summary>
|
||||||
|
public Type TargetDeclaringType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the method to replace
|
||||||
|
/// </summary>
|
||||||
|
public string TargetName { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Optional parameters of the method to replace. Null to ignore.
|
||||||
|
/// </summary>
|
||||||
|
public Type[] TargetParameters { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType,
|
||||||
|
/// replace the method defined as targetName in targetDeclaringType with a custom callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventDeclaringType">Type the event is declared in</param>
|
||||||
|
/// <param name="eventName">Name of the event</param>
|
||||||
|
/// <param name="targetDeclaringType">Type the method to remove is declared in</param>
|
||||||
|
/// <param name="targetName">Name of the method to remove</param>
|
||||||
|
public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType,
|
||||||
|
string targetName)
|
||||||
|
{
|
||||||
|
EventDeclaringType = eventDeclaringType;
|
||||||
|
EventName = eventName;
|
||||||
|
TargetDeclaringType = targetDeclaringType;
|
||||||
|
TargetName = targetName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
Torch/Utils/Reflected/ReflectedEventReplacer.cs
Normal file
135
Torch/Utils/Reflected/ReflectedEventReplacer.cs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instance of statefully replacing and restoring the callbacks of an event.
|
||||||
|
/// </summary>
|
||||||
|
public class ReflectedEventReplacer
|
||||||
|
{
|
||||||
|
private const BindingFlags BindFlagAll = BindingFlags.Static |
|
||||||
|
BindingFlags.Instance |
|
||||||
|
BindingFlags.Public |
|
||||||
|
BindingFlags.NonPublic;
|
||||||
|
|
||||||
|
private object _instance;
|
||||||
|
private Func<IEnumerable<Delegate>> _backingStoreReader;
|
||||||
|
private Action<Delegate> _callbackAdder;
|
||||||
|
private Action<Delegate> _callbackRemover;
|
||||||
|
private readonly ReflectedEventReplaceAttribute _attributes;
|
||||||
|
private readonly HashSet<Delegate> _registeredCallbacks = new HashSet<Delegate>();
|
||||||
|
private readonly MethodInfo _targetMethodInfo;
|
||||||
|
|
||||||
|
internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr)
|
||||||
|
{
|
||||||
|
_attributes = attr;
|
||||||
|
FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType);
|
||||||
|
if (backingStore == null)
|
||||||
|
throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
||||||
|
EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c));
|
||||||
|
if (evtInfo == null)
|
||||||
|
throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
||||||
|
_backingStoreReader = () => GetEventsInternal(_instance, backingStore);
|
||||||
|
_callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x);
|
||||||
|
_callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x);
|
||||||
|
if (attr.TargetParameters == null)
|
||||||
|
{
|
||||||
|
_targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll);
|
||||||
|
if (_targetMethodInfo == null)
|
||||||
|
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_targetMethodInfo =
|
||||||
|
attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null);
|
||||||
|
if (_targetMethodInfo == null)
|
||||||
|
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that this replacement can be performed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||||
|
/// <returns>true if possible, false if unsuccessful</returns>
|
||||||
|
public bool Test(object instance)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
_registeredCallbacks.Clear();
|
||||||
|
foreach (Delegate callback in _backingStoreReader.Invoke())
|
||||||
|
if (callback.Method == _targetMethodInfo)
|
||||||
|
_registeredCallbacks.Add(callback);
|
||||||
|
|
||||||
|
return _registeredCallbacks.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Delegate _newCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the target callback defined in the attribute and replaces it with the provided callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newCallback">The new event callback</param>
|
||||||
|
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||||
|
public void Replace(Delegate newCallback, object instance)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
if (_newCallback != null)
|
||||||
|
throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced");
|
||||||
|
_newCallback = newCallback;
|
||||||
|
Test(instance);
|
||||||
|
if (_registeredCallbacks.Count == 0)
|
||||||
|
throw new Exception("Reflected event replacer is in invalid state: Nothing to replace");
|
||||||
|
foreach (Delegate callback in _registeredCallbacks)
|
||||||
|
_callbackRemover.Invoke(callback);
|
||||||
|
_callbackAdder.Invoke(_newCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the callback is currently replaced
|
||||||
|
/// </summary>
|
||||||
|
public bool Replaced => _newCallback != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the callback added by <see cref="Replace"/> and puts the original callback back.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||||
|
public void Restore(object instance)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
if (_newCallback == null)
|
||||||
|
throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced");
|
||||||
|
_callbackRemover.Invoke(_newCallback);
|
||||||
|
foreach (Delegate callback in _registeredCallbacks)
|
||||||
|
_callbackAdder.Invoke(callback);
|
||||||
|
_newCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
|
||||||
|
|
||||||
|
private static FieldInfo GetEventBackingField(string eventName, Type baseType)
|
||||||
|
{
|
||||||
|
FieldInfo eventField = null;
|
||||||
|
Type type = baseType;
|
||||||
|
while (type != null && eventField == null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
|
||||||
|
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll);
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
return eventField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Delegate> GetEventsInternal(object instance, FieldInfo eventField)
|
||||||
|
{
|
||||||
|
if (eventField.GetValue(instance) is MulticastDelegate eventDel)
|
||||||
|
{
|
||||||
|
foreach (Delegate handle in eventDel.GetInvocationList())
|
||||||
|
yield return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Torch/Utils/Reflected/ReflectedFieldInfoAttribute.cs
Normal file
22
Torch/Utils/Reflected/ReflectedFieldInfoAttribute.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain the <see cref="System.Reflection.FieldInfo"/> instance for the given field.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected field info attribute using the given type and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type that contains the member</param>
|
||||||
|
/// <param name="name">Name of the member</param>
|
||||||
|
public ReflectedFieldInfoAttribute(Type type, string name)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Torch/Utils/Reflected/ReflectedGetterAttribute.cs
Normal file
28
Torch/Utils/Reflected/ReflectedGetterAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// <![CDATA[
|
||||||
|
/// [ReflectedGetterAttribute(Name="_instanceField")]
|
||||||
|
/// private static Func<Example, int> _instanceGetter;
|
||||||
|
///
|
||||||
|
/// [ReflectedGetterAttribute(Name="_staticField", Type=typeof(Example))]
|
||||||
|
/// private static Func<int> _staticGetter;
|
||||||
|
///
|
||||||
|
/// private class Example {
|
||||||
|
/// private int _instanceField;
|
||||||
|
/// private static int _staticField;
|
||||||
|
/// }
|
||||||
|
/// ]]>
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedGetterAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
15
Torch/Utils/Reflected/ReflectedLazyAttribute.cs
Normal file
15
Torch/Utils/Reflected/ReflectedLazyAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Utils.Reflected
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the type will perform its own call to <see cref="ReflectedManager.Process(Type)"/>
|
||||||
|
/// </summary>
|
||||||
|
public class ReflectedLazyAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -10,389 +10,24 @@ using System.Threading.Tasks;
|
|||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.Utils.Reflected;
|
||||||
|
|
||||||
namespace Torch.Utils
|
namespace Torch.Utils
|
||||||
{
|
{
|
||||||
public abstract class ReflectedMemberAttribute : Attribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the member to access. If null, the tagged field's name.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Declaring type of the member to access. If null, inferred from the instance argument type.
|
|
||||||
/// </summary>
|
|
||||||
public Type Type { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Assembly qualified name of <see cref="Type"/>
|
|
||||||
/// </summary>
|
|
||||||
public string TypeName
|
|
||||||
{
|
|
||||||
get => Type?.AssemblyQualifiedName;
|
|
||||||
set => Type = value == null ? null : Type.GetType(value, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region MemberInfoAttributes
|
#region MemberInfoAttributes
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain the <see cref="System.Reflection.FieldInfo"/> instance for the given field.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a reflected field info attribute using the given type and name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">Type that contains the member</param>
|
|
||||||
/// <param name="name">Name of the member</param>
|
|
||||||
public ReflectedFieldInfoAttribute(Type type, string name)
|
|
||||||
{
|
|
||||||
Type = type;
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain the <see cref="System.Reflection.MethodInfo"/> instance for the given method.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a reflected method info attribute using the given type and name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">Type that contains the member</param>
|
|
||||||
/// <param name="name">Name of the member</param>
|
|
||||||
public ReflectedMethodInfoAttribute(Type type, string name)
|
|
||||||
{
|
|
||||||
Type = type;
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Expected parameters of this method, or null if any parameters are accepted.
|
|
||||||
/// </summary>
|
|
||||||
public Type[] Parameters { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Assembly qualified names of <see cref="Parameters"/>
|
|
||||||
/// </summary>
|
|
||||||
public string[] ParameterNames
|
|
||||||
{
|
|
||||||
get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray();
|
|
||||||
set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Expected return type of this method, or null if any return type is accepted.
|
|
||||||
/// </summary>
|
|
||||||
public Type ReturnType { get; set; } = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain the <see cref="System.Reflection.PropertyInfo"/> instance for the given property.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a reflected property info attribute using the given type and name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">Type that contains the member</param>
|
|
||||||
/// <param name="name">Name of the member</param>
|
|
||||||
public ReflectedPropertyInfoAttribute(Type type, string name)
|
|
||||||
{
|
|
||||||
Type = type;
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region FieldPropGetSet
|
#region FieldPropGetSet
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
|
|
||||||
/// </summary>
|
|
||||||
/// <example>
|
|
||||||
/// <code>
|
|
||||||
/// <![CDATA[
|
|
||||||
/// [ReflectedGetterAttribute(Name="_instanceField")]
|
|
||||||
/// private static Func<Example, int> _instanceGetter;
|
|
||||||
///
|
|
||||||
/// [ReflectedGetterAttribute(Name="_staticField", Type=typeof(Example))]
|
|
||||||
/// private static Func<int> _staticGetter;
|
|
||||||
///
|
|
||||||
/// private class Example {
|
|
||||||
/// private int _instanceField;
|
|
||||||
/// private static int _staticField;
|
|
||||||
/// }
|
|
||||||
/// ]]>
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedGetterAttribute : ReflectedMemberAttribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain a delegate capable of setting the value of a field.
|
|
||||||
/// </summary>
|
|
||||||
/// <example>
|
|
||||||
/// <code>
|
|
||||||
/// <![CDATA[
|
|
||||||
/// [ReflectedSetterAttribute(Name="_instanceField")]
|
|
||||||
/// private static Action<Example, int> _instanceSetter;
|
|
||||||
///
|
|
||||||
/// [ReflectedSetterAttribute(Name="_staticField", Type=typeof(Example))]
|
|
||||||
/// private static Action<int> _staticSetter;
|
|
||||||
///
|
|
||||||
/// private class Example {
|
|
||||||
/// private int _instanceField;
|
|
||||||
/// private static int _staticField;
|
|
||||||
/// }
|
|
||||||
/// ]]>
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedSetterAttribute : ReflectedMemberAttribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Invoker
|
#region Invoker
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain a delegate capable of invoking an instance method.
|
|
||||||
/// </summary>
|
|
||||||
/// <example>
|
|
||||||
/// <code>
|
|
||||||
/// <![CDATA[
|
|
||||||
/// [ReflectedMethodAttribute]
|
|
||||||
/// private static Func<Example, int, float, string> ExampleInstance;
|
|
||||||
///
|
|
||||||
/// private class Example {
|
|
||||||
/// private int ExampleInstance(int a, float b) {
|
|
||||||
/// return a + ", " + b;
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ]]>
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedMethodAttribute : ReflectedMemberAttribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// When set the parameters types for the method are assumed to be this.
|
|
||||||
/// </summary>
|
|
||||||
public Type[] OverrideTypes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Assembly qualified names of <see cref="OverrideTypes"/>
|
|
||||||
/// </summary>
|
|
||||||
public string[] OverrideTypeNames
|
|
||||||
{
|
|
||||||
get => OverrideTypes.Select(x => x.AssemblyQualifiedName).ToArray();
|
|
||||||
set => OverrideTypes = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that this field should contain a delegate capable of invoking a static method.
|
|
||||||
/// </summary>
|
|
||||||
/// <example>
|
|
||||||
/// <code>
|
|
||||||
/// <![CDATA[
|
|
||||||
/// [ReflectedMethodAttribute(Type = typeof(Example)]
|
|
||||||
/// private static Func<int, float, string> ExampleStatic;
|
|
||||||
///
|
|
||||||
/// private class Example {
|
|
||||||
/// private static int ExampleStatic(int a, float b) {
|
|
||||||
/// return a + ", " + b;
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ]]>
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region EventReplacer
|
#region EventReplacer
|
||||||
/// <summary>
|
|
||||||
/// Instance of statefully replacing and restoring the callbacks of an event.
|
|
||||||
/// </summary>
|
|
||||||
public class ReflectedEventReplacer
|
|
||||||
{
|
|
||||||
private const BindingFlags BindFlagAll = BindingFlags.Static |
|
|
||||||
BindingFlags.Instance |
|
|
||||||
BindingFlags.Public |
|
|
||||||
BindingFlags.NonPublic;
|
|
||||||
|
|
||||||
private object _instance;
|
|
||||||
private Func<IEnumerable<Delegate>> _backingStoreReader;
|
|
||||||
private Action<Delegate> _callbackAdder;
|
|
||||||
private Action<Delegate> _callbackRemover;
|
|
||||||
private readonly ReflectedEventReplaceAttribute _attributes;
|
|
||||||
private readonly HashSet<Delegate> _registeredCallbacks = new HashSet<Delegate>();
|
|
||||||
private readonly MethodInfo _targetMethodInfo;
|
|
||||||
|
|
||||||
internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr)
|
|
||||||
{
|
|
||||||
_attributes = attr;
|
|
||||||
FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType);
|
|
||||||
if (backingStore == null)
|
|
||||||
throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
|
||||||
EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c));
|
|
||||||
if (evtInfo == null)
|
|
||||||
throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
|
||||||
_backingStoreReader = () => GetEventsInternal(_instance, backingStore);
|
|
||||||
_callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x);
|
|
||||||
_callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x);
|
|
||||||
if (attr.TargetParameters == null)
|
|
||||||
{
|
|
||||||
_targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll);
|
|
||||||
if (_targetMethodInfo == null)
|
|
||||||
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_targetMethodInfo =
|
|
||||||
attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null);
|
|
||||||
if (_targetMethodInfo == null)
|
|
||||||
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test that this replacement can be performed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="instance">The instance to operate on, or null if static</param>
|
|
||||||
/// <returns>true if possible, false if unsuccessful</returns>
|
|
||||||
public bool Test(object instance)
|
|
||||||
{
|
|
||||||
_instance = instance;
|
|
||||||
_registeredCallbacks.Clear();
|
|
||||||
foreach (Delegate callback in _backingStoreReader.Invoke())
|
|
||||||
if (callback.Method == _targetMethodInfo)
|
|
||||||
_registeredCallbacks.Add(callback);
|
|
||||||
|
|
||||||
return _registeredCallbacks.Count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Delegate _newCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the target callback defined in the attribute and replaces it with the provided callback.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newCallback">The new event callback</param>
|
|
||||||
/// <param name="instance">The instance to operate on, or null if static</param>
|
|
||||||
public void Replace(Delegate newCallback, object instance)
|
|
||||||
{
|
|
||||||
_instance = instance;
|
|
||||||
if (_newCallback != null)
|
|
||||||
throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced");
|
|
||||||
_newCallback = newCallback;
|
|
||||||
Test(instance);
|
|
||||||
if (_registeredCallbacks.Count == 0)
|
|
||||||
throw new Exception("Reflected event replacer is in invalid state: Nothing to replace");
|
|
||||||
foreach (Delegate callback in _registeredCallbacks)
|
|
||||||
_callbackRemover.Invoke(callback);
|
|
||||||
_callbackAdder.Invoke(_newCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the callback is currently replaced
|
|
||||||
/// </summary>
|
|
||||||
public bool Replaced => _newCallback != null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the callback added by <see cref="Replace"/> and puts the original callback back.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="instance">The instance to operate on, or null if static</param>
|
|
||||||
public void Restore(object instance)
|
|
||||||
{
|
|
||||||
_instance = instance;
|
|
||||||
if (_newCallback == null)
|
|
||||||
throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced");
|
|
||||||
_callbackRemover.Invoke(_newCallback);
|
|
||||||
foreach (Delegate callback in _registeredCallbacks)
|
|
||||||
_callbackAdder.Invoke(callback);
|
|
||||||
_newCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
|
|
||||||
|
|
||||||
private static FieldInfo GetEventBackingField(string eventName, Type baseType)
|
|
||||||
{
|
|
||||||
FieldInfo eventField = null;
|
|
||||||
Type type = baseType;
|
|
||||||
while (type != null && eventField == null)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
|
|
||||||
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll);
|
|
||||||
type = type.BaseType;
|
|
||||||
}
|
|
||||||
return eventField;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<Delegate> GetEventsInternal(object instance, FieldInfo eventField)
|
|
||||||
{
|
|
||||||
if (eventField.GetValue(instance) is MulticastDelegate eventDel)
|
|
||||||
{
|
|
||||||
foreach (Delegate handle in eventDel.GetInvocationList())
|
|
||||||
yield return handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attribute used to indicate that the the given field, of type <![CDATA[Func<ReflectedEventReplacer>]]>, should be filled with
|
|
||||||
/// a function used to create a new event replacer.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public class ReflectedEventReplaceAttribute : Attribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Type that the event is declared in
|
|
||||||
/// </summary>
|
|
||||||
public Type EventDeclaringType { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the event
|
|
||||||
/// </summary>
|
|
||||||
public string EventName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Type that the method to replace is declared in
|
|
||||||
/// </summary>
|
|
||||||
public Type TargetDeclaringType { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the method to replace
|
|
||||||
/// </summary>
|
|
||||||
public string TargetName { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Optional parameters of the method to replace. Null to ignore.
|
|
||||||
/// </summary>
|
|
||||||
public Type[] TargetParameters { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType,
|
|
||||||
/// replace the method defined as targetName in targetDeclaringType with a custom callback.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventDeclaringType">Type the event is declared in</param>
|
|
||||||
/// <param name="eventName">Name of the event</param>
|
|
||||||
/// <param name="targetDeclaringType">Type the method to remove is declared in</param>
|
|
||||||
/// <param name="targetName">Name of the method to remove</param>
|
|
||||||
public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType,
|
|
||||||
string targetName)
|
|
||||||
{
|
|
||||||
EventDeclaringType = eventDeclaringType;
|
|
||||||
EventName = eventName;
|
|
||||||
TargetDeclaringType = targetDeclaringType;
|
|
||||||
TargetName = targetName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -438,7 +73,8 @@ namespace Torch.Utils
|
|||||||
public static void Process(Assembly asm)
|
public static void Process(Assembly asm)
|
||||||
{
|
{
|
||||||
foreach (Type type in asm.GetTypes())
|
foreach (Type type in asm.GetTypes())
|
||||||
Process(type);
|
if (!type.HasAttribute<ReflectedLazyAttribute>())
|
||||||
|
Process(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
26
Torch/Utils/Reflected/ReflectedMemberAttribute.cs
Normal file
26
Torch/Utils/Reflected/ReflectedMemberAttribute.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
public abstract class ReflectedMemberAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the member to access. If null, the tagged field's name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Declaring type of the member to access. If null, inferred from the instance argument type.
|
||||||
|
/// </summary>
|
||||||
|
public Type Type { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly qualified name of <see cref="Type"/>
|
||||||
|
/// </summary>
|
||||||
|
public string TypeName
|
||||||
|
{
|
||||||
|
get => Type?.AssemblyQualifiedName;
|
||||||
|
set => Type = value == null ? null : Type.GetType(value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
Torch/Utils/Reflected/ReflectedMethodAttribute.cs
Normal file
40
Torch/Utils/Reflected/ReflectedMethodAttribute.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain a delegate capable of invoking an instance method.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// <![CDATA[
|
||||||
|
/// [ReflectedMethodAttribute]
|
||||||
|
/// private static Func<Example, int, float, string> ExampleInstance;
|
||||||
|
///
|
||||||
|
/// private class Example {
|
||||||
|
/// private int ExampleInstance(int a, float b) {
|
||||||
|
/// return a + ", " + b;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ]]>
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedMethodAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When set the parameters types for the method are assumed to be this.
|
||||||
|
/// </summary>
|
||||||
|
public Type[] OverrideTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly qualified names of <see cref="OverrideTypes"/>
|
||||||
|
/// </summary>
|
||||||
|
public string[] OverrideTypeNames
|
||||||
|
{
|
||||||
|
get => OverrideTypes.Select(x => x.AssemblyQualifiedName).ToArray();
|
||||||
|
set => OverrideTypes = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Torch/Utils/Reflected/ReflectedMethodInfoAttribute.cs
Normal file
41
Torch/Utils/Reflected/ReflectedMethodInfoAttribute.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain the <see cref="System.Reflection.MethodInfo"/> instance for the given method.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected method info attribute using the given type and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type that contains the member</param>
|
||||||
|
/// <param name="name">Name of the member</param>
|
||||||
|
public ReflectedMethodInfoAttribute(Type type, string name)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Expected parameters of this method, or null if any parameters are accepted.
|
||||||
|
/// </summary>
|
||||||
|
public Type[] Parameters { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly qualified names of <see cref="Parameters"/>
|
||||||
|
/// </summary>
|
||||||
|
public string[] ParameterNames
|
||||||
|
{
|
||||||
|
get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray();
|
||||||
|
set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expected return type of this method, or null if any return type is accepted.
|
||||||
|
/// </summary>
|
||||||
|
public Type ReturnType { get; set; } = null;
|
||||||
|
}
|
||||||
|
}
|
22
Torch/Utils/Reflected/ReflectedPropertyInfoAttribute.cs
Normal file
22
Torch/Utils/Reflected/ReflectedPropertyInfoAttribute.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain the <see cref="System.Reflection.PropertyInfo"/> instance for the given property.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected property info attribute using the given type and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type that contains the member</param>
|
||||||
|
/// <param name="name">Name of the member</param>
|
||||||
|
public ReflectedPropertyInfoAttribute(Type type, string name)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Torch/Utils/Reflected/ReflectedSetterAttribute.cs
Normal file
28
Torch/Utils/Reflected/ReflectedSetterAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain a delegate capable of setting the value of a field.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// <![CDATA[
|
||||||
|
/// [ReflectedSetterAttribute(Name="_instanceField")]
|
||||||
|
/// private static Action<Example, int> _instanceSetter;
|
||||||
|
///
|
||||||
|
/// [ReflectedSetterAttribute(Name="_staticField", Type=typeof(Example))]
|
||||||
|
/// private static Action<int> _staticSetter;
|
||||||
|
///
|
||||||
|
/// private class Example {
|
||||||
|
/// private int _instanceField;
|
||||||
|
/// private static int _staticField;
|
||||||
|
/// }
|
||||||
|
/// ]]>
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedSetterAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
26
Torch/Utils/Reflected/ReflectedStaticMethodAttribute.cs
Normal file
26
Torch/Utils/Reflected/ReflectedStaticMethodAttribute.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain a delegate capable of invoking a static method.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// <![CDATA[
|
||||||
|
/// [ReflectedMethodAttribute(Type = typeof(Example)]
|
||||||
|
/// private static Func<int, float, string> ExampleStatic;
|
||||||
|
///
|
||||||
|
/// private class Example {
|
||||||
|
/// private static int ExampleStatic(int a, float b) {
|
||||||
|
/// return a + ", " + b;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ]]>
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -38,6 +38,8 @@ namespace Torch.Utils
|
|||||||
return path.StartsWith(_removablePathPrefix) ? path.Substring(_removablePathPrefix.Length) : path;
|
return path.StartsWith(_removablePathPrefix) ? path.Substring(_removablePathPrefix.Length) : path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly string[] _tryExtensions = {".dll", ".exe"};
|
||||||
|
|
||||||
private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
|
private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
|
||||||
{
|
{
|
||||||
string assemblyName = new AssemblyName(args.Name).Name;
|
string assemblyName = new AssemblyName(args.Name).Name;
|
||||||
@@ -57,18 +59,21 @@ namespace Torch.Utils
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string assemblyPath = Path.Combine(path, assemblyName + ".dll");
|
foreach (var tryExt in _tryExtensions)
|
||||||
if (!File.Exists(assemblyPath))
|
{
|
||||||
continue;
|
string assemblyPath = Path.Combine(path, assemblyName + tryExt);
|
||||||
_log.Trace("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath));
|
if (!File.Exists(assemblyPath))
|
||||||
LogManager.Flush();
|
continue;
|
||||||
Assembly asm = Assembly.LoadFrom(assemblyPath);
|
_log.Trace("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath));
|
||||||
_assemblies.Add(assemblyName, asm);
|
LogManager.Flush();
|
||||||
// Recursively load SE dependencies since they don't trigger AssemblyResolve.
|
Assembly asm = Assembly.LoadFrom(assemblyPath);
|
||||||
// This trades some performance on load for actually working code.
|
_assemblies.Add(assemblyName, asm);
|
||||||
foreach (AssemblyName dependency in asm.GetReferencedAssemblies())
|
// Recursively load SE dependencies since they don't trigger AssemblyResolve.
|
||||||
CurrentDomainOnAssemblyResolve(sender, new ResolveEventArgs(dependency.Name, asm));
|
// This trades some performance on load for actually working code.
|
||||||
return asm;
|
foreach (AssemblyName dependency in asm.GetReferencedAssemblies())
|
||||||
|
CurrentDomainOnAssemblyResolve(sender, new ResolveEventArgs(dependency.Name, asm));
|
||||||
|
return asm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user