Compare commits

...

19 Commits

Author SHA1 Message Date
z__
feda84fac8 Merge remote-tracking branch 'zznty/master' 2022-02-18 17:27:52 +07:00
z__
2503cd6372 fixed NRE in edit roles button 2022-02-18 17:27:40 +07:00
zznty
f321034eeb Update README.md 2022-02-16 10:08:20 +07:00
z__
7573684520 remove unnecessary information that breaks common suffixes 2022-02-12 02:02:09 +07:00
z__
223eaa9fd0 rebase online players counter to events 2022-02-12 01:58:00 +07:00
z__
d138a46f25 forced stop & restart in separate thread 2022-02-12 01:42:46 +07:00
z__
ce2bbd4a61 fix not implemented property 2022-02-12 01:40:38 +07:00
z__
85dd4b46b8 mods loading fixes 2022-02-12 00:40:10 +07:00
z__
166a9d1dbe logs limit 2022-02-12 00:37:00 +07:00
z__
dfc15354ca replace log config if it's from previous versions 2022-02-11 23:04:03 +07:00
z__
57c977deb4 remove load order display 2022-02-11 21:26:47 +07:00
z__
f6cdc2fe79 removed missing dockerfile in solution files 2022-02-11 15:35:53 +07:00
z__
f42b9c6674 dont forget to call 2022-02-09 20:26:37 +07:00
zznty
227557f421 Merge branch 'fixes' 2022-02-09 20:25:46 +07:00
z__
0632f68aaf it was needed, but with some checks 2022-02-09 20:25:09 +07:00
z__
0a9f299527 we dont need it anymore 2022-02-09 15:34:13 +07:00
z__
67f25ab20b transform ctor to extension method 2022-02-08 23:36:39 +07:00
z__
ad19a7dc9e better exception for invalid operand type 2022-02-08 23:26:25 +07:00
z__
dd854a159a ok high iq solutions is bad for compatibility 2022-02-04 15:11:49 +07:00
17 changed files with 309 additions and 468 deletions

View File

