Replace mod text box by separate tab with workshop support (#263)
* Implement ModList tab which fetches and displays mod information from the workshop.
* ModListEditor: Implement drag and drop ordering, adding, removing and saving.
* Add SteamWorkshopService to VCS
* Add missing file to SteamworkshopService project.
* ModlistControl: Implement checkbox for hiding/showing dependency mods
disable until config is loaded.
design improvements.
* Add documentation for the new classes.
* Comply to naming conventions.
* Update Torch.Server.csproj
* Fix Mod.IsDependency not being serialized when saving
* Remove superfluous update of mod meta data.
Remove commented section in ConfigControl.xaml.
* Optimized SteamworkshopService according to commit review.
* Move SteamWorkshopService to Torch.Utils.SteamworkshopTools
* Remove debug output.
* Don't break stack trace with custom exception in SteamWorkshopTools.
* User ViewModel base class for ModItemInfo instead of implementing INotifyProperty directly.
* Wrap ModListControl in ScrollViewer.
* Rename SteamWorkshopTools utility to WebAPI.
* Revert steamkit call to use dynamic typing for clarity :/
* Mark webAPI based method for downloading workshop content as obsolete.
* Update Torch project definition.
* Disable building Torch client
* Update readme
* Change init order to ensure paths are initialized for plugins
* Reorder exception logging to reduce duplication
* Use thread safe queues in MtObservableCollectionBase
* Revert "Change init order to ensure paths are initialized for plugins"
This reverts commit 3f803b8107
.
* Fix layout of ModListControl
* Combine Invokes to reduce allocations
* Replace string comparisons by string.Equals / string.IsNullOrEmpty
* Replace string comparisons by string.Equals / string.IsNullOrEmpty
* Use MtObservableList for Modlist to avoid race conditions.
This commit is contained in:
@@ -18,18 +18,10 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
|
||||
* 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.
|
||||
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
|
||||
|
||||
## Torch.Client
|
||||
* An optional client-side version of Torch. More documentation to come.
|
||||
|
||||
# Building
|
||||
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
|
||||
|
||||
In both cases you will need to set the InstancePath in TorchConfig.xml to an existing dedicated server instance as Torch can't fully generate it on its own yet.
|
||||
|
||||
# Official Plugins
|
||||
Install plugins by unzipping them into the 'Plugins' folder which should be in the same location as the Torch files. If it doesn't exist you can simply create it.
|
||||
* [Essentials](https://github.com/TorchAPI/Essentials): Adds a slew of chat commands and other tools to help manage your server.
|
||||
* [Concealment](https://github.com/TorchAPI/Concealment): Adds game logic and physics optimizations that significantly improve sim speed.
|
||||
|
||||
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon.
|
||||
[](https://www.patreon.com/bePatron?u=847269)!
|
||||
|
@@ -191,20 +191,28 @@ quit";
|
||||
|
||||
private void LogException(Exception ex)
|
||||
{
|
||||
if (ex.InnerException != null)
|
||||
if (ex is AggregateException ag)
|
||||
{
|
||||
LogException(ex.InnerException);
|
||||
foreach (var e in ag.InnerExceptions)
|
||||
LogException(e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Fatal(ex);
|
||||
|
||||
if (ex is ReflectionTypeLoadException exti)
|
||||
foreach (Exception exl in exti.LoaderExceptions)
|
||||
if (ex is ReflectionTypeLoadException extl)
|
||||
{
|
||||
foreach (var exl in extl.LoaderExceptions)
|
||||
LogException(exl);
|
||||
|
||||
if (ex is AggregateException ag)
|
||||
foreach (Exception e in ag.InnerExceptions)
|
||||
LogException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
LogException(ex.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleException(object sender, UnhandledExceptionEventArgs e)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -14,6 +15,7 @@ using Sandbox.Game;
|
||||
using Sandbox.Game.Gui;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Collections;
|
||||
using Torch.Managers;
|
||||
using Torch.Mod;
|
||||
using Torch.Server.ViewModels;
|
||||
@@ -94,7 +96,8 @@ namespace Torch.Server.Managers
|
||||
//remove the Torch mod to avoid running multiple copies of it
|
||||
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
||||
DedicatedConfig.Mods.Add(m.PublishedFileId);
|
||||
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +111,8 @@ namespace Torch.Server.Managers
|
||||
//remove the Torch mod to avoid running multiple copies of it
|
||||
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
||||
DedicatedConfig.Mods.Add(m.PublishedFileId);
|
||||
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +123,10 @@ namespace Torch.Server.Managers
|
||||
|
||||
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var mods = new MtObservableList<ModItemInfo>();
|
||||
foreach (var mod in world.Checkpoint.Mods)
|
||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
||||
|
||||
DedicatedConfig.Mods = world.Checkpoint.Mods.Select(x => x.PublishedFileId).ToList();
|
||||
mods.Add(new ModItemInfo(mod));
|
||||
DedicatedConfig.Mods = mods;
|
||||
|
||||
|
||||
Log.Debug("Loaded mod list from world");
|
||||
@@ -151,7 +154,10 @@ namespace Torch.Server.Managers
|
||||
return;
|
||||
}
|
||||
|
||||
DedicatedConfig.Mods = checkpoint.Mods.Select(x => x.PublishedFileId).ToList();
|
||||
var mods = new MtObservableList<ModItemInfo>();
|
||||
foreach (var mod in checkpoint.Mods)
|
||||
mods.Add(new ModItemInfo(mod));
|
||||
DedicatedConfig.Mods = mods;
|
||||
|
||||
Log.Debug("Loaded mod list from world");
|
||||
|
||||
@@ -194,8 +200,13 @@ namespace Torch.Server.Managers
|
||||
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
||||
checkpoint.Mods.Clear();
|
||||
|
||||
foreach (var modId in DedicatedConfig.Mods)
|
||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
||||
foreach (var mod in DedicatedConfig.Mods)
|
||||
{
|
||||
var savedMod = new MyObjectBuilder_Checkpoint.ModItem(mod.Name, mod.PublishedFileId, mod.FriendlyName);
|
||||
savedMod.IsDependency = mod.IsDependency;
|
||||
checkpoint.Mods.Add(savedMod);
|
||||
}
|
||||
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||
|
||||
MyObjectBuilderSerializer.SerializeXML(sandboxPath, false, checkpoint);
|
||||
|
||||
|
@@ -80,6 +80,9 @@
|
||||
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Win32.Registry, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Win32.Registry.4.4.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -87,6 +90,9 @@
|
||||
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="protobuf-net, Version=2.1.0.0, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\protobuf-net.2.1.0\lib\net451\protobuf-net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||
@@ -126,6 +132,12 @@
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Security.AccessControl, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.AccessControl.4.4.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Principal.Windows, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Principal.Windows.4.4.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
@@ -245,15 +257,21 @@
|
||||
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
||||
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
||||
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
|
||||
<Compile Include="ViewModels\ModItemInfo.cs" />
|
||||
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
|
||||
<Compile Include="Views\Converters\DefinitionToIdConverter.cs" />
|
||||
<Compile Include="Views\Converters\BooleanAndConverter.cs" />
|
||||
<Compile Include="Views\Converters\ListConverter.cs" />
|
||||
<Compile Include="MultiTextWriter.cs" />
|
||||
<Compile Include="RichTextBoxWriter.cs" />
|
||||
<Compile Include="Views\Converters\ListConverterWorkshopId.cs" />
|
||||
<Compile Include="Views\Converters\ModToIdConverter.cs" />
|
||||
<Compile Include="Views\Entities\CharacterView.xaml.cs">
|
||||
<DependentUpon>CharacterView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ModListControl.xaml.cs">
|
||||
<DependentUpon>ModListControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ThemeControl.xaml.cs">
|
||||
<DependentUpon>ThemeControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -414,6 +432,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\ModListControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\PluginsControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
@@ -10,6 +10,8 @@ using Torch.Collections;
|
||||
using Torch.Server.Managers;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using Torch.Utils.SteamWorkshopTools;
|
||||
using Torch.Collections;
|
||||
|
||||
namespace Torch.Server.ViewModels
|
||||
{
|
||||
@@ -29,6 +31,7 @@ namespace Torch.Server.ViewModels
|
||||
_config = configDedicated;
|
||||
_config.IgnoreLastSession = true;
|
||||
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
|
||||
Task.Run(() => UpdateAllModInfosAsync());
|
||||
}
|
||||
|
||||
public void Save(string path = null)
|
||||
@@ -73,14 +76,61 @@ namespace Torch.Server.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateAllModInfosAsync(Action<string> messageHandler = null)
|
||||
{
|
||||
if (Mods.Count() == 0)
|
||||
return;
|
||||
|
||||
var ids = Mods.Select(m => m.PublishedFileId);
|
||||
var workshopService = WebAPI.Instance;
|
||||
Dictionary<ulong, PublishedItemDetails> modInfos = null;
|
||||
|
||||
try
|
||||
{
|
||||
modInfos = (await workshopService.GetPublishedFileDetails(ids.ToArray()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info($"Mods Info successfully retrieved!");
|
||||
|
||||
foreach (var mod in Mods)
|
||||
{
|
||||
if (!modInfos.ContainsKey(mod.PublishedFileId) || modInfos[mod.PublishedFileId] == null)
|
||||
{
|
||||
Log.Error($"Failed to retrieve info for mod with workshop id '{mod.PublishedFileId}'!");
|
||||
}
|
||||
//else if (!modInfo.Tags.Contains(""))
|
||||
else
|
||||
{
|
||||
mod.FriendlyName = modInfos[mod.PublishedFileId].Title;
|
||||
mod.Description = modInfos[mod.PublishedFileId].Description;
|
||||
//mod.Name = modInfos[mod.PublishedFileId].FileName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<string> Administrators { get => _config.Administrators; set => SetValue(x => _config.Administrators = x, value); }
|
||||
|
||||
public List<ulong> Banned { get => _config.Banned; set => SetValue(x => _config.Banned = x, value); }
|
||||
|
||||
private MtObservableList<ModItemInfo> _mods = new MtObservableList<ModItemInfo>();
|
||||
public MtObservableList<ModItemInfo> Mods
|
||||
{
|
||||
get => _mods;
|
||||
set
|
||||
{
|
||||
SetValue(x => _mods = x, value);
|
||||
Task.Run(() => UpdateAllModInfosAsync());
|
||||
}
|
||||
}
|
||||
|
||||
public List<ulong> Reserved { get => _config.Reserved; set => SetValue(x => _config.Reserved = x, value); }
|
||||
|
||||
private List<ulong> _mods = new List<ulong>();
|
||||
public List<ulong> Mods { get => _mods; set => SetValue(x => _mods = x, value); }
|
||||
|
||||
public int AsteroidAmount { get => _config.AsteroidAmount; set => SetValue(x => _config.AsteroidAmount = x, value); }
|
||||
|
||||
|
131
Torch.Server/ViewModels/ModItemInfo.cs
Normal file
131
Torch.Server/ViewModels/ModItemInfo.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
using NLog;
|
||||
using VRage.Game;
|
||||
using Torch.Server.Annotations;
|
||||
using Torch.Utils.SteamWorkshopTools;
|
||||
|
||||
namespace Torch.Server.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper around VRage.Game.Objectbuilder_Checkpoint.ModItem
|
||||
/// that holds additional meta information
|
||||
/// (e.g. workshop description)
|
||||
/// </summary>
|
||||
public class ModItemInfo : ViewModel
|
||||
{
|
||||
MyObjectBuilder_Checkpoint.ModItem _modItem;
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// Human friendly name of the mod
|
||||
/// </summary>
|
||||
public string FriendlyName
|
||||
{
|
||||
get { return _modItem.FriendlyName; }
|
||||
set {
|
||||
SetValue(ref _modItem.FriendlyName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Workshop ID of the mod
|
||||
/// </summary>
|
||||
public ulong PublishedFileId
|
||||
{
|
||||
get { return _modItem.PublishedFileId; }
|
||||
set
|
||||
{
|
||||
SetValue(ref _modItem.PublishedFileId, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local filename of the mod
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _modItem.Name; }
|
||||
set
|
||||
{
|
||||
SetValue(ref _modItem.FriendlyName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the mod was added
|
||||
/// because another mod depends on it
|
||||
/// </summary>
|
||||
public bool IsDependency
|
||||
{
|
||||
get { return _modItem.IsDependency; }
|
||||
set
|
||||
{
|
||||
SetValue(ref _modItem.IsDependency, value);
|
||||
}
|
||||
}
|
||||
|
||||
private string _description;
|
||||
/// <summary>
|
||||
/// Workshop description of the mod
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get { return _description; }
|
||||
set
|
||||
{
|
||||
SetValue(ref _description, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor, returns a new ModItemInfo instance
|
||||
/// </summary>
|
||||
/// <param name="mod">The wrapped mod</param>
|
||||
public ModItemInfo(MyObjectBuilder_Checkpoint.ModItem mod)
|
||||
{
|
||||
_modItem = mod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve information about the
|
||||
/// wrapped mod from the workhop asynchronously
|
||||
/// via the Steam web API.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> UpdateModInfoAsync()
|
||||
{
|
||||
var msg = "";
|
||||
var workshopService = WebAPI.Instance;
|
||||
PublishedItemDetails modInfo = null;
|
||||
try
|
||||
{
|
||||
modInfo = (await workshopService.GetPublishedFileDetails(new ulong[] { PublishedFileId }))?[PublishedFileId];
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
}
|
||||
if (modInfo == null)
|
||||
{
|
||||
Log.Error($"Failed to retrieve mod with workshop id '{PublishedFileId}'!");
|
||||
return false;
|
||||
}
|
||||
//else if (!modInfo.Tags.Contains(""))
|
||||
else
|
||||
{
|
||||
Log.Info($"Mod Info successfully retrieved!");
|
||||
FriendlyName = modInfo.Title;
|
||||
Description = modInfo.Description;
|
||||
//Name = modInfo.FileName;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -58,7 +58,7 @@
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Disabled">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
77
Torch.Server/Views/Converters/ListConverterWorkshopId.cs
Normal file
77
Torch.Server/Views/Converters/ListConverterWorkshopId.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using Torch.Server.ViewModels;
|
||||
using VRage.Game;
|
||||
|
||||
namespace Torch.Server.Views.Converters
|
||||
{
|
||||
class ListConverterWorkshopId : IValueConverter
|
||||
{
|
||||
public Type Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of ModItemInfo objects into a list of their workshop IDs (PublishedFileIds).
|
||||
/// </summary>
|
||||
/// <param name="valueList">
|
||||
/// Expected to contain a list of ModItemInfo objects
|
||||
/// </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>A string containing the workshop ids of all mods, one per line</returns>
|
||||
public object Convert(object valueList, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (!(valueList is IList list))
|
||||
throw new InvalidOperationException("Value is not the proper type.");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var item in list)
|
||||
{
|
||||
sb.AppendLine(((ModItemInfo) item).PublishedFileId.ToString());
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of workshop ids into a list of ModItemInfo objects
|
||||
/// </summary>
|
||||
/// <param name="value">A string containing workshop ids separated by new lines</param>
|
||||
/// <param name="targetType">This parameter will be ignored</param>
|
||||
/// <param name="parameter">
|
||||
/// A list of ModItemInfos which should
|
||||
/// contain the requestted mods
|
||||
/// (or they will be dropped)
|
||||
/// </param>
|
||||
/// <param name="culture">This parameter will be ignored</param>
|
||||
/// <returns>A list of ModItemInfo objects</returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(Type));
|
||||
var mods = parameter as ICollection<ModItemInfo>;
|
||||
if (mods == null)
|
||||
throw new ArgumentException("parameter needs to be of type ICollection<ModItemInfo>!");
|
||||
var input = ((string)value).Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var item in input)
|
||||
{
|
||||
if( ulong.TryParse(item, out ulong id))
|
||||
{
|
||||
var mod = mods.FirstOrDefault((m) => m.PublishedFileId == id);
|
||||
if (mod != null)
|
||||
list.Add(mod);
|
||||
else
|
||||
list.Add(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
53
Torch.Server/Views/Converters/ModToIdConverter.cs
Normal file
53
Torch.Server/Views/Converters/ModToIdConverter.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
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!");
|
||||
var mod = (ModItemInfo) values[0];
|
||||
var theModList = (MtObservableList<ModItemInfo>) values[1];
|
||||
return theModList.IndexOf(mod);
|
||||
}
|
||||
|
||||
/// <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!");
|
||||
}
|
||||
}
|
||||
}
|
125
Torch.Server/Views/ModListControl.xaml
Normal file
125
Torch.Server/Views/ModListControl.xaml
Normal file
@@ -0,0 +1,125 @@
|
||||
<UserControl x:Class="Torch.Server.Views.ModListControl"
|
||||
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:viewModels="clr-namespace:Torch.Server.ViewModels"
|
||||
xmlns:s="clr-namespace:System"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
MouseMove="UserControl_MouseMove">
|
||||
<!--<UserControl.DataContext>
|
||||
<viewModels:ConfigDedicatedViewModel />
|
||||
</UserControl.DataContext>-->
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="Grid" x:Key="RootGridStyle">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged, BindingGroupName=RootEnabledBinding}" Value="{x:Null}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.DataContext>
|
||||
<viewModels:ConfigDedicatedViewModel />
|
||||
</UserControl.DataContext>
|
||||
<Grid Style="{StaticResource RootGridStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="500px"/>
|
||||
<ColumnDefinition Width="10px"/>
|
||||
<ColumnDefinition Width="*" MinWidth="200px"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="80px"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DataGrid Name="ModList" Grid.Column="0" Grid.ColumnSpan="1" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}"
|
||||
Sorting="ModList_Sorting"
|
||||
SelectionMode="Single"
|
||||
SelectionUnit="FullRow"
|
||||
AllowDrop="True"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
PreviewMouseLeftButtonDown="ModList_MouseLeftButtonDown"
|
||||
MouseLeftButtonUp="ModList_MouseLeftButtonUp"
|
||||
SelectedCellsChanged="ModList_Selected"
|
||||
AutoGenerateColumns="False">
|
||||
<!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">-->
|
||||
<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"
|
||||
IsReadOnly="True"
|
||||
Binding="{Binding PublishedFileId, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
|
||||
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Name"
|
||||
Width="*"
|
||||
IsReadOnly="True"
|
||||
Binding="{Binding FriendlyName, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.ItemContainerStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsDependency}" Value="True">
|
||||
<Setter Property="Foreground" Value="#222222"/>
|
||||
<Setter Property="Background" Value="#FFCCAA"/>
|
||||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsDependency}" Value="True"/>
|
||||
<Condition Binding="{Binding ElementName=ShowDependencyModsCheckBox, Path=IsChecked}" Value="False"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Height" Value="0px"/>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.ItemContainerStyle>
|
||||
</DataGrid>
|
||||
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838">
|
||||
<TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px"
|
||||
Text="{Binding ElementName=ModList, Path=SelectedItem.Description}">
|
||||
</TextBlock>
|
||||
</ScrollViewer>
|
||||
<Grid Grid.Row="2" Margin="0 0 0 6px">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition MinHeight="40px"/>
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center"
|
||||
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="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
|
||||
<TextBox Name="AddModIDTextBox" Grid.Column="1" VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/>
|
||||
<Button Content="Add" Grid.Column="2" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/>
|
||||
<Button Content="Remove" Grid.Column="4" Margin="6px 0" Width="60px" Height="40px" Click="RemoveBtn_OnClick" Grid.Row="1"
|
||||
IsEnabled="{Binding ElementName=ModList, Path=SelectedItems.Count}"/>
|
||||
</Grid>
|
||||
|
||||
<Button Content="Save Config" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/>
|
||||
</Grid>
|
||||
</UserControl>
|
248
Torch.Server/Views/ModListControl.xaml.cs
Normal file
248
Torch.Server/Views/ModListControl.xaml.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
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 Torch.Server.Managers;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Server.ViewModels;
|
||||
using Torch.Server.Annotations;
|
||||
using Torch.Collections;
|
||||
|
||||
namespace Torch.Server.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ModListControl.xaml
|
||||
/// </summary>
|
||||
public partial class ModListControl : UserControl, INotifyPropertyChanged
|
||||
{
|
||||
private static Logger Log = LogManager.GetLogger(nameof(ModListControl));
|
||||
private InstanceManager _instanceManager;
|
||||
ModItemInfo _draggedMod;
|
||||
bool _hasOrderChanged = false;
|
||||
bool _isSortedByLoadOrder = true;
|
||||
|
||||
//private List<BindingExpression> _bindingExpressions = new List<BindingExpression>();
|
||||
/// <summary>
|
||||
/// Constructor for ModListControl
|
||||
/// </summary>
|
||||
public ModListControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
|
||||
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
|
||||
//var mods = _instanceManager.DedicatedConfig?.Mods;
|
||||
//if( mods != null)
|
||||
// DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>();
|
||||
DataContext = _instanceManager.DedicatedConfig?.Mods;
|
||||
|
||||
// Gets called once all children are loaded
|
||||
//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)
|
||||
{
|
||||
Log.Info("Instance loaded.");
|
||||
Dispatcher.Invoke(() => {
|
||||
DataContext = obj?.Mods ?? new MtObservableList<ModItemInfo>();
|
||||
UpdateLayout();
|
||||
((MtObservableList<ModItemInfo>)DataContext).CollectionChanged += OnModlistUpdate;
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_instanceManager.SaveConfig();
|
||||
}
|
||||
|
||||
|
||||
private void AddBtn_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (TryExtractId(AddModIDTextBox.Text, out ulong id))
|
||||
{
|
||||
var mod = new ModItemInfo(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||
//mod.PublishedFileId = id;
|
||||
_instanceManager.DedicatedConfig.Mods.Add(mod);
|
||||
Task.Run(mod.UpdateModInfoAsync)
|
||||
.ContinueWith((t) =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
_instanceManager.DedicatedConfig.Save();
|
||||
});
|
||||
});
|
||||
AddModIDTextBox.Text = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModIDTextBox.BorderBrush = Brushes.Red;
|
||||
Log.Warn("Invalid mod id!");
|
||||
MessageBox.Show("Invalid mod id!");
|
||||
}
|
||||
}
|
||||
private void RemoveBtn_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var modList = ((MtObservableList<ModItemInfo>)DataContext);
|
||||
if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod))
|
||||
modList.Remove(mod);
|
||||
}
|
||||
|
||||
private bool TryExtractId(string input, out ulong result)
|
||||
{
|
||||
var match = Regex.Match(input, @"(?<=id=)\d+").Value;
|
||||
|
||||
bool success;
|
||||
if (string.IsNullOrEmpty(match))
|
||||
success = ulong.TryParse(input, out result);
|
||||
else
|
||||
success = ulong.TryParse(match, out result);
|
||||
|
||||
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(_draggedMod), modList.IndexOf(targetMod));
|
||||
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)
|
||||
{
|
||||
if (_draggedMod != null)
|
||||
ModList.SelectedItem = _draggedMod;
|
||||
else if( e.AddedCells.Count > 0)
|
||||
ModList.SelectedItem = e.AddedCells[0].Item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,5 +18,7 @@
|
||||
</Style>
|
||||
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
|
||||
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
|
||||
<converters:ModToListIdConverter x:Key="ModToListIdConverter"/>
|
||||
<converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/>
|
||||
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
|
||||
</ResourceDictionary>
|
@@ -80,6 +80,9 @@
|
||||
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding CanRun}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mods">
|
||||
<views:ModListControl/>
|
||||
</TabItem>
|
||||
<TabItem Header="Chat/Players">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
@@ -2,7 +2,12 @@
|
||||
<packages>
|
||||
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
||||
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.4.0" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
<package id="protobuf-net" version="2.1.0" targetFramework="net461" />
|
||||
<package id="SteamKit2" version="2.1.0" targetFramework="net461" />
|
||||
<package id="System.Security.AccessControl" version="4.4.0" targetFramework="net461" />
|
||||
<package id="System.Security.Principal.Windows" version="4.4.0" targetFramework="net461" />
|
||||
</packages>
|
@@ -55,10 +55,8 @@ Global
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.Build.0 = Debug|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.Build.0 = Release|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
|
||||
@@ -79,10 +77,8 @@ Global
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.Build.0 = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.ActiveCfg = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
@@ -123,10 +124,10 @@ namespace Torch.Collections
|
||||
private readonly Timer _flushEventQueue;
|
||||
private const int _eventRaiseDelay = 50;
|
||||
|
||||
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
|
||||
new Queue<NotifyCollectionChangedEventArgs>();
|
||||
private readonly ConcurrentQueue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
|
||||
new ConcurrentQueue<NotifyCollectionChangedEventArgs>();
|
||||
|
||||
private readonly Queue<string> _propertyEventQueue = new Queue<string>();
|
||||
private readonly ConcurrentQueue<string> _propertyEventQueue = new ConcurrentQueue<string>();
|
||||
|
||||
private void FlushEventQueue(object data)
|
||||
{
|
||||
@@ -137,7 +138,8 @@ namespace Torch.Collections
|
||||
// :/, but works better
|
||||
bool reset = _collectionEventQueue.Count > 0;
|
||||
if (reset)
|
||||
_collectionEventQueue.Clear();
|
||||
while (_collectionEventQueue.Count > 0)
|
||||
_collectionEventQueue.TryDequeue(out _);
|
||||
else
|
||||
while (_collectionEventQueue.TryDequeue(out NotifyCollectionChangedEventArgs e))
|
||||
_collectionChangedEvent.Raise(this, e);
|
||||
|
@@ -47,6 +47,9 @@
|
||||
<Reference Include="MahApps.Metro, Version=1.6.1.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Win32.Registry, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Win32.Registry.4.4.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\Newtonsoft.Json.dll</HintPath>
|
||||
@@ -84,6 +87,9 @@
|
||||
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="SteamKit2, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SteamKit2.2.1.0\lib\netstandard2.0\SteamKit2.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Steamworks.NET">
|
||||
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -92,6 +98,12 @@
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Security.AccessControl, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.AccessControl.4.4.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Principal.Windows, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Principal.Windows.4.4.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@@ -233,6 +245,7 @@
|
||||
<Compile Include="Managers\UpdateManager.cs" />
|
||||
<Compile Include="Persistent.cs" />
|
||||
<Compile Include="Plugins\PluginManifest.cs" />
|
||||
<Compile Include="Utils\SteamWorkshopTools\KeyValueExtensions.cs" />
|
||||
<Compile Include="Utils\MiscExtensions.cs" />
|
||||
<Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" />
|
||||
<Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" />
|
||||
@@ -247,6 +260,7 @@
|
||||
<Compile Include="Utils\Reflected\ReflectedStaticMethodAttribute.cs" />
|
||||
<Compile Include="Utils\Reflection.cs" />
|
||||
<Compile Include="Managers\ScriptingManager.cs" />
|
||||
<Compile Include="Utils\SteamWorkshopTools\WebAPI.cs" />
|
||||
<Compile Include="Utils\StringUtils.cs" />
|
||||
<Compile Include="Utils\SynchronizationExtensions.cs" />
|
||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||
@@ -256,6 +270,7 @@
|
||||
<Compile Include="TorchPluginBase.cs" />
|
||||
<Compile Include="Session\TorchSession.cs" />
|
||||
<Compile Include="Utils\TorchLauncher.cs" />
|
||||
<Compile Include="Utils\SteamWorkshopTools\PublishedItemDetails.cs" />
|
||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||
<Compile Include="Extensions\StringExtensions.cs" />
|
||||
|
48
Torch/Utils/SteamWorkshopTools/KeyValueExtensions.cs
Normal file
48
Torch/Utils/SteamWorkshopTools/KeyValueExtensions.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using SteamKit2;
|
||||
|
||||
namespace Torch.Utils.SteamWorkshopTools
|
||||
{
|
||||
public static class KeyValueExtensions
|
||||
{
|
||||
private static Logger Log = LogManager.GetLogger("SteamWorkshopService");
|
||||
|
||||
public static T GetValueOrDefault<T>(this KeyValue kv, string key)
|
||||
{
|
||||
kv.TryGetValueOrDefault<T>(key, out T result);
|
||||
return result;
|
||||
}
|
||||
public static bool TryGetValueOrDefault<T>(this KeyValue kv, string key, out T typedValue)
|
||||
{
|
||||
var match = kv.Children?.Find((KeyValue item) => item.Name == key);
|
||||
object result = default(T);
|
||||
if (match == null)
|
||||
{
|
||||
typedValue = (T) result;
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = match.Value ?? "";
|
||||
|
||||
try
|
||||
{
|
||||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
result = converter.ConvertFromString(value);
|
||||
typedValue = (T)result;
|
||||
return true;
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
throw new Exception($"Unexpected Type '{typeof(T)}'!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
Torch/Utils/SteamWorkshopTools/PublishedItemDetails.cs
Normal file
26
Torch/Utils/SteamWorkshopTools/PublishedItemDetails.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Utils.SteamWorkshopTools
|
||||
{
|
||||
public class PublishedItemDetails
|
||||
{
|
||||
public ulong PublishedFileId;
|
||||
public uint Views;
|
||||
public uint Subscriptions;
|
||||
public DateTime TimeUpdated;
|
||||
public DateTime TimeCreated;
|
||||
public string Description;
|
||||
public string Title;
|
||||
public string FileUrl;
|
||||
public long FileSize;
|
||||
public string FileName;
|
||||
public ulong ConsumerAppId;
|
||||
public ulong CreatorAppId;
|
||||
public ulong Creator;
|
||||
public string[] Tags;
|
||||
}
|
||||
}
|
291
Torch/Utils/SteamWorkshopTools/WebAPI.cs
Normal file
291
Torch/Utils/SteamWorkshopTools/WebAPI.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using SteamKit2;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Torch.Utils.SteamWorkshopTools
|
||||
{
|
||||
public class WebAPI
|
||||
{
|
||||
private static Logger Log = LogManager.GetLogger("SteamWorkshopService");
|
||||
public const uint AppID = 244850U;
|
||||
public string Username { get; private set; }
|
||||
private string password;
|
||||
public bool IsReady { get; private set; }
|
||||
public bool IsRunning { get; private set; }
|
||||
private TaskCompletionSource<bool> logonTaskCompletionSource;
|
||||
|
||||
private SteamClient steamClient;
|
||||
private CallbackManager cbManager;
|
||||
private SteamUser steamUser;
|
||||
|
||||
private static WebAPI _instance;
|
||||
public static WebAPI Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance ?? (_instance = new WebAPI());
|
||||
}
|
||||
}
|
||||
|
||||
private WebAPI()
|
||||
{
|
||||
steamClient = new SteamClient();
|
||||
cbManager = new CallbackManager(steamClient);
|
||||
|
||||
IsRunning = true;
|
||||
}
|
||||
|
||||
public async Task<bool> Logon(string user = "anonymous", string pw = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(user))
|
||||
throw new ArgumentNullException("User can't be null!");
|
||||
if (!user.Equals("anonymous") && !pw.Equals(""))
|
||||
throw new ArgumentNullException("Password can't be null if user is not anonymous!");
|
||||
|
||||
Username = user;
|
||||
password = pw;
|
||||
|
||||
logonTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
|
||||
steamUser = steamClient.GetHandler<SteamUser>();
|
||||
cbManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
|
||||
cbManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);
|
||||
cbManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
|
||||
cbManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
|
||||
|
||||
Log.Info("Connecting to Steam...");
|
||||
|
||||
steamClient.Connect();
|
||||
|
||||
await logonTaskCompletionSource.Task;
|
||||
return logonTaskCompletionSource.Task.Result;
|
||||
}
|
||||
|
||||
public void CancelLogon()
|
||||
{
|
||||
logonTaskCompletionSource?.SetCanceled();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<ulong, PublishedItemDetails>> GetPublishedFileDetails(IEnumerable<ulong> workshopIds)
|
||||
{
|
||||
//if (!IsReady)
|
||||
// throw new Exception("SteamWorkshopService not initialized!");
|
||||
|
||||
using (dynamic remoteStorage = SteamKit2.WebAPI.GetInterface("ISteamRemoteStorage"))
|
||||
{
|
||||
KeyValue allFilesDetails = null ;
|
||||
remoteStorage.Timeout = TimeSpan.FromSeconds(30);
|
||||
allFilesDetails = await Task.Run(delegate {
|
||||
try
|
||||
{
|
||||
return remoteStorage.GetPublishedFileDetails1(
|
||||
itemcount: workshopIds.Count(),
|
||||
publishedfileids: workshopIds,
|
||||
method: HttpMethod.Post);
|
||||
// var ifaceArgs = new Dictionary<string, string>();
|
||||
// ifaceArgs["itemcount"] = workshopIds.Count().ToString();
|
||||
// no idea if that formatting is correct - in fact I get a 404 response
|
||||
// ifaceArgs["publishedfileids"] = string.Join(",", workshopIds);
|
||||
// return remoteStorage.Call(HttpMethod.Post, "GetPublishedFileDetails", args: ifaceArgs);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Log.Error($"Fetching File Details failed: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (allFilesDetails == null)
|
||||
return null;
|
||||
//fileDetails = remoteStorage.Call(HttpMethod.Post, "GetPublishedFileDetails", 1, new Dictionary<string, string>() { { "itemcount", workshopIds.Count().ToString() }, { "publishedfileids", workshopIds.ToString() } });
|
||||
var detailsList = allFilesDetails?.Children.Find((KeyValue kv) => kv.Name == "publishedfiledetails")?.Children;
|
||||
var resultCount = allFilesDetails?.GetValueOrDefault<int>("resultcount");
|
||||
if( detailsList == null || resultCount == null)
|
||||
{
|
||||
Log.Error("Received invalid data: ");
|
||||
#if DEBUG
|
||||
if(allFilesDetails != null)
|
||||
PrintKeyValue(allFilesDetails);
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
if ( detailsList.Count != workshopIds.Count() || resultCount != workshopIds.Count())
|
||||
{
|
||||
Log.Error($"Received unexpected number of fileDetails. Expected: {workshopIds.Count()}, Received: {resultCount}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new Dictionary<ulong, PublishedItemDetails>();
|
||||
for( int i = 0; i < resultCount; i++ )
|
||||
{
|
||||
var fileDetails = detailsList[i];
|
||||
|
||||
var tagContainer = fileDetails.Children.Find(item => item.Name == "tags");
|
||||
List<string> tags = new List<string>();
|
||||
if (tagContainer != null)
|
||||
foreach (var tagKv in tagContainer.Children)
|
||||
{
|
||||
var tag = tagKv.Children.Find(item => item.Name == "tag")?.Value;
|
||||
if( tag != null)
|
||||
tags.Add(tag);
|
||||
}
|
||||
|
||||
var publishedFileId = fileDetails.GetValueOrDefault<ulong>("publishedfileid");
|
||||
result[publishedFileId] = new PublishedItemDetails()
|
||||
{
|
||||
PublishedFileId = publishedFileId,
|
||||
Views = fileDetails.GetValueOrDefault<uint>("views"),
|
||||
Subscriptions = fileDetails.GetValueOrDefault<uint>("subscriptions"),
|
||||
TimeUpdated = DateTimeOffset.FromUnixTimeSeconds(fileDetails.GetValueOrDefault<long>("time_updated")).DateTime,
|
||||
TimeCreated = DateTimeOffset.FromUnixTimeSeconds(fileDetails.GetValueOrDefault<long>("time_created")).DateTime,
|
||||
Description = fileDetails.GetValueOrDefault<string>("description"),
|
||||
Title = fileDetails.GetValueOrDefault<string>("title"),
|
||||
FileUrl = fileDetails.GetValueOrDefault<string>("file_url"),
|
||||
FileSize = fileDetails.GetValueOrDefault<long>("file_size"),
|
||||
FileName = fileDetails.GetValueOrDefault<string>("filename"),
|
||||
ConsumerAppId = fileDetails.GetValueOrDefault<ulong>("consumer_app_id"),
|
||||
CreatorAppId = fileDetails.GetValueOrDefault<ulong>("creator_app_id"),
|
||||
Creator = fileDetails.GetValueOrDefault<ulong>("creator"),
|
||||
Tags = tags.ToArray()
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Space Engineers has transitioned to Steam's UGC api, therefore this method might not always work!")]
|
||||
public async Task DownloadPublishedFile(PublishedItemDetails fileDetails, string dir, string name = null)
|
||||
{
|
||||
var fullPath = Path.Combine(dir, name);
|
||||
if (name == null)
|
||||
name = fileDetails.FileName;
|
||||
var expectedSize = (fileDetails.FileSize == 0) ? -1 : fileDetails.FileSize;
|
||||
|
||||
using (var client = new WebClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
var downloadTask = client.DownloadFileTaskAsync(fileDetails.FileUrl, Path.Combine(dir, name));
|
||||
DateTime start = DateTime.Now;
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
if (downloadTask.IsCompleted)
|
||||
break;
|
||||
}
|
||||
if ( !downloadTask.IsCompleted )
|
||||
{
|
||||
client.CancelAsync();
|
||||
throw new Exception("Timeout while attempting to downloading published workshop item!");
|
||||
}
|
||||
//var text = await client.DownloadStringTaskAsync(url);
|
||||
//File.WriteAllText(fullPath, text);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failed to download workshop item! /n" +
|
||||
$"{e.Message} - url: {fileDetails.FileUrl}, path: {Path.Combine(dir, name)}");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Printable
|
||||
{
|
||||
public KeyValue Data;
|
||||
public int Offset;
|
||||
|
||||
public void Print()
|
||||
{
|
||||
Log.Info($"{new string(' ', Offset)}{Data.Name}: {Data.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintKeyValue(KeyValue data)
|
||||
{
|
||||
|
||||
var dataSet = new Stack<Printable>();
|
||||
dataSet.Push(new Printable()
|
||||
{
|
||||
Data = data,
|
||||
Offset = 0
|
||||
});
|
||||
while (dataSet.Count != 0)
|
||||
{
|
||||
var printable = dataSet.Pop();
|
||||
foreach (var child in printable.Data.Children)
|
||||
dataSet.Push(new Printable()
|
||||
{
|
||||
Data = child,
|
||||
Offset = printable.Offset + 2
|
||||
});
|
||||
printable.Print();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region CALLBACKS
|
||||
private void OnConnected( SteamClient.ConnectedCallback callback)
|
||||
{
|
||||
Log.Info("Connected to Steam! Logging in '{0}'...", Username);
|
||||
if( Username == "anonymous" )
|
||||
steamUser.LogOnAnonymous();
|
||||
else
|
||||
steamUser.LogOn(new SteamUser.LogOnDetails
|
||||
{
|
||||
Username = Username,
|
||||
Password = password
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDisconnected( SteamClient.DisconnectedCallback callback )
|
||||
{
|
||||
Log.Info("Disconnected from Steam");
|
||||
IsReady = false;
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
private void OnLoggedOn( SteamUser.LoggedOnCallback callback )
|
||||
{
|
||||
if( callback.Result != EResult.OK )
|
||||
{
|
||||
string msg;
|
||||
if( callback.Result == EResult.AccountLogonDenied )
|
||||
{
|
||||
msg = "Unable to logon to Steam: This account is Steamguard protected.";
|
||||
Log.Warn(msg);
|
||||
logonTaskCompletionSource.SetException(new Exception(msg));
|
||||
IsRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
msg = $"Unable to logon to Steam: {callback.Result} / {callback.ExtendedResult}";
|
||||
Log.Warn(msg);
|
||||
logonTaskCompletionSource.SetException(new Exception(msg));
|
||||
IsRunning = false;
|
||||
return;
|
||||
}
|
||||
IsReady = true;
|
||||
Log.Info("Successfully logged on!");
|
||||
logonTaskCompletionSource.SetResult(true);
|
||||
}
|
||||
|
||||
private void OnLoggedOff( SteamUser.LoggedOffCallback callback )
|
||||
{
|
||||
IsReady = false;
|
||||
Log.Info($"Logged off of Steam: {callback.Result}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
@@ -2,7 +2,12 @@
|
||||
<packages>
|
||||
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
||||
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.4.0" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
<package id="Octokit" version="0.24.0" targetFramework="net461" />
|
||||
<package id="protobuf-net" version="2.1.0" targetFramework="net461" />
|
||||
<package id="SteamKit2" version="2.1.0" targetFramework="net461" />
|
||||
<package id="System.Security.AccessControl" version="4.4.0" targetFramework="net461" />
|
||||
<package id="System.Security.Principal.Windows" version="4.4.0" targetFramework="net461" />
|
||||
</packages>
|
Reference in New Issue
Block a user