diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs index a1066ff..f49352e 100644 --- a/Torch.Server/Initializer.cs +++ b/Torch.Server/Initializer.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; using System.Windows.Threading; using NLog; using Torch.Utils; @@ -84,27 +85,21 @@ quit"; public void Run() { _server = new TorchServer(_config); + try { - - var initTask = Task.Run(() => { _server.Init(); }); + _server.Init(); if (!_config.NoGui) { _server.Init(); - if (!_config.NoGui) - { - var ui = new TorchUI(_server); - if (_config.Autostart) - _server.Start(); - ui.ShowDialog(); - } - else - _server.Start(); + if (_config.Autostart) + Task.Run(() => _server.Start()); + + new TorchUI(_server).ShowDialog(); } else { - initTask.Wait(); _server.Start(); } } @@ -116,101 +111,105 @@ quit"; } } - private TorchConfig InitConfig() + private TorchConfig InitConfig() + { + var configName = "Torch.cfg"; + var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName); + if (File.Exists(configName)) { - var configName = "Torch.cfg"; - var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName); - if (File.Exists(configName)) - { - Log.Info($"Loading config {configPath}"); - return TorchConfig.LoadFrom(configPath); - } - else - { - Log.Info($"Generating default config at {configPath}"); - var config = new TorchConfig {InstancePath = Path.GetFullPath("Instance")}; - config.Save(configPath); - return config; - } + Log.Info($"Loading config {configPath}"); + return TorchConfig.LoadFrom(configPath); } - - private static void RunSteamCmd() + else { - var log = LogManager.GetLogger("SteamCMD"); - - if (!Directory.Exists(STEAMCMD_DIR)) - { - Directory.CreateDirectory(STEAMCMD_DIR); - } - - if (!File.Exists(RUNSCRIPT_PATH)) - File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT); - - if (!File.Exists(STEAMCMD_PATH)) - { - try - { - log.Info("Downloading SteamCMD."); - using (var client = new WebClient()) - client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP); - - ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR); - File.Delete(STEAMCMD_ZIP); - log.Info("SteamCMD downloaded successfully!"); - } - catch - { - log.Error("Failed to download SteamCMD, unable to update the DS."); - return; - } - } - - log.Info("Checking for DS updates."); - var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt") - { - WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR), - UseShellExecute = false, - RedirectStandardOutput = true, - StandardOutputEncoding = Encoding.ASCII - }; - var cmd = Process.Start(steamCmdProc); - - // ReSharper disable once PossibleNullReferenceException - while (!cmd.HasExited) - { - log.Info(cmd.StandardOutput.ReadLine()); - Thread.Sleep(100); - } - } - - private void LogException(Exception ex) - { - if (ex.InnerException != null) - { - LogException(ex.InnerException); - } - - Log.Fatal(ex); - if (ex is ReflectionTypeLoadException exti) - foreach (Exception exl in exti.LoaderExceptions) - LogException(exl); - } - - private void HandleException(object sender, UnhandledExceptionEventArgs e) - { - var ex = (Exception)e.ExceptionObject; - LogException(ex); - Console.WriteLine("Exiting in 5 seconds."); - Thread.Sleep(5000); - LogManager.Flush(); - if (_config.RestartOnCrash) - { - var exe = typeof(Program).Assembly.Location; - _config.WaitForPID = Process.GetCurrentProcess().Id.ToString(); - Process.Start(exe, _config.ToString()); - } - - Process.GetCurrentProcess().Kill(); + Log.Info($"Generating default config at {configPath}"); + var config = new TorchConfig {InstancePath = Path.GetFullPath("Instance")}; + config.Save(configPath); + return config; } } - } \ No newline at end of file + + private static void RunSteamCmd() + { + var log = LogManager.GetLogger("SteamCMD"); + + if (!Directory.Exists(STEAMCMD_DIR)) + { + Directory.CreateDirectory(STEAMCMD_DIR); + } + + if (!File.Exists(RUNSCRIPT_PATH)) + File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT); + + if (!File.Exists(STEAMCMD_PATH)) + { + try + { + log.Info("Downloading SteamCMD."); + using (var client = new WebClient()) + client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP); + + ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR); + File.Delete(STEAMCMD_ZIP); + log.Info("SteamCMD downloaded successfully!"); + } + catch + { + log.Error("Failed to download SteamCMD, unable to update the DS."); + return; + } + } + + log.Info("Checking for DS updates."); + var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt") + { + WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR), + UseShellExecute = false, + RedirectStandardOutput = true, + StandardOutputEncoding = Encoding.ASCII + }; + var cmd = Process.Start(steamCmdProc); + + // ReSharper disable once PossibleNullReferenceException + while (!cmd.HasExited) + { + log.Info(cmd.StandardOutput.ReadLine()); + Thread.Sleep(100); + } + } + + private void LogException(Exception ex) + { + if (ex.InnerException != null) + { + LogException(ex.InnerException); + } + + Log.Fatal(ex); + if (ex is ReflectionTypeLoadException exti) + foreach (Exception exl in exti.LoaderExceptions) + LogException(exl); + } + + private void HandleException(object sender, UnhandledExceptionEventArgs e) + { + var ex = (Exception)e.ExceptionObject; + LogException(ex); + LogManager.Flush(); + if (_config.RestartOnCrash) + { + Console.WriteLine("Restarting in 5 seconds."); + Thread.Sleep(5000); + var exe = typeof(Program).Assembly.Location; + _config.WaitForPID = Process.GetCurrentProcess().Id.ToString(); + Process.Start(exe, _config.ToString()); + } + else + { + MessageBox.Show("Torch encountered a fatal error and needs to close. Please check the logs for details."); + } + + Process.GetCurrentProcess().Kill(); + } + } +} \ No newline at end of file diff --git a/Torch.Server/Managers/InstanceManager.cs b/Torch.Server/Managers/InstanceManager.cs index ea343a0..c4d7f53 100644 --- a/Torch.Server/Managers/InstanceManager.cs +++ b/Torch.Server/Managers/InstanceManager.cs @@ -10,13 +10,18 @@ using Havok; using NLog; using Sandbox.Engine.Networking; using Sandbox.Engine.Utils; +using Sandbox.Game; +using Sandbox.Game.Gui; using Torch.API; using Torch.API.Managers; using Torch.Managers; using Torch.Server.ViewModels; +using VRage; using VRage.FileSystem; using VRage.Game; +using VRage.Game.ObjectBuilder; using VRage.ObjectBuilders; +using VRage.Plugins; namespace Torch.Server.Managers { @@ -37,6 +42,8 @@ namespace Torch.Server.Managers public void LoadInstance(string path, bool validate = true) { + Log.Info($"Loading instance {path}"); + if (validate) ValidateInstance(path); @@ -56,6 +63,7 @@ namespace Torch.Server.Managers config.Load(configPath); DedicatedConfig = new ConfigDedicatedViewModel(config); + var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves")); foreach (var f in worldFolders) diff --git a/Torch.Server/MultiTextWriter.cs b/Torch.Server/MultiTextWriter.cs new file mode 100644 index 0000000..b0b34fa --- /dev/null +++ b/Torch.Server/MultiTextWriter.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Torch.Server +{ + public class MultiTextWriter : TextWriter + { + private IEnumerable writers; + public MultiTextWriter(IEnumerable writers) + { + this.writers = writers.ToList(); + } + public MultiTextWriter(params TextWriter[] writers) + { + this.writers = writers; + } + + public override void Write(char value) + { + foreach (var writer in writers) + writer.Write(value); + } + + public override void Write(string value) + { + foreach (var writer in writers) + writer.Write(value); + } + + public override void Flush() + { + foreach (var writer in writers) + writer.Flush(); + } + + public override void Close() + { + foreach (var writer in writers) + writer.Close(); + } + + public override Encoding Encoding + { + get { return Encoding.ASCII; } + } + } +} \ No newline at end of file diff --git a/Torch.Server/RichTextBoxWriter.cs b/Torch.Server/RichTextBoxWriter.cs new file mode 100644 index 0000000..df3231c --- /dev/null +++ b/Torch.Server/RichTextBoxWriter.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Torch.Server +{ + public class RichTextBoxWriter : TextWriter + { + private RichTextBox textbox; + private StringBuilder _cache = new StringBuilder(); + public RichTextBoxWriter(RichTextBox textbox) + { + this.textbox = textbox; + textbox.Document.Background = new SolidColorBrush(UnpackColor(Console.BackgroundColor)); + textbox.Document.Blocks.Clear(); + textbox.Document.Blocks.Add(new Paragraph {LineHeight = 12}); + } + + public override void Write(char value) + { + if (value == '\r') + return; + + _cache.Append(value); + if (value == '\n') + { + var str = _cache.ToString(); + _cache.Clear(); + + var brush = _brushes[Console.ForegroundColor]; + textbox.Dispatcher.BeginInvoke(() => + { + var p = (Paragraph)textbox.Document.Blocks.FirstBlock; + p.Inlines.Add(new Run(str) { Foreground = brush }); + textbox.ScrollToEnd(); + }); + } + + } + + public override void Write(string value) + { + var brush = _brushes[Console.ForegroundColor]; + textbox.Dispatcher.BeginInvoke(() => + { + var p = (Paragraph)textbox.Document.Blocks.FirstBlock; + p.Inlines.Add(new Run(value) { Foreground = brush }); + textbox.ScrollToEnd(); + }); + } + + public override Encoding Encoding + { + get { return Encoding.ASCII; } + } + + static RichTextBoxWriter() + { + foreach (var value in (ConsoleColor[])Enum.GetValues(typeof(ConsoleColor))) + { + _brushes.Add(value, new SolidColorBrush(UnpackColor(value))); + } + } + + private static Dictionary _brushes = new Dictionary(); + + private static Color UnpackColor(ConsoleColor color) + { + var colorByte = (byte)color; + var isBright = (colorByte & 0b1000) >> 3 > 0; + var brightness = isBright ? (byte)255 : (byte)128; + var red = (colorByte & 0b0100) >> 2; + var green = (colorByte & 0b0010) >> 1; + var blue = (colorByte & 0b0001); + + return new Color + { + R = (byte)(brightness * red), + G = (byte)(brightness * green), + B = (byte)(brightness * blue), + A = 255 + }; + } + } +} \ No newline at end of file diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index b215745..ff54836 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -227,6 +227,8 @@ + + EntityControlHost.xaml diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 7e0ae9f..5f1ac45 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -70,6 +70,11 @@ namespace Torch.Server } } + private bool _canRun; + public bool CanRun { get => _canRun; set => SetValue(ref _canRun, value); } + + private bool _hasRun; + /// public InstanceManager DedicatedInstance { get; } @@ -107,12 +112,13 @@ namespace Torch.Server /// public override void Init() { - Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'"); Sandbox.Engine.Platform.Game.IsDedicated = true; - base.Init(); + Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'"); + Managers.GetManager().SessionStateChanged += OnSessionStateChanged; GetManager().LoadInstance(Config.InstancePath); + CanRun = true; } private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState) @@ -129,8 +135,17 @@ namespace Torch.Server { if (State != ServerState.Stopped) return; + + if (_hasRun) + { + Restart(); + return; + } + State = ServerState.Starting; IsRunning = true; + CanRun = false; + _hasRun = true; Log.Info("Starting server."); MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model; @@ -150,6 +165,7 @@ namespace Torch.Server State = ServerState.Stopped; IsRunning = false; + CanRun = true; } /// @@ -157,12 +173,17 @@ namespace Torch.Server /// public override void Restart() { - Save().ContinueWith((task, torchO) => + if (IsRunning) + Save().ContinueWith(DoRestart, this, TaskContinuationOptions.RunContinuationsAsynchronously); + else + DoRestart(null, this); + + void DoRestart(Task task, object torch0) { - var torch = (TorchServer) torchO; + var torch = (TorchServer)torch0; torch.Stop(); // TODO clone this - var config = (TorchConfig) torch.Config; + var config = (TorchConfig)torch.Config; LogManager.Flush(); string exe = Assembly.GetExecutingAssembly().Location; @@ -172,7 +193,7 @@ namespace Torch.Server Process.Start(exe, config.ToString()); Process.GetCurrentProcess().Kill(); - }, this, TaskContinuationOptions.RunContinuationsAsynchronously); + } } /// diff --git a/Torch.Server/Views/TorchUI.xaml b/Torch.Server/Views/TorchUI.xaml index 8c368da..5f61026 100644 --- a/Torch.Server/Views/TorchUI.xaml +++ b/Torch.Server/Views/TorchUI.xaml @@ -28,22 +28,18 @@ - - - -