@@ -1,4 +1,3 @@
[![Discord](https://discordapp.com/api/guilds/929141809769226271/widget.png)](https://discord.gg/trK6sYdcNE)
[![Build status](https://ci.appveyor.com/api/projects/status/us64kmwshl50f5a3/branch/master?svg=true)](https://ci.appveyor.com/project/zznty/torch/branch/master) [![Build status](https://ci.appveyor.com/api/projects/status/us64kmwshl50f5a3/branch/master?svg=true)](https://ci.appveyor.com/project/zznty/torch/branch/master)
# What is Torch? # What is Torch?
@@ -17,6 +16,10 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
* .NET 6.0 runtime * .NET 6.0 runtime
* Additional options & features * Additional options & features
### Discord
If you have any questions or issues please join our [discord](https://discord.gg/UyYFSe3TyQ)
### Installation ### Installation
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files. * Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.

View File

@@ -17,7 +17,7 @@ namespace Torch.Server
{ {
public IList<LogEntry> LogEntries { get; set; } public IList<LogEntry> LogEntries { get; set; }
public SynchronizationContext TargetContext { get; set; } public SynchronizationContext TargetContext { get; set; }
private readonly int _maxLines = 1000; private const int MAX_LINES = 1000;
/// <inheritdoc /> /// <inheritdoc />
protected override void Write(LogEventInfo logEvent) protected override void Write(LogEventInfo logEvent)
@@ -29,6 +29,11 @@ namespace Torch.Server
{ {
var logEvent = (LogEventInfo) state; var logEvent = (LogEventInfo) state;
LogEntries?.Add(new(logEvent.TimeStamp, Layout.Render(logEvent), LogLevelColors[logEvent.Level])); LogEntries?.Add(new(logEvent.TimeStamp, Layout.Render(logEvent), LogLevelColors[logEvent.Level]));
if (LogEntries is not {Count: > MAX_LINES}) return;
for (var i = 0; LogEntries.Count > MAX_LINES; i++)
{
LogEntries.RemoveAt(i);
}
} }
private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new() private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new()

View File

@@ -234,9 +234,11 @@ namespace Torch.Server.Managers
try try
{ {
var world = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == DedicatedConfig.LoadWorld) ?? new WorldViewModel(DedicatedConfig.LoadWorld); var world = DedicatedConfig.SelectedWorld;
world.Checkpoint.SessionName = DedicatedConfig.WorldName; world.Checkpoint.SessionName = string.IsNullOrEmpty(DedicatedConfig.WorldName)
? Path.GetDirectoryName(DedicatedConfig.LoadWorld)
: DedicatedConfig.WorldName;
world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings; world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings;
world.WorldConfiguration.Mods.Clear(); world.WorldConfiguration.Mods.Clear();
@@ -268,7 +270,7 @@ namespace Torch.Server.Managers
private void ValidateInstance(string path) private void ValidateInstance(string path)
{ {
Directory.CreateDirectory(Path.Combine(path, "Saves")); Directory.CreateDirectory(Path.Combine(path, "Saves"));
Directory.CreateDirectory(Path.Combine(path, "Mods")); // Directory.CreateDirectory(Path.Combine(path, "Mods"));
var configPath = Path.Combine(path, CONFIG_NAME); var configPath = Path.Combine(path, CONFIG_NAME);
if (File.Exists(configPath)) if (File.Exists(configPath))
return; return;

View File

@@ -33,6 +33,9 @@ public static class CheckpointLoadPatch
return false; return false;
} }
world.KeenCheckpoint.Settings = world.WorldConfiguration.Settings;
world.KeenCheckpoint.Mods = world.WorldConfiguration.Mods;
__result = world.Checkpoint; __result = world.Checkpoint;
return false; return false;
} }

View File

@@ -50,7 +50,7 @@ namespace Torch.Server
var oldNlog = Path.Combine(workingDir, "NLog.config"); var oldNlog = Path.Combine(workingDir, "NLog.config");
var newNlog = Path.Combine(instancePath, "NLog.config"); var newNlog = Path.Combine(instancePath, "NLog.config");
if (File.Exists(oldNlog)) if (File.Exists(oldNlog) && !File.ReadAllText(oldNlog).Contains("FlowDocument", StringComparison.Ordinal))
File.Move(oldNlog, newNlog, true); File.Move(oldNlog, newNlog, true);
else if (!File.Exists(newNlog)) else if (!File.Exists(newNlog))
using (var f = File.Create(newNlog)) using (var f = File.Create(newNlog))
@@ -62,9 +62,6 @@ namespace Torch.Server
if (File.Exists(oldTorchCfg)) if (File.Exists(oldTorchCfg))
File.Move(oldTorchCfg, torchCfg, true); File.Move(oldTorchCfg, torchCfg, true);
Target.Register<LogViewerTarget>(nameof(LogViewerTarget));
TorchLogManager.SetConfiguration(new XmlLoggingConfiguration(newNlog));
var config = Persistent<TorchConfig>.Load(torchCfg); var config = Persistent<TorchConfig>.Load(torchCfg);
config.Data.InstanceName = instanceName; config.Data.InstanceName = instanceName;
config.Data.InstancePath = instancePath; config.Data.InstancePath = instancePath;
@@ -77,23 +74,38 @@ namespace Torch.Server
var handler = new UnhandledExceptionHandler(config.Data, isService); var handler = new UnhandledExceptionHandler(config.Data, isService);
AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException; AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
Target.Register<LogViewerTarget>(nameof(LogViewerTarget));
TorchLogManager.RegisterTargets(Environment.GetEnvironmentVariable("TORCH_LOG_EXTENSIONS_PATH") ??
Path.Combine(instancePath, "LoggingExtensions"));
TorchLogManager.SetConfiguration(new XmlLoggingConfiguration(newNlog));
var initializer = new Initializer(workingDir, config); var initializer = new Initializer(workingDir, config);
if (!initializer.Initialize(args)) if (!initializer.Initialize(args))
Environment.Exit(1); Environment.Exit(1);
TorchLauncher.Launch(workingDir, binDir); TorchLauncher.Launch(workingDir, binDir);
TorchLogManager.SetConfiguration(TorchLogManager.Configuration,
Environment.GetEnvironmentVariable("TORCH_LOG_EXTENSIONS_PATH") ??
Path.Combine(instancePath, "LoggingExtensions"));
CopyNative(binDir); CopyNative(binDir);
initializer.Run(isService, instanceName, instancePath); initializer.Run(isService, instanceName, instancePath);
} }
private static void CopyNative(string binPath) private static void CopyNative(string binPath)
{
var log = LogManager.GetLogger("TorchLauncher");
var workingDir = new DirectoryInfo(Directory.GetCurrentDirectory());
if (workingDir.Attributes.HasFlag(FileAttributes.ReadOnly))
{
log.Warn("Game directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually");
return;
}
try
{ {
var apiSource = Path.Combine(binPath, "steam_api64.dll"); var apiSource = Path.Combine(binPath, "steam_api64.dll");
var apiTarget = Path.Combine(AppContext.BaseDirectory, "steam_api64.dll"); var apiTarget = Path.Combine(workingDir.FullName, "steam_api64.dll");
if (!File.Exists(apiTarget)) if (!File.Exists(apiTarget))
{ {
File.Copy(apiSource, apiTarget); File.Copy(apiSource, apiTarget);
@@ -105,7 +117,7 @@ namespace Torch.Server
} }
var havokSource = Path.Combine(binPath, "Havok.dll"); var havokSource = Path.Combine(binPath, "Havok.dll");
var havokTarget = Path.Combine(AppContext.BaseDirectory, "Havok.dll"); var havokTarget = Path.Combine(workingDir.FullName, "Havok.dll");
if (!File.Exists(havokTarget)) if (!File.Exists(havokTarget))
{ {
@@ -117,5 +129,14 @@ namespace Torch.Server
File.Copy(havokSource, havokTarget); File.Copy(havokSource, havokTarget);
} }
} }
catch (UnauthorizedAccessException)
{
// file is being used by another process, probably previous torch has not been closed yet
}
catch (Exception e)
{
log.Error(e);
}
}
} }
} }

View File

