Refactor, fix chat scroll, rework automatic update system, remove manual install method, add documentation
This commit is contained in:
@@ -8,32 +8,99 @@ using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.API
|
||||
{
|
||||
/// <summary>
|
||||
/// API for Torch functions shared between client and server.
|
||||
/// </summary>
|
||||
public interface ITorchBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when the session begins loading.
|
||||
/// </summary>
|
||||
event Action SessionLoading;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the session finishes loading.
|
||||
/// </summary>
|
||||
event Action SessionLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the session begins unloading.
|
||||
/// </summary>
|
||||
event Action SessionUnloading;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the session finishes unloading.
|
||||
/// </summary>
|
||||
event Action SessionUnloaded;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the current instance.
|
||||
/// </summary>
|
||||
ITorchConfig Config { get; }
|
||||
|
||||
/// <inheritdoc cref="IMultiplayerManager"/>
|
||||
IMultiplayerManager Multiplayer { get; }
|
||||
|
||||
/// <inheritdoc cref="IPluginManager"/>
|
||||
IPluginManager Plugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The binary version of the current instance.
|
||||
/// </summary>
|
||||
Version TorchVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoke an action on the game thread.
|
||||
/// </summary>
|
||||
void Invoke(Action action);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke an action on the game thread and block until it has completed.
|
||||
/// If this is called on the game thread the action will execute immediately.
|
||||
/// </summary>
|
||||
void InvokeBlocking(Action action);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke an action on the game thread asynchronously.
|
||||
/// </summary>
|
||||
Task InvokeAsync(Action action);
|
||||
string[] RunArgs { get; set; }
|
||||
bool IsOnGameThread();
|
||||
|
||||
/// <summary>
|
||||
/// Start the Torch instance.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Torch instance.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Torch instance.
|
||||
/// </summary>
|
||||
void Init();
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="IManager"/> that is part of the Torch instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Manager type</typeparam>
|
||||
T GetManager<T>() where T : class, IManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API for the Torch server.
|
||||
/// </summary>
|
||||
public interface ITorchServer : ITorchBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Path of the dedicated instance folder.
|
||||
/// </summary>
|
||||
string InstancePath { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API for the Torch client.
|
||||
/// </summary>
|
||||
public interface ITorchClient : ITorchBase
|
||||
{
|
||||
|
||||
|
@@ -1,14 +1,47 @@
|
||||
namespace Torch
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public interface ITorchConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// (server) Name of the instance.
|
||||
/// </summary>
|
||||
string InstanceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// (server) Dedicated instance path.
|
||||
/// </summary>
|
||||
string InstancePath { get; set; }
|
||||
bool RedownloadPlugins { get; set; }
|
||||
bool AutomaticUpdates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable automatic Torch updates.
|
||||
/// </summary>
|
||||
bool GetTorchUpdates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable automatic Torch updates.
|
||||
/// </summary>
|
||||
bool GetPluginUpdates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Restart Torch automatically if it crashes.
|
||||
/// </summary>
|
||||
bool RestartOnCrash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time-out in seconds for the Torch watchdog (to detect a hung session).
|
||||
/// </summary>
|
||||
int TickTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of plugins that should be installed.
|
||||
/// </summary>
|
||||
List<string> Plugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Saves the config.
|
||||
/// </summary>
|
||||
bool Save(string path = null);
|
||||
}
|
||||
}
|
@@ -6,8 +6,14 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for Torch managers.
|
||||
/// </summary>
|
||||
public interface IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the manager. Called after Torch is initialized.
|
||||
/// </summary>
|
||||
void Init();
|
||||
}
|
||||
}
|
||||
|
@@ -6,17 +6,56 @@ using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for received messages.
|
||||
/// </summary>
|
||||
/// <param name="message">Message data.</param>
|
||||
/// <param name="sendToOthers">Flag to broadcast message to other players.</param>
|
||||
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
|
||||
|
||||
/// <summary>
|
||||
/// API for multiplayer related functions.
|
||||
/// </summary>
|
||||
public interface IMultiplayerManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when a player joins.
|
||||
/// </summary>
|
||||
event Action<IPlayer> PlayerJoined;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a player disconnects.
|
||||
/// </summary>
|
||||
event Action<IPlayer> PlayerLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a chat message is received.
|
||||
/// </summary>
|
||||
event MessageReceivedDel MessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message to all or one specific player.
|
||||
/// </summary>
|
||||
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
||||
|
||||
/// <summary>
|
||||
/// Kicks the player from the game.
|
||||
/// </summary>
|
||||
void KickPlayer(ulong steamId);
|
||||
|
||||
/// <summary>
|
||||
/// Bans or unbans a player from the game.
|
||||
/// </summary>
|
||||
void BanPlayer(ulong steamId, bool banned = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
||||
/// </summary>
|
||||
IMyPlayer GetPlayerBySteamId(ulong id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a player by their display name or returns null if the player isn't found.
|
||||
/// </summary>
|
||||
IMyPlayer GetPlayerByName(string name);
|
||||
}
|
||||
}
|
@@ -9,14 +9,30 @@ using VRage.Network;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// API for the network intercept.
|
||||
/// </summary>
|
||||
public interface INetworkManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Register a network handler.
|
||||
/// </summary>
|
||||
void RegisterNetworkHandler(INetworkHandler handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for multiplayer network messages.
|
||||
/// </summary>
|
||||
public interface INetworkHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if the handler can process the call site.
|
||||
/// </summary>
|
||||
bool CanHandle(CallSite callSite);
|
||||
|
||||
/// <summary>
|
||||
/// Processes a network message.
|
||||
/// </summary>
|
||||
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,29 @@ using VRage.Plugins;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// API for the Torch plugin manager.
|
||||
/// </summary>
|
||||
public interface IPluginManager : IManager, IEnumerable<ITorchPlugin>
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when plugins are loaded.
|
||||
/// </summary>
|
||||
event Action<List<ITorchPlugin>> PluginsLoaded;
|
||||
List<ITorchPlugin> Plugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of loaded plugins.
|
||||
/// </summary>
|
||||
ObservableCollection<ITorchPlugin> Plugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates all loaded plugins.
|
||||
/// </summary>
|
||||
void UpdatePlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all loaded plugins.
|
||||
/// </summary>
|
||||
void DisposePlugins();
|
||||
}
|
||||
}
|
@@ -6,11 +6,29 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to indicate the state of the dedicated server.
|
||||
/// </summary>
|
||||
public enum ServerState
|
||||
{
|
||||
/// <summary>
|
||||
/// The server is not running.
|
||||
/// </summary>
|
||||
Stopped,
|
||||
|
||||
/// <summary>
|
||||
/// The server is starting/loading the session.
|
||||
/// </summary>
|
||||
Starting,
|
||||
|
||||
/// <summary>
|
||||
/// The server is running.
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// The server encountered an error.
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.182.329")]
|
||||
[assembly: AssemblyFileVersion("1.0.182.329")]
|
||||
[assembly: AssemblyVersion("1.0.186.642")]
|
||||
[assembly: AssemblyFileVersion("1.0.186.642")]
|
@@ -45,6 +45,10 @@
|
||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Sandbox.Game">
|
||||
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
@@ -9,6 +9,7 @@ using Sandbox;
|
||||
using Sandbox.Engine.Platform;
|
||||
using Sandbox.Engine.Utils;
|
||||
using Sandbox.Game;
|
||||
using Sandbox.ModAPI;
|
||||
using SpaceEngineers.Game;
|
||||
using Torch.API;
|
||||
using VRage.FileSystem;
|
||||
|
@@ -34,8 +34,8 @@ namespace Torch.Server
|
||||
private static ITorchServer _server;
|
||||
private static Logger _log = LogManager.GetLogger("Torch");
|
||||
private static bool _restartOnCrash;
|
||||
public static bool IsManualInstall;
|
||||
private static TorchCli _cli;
|
||||
private static TorchConfig _config;
|
||||
private static bool _steamCmdDone;
|
||||
|
||||
/// <summary>
|
||||
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
|
||||
@@ -46,10 +46,10 @@ namespace Torch.Server
|
||||
//Ensures that all the files are downloaded in the Torch directory.
|
||||
Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString());
|
||||
|
||||
IsManualInstall = File.Exists("SpaceEngineersDedicated.exe");
|
||||
if (!IsManualInstall)
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.old"))
|
||||
File.Delete(file);
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
|
||||
if (!Environment.UserInteractive)
|
||||
@@ -63,50 +63,40 @@ namespace Torch.Server
|
||||
|
||||
var configName = "TorchConfig.xml";
|
||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
||||
TorchConfig options;
|
||||
if (File.Exists(configName))
|
||||
{
|
||||
_log.Info($"Loading config {configPath}");
|
||||
options = TorchConfig.LoadFrom(configPath);
|
||||
_config = TorchConfig.LoadFrom(configPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info($"Generating default config at {configPath}");
|
||||
options = new TorchConfig();
|
||||
|
||||
if (!IsManualInstall)
|
||||
{
|
||||
//new ConfigManager().CreateInstance("Instance");
|
||||
options.InstancePath = Path.GetFullPath("Instance");
|
||||
_config = new TorchConfig {InstancePath = Path.GetFullPath("Instance")};
|
||||
|
||||
_log.Warn("Would you like to enable automatic updates? (Y/n):");
|
||||
|
||||
var input = Console.ReadLine() ?? "";
|
||||
var autoUpdate = !input.Equals("n", StringComparison.InvariantCultureIgnoreCase);
|
||||
options.AutomaticUpdates = autoUpdate;
|
||||
var autoUpdate = string.IsNullOrEmpty(input) || input.Equals("y", StringComparison.InvariantCultureIgnoreCase);
|
||||
_config.GetTorchUpdates = _config.GetPluginUpdates = autoUpdate;
|
||||
if (autoUpdate)
|
||||
{
|
||||
_log.Info("Automatic updates enabled, updating server.");
|
||||
_log.Info("Automatic updates enabled.");
|
||||
RunSteamCmd();
|
||||
}
|
||||
|
||||
_config.Save(configPath);
|
||||
}
|
||||
|
||||
//var setupDialog = new FirstTimeSetup { DataContext = options };
|
||||
//setupDialog.ShowDialog();
|
||||
options.Save(configPath);
|
||||
}
|
||||
|
||||
_cli = new TorchCli { Config = options };
|
||||
if (!_cli.Parse(args))
|
||||
if (!_config.Parse(args))
|
||||
return;
|
||||
|
||||
_log.Debug(_cli.ToString());
|
||||
_log.Debug(_config.ToString());
|
||||
|
||||
if (!string.IsNullOrEmpty(_cli.WaitForPID))
|
||||
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
||||
{
|
||||
try
|
||||
{
|
||||
var pid = int.Parse(_cli.WaitForPID);
|
||||
var pid = int.Parse(_config.WaitForPID);
|
||||
var waitProc = Process.GetProcessById(pid);
|
||||
_log.Warn($"Waiting for process {pid} to exit.");
|
||||
waitProc.WaitForExit();
|
||||
@@ -117,18 +107,13 @@ namespace Torch.Server
|
||||
}
|
||||
}
|
||||
|
||||
_restartOnCrash = _cli.RestartOnCrash;
|
||||
_restartOnCrash = _config.RestartOnCrash;
|
||||
|
||||
if (options.AutomaticUpdates || _cli.Update)
|
||||
{
|
||||
if (IsManualInstall)
|
||||
_log.Warn("Detected manual install, won't attempt to update DS");
|
||||
else
|
||||
if (_config.GetTorchUpdates || _config.Update)
|
||||
{
|
||||
RunSteamCmd();
|
||||
}
|
||||
}
|
||||
RunServer(options, _cli);
|
||||
RunServer(_config);
|
||||
}
|
||||
|
||||
private const string STEAMCMD_DIR = "steamcmd";
|
||||
@@ -142,6 +127,9 @@ quit";
|
||||
|
||||
public static void RunSteamCmd()
|
||||
{
|
||||
if (_steamCmdDone)
|
||||
return;
|
||||
|
||||
var log = LogManager.GetLogger("SteamCMD");
|
||||
|
||||
if (!Directory.Exists(STEAMCMD_DIR))
|
||||
@@ -187,38 +175,40 @@ quit";
|
||||
log.Info(cmd.StandardOutput.ReadLine());
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
_steamCmdDone = true;
|
||||
}
|
||||
|
||||
public static void RunServer(TorchConfig options, TorchCli cli)
|
||||
public static void RunServer(TorchConfig config)
|
||||
{
|
||||
|
||||
|
||||
/*
|
||||
if (!parser.ParseArguments(args, options))
|
||||
if (!parser.ParseArguments(args, config))
|
||||
{
|
||||
_log.Error($"Parsing arguments failed: {string.Join(" ", args)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(options.Config) && File.Exists(options.Config))
|
||||
if (!string.IsNullOrEmpty(config.Config) && File.Exists(config.Config))
|
||||
{
|
||||
options = ServerConfig.LoadFrom(options.Config);
|
||||
parser.ParseArguments(args, options);
|
||||
config = ServerConfig.LoadFrom(config.Config);
|
||||
parser.ParseArguments(args, config);
|
||||
}*/
|
||||
|
||||
//RestartOnCrash autostart autosave=15
|
||||
//gamepath ="C:\Program Files\Space Engineers DS" instance="Hydro Survival" instancepath="C:\ProgramData\SpaceEngineersDedicated\Hydro Survival"
|
||||
|
||||
/*
|
||||
if (options.InstallService)
|
||||
if (config.InstallService)
|
||||
{
|
||||
var serviceName = $"\"Torch - {options.InstanceName}\"";
|
||||
var serviceName = $"\"Torch - {config.InstanceName}\"";
|
||||
// Working on installing the service properly instead of with sc.exe
|
||||
_log.Info($"Installing service '{serviceName}");
|
||||
var exePath = $"\"{Assembly.GetExecutingAssembly().Location}\"";
|
||||
var createInfo = new ServiceCreateInfo
|
||||
{
|
||||
Name = options.InstanceName,
|
||||
Name = config.InstanceName,
|
||||
BinaryPath = exePath,
|
||||
};
|
||||
_log.Info("Service Installed");
|
||||
@@ -238,7 +228,7 @@ quit";
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.UninstallService)
|
||||
if (config.UninstallService)
|
||||
{
|
||||
_log.Info("Uninstalling Torch service");
|
||||
var startInfo = new ProcessStartInfo
|
||||
@@ -254,18 +244,18 @@ quit";
|
||||
return;
|
||||
}*/
|
||||
|
||||
_server = new TorchServer(options);
|
||||
_server = new TorchServer(config);
|
||||
_server.Init();
|
||||
|
||||
if (cli.NoGui || cli.Autostart)
|
||||
if (config.NoGui || config.Autostart)
|
||||
{
|
||||
new Thread(() => _server.Start()).Start();
|
||||
}
|
||||
|
||||
if (!cli.NoGui)
|
||||
if (!config.NoGui)
|
||||
{
|
||||
var ui = new TorchUI((TorchServer)_server);
|
||||
ui.LoadConfig(options);
|
||||
ui.LoadConfig(config);
|
||||
ui.ShowDialog();
|
||||
}
|
||||
}
|
||||
@@ -295,17 +285,9 @@ quit";
|
||||
Thread.Sleep(5000);
|
||||
if (_restartOnCrash)
|
||||
{
|
||||
/* Throws an exception somehow and I'm too lazy to debug it.
|
||||
try
|
||||
{
|
||||
if (MySession.Static != null && MySession.Static.AutoSaveInMinutes > 0)
|
||||
MySession.Static.Save();
|
||||
}
|
||||
catch { }*/
|
||||
|
||||
var exe = typeof(Program).Assembly.Location;
|
||||
_cli.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||
Process.Start(exe, _cli.ToString());
|
||||
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||
Process.Start(exe, _config.ToString());
|
||||
}
|
||||
//1627 = Function failed during execution.
|
||||
Environment.Exit(1627);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.182.329")]
|
||||
[assembly: AssemblyFileVersion("1.0.182.329")]
|
||||
[assembly: AssemblyVersion("1.0.186.642")]
|
||||
[assembly: AssemblyFileVersion("1.0.186.642")]
|
@@ -184,7 +184,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Managers\ConfigManager.cs" />
|
||||
<Compile Include="TorchCli.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
|
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public class TorchCli : CommandLine
|
||||
{
|
||||
public TorchConfig Config { get; set; }
|
||||
|
||||
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
||||
public string InstancePath { get => Config.InstancePath; set => Config.InstancePath = value; }
|
||||
|
||||
[Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
||||
public bool NoUpdate { get => !Config.AutomaticUpdates; set => Config.AutomaticUpdates = !value; }
|
||||
|
||||
[Arg("update", "Manually check for and install updates.")]
|
||||
public bool Update { get; set; }
|
||||
|
||||
//TODO: backend code for this
|
||||
//[Arg("worldpath", "Path to the game world folder to load.")]
|
||||
public string WorldPath { get; set; }
|
||||
|
||||
[Arg("autostart", "Start the server immediately.")]
|
||||
public bool Autostart { get; set; }
|
||||
|
||||
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
|
||||
public bool RestartOnCrash { get => Config.RestartOnCrash; set => Config.RestartOnCrash = value; }
|
||||
|
||||
[Arg("nogui", "Do not show the Torch UI.")]
|
||||
public bool NoGui { get; set; }
|
||||
|
||||
[Arg("silent", "Do not show the Torch UI or the command line.")]
|
||||
public bool Silent { get; set; }
|
||||
|
||||
[Arg("waitforpid", "Makes Torch wait for another process to exit.")]
|
||||
public string WaitForPID { get; set; }
|
||||
}
|
||||
}
|
@@ -7,25 +7,54 @@ using NLog;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public class TorchConfig : ITorchConfig
|
||||
public class TorchConfig : CommandLine, ITorchConfig
|
||||
{
|
||||
private static Logger _log = LogManager.GetLogger("Config");
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
||||
public string InstancePath { get; set; }
|
||||
public string InstanceName { get; set; }
|
||||
#warning World Path not implemented
|
||||
public string WorldPath { get; set; }
|
||||
public bool AutomaticUpdates { get; set; } = true;
|
||||
public bool RedownloadPlugins { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
||||
public bool NoUpdate { get => false; set => GetTorchUpdates = GetPluginUpdates = !value; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("update", "Manually check for and install updates.")]
|
||||
public bool Update { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("autostart", "Start the server immediately.")]
|
||||
public bool Autostart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
|
||||
public bool RestartOnCrash { get; set; }
|
||||
/// <summary>
|
||||
/// How long in seconds to wait before automatically resetting a frozen server.
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("nogui", "Do not show the Torch UI.")]
|
||||
public bool NoGui { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("waitforpid", "Makes Torch wait for another process to exit.")]
|
||||
public string WaitForPID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("instancename", "The name of the Torch instance.")]
|
||||
public string InstanceName { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetTorchUpdates { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetPluginUpdates { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int TickTimeout { get; set; } = 60;
|
||||
/// <summary>
|
||||
/// A list of plugins to install or update. TODO
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> Plugins { get; set; } = new List<string>();
|
||||
|
||||
internal Point WindowSize { get; set; } = new Point(800, 600);
|
||||
internal Point WindowPosition { get; set; } = new Point();
|
||||
[NonSerialized]
|
||||
|
@@ -16,6 +16,7 @@ using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.ModAPI;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.Managers;
|
||||
using VRage.Dedicated;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
@@ -37,7 +38,9 @@ namespace Torch.Server
|
||||
public Thread GameThread { get; private set; }
|
||||
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
|
||||
public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } }
|
||||
/// <inheritdoc />
|
||||
public string InstanceName => Config?.InstanceName;
|
||||
/// <inheritdoc />
|
||||
public string InstancePath => Config?.InstancePath;
|
||||
|
||||
private bool _isRunning;
|
||||
@@ -53,6 +56,7 @@ namespace Torch.Server
|
||||
MyFakes.ENABLE_INFINARIO = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -81,7 +85,7 @@ namespace Torch.Server
|
||||
RuntimeHelpers.RunClassConstructor(typeof(MyObjectBuilder_Base).TypeHandle);
|
||||
}
|
||||
|
||||
public void InvokeBeforeRun()
|
||||
private void InvokeBeforeRun()
|
||||
{
|
||||
|
||||
var contentPath = "Content";
|
||||
@@ -91,16 +95,7 @@ namespace Torch.Server
|
||||
Log.Debug("MyFileSystem already initialized");
|
||||
else
|
||||
{
|
||||
if (Program.IsManualInstall)
|
||||
{
|
||||
var rootPath = new FileInfo(MyFileSystem.ExePath).Directory.FullName;
|
||||
contentPath = Path.Combine(rootPath, "Content");
|
||||
}
|
||||
else
|
||||
{
|
||||
MyFileSystem.ExePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DedicatedServer64");
|
||||
}
|
||||
|
||||
MyFileSystem.ExePath = Path.Combine(GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
|
||||
MyFileSystem.Init(contentPath, InstancePath);
|
||||
}
|
||||
|
||||
@@ -132,14 +127,13 @@ namespace Torch.Server
|
||||
MySandboxGame.Config.Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start server on the current thread.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override void Start()
|
||||
{
|
||||
if (State != ServerState.Stopped)
|
||||
return;
|
||||
|
||||
IsRunning = true;
|
||||
GameThread = Thread.CurrentThread;
|
||||
Config.Save();
|
||||
State = ServerState.Starting;
|
||||
@@ -196,9 +190,7 @@ namespace Torch.Server
|
||||
Log.Debug("Server watchdog responded");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the server.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override void Stop()
|
||||
{
|
||||
if (State == ServerState.Stopped)
|
||||
@@ -220,6 +212,12 @@ namespace Torch.Server
|
||||
Log.Info("Server stopped.");
|
||||
_stopHandle.Set();
|
||||
State = ServerState.Stopped;
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -41,6 +42,17 @@ namespace Torch.Server
|
||||
_server = (TorchBase)server;
|
||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
||||
DataContext = _multiplayer;
|
||||
_multiplayer.ChatHistory.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
}
|
||||
|
||||
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (VisualTreeHelper.GetChildrenCount(ChatItems) > 0)
|
||||
{
|
||||
Border border = (Border)VisualTreeHelper.GetChild(ChatItems, 0);
|
||||
ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
|
||||
scrollViewer.ScrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
private void SendButton_Click(object sender, RoutedEventArgs e)
|
||||
|
@@ -5,14 +5,18 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Torch.Server"
|
||||
xmlns:views="clr-namespace:Torch.Server.Views"
|
||||
xmlns:converters="clr-namespace:Torch.Server.Views.Converters"
|
||||
mc:Ignorable="d"
|
||||
Title="Torch">
|
||||
<Window.Resources>
|
||||
<converters:InverseBooleanConverter x:Key="inverseBool"/>
|
||||
</Window.Resources>
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Margin="5,5,5,5" Orientation="Horizontal">
|
||||
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True" />
|
||||
HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True" IsEnabled="{Binding IsRunning, Converter={StaticResource inverseBool}}"/>
|
||||
<Button x:Name="BtnStop" Content="Stop" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left"
|
||||
Click="BtnStop_Click" IsEnabled="False" />
|
||||
Click="BtnStop_Click" IsEnabled="{Binding IsRunning}" />
|
||||
<Label>
|
||||
<Label.Content>
|
||||
<TextBlock Text="{Binding State, StringFormat=Status: {0}}"></TextBlock>
|
||||
@@ -37,13 +41,13 @@
|
||||
<TextBox x:Name="InstancePathBox" Margin="3" Height="20"
|
||||
TextChanged="InstancePathBox_OnTextChanged" IsEnabled="False" />
|
||||
</DockPanel>
|
||||
<views:ConfigControl x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" />
|
||||
<views:ConfigControl x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding IsRunning, Converter={StaticResource inverseBool}}"/>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="Chat/Players">
|
||||
<DockPanel>
|
||||
<local:PlayerListControl x:Name="PlayerList" DockPanel.Dock="Right" Width="250" />
|
||||
<local:ChatControl x:Name="Chat" />
|
||||
<local:ChatControl x:Name="Chat" IsEnabled="{Binding IsRunning}"/>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="Entity Manager">
|
||||
|
@@ -65,18 +65,12 @@ namespace Torch.Server
|
||||
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_config.Save();
|
||||
((Button) sender).IsEnabled = false;
|
||||
BtnStop.IsEnabled = true;
|
||||
ConfigControl.SaveConfig();
|
||||
new Thread(_server.Start).Start();
|
||||
}
|
||||
|
||||
private void BtnStop_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_config.Save();
|
||||
((Button) sender).IsEnabled = false;
|
||||
//HACK: Uncomment when restarting is possible.
|
||||
//BtnStart.IsEnabled = true;
|
||||
_server.Stop();
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ using VRage.Network;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public struct ChatMessage : IChatMessage
|
||||
public class ChatMessage : IChatMessage
|
||||
{
|
||||
public DateTime Timestamp { get; }
|
||||
public ulong SteamId { get; }
|
||||
|
@@ -9,6 +9,7 @@ using System.Windows.Threading;
|
||||
|
||||
namespace Torch.Collections
|
||||
{
|
||||
[Serializable]
|
||||
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
51
Torch/Managers/FilesystemManager.cs
Normal file
51
Torch/Managers/FilesystemManager.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.API;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
public class FilesystemManager : Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary directory for Torch that is cleared every time the program is started.
|
||||
/// </summary>
|
||||
public string TempDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory that contains the current Torch assemblies.
|
||||
/// </summary>
|
||||
public string TorchDirectory { get; }
|
||||
|
||||
public FilesystemManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
var temp = Path.Combine(Path.GetTempPath(), "Torch");
|
||||
TempDirectory = Directory.CreateDirectory(temp).FullName;
|
||||
var torch = new FileInfo(typeof(FilesystemManager).Assembly.Location).Directory.FullName;
|
||||
TorchDirectory = torch;
|
||||
|
||||
ClearTemp();
|
||||
}
|
||||
|
||||
private void ClearTemp()
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(TempDirectory, "*", SearchOption.AllDirectories))
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the given file (if it exists) to a temporary directory that will be cleared the next time the application starts.
|
||||
/// </summary>
|
||||
public void SoftDelete(string file)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
var rand = Path.GetRandomFileName();
|
||||
var dest = Path.Combine(TempDirectory, rand);
|
||||
File.Move(file, dest);
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,20 +33,21 @@ using VRage.Utils;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a proxy to the game's multiplayer-related functions.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public class MultiplayerManager : Manager, IMultiplayerManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerJoined;
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerLeft;
|
||||
/// <inheritdoc />
|
||||
public event MessageReceivedDel MessageReceived;
|
||||
|
||||
public MTObservableCollection<IChatMessage> ChatHistory { get; } = new MTObservableCollection<IChatMessage>();
|
||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
||||
private static readonly Logger _log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
private static readonly Logger _chatLog = LogManager.GetLogger("Chat");
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
private static readonly Logger ChatLog = LogManager.GetLogger("Chat");
|
||||
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
|
||||
|
||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
||||
@@ -65,12 +66,14 @@ namespace Torch.Managers
|
||||
{
|
||||
var message = ChatMessage.FromChatMsg(msg);
|
||||
ChatHistory.Add(message);
|
||||
_chatLog.Info($"{message.Name}: {message.Message}");
|
||||
ChatLog.Info($"{message.Name}: {message.Message}");
|
||||
MessageReceived?.Invoke(message, ref sendToOthers);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BanPlayer(ulong steamId, bool banned = true)
|
||||
{
|
||||
Torch.Invoke(() =>
|
||||
@@ -81,12 +84,14 @@ namespace Torch.Managers
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerByName(string name)
|
||||
{
|
||||
ValidateOnlinePlayersList();
|
||||
return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||
{
|
||||
ValidateOnlinePlayersList();
|
||||
@@ -94,14 +99,13 @@ namespace Torch.Managers
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetSteamUsername(ulong steamId)
|
||||
{
|
||||
return MyMultiplayer.Static.GetMemberName(steamId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message in chat.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
||||
{
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", message));
|
||||
@@ -126,7 +130,7 @@ namespace Torch.Managers
|
||||
|
||||
private void OnSessionLoaded()
|
||||
{
|
||||
_log.Info("Initializing Steam auth");
|
||||
Log.Info("Initializing Steam auth");
|
||||
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||
|
||||
@@ -138,7 +142,7 @@ namespace Torch.Managers
|
||||
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
|
||||
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||
_log.Info("Steam auth initialized");
|
||||
Log.Info("Steam auth initialized");
|
||||
}
|
||||
|
||||
private void OnClientKicked(ulong steamId)
|
||||
@@ -151,7 +155,7 @@ namespace Torch.Managers
|
||||
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
||||
if (vm == null)
|
||||
vm = new PlayerViewModel(steamId);
|
||||
_log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||
Log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||
PlayerLeft?.Invoke(vm);
|
||||
Players.Remove(steamId);
|
||||
}
|
||||
@@ -178,7 +182,7 @@ namespace Torch.Managers
|
||||
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
|
||||
{
|
||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= handle as ValidateAuthTicketResponse;
|
||||
_log.Debug("Removed GameServer_ValidateAuthTicketResponse");
|
||||
Log.Debug("Removed GameServer_ValidateAuthTicketResponse");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +195,7 @@ namespace Torch.Managers
|
||||
if (handle.Method.Name == "GameServer_UserGroupStatus")
|
||||
{
|
||||
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
|
||||
_log.Debug("Removed GameServer_UserGroupStatus");
|
||||
Log.Debug("Removed GameServer_UserGroupStatus");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,16 +204,16 @@ namespace Torch.Managers
|
||||
//Largely copied from SE
|
||||
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
|
||||
{
|
||||
_log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
||||
Log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
||||
|
||||
if (steamID != ownerSteamID)
|
||||
{
|
||||
_log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
||||
Log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
||||
_gameOwnerIds[steamID] = ownerSteamID;
|
||||
|
||||
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
|
||||
{
|
||||
_log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
||||
Log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
||||
UserRejected(steamID, JoinResult.BannedByAdmins);
|
||||
BanPlayer(steamID);
|
||||
}
|
||||
@@ -306,7 +310,7 @@ namespace Torch.Managers
|
||||
{
|
||||
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
|
||||
var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected};
|
||||
_log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||
Players.Add(steamId, vm);
|
||||
PlayerJoined?.Invoke(vm);
|
||||
}
|
||||
|
@@ -64,6 +64,11 @@ namespace Torch.Managers
|
||||
/// Loads the network intercept system
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
{
|
||||
Torch.SessionLoaded += OnSessionLoaded;
|
||||
}
|
||||
|
||||
private void OnSessionLoaded()
|
||||
{
|
||||
if (_init)
|
||||
return;
|
||||
|
@@ -5,42 +5,30 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Plugins;
|
||||
using Torch.Commands;
|
||||
using Torch.Managers;
|
||||
using Torch.Updater;
|
||||
using VRage.Plugins;
|
||||
using VRage.Collections;
|
||||
using VRage.Library.Collections;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
public class PluginManager : IPluginManager
|
||||
/// <inheritdoc />
|
||||
public class PluginManager : Manager, IPluginManager
|
||||
{
|
||||
private readonly ITorchBase _torch;
|
||||
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||
private UpdateManager _updateManager;
|
||||
|
||||
public List<ITorchPlugin> Plugins { get; } = new List<ITorchPlugin>();
|
||||
|
||||
public float LastUpdateMs => _lastUpdateMs;
|
||||
private volatile float _lastUpdateMs;
|
||||
/// <inheritdoc />
|
||||
public ObservableCollection<ITorchPlugin> Plugins { get; } = new ObservableCollection<ITorchPlugin>();
|
||||
|
||||
public event Action<List<ITorchPlugin>> PluginsLoaded;
|
||||
|
||||
public PluginManager(ITorchBase torch)
|
||||
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
_torch = torch;
|
||||
|
||||
if (!Directory.Exists(PluginDir))
|
||||
Directory.CreateDirectory(PluginDir);
|
||||
}
|
||||
@@ -50,11 +38,8 @@ namespace Torch.Managers
|
||||
/// </summary>
|
||||
public void UpdatePlugins()
|
||||
{
|
||||
var s = Stopwatch.StartNew();
|
||||
foreach (var plugin in Plugins)
|
||||
plugin.Update();
|
||||
s.Stop();
|
||||
_lastUpdateMs = (float)s.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,40 +55,42 @@ namespace Torch.Managers
|
||||
|
||||
private void DownloadPlugins()
|
||||
{
|
||||
_log.Info("Downloading plugins");
|
||||
var updater = new PluginUpdater(this);
|
||||
|
||||
var folders = Directory.GetDirectories(PluginDir);
|
||||
var taskList = new List<Task>();
|
||||
if (_torch.Config.RedownloadPlugins)
|
||||
_log.Warn("Force downloading all plugins because the RedownloadPlugins flag is set in the config");
|
||||
|
||||
//Copy list because we don't want to modify the config.
|
||||
var toDownload = Torch.Config.Plugins.ToList();
|
||||
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
var manifestPath = Path.Combine(folder, "manifest.xml");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
_log.Info($"No manifest in {folder}, skipping");
|
||||
_log.Debug($"No manifest in {folder}, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
_log.Info($"Checking for updates for {folder}");
|
||||
var manifest = PluginManifest.Load(manifestPath);
|
||||
taskList.Add(updater.CheckAndUpdate(manifest, _torch.Config.RedownloadPlugins));
|
||||
toDownload.Remove(manifest.Repository);
|
||||
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||
}
|
||||
|
||||
foreach (var repository in toDownload)
|
||||
{
|
||||
var manifest = new PluginManifest {Repository = repository, Version = "0.0"};
|
||||
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||
}
|
||||
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
_torch.Config.RedownloadPlugins = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and creates instances of all plugins in the <see cref="PluginDir"/> folder.
|
||||
/// </summary>
|
||||
public void Init()
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
var commands = ((TorchBase)_torch).Commands;
|
||||
_updateManager = Torch.GetManager<UpdateManager>();
|
||||
var commands = Torch.GetManager<CommandManager>();
|
||||
|
||||
if (_torch.Config.AutomaticUpdates)
|
||||
if (Torch.Config.GetPluginUpdates)
|
||||
DownloadPlugins();
|
||||
else
|
||||
_log.Warn("Automatic plugin updates are disabled.");
|
||||
@@ -129,7 +116,7 @@ namespace Torch.Managers
|
||||
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
|
||||
|
||||
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
|
||||
plugin.StoragePath = _torch.Config.InstancePath;
|
||||
plugin.StoragePath = Torch.Config.InstancePath;
|
||||
Plugins.Add(plugin);
|
||||
|
||||
commands.RegisterPluginCommands(plugin);
|
||||
@@ -143,8 +130,8 @@ namespace Torch.Managers
|
||||
}
|
||||
}
|
||||
|
||||
Plugins.ForEach(p => p.Init(_torch));
|
||||
PluginsLoaded?.Invoke(Plugins);
|
||||
Plugins.ForEach(p => p.Init(Torch));
|
||||
PluginsLoaded?.Invoke(Plugins.ToList());
|
||||
}
|
||||
|
||||
public IEnumerator<ITorchPlugin> GetEnumerator()
|
||||
|
@@ -1,28 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Packaging;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Octokit;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles updating of the DS and Torch plugins.
|
||||
/// </summary>
|
||||
public class UpdateManager : IDisposable
|
||||
public class UpdateManager : Manager, IDisposable
|
||||
{
|
||||
private Timer _updatePollTimer;
|
||||
private GitHubClient _gitClient = new GitHubClient(new ProductHeaderValue("Torch"));
|
||||
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName;
|
||||
private Logger _log = LogManager.GetLogger(nameof(UpdateManager));
|
||||
private FilesystemManager _fsManager;
|
||||
|
||||
public UpdateManager()
|
||||
public UpdateManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
_updatePollTimer = new Timer(CheckForUpdates, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||
//_updatePollTimer = new Timer(TimerElapsed, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
private void CheckForUpdates(object state)
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
_fsManager = Torch.GetManager<FilesystemManager>();
|
||||
CheckAndUpdateTorch();
|
||||
}
|
||||
|
||||
private void TimerElapsed(object state)
|
||||
{
|
||||
CheckAndUpdateTorch();
|
||||
}
|
||||
|
||||
private async Task<Tuple<Version, string>> GetLatestRelease(string owner, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var latest = await _gitClient.Repository.Release.GetLatest(owner, name).ConfigureAwait(false);
|
||||
if (latest == null)
|
||||
return new Tuple<Version, string>(new Version(), null);
|
||||
|
||||
var zip = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip"));
|
||||
return new Tuple<Version, string>(new Version(latest.TagName ?? "0"), zip?.BrowserDownloadUrl);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_log.Error($"An error occurred getting release information for '{owner}/{name}'");
|
||||
return new Tuple<Version, string>(new Version(), null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckAndUpdatePlugin(PluginManifest manifest)
|
||||
{
|
||||
if (!Torch.Config.GetPluginUpdates)
|
||||
return;
|
||||
|
||||
var name = manifest.Repository.Split('/');
|
||||
if (name.Length != 2)
|
||||
{
|
||||
_log.Error($"'{manifest.Repository}' is not a valid GitHub repository.");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentVersion = new Version(manifest.Version);
|
||||
var releaseInfo = await GetLatestRelease(name[0], name[1]).ConfigureAwait(false);
|
||||
if (releaseInfo.Item1 > currentVersion)
|
||||
{
|
||||
_log.Warn($"Updating {manifest.Repository} from version {currentVersion} to version {releaseInfo.Item1}");
|
||||
var updateName = Path.Combine(_fsManager.TempDirectory, $"{name[0]}_{name[1]}.zip");
|
||||
var updatePath = Path.Combine(_torchDir, "Plugins");
|
||||
await new WebClient().DownloadFileTaskAsync(new Uri(releaseInfo.Item2), updateName).ConfigureAwait(false);
|
||||
UpdateFromZip(updateName, updatePath);
|
||||
File.Delete(updateName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info($"{manifest.Repository} is up to date. ({currentVersion})");
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckAndUpdateTorch()
|
||||
{
|
||||
if (!Torch.Config.GetTorchUpdates)
|
||||
return;
|
||||
|
||||
var releaseInfo = await GetLatestRelease("TorchAPI", "Torch").ConfigureAwait(false);
|
||||
if (releaseInfo.Item1 > Torch.TorchVersion)
|
||||
{
|
||||
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {releaseInfo.Item1}");
|
||||
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip");
|
||||
new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName);
|
||||
UpdateFromZip(updateName, _torchDir);
|
||||
File.Delete(updateName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("Torch is up to date.");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFromZip(string zipFile, string extractPath)
|
||||
{
|
||||
using (var zip = ZipFile.OpenRead(zipFile))
|
||||
{
|
||||
foreach (var file in zip.Entries)
|
||||
{
|
||||
_log.Debug($"Unzipping {file.FullName}");
|
||||
var targetFile = Path.Combine(extractPath, file.FullName);
|
||||
_fsManager.SoftDelete(targetFile);
|
||||
}
|
||||
|
||||
zip.ExtractToDirectory(extractPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -10,12 +12,14 @@ namespace Torch
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using JSON serialization.
|
||||
/// Can automatically save on changes by implementing <see cref="INotifyPropertyChanged"/> in the data class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Data class type</typeparam>
|
||||
public sealed class Persistent<T> : IDisposable where T : new()
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public T Data { get; private set; }
|
||||
private Timer _saveTimer;
|
||||
|
||||
~Persistent()
|
||||
{
|
||||
@@ -24,8 +28,21 @@ namespace Torch
|
||||
|
||||
public Persistent(string path, T data = default(T))
|
||||
{
|
||||
_saveTimer = new Timer(Callback);
|
||||
Path = path;
|
||||
Data = data;
|
||||
if (Data is INotifyPropertyChanged npc)
|
||||
npc.PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
_saveTimer.Change(5000, -1);
|
||||
}
|
||||
|
||||
private void Callback(object state)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save(string path = null)
|
||||
@@ -65,6 +82,9 @@ namespace Torch
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Data is INotifyPropertyChanged npc)
|
||||
npc.PropertyChanged -= OnPropertyChanged;
|
||||
_saveTimer.Dispose();
|
||||
Save();
|
||||
}
|
||||
catch
|
||||
|
@@ -81,6 +81,8 @@
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
@@ -160,19 +162,19 @@
|
||||
<Compile Include="Commands\TorchCommands.cs" />
|
||||
<Compile Include="Managers\ChatManager.cs" />
|
||||
<Compile Include="Managers\EntityManager.cs" />
|
||||
<Compile Include="Managers\FilesystemManager.cs" />
|
||||
<Compile Include="Managers\Manager.cs" />
|
||||
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
|
||||
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
|
||||
<Compile Include="Managers\MultiplayerManager.cs" />
|
||||
<Compile Include="Managers\UpdateManager.cs" />
|
||||
<Compile Include="Persistent.cs" />
|
||||
<Compile Include="Updater\PluginManifest.cs" />
|
||||
<Compile Include="PluginManifest.cs" />
|
||||
<Compile Include="Reflection.cs" />
|
||||
<Compile Include="Managers\ScriptingManager.cs" />
|
||||
<Compile Include="TorchBase.cs" />
|
||||
<Compile Include="SteamService.cs" />
|
||||
<Compile Include="TorchPluginBase.cs" />
|
||||
<Compile Include="Updater\PluginUpdater.cs" />
|
||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||
|
@@ -41,22 +41,38 @@ namespace Torch
|
||||
/// Use only if necessary, prefer dependency injection.
|
||||
/// </summary>
|
||||
public static ITorchBase Instance { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ITorchConfig Config { get; protected set; }
|
||||
protected static Logger Log { get; } = LogManager.GetLogger("Torch");
|
||||
/// <inheritdoc />
|
||||
public Version TorchVersion { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public Version GameVersion { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string[] RunArgs { get; set; }
|
||||
/// <inheritdoc />
|
||||
public IPluginManager Plugins { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public IMultiplayerManager Multiplayer { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public EntityManager Entities { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public INetworkManager Network { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public CommandManager Commands { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public event Action SessionLoading;
|
||||
/// <inheritdoc />
|
||||
public event Action SessionLoaded;
|
||||
/// <inheritdoc />
|
||||
public event Action SessionUnloading;
|
||||
/// <inheritdoc />
|
||||
public event Action SessionUnloaded;
|
||||
private readonly List<IManager> _managers;
|
||||
|
||||
/// <summary>
|
||||
/// Common log for the Torch instance.
|
||||
/// </summary>
|
||||
protected static Logger Log { get; } = LogManager.GetLogger("Torch");
|
||||
private readonly List<IManager> _managers;
|
||||
private bool _init;
|
||||
|
||||
/// <summary>
|
||||
@@ -79,22 +95,25 @@ namespace Torch
|
||||
Network = new NetworkManager(this);
|
||||
Commands = new CommandManager(this);
|
||||
|
||||
_managers = new List<IManager> {Network, Commands, Plugins, Multiplayer, Entities, new ChatManager(this)};
|
||||
_managers = new List<IManager> { new FilesystemManager(this), new UpdateManager(this), Network, Commands, Plugins, Multiplayer, Entities, new ChatManager(this), };
|
||||
|
||||
|
||||
TorchAPI.Instance = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ListReader<IManager> GetManagers()
|
||||
{
|
||||
return new ListReader<IManager>(_managers);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetManager<T>() where T : class, IManager
|
||||
{
|
||||
return _managers.FirstOrDefault(m => m is T) as T;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AddManager<T>(T manager) where T : class, IManager
|
||||
{
|
||||
if (_managers.Any(x => x is T))
|
||||
@@ -104,11 +123,6 @@ namespace Torch
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsOnGameThread()
|
||||
{
|
||||
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
|
||||
}
|
||||
|
||||
public async Task SaveGameAsync()
|
||||
{
|
||||
Log.Info("Saving game");
|
||||
@@ -201,6 +215,7 @@ namespace Torch
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Init()
|
||||
{
|
||||
Debug.Assert(!_init, "Torch instance is already initialized.");
|
||||
@@ -236,15 +251,14 @@ namespace Torch
|
||||
MySession.OnUnloading += OnSessionUnloading;
|
||||
MySession.OnUnloaded += OnSessionUnloaded;
|
||||
RegisterVRagePlugin();
|
||||
|
||||
foreach (var manager in _managers)
|
||||
manager.Init();
|
||||
_init = true;
|
||||
}
|
||||
|
||||
private void OnSessionLoading()
|
||||
{
|
||||
Log.Debug("Session loading");
|
||||
foreach (var manager in _managers)
|
||||
manager.Init();
|
||||
SessionLoading?.Invoke();
|
||||
}
|
||||
|
||||
@@ -279,11 +293,13 @@ namespace Torch
|
||||
pluginList.Add(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Stop() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Octokit;
|
||||
using Torch.API;
|
||||
using Torch.Managers;
|
||||
using VRage.Compression;
|
||||
|
||||
namespace Torch.Updater
|
||||
{
|
||||
public class PluginUpdater
|
||||
{
|
||||
private readonly PluginManager _pluginManager;
|
||||
private static readonly Logger Log = LogManager.GetLogger("PluginUpdater");
|
||||
|
||||
public PluginUpdater(PluginManager pm)
|
||||
{
|
||||
_pluginManager = pm;
|
||||
}
|
||||
|
||||
public async Task CheckAndUpdate(PluginManifest manifest, bool force = false)
|
||||
{
|
||||
Log.Info($"Checking for update at {manifest.Repository}");
|
||||
var split = manifest.Repository.Split('/');
|
||||
|
||||
if (split.Length != 2)
|
||||
{
|
||||
Log.Warn($"Manifest has an invalid repository name: {manifest.Repository}");
|
||||
return;
|
||||
}
|
||||
|
||||
var gitClient = new GitHubClient(new ProductHeaderValue("Torch"));
|
||||
var releases = await gitClient.Repository.Release.GetAll(split[0], split[1]).ConfigureAwait(false);
|
||||
|
||||
if (releases.Count == 0)
|
||||
{
|
||||
Log.Debug("No releases in repo");
|
||||
return;
|
||||
}
|
||||
|
||||
Version currentVersion;
|
||||
Version latestVersion;
|
||||
|
||||
try
|
||||
{
|
||||
currentVersion = new Version(manifest.Version);
|
||||
latestVersion = new Version(releases[0].TagName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Warn("Invalid version number on manifest or GitHub release");
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || latestVersion > currentVersion)
|
||||
{
|
||||
var webClient = new WebClient();
|
||||
var assets = await gitClient.Repository.Release.GetAllAssets(split[0], split[1], releases[0].Id).ConfigureAwait(false);
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
if (asset.Name.EndsWith(".zip"))
|
||||
{
|
||||
Log.Debug(asset.BrowserDownloadUrl);
|
||||
var localPath = Path.Combine(Path.GetTempPath(), asset.Name);
|
||||
await webClient.DownloadFileTaskAsync(new Uri(asset.BrowserDownloadUrl), localPath).ConfigureAwait(false);
|
||||
UnzipPlugin(localPath);
|
||||
Log.Info($"Downloaded update for {manifest.Repository}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info($"{manifest.Repository} is up to date.");
|
||||
}
|
||||
}
|
||||
|
||||
public void UnzipPlugin(string zipName)
|
||||
{
|
||||
if (!File.Exists(zipName))
|
||||
return;
|
||||
|
||||
MyZipArchive.ExtractToDirectory(zipName, _pluginManager.PluginDir);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user