@@ -107,7 +107,7 @@ namespace Torch.Server
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set => SetValue(ref _elapsedPlayTime, value); } public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set => SetValue(ref _elapsedPlayTime, value); }
/// <inheritdoc /> /// <inheritdoc />
public Thread GameThread { get; private set; } public Thread GameThread => MySandboxGame.Static?.UpdateThread;
/// <inheritdoc /> /// <inheritdoc />
public bool IsRunning { get => _isRunning; set => SetValue(ref _isRunning, value); } public bool IsRunning { get => _isRunning; set => SetValue(ref _isRunning, value); }
@@ -178,6 +178,17 @@ namespace Torch.Server
{ {
if (State == ServerState.Stopped) if (State == ServerState.Stopped)
Log.Error("Server is already stopped"); Log.Error("Server is already stopped");
if (Thread.CurrentThread == GameThread)
new Thread(StopInternal)
{
Name = "Stopping Thread"
}.Start();
else
StopInternal();
}
private void StopInternal()
{
Log.Info("Stopping server."); Log.Info("Stopping server.");
base.Stop(); base.Stop();
Log.Info("Server stopped."); Log.Info("Server stopped.");
@@ -185,6 +196,7 @@ namespace Torch.Server
State = ServerState.Stopped; State = ServerState.Stopped;
IsRunning = false; IsRunning = false;
CanRun = true; CanRun = true;
SimulationRatio = 0;
} }
/// <summary> /// <summary>
@@ -202,8 +214,9 @@ namespace Torch.Server
Log.Info("Ejected all players from server for restart."); Log.Info("Ejected all players from server for restart.");
} }
Stop(); new Thread(() =>
// TODO clone this {
StopInternal();
var config = (TorchConfig)Config; var config = (TorchConfig)Config;
LogManager.Flush(); LogManager.Flush();
@@ -213,6 +226,10 @@ namespace Torch.Server
Process.Start(exe, config.ToString()); Process.Start(exe, config.ToString());
Environment.Exit(0); Environment.Exit(0);
})
{
Name = "Restart thread"
}.Start();
} }
[SuppressPropertyChangedWarnings] [SuppressPropertyChangedWarnings]
@@ -228,11 +245,23 @@ namespace Torch.Server
if (newState == TorchSessionState.Loaded) if (newState == TorchSessionState.Loaded)
{ {
_multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>(); _multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>();
_multiplayerManagerDedicated.PlayerJoined += MultiplayerManagerDedicatedOnPlayerJoined;
_multiplayerManagerDedicated.PlayerLeft += MultiplayerManagerDedicatedOnPlayerLeft;
CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands)); CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands));
ModCommunication.Register(); ModCommunication.Register();
} }
} }
private void MultiplayerManagerDedicatedOnPlayerLeft(IPlayer player)
{
OnlinePlayers--;
}
private void MultiplayerManagerDedicatedOnPlayerJoined(IPlayer player)
{
OnlinePlayers++;
}
/// <inheritdoc /> /// <inheritdoc />
public override void Init(object gameInstance) public override void Init(object gameInstance)
{ {
@@ -251,7 +280,6 @@ namespace Torch.Server
SimulationRatio = Math.Min(Sync.ServerSimulationRatio, 1); SimulationRatio = Math.Min(Sync.ServerSimulationRatio, 1);
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds)); var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
ElapsedPlayTime = elapsed; ElapsedPlayTime = elapsed;
OnlinePlayers = _multiplayerManagerDedicated?.Players.Count ?? 0;
if (_watchdog == null && Config.TickTimeout > 0) if (_watchdog == null && Config.TickTimeout > 0)
{ {
@@ -350,17 +378,6 @@ namespace Torch.Server
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendFormat(
"ManagedThreadId: {0}, Name: {1}, OSThreadId: {2}, Thread: IsAlive: {3}, IsBackground: {4}, IsThreadPool: {5}",
thread.ManagedThreadId,
thread.Name,
clrThread.OSThreadId,
thread.IsAlive,
thread.IsBackground,
thread.IsThreadPoolThread)
.AppendLine();
sb.AppendLine("Stack trace:");
foreach (var frame in clrThread.EnumerateStackTrace()) foreach (var frame in clrThread.EnumerateStackTrace())
{ {
sb.Append('\t'); sb.Append('\t');

View File

@@ -144,22 +144,17 @@ namespace Torch.Server.Views
{ {
//var w = new RoleEditor(_instanceManager.DedicatedConfig.SelectedWorld); //var w = new RoleEditor(_instanceManager.DedicatedConfig.SelectedWorld);
//w.Show(); //w.Show();
var d = new RoleEditor();
var w = _instanceManager.DedicatedConfig.SelectedWorld; var w = _instanceManager.DedicatedConfig.SelectedWorld;
if(w.Checkpoint.PromotedUsers == null) { if (w is null)
w.Checkpoint.PromotedUsers = new VRage.Serialization.SerializableDictionary<ulong, MyPromoteLevel>();
}
if (w == null)
{ {
MessageBox.Show("A world is not selected."); MessageBox.Show("A world is not selected.");
return; return;
} }
if (w.Checkpoint.PromotedUsers == null) w.Checkpoint.PromotedUsers ??= new();
w.Checkpoint.PromotedUsers = new SerializableDictionary<ulong, MyPromoteLevel>();
d.Edit(w.Checkpoint.PromotedUsers.Dictionary); new RoleEditor().Edit(w.Checkpoint.PromotedUsers.Dictionary);
_instanceManager.DedicatedConfig.Administrators = w.Checkpoint.PromotedUsers.Dictionary.Where(k => k.Value >= MyPromoteLevel.Admin).Select(k => k.Key.ToString()).ToList(); _instanceManager.DedicatedConfig.Administrators = w.Checkpoint.PromotedUsers.Dictionary.Where(k => k.Value >= MyPromoteLevel.Admin).Select(k => k.Key.ToString()).ToList();
} }
} }

View File

@@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Threading.Tasks;
using Torch.Server.ViewModels;
using NLog;
using Torch.Collections;
namespace Torch.Server.Views.Converters
{
/// <summary>
/// A converter to get the index of a ModItemInfo object within a collection of ModItemInfo objects
/// </summary>
public class ModToListIdConverter : IMultiValueConverter
{
/// <summary>
/// Converts a ModItemInfo object into its index within a Collection of ModItemInfo objects
/// </summary>
/// <param name="values">
/// Expected to contain a ModItemInfo object at index 0
/// and a Collection of ModItemInfo objects at index 1
/// </param>
/// <param name="targetType">This parameter will be ignored</param>
/// <param name="parameter">This parameter will be ignored</param>
/// <param name="culture"> This parameter will be ignored</param>
/// <returns>the index of the mod within the provided mod list.</returns>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//if (targetType != typeof(int))
// throw new NotSupportedException("ModToIdConverter can only convert mods into int values or vise versa!");
if (values[0] is ModItemInfo mod && values[1] is MtObservableList<ModItemInfo> modList)
{
return modList.IndexOf(mod);
}
else
{
return null;
}
}
/// <summary>
/// It is not supported to reverse this converter
/// </summary>
/// <param name="values"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns>Raises a NotSupportedException</returns>
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("ModToIdConverter can not convert back!");
}
}
}

View File

@@ -5,8 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels" xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800">
MouseMove="UserControl_MouseMove">
<!--<UserControl.DataContext> <!--<UserControl.DataContext>
<viewModels:ConfigDedicatedViewModel /> <viewModels:ConfigDedicatedViewModel />
</UserControl.DataContext>--> </UserControl.DataContext>-->
@@ -18,7 +17,7 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<Style TargetType="Grid" x:Key="RootGridStyle"> <Style TargetType="Grid" x:Key="RootGridStyle">
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged, BindingGroupName=RootEnabledBinding}" Value="{x:Null}"> <DataTrigger Binding="{Binding Mode=OneWay, BindingGroupName=RootEnabledBinding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/> <Setter Property="IsEnabled" Value="False"/>
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>
@@ -36,38 +35,25 @@
<RowDefinition Height="80px"/> <RowDefinition Height="80px"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DataGrid Name="ModList" Grid.Column="0" Grid.ColumnSpan="1" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}" <DataGrid Name="ModList" Grid.Column="0" ItemsSource="{Binding Mods}"
Sorting="ModList_Sorting"
SelectionMode="Single" SelectionMode="Single"
SelectionUnit="FullRow" SelectionUnit="FullRow"
AllowDrop="True" AllowDrop="True"
CanUserReorderColumns="False" CanUserReorderColumns="False"
CanUserSortColumns="True" CanUserSortColumns="True"
PreviewMouseLeftButtonDown="ModList_MouseLeftButtonDown"
MouseLeftButtonUp="ModList_MouseLeftButtonUp"
SelectedCellsChanged="ModList_Selected" SelectedCellsChanged="ModList_Selected"
AutoGenerateColumns="False"> AutoGenerateColumns="False">
<!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">--> <!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">-->
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="Load Order"
Width="Auto"
IsReadOnly="True">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource ModToListIdConverter}" StringFormat="{}{0}">
<Binding />
<Binding ElementName="ModList" Path="DataContext"></Binding>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="Workshop Id" <DataGridTextColumn Header="Workshop Id"
IsReadOnly="True" IsReadOnly="True"
Binding="{Binding PublishedFileId, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}"> Binding="{Binding PublishedFileId}">
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="Name" <DataGridTextColumn Header="Name"
Width="*" Width="*"
IsReadOnly="True" IsReadOnly="True"
Binding="{Binding FriendlyName, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}"> Binding="{Binding FriendlyName}">
</DataGridTextColumn> </DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
<DataGrid.ItemContainerStyle> <DataGrid.ItemContainerStyle>
@@ -88,12 +74,12 @@
</Style> </Style>
</DataGrid.ItemContainerStyle> </DataGrid.ItemContainerStyle>
</DataGrid> </DataGrid>
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838"> <ScrollViewer Grid.Row="0" Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838">
<TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px" <TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px"
Text="{Binding ElementName=ModList, Path=SelectedItem.Description}"> Text="{Binding ElementName=ModList, Path=SelectedItem.Description}">
</TextBlock> </TextBlock>
</ScrollViewer> </ScrollViewer>
<Grid Grid.Row="2" Margin="0 0 0 6px"> <Grid Grid.Row="1" Grid.Column="0" Margin="0 0 0 6px">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -108,10 +94,10 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center" <CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center"
HorizontalAlignment="Left" Margin="6px 0" Grid.Column="0" Grid.Row="0"/> HorizontalAlignment="Left" Margin="6px 0" Grid.Column="0" Grid.Row="0"/>
<Label Content="Show Dependency Mods" Padding="0" Margin="6px 0" Grid.Column="1" VerticalAlignment="Center"/> <Label Content="Show Dependency Mods" Padding="0" Margin="6px 0" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"/>
<Label Content="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left" <Label Content="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left"
VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/> VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
<TextBox Name="AddModIDTextBox" Grid.Column="1" VerticalContentAlignment="Center" <TextBox Name="AddModIdTextBox" Grid.Column="1" VerticalContentAlignment="Center"
HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/> HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/>
<ComboBox Grid.Column="2" Grid.Row="1" x:Name="UgcServiceTypeBox" SelectionChanged="UgcServiceTypeBox_OnSelectionChanged" SelectedValuePath="Value" DisplayMemberPath="Key"/> <ComboBox Grid.Column="2" Grid.Row="1" x:Name="UgcServiceTypeBox" SelectionChanged="UgcServiceTypeBox_OnSelectionChanged" SelectedValuePath="Value" DisplayMemberPath="Key"/>
<Button Content="Add" Grid.Column="3" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/> <Button Content="Add" Grid.Column="3" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/>
@@ -120,6 +106,6 @@
<Button Content="Bulk Edit" Grid.Column="5" Margin="6px 0" Width="60px" Height="40px" Click="BulkButton_OnClick" Grid.Row="1"/> <Button Content="Bulk Edit" Grid.Column="5" Margin="6px 0" Width="60px" Height="40px" Click="BulkButton_OnClick" Grid.Row="1"/>
</Grid> </Grid>
<Button Content="Save Config" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/> <Button Content="Save Config" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -1,32 +1,15 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.CompilerServices;
using System.Windows.Threading;
using VRage.Game;
using NLog; using NLog;
using Sandbox.Engine.Networking;
using Torch.API; using Torch.API;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
using Torch.Server.Annotations;
using Torch.Collections;
using Torch.Utils; using Torch.Utils;
using Torch.Views; using Torch.Views;
@@ -35,14 +18,12 @@ namespace Torch.Server.Views
/// <summary> /// <summary>
/// Interaction logic for ModListControl.xaml /// Interaction logic for ModListControl.xaml
/// </summary> /// </summary>
public partial class ModListControl : UserControl, INotifyPropertyChanged public partial class ModListControl : UserControl
{ {
private static Logger Log = LogManager.GetLogger(nameof(ModListControl)); private static Logger Log = LogManager.GetLogger(nameof(ModListControl));
private InstanceManager _instanceManager; private InstanceManager _instanceManager;
ModItemInfo _draggedMod;
bool _hasOrderChanged = false;
bool _isSortedByLoadOrder = true;
private readonly ITorchConfig _config; private readonly ITorchConfig _config;
private ConfigDedicatedViewModel _viewModel;
//private List<BindingExpression> _bindingExpressions = new List<BindingExpression>(); //private List<BindingExpression> _bindingExpressions = new List<BindingExpression>();
/// <summary> /// <summary>
@@ -51,9 +32,11 @@ namespace Torch.Server.Views
public ModListControl() public ModListControl()
{ {
InitializeComponent(); InitializeComponent();
#pragma warning disable CS0618
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>(); _instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
_config = TorchBase.Instance.Config; _config = TorchBase.Instance.Config;
#pragma warning restore CS0618
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
//var mods = _instanceManager.DedicatedConfig?.Mods; //var mods = _instanceManager.DedicatedConfig?.Mods;
//if( mods != null) //if( mods != null)
// DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>(); // DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>();
@@ -67,24 +50,14 @@ namespace Torch.Server.Views
//Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles)); //Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles));
} }
private void ModListControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
throw new NotImplementedException();
}
private void ResetSorting()
{
CollectionViewSource.GetDefaultView(ModList.ItemsSource).SortDescriptions.Clear();
}
private void _instanceManager_InstanceLoaded(ConfigDedicatedViewModel obj) private void _instanceManager_InstanceLoaded(ConfigDedicatedViewModel obj)
{ {
Dispatcher.Invoke(() => { Dispatcher.InvokeAsync(() =>
DataContext = obj?.Mods ?? new MtObservableList<ModItemInfo>(); {
_viewModel = obj;
DataContext = obj;
UpdateLayout(); UpdateLayout();
((MtObservableList<ModItemInfo>)DataContext).CollectionChanged += OnModlistUpdate;
if (obj is { })
Task.Run(async () => Task.Run(async () =>
{ {
await obj.UpdateAllModInfosAsync(); await obj.UpdateAllModInfosAsync();
@@ -93,13 +66,6 @@ namespace Torch.Server.Views
}); });
} }
private void OnModlistUpdate(object sender, NotifyCollectionChangedEventArgs e)
{
ModList.Items.Refresh();
//if (e.Action == NotifyCollectionChangedAction.Remove)
// _instanceManager.SaveConfig();
}
private void SaveBtn_OnClick(object sender, RoutedEventArgs e) private void SaveBtn_OnClick(object sender, RoutedEventArgs e)
{ {
_instanceManager.SaveConfig(); _instanceManager.SaveConfig();
@@ -108,31 +74,28 @@ namespace Torch.Server.Views
private void AddBtn_OnClick(object sender, RoutedEventArgs e) private void AddBtn_OnClick(object sender, RoutedEventArgs e)
{ {
if (TryExtractId(AddModIDTextBox.Text, out ulong id)) if (TryExtractId(AddModIdTextBox.Text, out ulong id))
{ {
var mod = new ModItemInfo(ModItemUtils.Create(id, UgcServiceTypeBox.SelectedValue?.ToString())); var mod = new ModItemInfo(ModItemUtils.Create(id, UgcServiceTypeBox.SelectedValue?.ToString()));
_instanceManager.DedicatedConfig.Mods.Add(mod); _instanceManager.DedicatedConfig.Mods.Add(mod);
Task.Run(mod.UpdateModInfoAsync) Task.Run(mod.UpdateModInfoAsync)
.ContinueWith((t) => .ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{ {
_instanceManager.DedicatedConfig.Save(); _instanceManager.DedicatedConfig.Save();
}); });
}); AddModIdTextBox.Text = "";
AddModIDTextBox.Text = "";
} }
else else
{ {
AddModIDTextBox.BorderBrush = Brushes.Red; AddModIdTextBox.BorderBrush = Brushes.Red;
Log.Warn("Invalid mod id!"); Log.Warn("Invalid mod id!");
MessageBox.Show("Invalid mod id!"); MessageBox.Show("Invalid mod id!");
} }
} }
private void RemoveBtn_OnClick(object sender, RoutedEventArgs e) private void RemoveBtn_OnClick(object sender, RoutedEventArgs e)
{ {
var modList = ((MtObservableList<ModItemInfo>)DataContext); var modList = _viewModel.Mods;
if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod)) if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod))
modList.Remove(mod); modList.Remove(mod);
} }
@@ -150,113 +113,9 @@ namespace Torch.Server.Views
return success; return success;
} }
private void ModList_Sorting(object sender, DataGridSortingEventArgs e)
{
Log.Info($"Sorting by '{e.Column.Header}'");
if (e.Column == ModList.Columns[0])
{
var dataView = CollectionViewSource.GetDefaultView(ModList.ItemsSource);
dataView.SortDescriptions.Clear();
dataView.Refresh();
_isSortedByLoadOrder = true;
}
else
_isSortedByLoadOrder = false;
}
private void ModList_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//return;
_draggedMod = (ModItemInfo) TryFindRowAtPoint((UIElement) sender, e.GetPosition(ModList))?.DataContext;
//DraggedMod = (ModItemInfo) ModList.SelectedItem;
}
private static DataGridRow TryFindRowAtPoint(UIElement reference, Point point)
{
var element = reference.InputHitTest(point) as DependencyObject;
if (element == null)
return null;
if (element is DataGridRow row)
return row;
else
return TryFindParent<DataGridRow>(element);
}
private static T TryFindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject parent;
if (child == null)
return null;
if (child is ContentElement contentElement)
{
parent = ContentOperations.GetParent(contentElement);
if (parent == null && child is FrameworkContentElement fce)
parent = fce.Parent;
}
else
{
parent = VisualTreeHelper.GetParent(child);
}
if (parent is T result)
return result;
else
return TryFindParent<T>(parent);
}
private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (_draggedMod == null)
return;
if (!_isSortedByLoadOrder)
return;
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
if( targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
{
_hasOrderChanged = true;
var modList = (MtObservableList<ModItemInfo>)DataContext;
modList.Move(modList.IndexOf(targetMod), _draggedMod);
//modList.RemoveAt(modList.IndexOf(_draggedMod));
//modList.Insert(modList.IndexOf(targetMod), _draggedMod);
ModList.Items.Refresh();
ModList.SelectedItem = _draggedMod;
}
}
private void ModList_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!_isSortedByLoadOrder)
{
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
if (targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
{
var msg = "Drag and drop is only available when sorted by load order!";
Log.Warn(msg);
MessageBox.Show(msg);
}
}
//if (DraggedMod != null && HasOrderChanged)
//Log.Info("Dragging over, saving...");
//_instanceManager.SaveConfig();
_draggedMod = null;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ModList_Selected(object sender, SelectedCellsChangedEventArgs e) private void ModList_Selected(object sender, SelectedCellsChangedEventArgs e)
{ {
if (_draggedMod != null) if( e.AddedCells.Count > 0)
ModList.SelectedItem = _draggedMod;
else if( e.AddedCells.Count > 0)
ModList.SelectedItem = e.AddedCells[0].Item; ModList.SelectedItem = e.AddedCells[0].Item;
} }
@@ -265,7 +124,7 @@ namespace Torch.Server.Views
var editor = new CollectionEditor(); var editor = new CollectionEditor();
//let's see just how poorly we can do this //let's see just how poorly we can do this
var modList = ((MtObservableList<ModItemInfo>)DataContext).ToList(); var modList = _viewModel.Mods.ToList();
var idList = modList.Select(m => m.ToString()).ToList(); var idList = modList.Select(m => m.ToString()).ToList();
var tasks = new List<Task>(); var tasks = new List<Task>();
//blocking //blocking

View File

@@ -18,7 +18,6 @@
</Style> </Style>
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/> <converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/> <converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
<converters:ModToListIdConverter x:Key="ModToListIdConverter"/>
<converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/> <converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/>
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/> <converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -12,7 +12,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
NLog.config = NLog.config NLog.config = NLog.config
Dockerfile = Dockerfile
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"

View File

@@ -197,7 +197,7 @@ namespace Torch.Managers.PatchManager
lock (_log) lock (_log)
{ {
var instructions = context.Body.Instructions var instructions = context.Body.Instructions
.Select(b => new MsilInstruction(b)).ToList(); .Select(b => b.ToMsilInstruction()).ToList();
LogTarget(PrintModeEnum.Patched, false, "========== Patched method =========="); LogTarget(PrintModeEnum.Patched, false, "========== Patched method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), instructions, true); MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), instructions, true);
LogTarget(PrintModeEnum.Patched, false, gap); LogTarget(PrintModeEnum.Patched, false, gap);

View File

@@ -0,0 +1,136 @@
using System;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
using Torch.Managers.PatchManager.Transpile;
using OperandType = System.Reflection.Emit.OperandType;
namespace Torch.Managers.PatchManager.MSIL;
internal static class InstructionExtensions
{
public static MsilInstruction ToMsilInstruction(this Instruction instruction)
{
static System.Reflection.Emit.Label CreateLabel(int pos)
{
var instance = Activator.CreateInstance(typeof(System.Reflection.Emit.Label),
BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {pos}, null);
if (instance == null)
return default;
return (System.Reflection.Emit.Label) instance;
}
var systemOpCode = MethodContext.OpCodeLookup[instruction.OpCode.Value];
var msil = new MsilInstruction(systemOpCode);
if (instruction.Operand is null || instruction.OpCode.OperandType == Mono.Cecil.Cil.OperandType.InlineNone)
return msil;
var opType = systemOpCode.OperandType;
switch (instruction.Operand)
{
case Instruction targetInstruction when opType is OperandType.InlineBrTarget or OperandType.ShortInlineBrTarget:
msil.Operand = new MsilOperandBrTarget(msil)
{
Target = new MsilLabel(CreateLabel(targetInstruction.Offset))
};
break;
case FieldReference reference when opType == OperandType.InlineField:
msil.Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(msil)
{
Value = reference.ResolveReflection()
};
break;
case int int32 when opType is OperandType.InlineI or OperandType.ShortInlineI:
msil.Operand = new MsilOperandInline.MsilOperandInt32(msil)
{
Value = int32
};
break;
case long int64 when opType is OperandType.InlineI8:
msil.Operand = new MsilOperandInline.MsilOperandInt64(msil)
{
Value = int64
};
break;
case MethodReference methodReference when opType is OperandType.InlineMethod:
msil.Operand = new MsilOperandInline.MsilOperandReflected<MethodBase>(msil)
{
Value = methodReference.ResolveReflection()
};
break;
case double @double when opType is OperandType.InlineR:
msil.Operand = new MsilOperandInline.MsilOperandDouble(msil)
{
Value = @double
};
break;
case null when opType is OperandType.InlineSig:
throw new NotSupportedException("InlineSignature is not supported by instruction converter");
case string @string when opType == OperandType.InlineString:
msil.Operand = new MsilOperandInline.MsilOperandString(msil)
{
Value = @string
};
break;
case Instruction[] targetInstructions when opType is OperandType.InlineSwitch:
msil.Operand = new MsilOperandSwitch(msil)
{
Labels = targetInstructions.Select(b => new MsilLabel(CreateLabel(b.Offset))).ToArray()
};
break;
case MemberReference memberReference when opType is OperandType.InlineTok:
msil.Operand = new MsilOperandInline.MsilOperandReflected<MemberInfo>(msil)
{
Value = memberReference.ResolveReflection()
};
break;
case TypeReference typeReference when opType is OperandType.InlineType:
msil.Operand = new MsilOperandInline.MsilOperandReflected<Type>(msil)
{
Value = typeReference.ResolveReflection()
};
break;
case VariableDefinition variableDefinition when opType is OperandType.InlineVar or OperandType.ShortInlineVar:
if (systemOpCode.IsLocalStore() || systemOpCode.IsLocalLoad() || systemOpCode.IsLocalLoadByRef())
msil.Operand = new MsilOperandInline.MsilOperandLocal(msil)
{
Value = new MsilLocal(variableDefinition.Index)
};
else
msil.Operand = new MsilOperandInline.MsilOperandArgument(msil)
{
Value = new MsilArgument(variableDefinition.Index)
};
break;
case ParameterDefinition parameterDefinition when opType is OperandType.InlineVar or OperandType.ShortInlineVar:
if (systemOpCode.IsLocalStore() || systemOpCode.IsLocalLoad() || systemOpCode.IsLocalLoadByRef())
msil.Operand = new MsilOperandInline.MsilOperandLocal(msil)
{
Value = new MsilLocal(parameterDefinition.Index)
};
else
msil.Operand = new MsilOperandInline.MsilOperandArgument(msil)
{
Value = new MsilArgument(parameterDefinition.Index)
};
break;
case float @float when opType == OperandType.ShortInlineR:
msil.Operand = new MsilOperandInline.MsilOperandSingle(msil)
{
Value = @float
};
break;
#pragma warning disable 618
case null when opType == OperandType.InlinePhi:
#pragma warning restore 618
default:
throw new ArgumentOutOfRangeException(nameof(instruction.Operand), instruction.Operand, "Invalid operand type");
}
return msil;
}
}

View File

@@ -12,6 +12,7 @@ using Mono.Cecil.Cil;
using MonoMod.Utils; using MonoMod.Utils;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
using Torch.Utils; using Torch.Utils;
using VRage.Game.VisualScripting;
using OpCode = System.Reflection.Emit.OpCode; using OpCode = System.Reflection.Emit.OpCode;
using OpCodes = System.Reflection.Emit.OpCodes; using OpCodes = System.Reflection.Emit.OpCodes;
using OperandType = System.Reflection.Emit.OperandType; using OperandType = System.Reflection.Emit.OperandType;
@@ -88,132 +89,6 @@ namespace Torch.Managers.PatchManager.MSIL
} }
} }
public MsilInstruction(Instruction instruction)
{
Label CreateLabel(int pos)
{
var instance = Activator.CreateInstance(typeof(Label),
BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {pos}, null);
if (instance == null)
return default;
return (Label) instance;
}
if (!MethodContext.OpCodeLookup.TryGetValue(instruction.OpCode.Value, out var opCode))
return;
OpCode = opCode;
var opType = opCode.OperandType;
if (opType == OperandType.InlineNone)
{
Operand = null;
return;
}
switch (instruction.Operand)
{
case OperandType.InlineNone:
break;
case Instruction targetInstruction when opType == OperandType.InlineBrTarget || opType == OperandType.ShortInlineBrTarget:
Operand = new MsilOperandBrTarget(this)
{
Target = new MsilLabel(CreateLabel(targetInstruction.Offset))
};
break;
case FieldReference reference when opType == OperandType.InlineField:
Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(this)
{
Value = reference.ResolveReflection()
};
break;
case int int32 when opType == OperandType.InlineI || opType == OperandType.ShortInlineI:
Operand = new MsilOperandInline.MsilOperandInt32(this)
{
Value = int32
};
break;
case long int64 when opType == OperandType.InlineI8:
Operand = new MsilOperandInline.MsilOperandInt64(this)
{
Value = int64
};
break;
case MethodReference methodReference when opType == OperandType.InlineMethod:
Operand = new MsilOperandInline.MsilOperandReflected<MethodBase>(this)
{
Value = methodReference.ResolveReflection()
};
break;
case double @double when opType == OperandType.InlineR:
Operand = new MsilOperandInline.MsilOperandDouble(this)
{
Value = @double
};
break;
case null when opType == OperandType.InlineSig:
throw new NotSupportedException("InlineSignature is not supported by instruction converter");
case string @string when opType == OperandType.InlineString:
Operand = new MsilOperandInline.MsilOperandString(this)
{
Value = @string
};
break;
case Instruction[] targetInstructions when opType == OperandType.InlineSwitch:
Operand = new MsilOperandSwitch(this)
{
Labels = targetInstructions.Select(b => new MsilLabel(CreateLabel(b.Offset))).ToArray()
};
break;
case MemberReference memberReference when opType == OperandType.InlineTok:
Operand = new MsilOperandInline.MsilOperandReflected<MemberInfo>(this)
{
Value = memberReference.ResolveReflection()
};
break;
case TypeReference typeReference when opType == OperandType.InlineType:
Operand = new MsilOperandInline.MsilOperandReflected<Type>(this)
{
Value = typeReference.ResolveReflection()
};
break;
case VariableDefinition variableDefinition when opType == OperandType.InlineVar || opType == OperandType.ShortInlineVar:
if (OpCode.IsLocalStore() || OpCode.IsLocalLoad() || OpCode.IsLocalLoadByRef())
Operand = new MsilOperandInline.MsilOperandLocal(this)
{
Value = new MsilLocal(variableDefinition.Index)
};
else
Operand = new MsilOperandInline.MsilOperandArgument(this)
{
Value = new MsilArgument(variableDefinition.Index)
};
break;
case ParameterDefinition parameterDefinition when opType == OperandType.InlineVar || opType == OperandType.ShortInlineVar:
if (OpCode.IsLocalStore() || OpCode.IsLocalLoad() || OpCode.IsLocalLoadByRef())
Operand = new MsilOperandInline.MsilOperandLocal(this)
{
Value = new MsilLocal(parameterDefinition.Index)
};
else
Operand = new MsilOperandInline.MsilOperandArgument(this)
{
Value = new MsilArgument(parameterDefinition.Index)
};
break;
case float @float when opType == OperandType.ShortInlineR:
Operand = new MsilOperandInline.MsilOperandSingle(this)
{
Value = @float
};
break;
#pragma warning disable 618
case null when opType == OperandType.InlinePhi:
#pragma warning restore 618
default:
throw new ArgumentOutOfRangeException(nameof(instruction.Operand), instruction.Operand, "Invalid operand type");
}
}
/// <summary> /// <summary>
/// Opcode of this instruction /// Opcode of this instruction
/// </summary> /// </summary>
@@ -227,7 +102,7 @@ namespace Torch.Managers.PatchManager.MSIL
/// <summary> /// <summary>
/// The operand for this instruction, or null. /// The operand for this instruction, or null.
/// </summary> /// </summary>
public MsilOperand Operand { get; } public MsilOperand Operand { get; internal set; }
/// <summary> /// <summary>
/// Labels pointing to this instruction. /// Labels pointing to this instruction.
@@ -284,7 +159,11 @@ namespace Torch.Managers.PatchManager.MSIL
type = type.BaseType; type = type.BaseType;
} }
((MsilOperandInline<T>) Operand).Value = o; if (Operand is not MsilOperandInline<T> operandInline)
throw new InvalidOperationException(
$"Type {typeof(T).FullName} is not valid operand for {Operand?.GetType().Signature()}");
operandInline.Value = o;
return this; return this;
} }

View File

@@ -1,47 +1,41 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using NLog; using NLog;
using NLog.Config; using NLog.Config;
using NLog.Targets;
namespace Torch.Utils; namespace Torch.Utils;
public static class TorchLogManager public static class TorchLogManager
{ {
private static AssemblyLoadContext LoadContext; private static AssemblyLoadContext LoadContext = new("TorchLog");
public static LoggingConfiguration Configuration { get; private set; } public static LoggingConfiguration Configuration { get; private set; }
public static void SetConfiguration(LoggingConfiguration configuration, string extensionsDir = null) public static void SetConfiguration(LoggingConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
LogManager.Setup() LogManager.Configuration = configuration;
.SetupExtensions(builder =>
{
if (extensionsDir is null || !Directory.Exists(extensionsDir))
return;
if (LoadContext is null)
{
LoadContext = new("TorchLog");
foreach (var file in Directory.EnumerateFiles(extensionsDir, "*.dll", SearchOption.AllDirectories))
{
builder.RegisterAssembly(LoadContext.LoadFromAssemblyPath(file));
}
return;
}
foreach (var assembly in LoadContext.Assemblies)
{
builder.RegisterAssembly(assembly);
}
})
.SetupLogFactory(builder => builder.SetThrowConfigExceptions(true))
.LoadConfiguration(configuration);
LogManager.ReconfigExistingLoggers(); LogManager.ReconfigExistingLoggers();
} }
public static void RegisterTargets(string dir)
{
if (!Directory.Exists(dir)) return;
foreach (var type in Directory.EnumerateFiles(dir, "*.dll").Select(LoadContext.LoadFromAssemblyPath)
.SelectMany(b => b.ExportedTypes)
.Where(b => b.GetCustomAttribute<TargetAttribute>() is { }))
{
Target.Register(type.GetCustomAttribute<TargetAttribute>()!.Name, type);
}
}
public static void RestoreGlobalConfiguration() public static void RestoreGlobalConfiguration()
{ {
LogManager.Configuration = Configuration; SetConfiguration(Configuration);
LogManager.ReconfigExistingLoggers();
} }
} }

View File

@@ -472,7 +472,8 @@ namespace Torch
{ {
// Kinda icky, but we can't block the update and expect the state to change. // Kinda icky, but we can't block the update and expect the state to change.
if (Thread.CurrentThread == _updateThread) if (Thread.CurrentThread == _updateThread)
return _state == state; throw new InvalidOperationException(
"Waiting for game state is not possible from update thread (deadlock)");
DateTime? end = timeout.HasValue ? (DateTime?) (DateTime.Now + timeout.Value) : null; DateTime? end = timeout.HasValue ? (DateTime?) (DateTime.Now + timeout.Value) : null;
while (_state != state && (!end.HasValue || end > DateTime.Now + TimeSpan.FromSeconds(1))) while (_state != state && (!end.HasValue || end > DateTime.Now + TimeSpan.FromSeconds(1)))