diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs new file mode 100644 index 0000000..933d683 --- /dev/null +++ b/Torch.Server/Initializer.cs @@ -0,0 +1,184 @@ +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; +using System.Threading.Tasks; +using NLog; +using Torch.Utils; + +namespace Torch.Server +{ + public class Initializer + { + private static readonly Logger Log = LogManager.GetLogger(nameof(Initializer)); + private bool _init; + private const string STEAMCMD_DIR = "steamcmd"; + private const string STEAMCMD_ZIP = "temp.zip"; + private static readonly string STEAMCMD_PATH = $"{STEAMCMD_DIR}\\steamcmd.exe"; + private static readonly string RUNSCRIPT_PATH = $"{STEAMCMD_DIR}\\runscript.txt"; + private const string RUNSCRIPT = @"force_install_dir ../ +login anonymous +app_update 298740 +quit"; + + private TorchAssemblyResolver _resolver; + private TorchConfig _config; + private TorchServer _server; + private string _basePath; + + public TorchConfig Config => _config; + public TorchServer Server => _server; + + public Initializer(string basePath) + { + _basePath = basePath; + } + + public bool Initialize(string[] args) + { + if (_init) + return false; + + AppDomain.CurrentDomain.UnhandledException += HandleException; + + if (!args.Contains("-noupdate")) + RunSteamCmd(); + + _resolver = new TorchAssemblyResolver(Path.Combine(_basePath, "DedicatedServer64")); + _config = InitConfig(); + if (!_config.Parse(args)) + return false; + + if (!string.IsNullOrEmpty(_config.WaitForPID)) + { + try + { + var pid = int.Parse(_config.WaitForPID); + var waitProc = Process.GetProcessById(pid); + Log.Info("Continuing in 5 seconds."); + Thread.Sleep(5000); + if (!waitProc.HasExited) + { + Log.Warn($"Killing old process {pid}."); + waitProc.Kill(); + } + + } + catch + { + // ignored + } + } + + _init = true; + return true; + } + + public void Run() + { + _server = new TorchServer(_config); + _server.Init(); + + if (_config.NoGui || _config.Autostart) + { + new Thread(_server.Start).Start(); + } + + if (!_config.NoGui) + { + new TorchUI(_server).ShowDialog(); + } + + _resolver?.Dispose(); + } + + private TorchConfig InitConfig() + { + 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; + } + } + + 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 HandleException(object sender, UnhandledExceptionEventArgs e) + { + var ex = (Exception)e.ExceptionObject; + Log.Fatal(ex); + Console.WriteLine("Exiting in 5 seconds."); + Thread.Sleep(5000); + if (_config.RestartOnCrash) + { + var exe = typeof(Program).Assembly.Location; + _config.WaitForPID = Process.GetCurrentProcess().Id.ToString(); + Process.Start(exe, _config.ToString()); + } + //1627 = Function failed during execution. + Environment.Exit(1627); + } + } +} diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs index 4a4fac9..efcc40a 100644 --- a/Torch.Server/Program.cs +++ b/Torch.Server/Program.cs @@ -25,6 +25,7 @@ using System.IO.Compression; using System.Net; using System.Security.Policy; using Torch.Server.Managers; +using Torch.Utils; using VRage.FileSystem; using VRageRender; @@ -32,268 +33,32 @@ namespace Torch.Server { internal static class Program { - private static ITorchServer _server; - private static Logger _log = LogManager.GetLogger("Torch"); - private static bool _restartOnCrash; - private static TorchConfig _config; - private static bool _steamCmdDone; - - /// + /// /// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail. - /// + /// [STAThread] public static void Main(string[] args) { //Ensures that all the files are downloaded in the Torch directory. - Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString()); - - foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.old")) - File.Delete(file); - - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString(); + var binDir = Path.Combine(workingDir, "DedicatedServer64"); + Directory.SetCurrentDirectory(workingDir); if (!Environment.UserInteractive) { using (var service = new TorchService()) + using (new TorchAssemblyResolver(binDir)) { ServiceBase.Run(service); } return; } - //CommandLine reflection triggers assembly loading, so DS update must be completely separated. - if (!args.Contains("-noupdate")) - { - if (!Directory.Exists("DedicatedServer64")) - { - _log.Error("Game libraries not found. Press the Enter key to install the dedicated server."); - Console.ReadLine(); - } - RunSteamCmd(); - } - - InitConfig(); - - if (!_config.Parse(args)) + var initializer = new Initializer(workingDir); + if (!initializer.Initialize(args)) return; - if (!string.IsNullOrEmpty(_config.WaitForPID)) - { - try - { - var pid = int.Parse(_config.WaitForPID); - var waitProc = Process.GetProcessById(pid); - _log.Info("Continuing in 5 seconds."); - Thread.Sleep(5000); - if (!waitProc.HasExited) - { - _log.Warn($"Killing old process {pid}."); - waitProc.Kill(); - } - - } - catch - { - // ignored - } - } - - _restartOnCrash = _config.RestartOnCrash; - RunServer(_config); - } - - public static void InitConfig() - { - var configName = "Torch.cfg"; - var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName); - if (File.Exists(configName)) - { - _log.Info($"Loading config {configPath}"); - _config = TorchConfig.LoadFrom(configPath); - } - else - { - _log.Info($"Generating default config at {configPath}"); - _config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") }; - _config.Save(configPath); - } - } - - private const string STEAMCMD_DIR = "steamcmd"; - private const string STEAMCMD_ZIP = "temp.zip"; - private static readonly string STEAMCMD_PATH = $"{STEAMCMD_DIR}\\steamcmd.exe"; - private static readonly string RUNSCRIPT_PATH = $"{STEAMCMD_DIR}\\runscript.txt"; - private const string RUNSCRIPT = @"force_install_dir ../ -login anonymous -app_update 298740 -quit"; - - public static void RunSteamCmd() - { - if (_steamCmdDone) - return; - - 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); - } - - _steamCmdDone = true; - } - - public static void RunServer(TorchConfig config) - { - - - /* - if (!parser.ParseArguments(args, config)) - { - _log.Error($"Parsing arguments failed: {string.Join(" ", args)}"); - return; - } - - if (!string.IsNullOrEmpty(config.Config) && File.Exists(config.Config)) - { - 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 (config.InstallService) - { - 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 = config.InstanceName, - BinaryPath = exePath, - }; - _log.Info("Service Installed"); - - var runArgs = string.Join(" ", args.Skip(1)); - _log.Info($"Installing Torch as a service with arguments '{runArgs}'"); - var startInfo = new ProcessStartInfo - { - FileName = "sc.exe", - Arguments = $"create Torch binPath=\"{Assembly.GetExecutingAssembly().Location} {runArgs}\"", - CreateNoWindow = true, - UseShellExecute = true, - Verb = "runas" - }; - Process.Start(startInfo).WaitForExit(); - _log.Info("Torch service installed"); - return; - } - - if (config.UninstallService) - { - _log.Info("Uninstalling Torch service"); - var startInfo = new ProcessStartInfo - { - FileName = "sc.exe", - Arguments = "delete Torch", - CreateNoWindow = true, - UseShellExecute = true, - Verb = "runas" - }; - Process.Start(startInfo).WaitForExit(); - _log.Info("Torch service uninstalled"); - return; - }*/ - - _server = new TorchServer(config); - - _server.Init(); - if (config.NoGui || config.Autostart) - { - new Thread(() => _server.Start()).Start(); - } - - if (!config.NoGui) - { - var ui = new TorchUI((TorchServer)_server); - ui.ShowDialog(); - } - } - - private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) - { - try - { - var basePath = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "DedicatedServer64"); - string asmPath = Path.Combine(basePath, new AssemblyName(args.Name).Name + ".dll"); - if (File.Exists(asmPath)) - return Assembly.LoadFrom(asmPath); - } - catch - { - // ignored - } - - return null; - } - - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - var ex = (Exception)e.ExceptionObject; - _log.Fatal(ex); - Console.WriteLine("Exiting in 5 seconds."); - Thread.Sleep(5000); - if (_restartOnCrash) - { - var exe = typeof(Program).Assembly.Location; - _config.WaitForPID = Process.GetCurrentProcess().Id.ToString(); - Process.Start(exe, _config.ToString()); - } - //1627 = Function failed during execution. - Environment.Exit(1627); + initializer.Run(); } } } diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index 4d85e77..0f6489f 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -193,6 +193,7 @@ AssemblyInfo.tt + diff --git a/Torch.Server/TorchConfig.cs b/Torch.Server/TorchConfig.cs index 669f145..bb1c8ad 100644 --- a/Torch.Server/TorchConfig.cs +++ b/Torch.Server/TorchConfig.cs @@ -8,6 +8,7 @@ using NLog; namespace Torch.Server { + // TODO: redesign this gerbage public class TorchConfig : CommandLine, ITorchConfig { private static Logger _log = LogManager.GetLogger("Config"); diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index ca1ee43..c5ae5a2 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -144,9 +144,17 @@ namespace Torch.Server VRage.Service.ExitListenerSTA.OnExit += delegate { MySandboxGame.Static?.Exit(); }; base.Start(); - //Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager. + // Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager. MySandboxGame.IsReloading = true; - _dsRunInternal.Invoke(); + try + { + _dsRunInternal.Invoke(); + } + catch (TargetInvocationException e) + { + // Makes log formatting a little nicer. + throw e.InnerException ?? e; + } MySandboxGame.Log.Close(); State = ServerState.Stopped; diff --git a/Torch.Server/TorchService.cs b/Torch.Server/TorchService.cs index ca8edc7..e33fda3 100644 --- a/Torch.Server/TorchService.cs +++ b/Torch.Server/TorchService.cs @@ -14,13 +14,15 @@ namespace Torch.Server { public const string Name = "Torch (SEDS)"; private TorchServer _server; - private static Logger _log = LogManager.GetLogger("Torch"); + private Initializer _initializer; public TorchService() { - ServiceName = Name; + var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString(); + Directory.SetCurrentDirectory(workingDir); + _initializer = new Initializer(workingDir); - CanHandlePowerEvent = true; + ServiceName = Name; CanHandleSessionChangeEvent = false; CanPauseAndContinue = false; CanStop = true; @@ -31,17 +33,8 @@ namespace Torch.Server { base.OnStart(args); - string configName = args.Length > 0 ? args[0] : "Torch.cfg"; - var options = new TorchConfig("Torch"); - if (File.Exists(configName)) - options = TorchConfig.LoadFrom(configName); - else - options.Save(configName); - - _server = new TorchServer(options); - _server.Init(); - _server.RunArgs = args; - Task.Run(() => _server.Start()); + _initializer.Initialize(args); + _initializer.Run(); } /// @@ -50,17 +43,5 @@ namespace Torch.Server _server.Stop(); base.OnStop(); } - - /// - protected override void OnShutdown() - { - base.OnShutdown(); - } - - /// - protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) - { - return base.OnPowerEvent(powerStatus); - } } } diff --git a/Torch/Commands/Command.cs b/Torch/Commands/Command.cs index dc95c51..7aedcab 100644 --- a/Torch/Commands/Command.cs +++ b/Torch/Commands/Command.cs @@ -62,7 +62,7 @@ namespace Torch.Commands _parameters = commandMethod.GetParameters(); var sb = new StringBuilder(); - sb.Append($"/{string.Join(" ", Path)} "); + sb.Append($"!{string.Join(" ", Path)} "); for (var i = 0; i < _parameters.Length; i++) { var param = _parameters[i]; diff --git a/Torch/Managers/UpdateManager.cs b/Torch/Managers/UpdateManager.cs index 8702299..02d0a2a 100644 --- a/Torch/Managers/UpdateManager.cs +++ b/Torch/Managers/UpdateManager.cs @@ -108,6 +108,9 @@ namespace Torch.Managers private async void CheckAndUpdateTorch() { + // Doesn't work properly or reliably, TODO update when Jenkins is fully configured + return; + if (!Torch.Config.GetTorchUpdates) return; diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 7bdc650..3c6d361 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -57,14 +57,19 @@ namespace Torch /// public string[] RunArgs { get; set; } /// + [Obsolete("Use GetManager() or the [Dependency] attribute.")] public IPluginManager Plugins { get; protected set; } /// + [Obsolete("Use GetManager() or the [Dependency] attribute.")] public IMultiplayerManager Multiplayer { get; protected set; } /// + [Obsolete("Use GetManager() or the [Dependency] attribute.")] public EntityManager Entities { get; protected set; } /// + [Obsolete("Use GetManager() or the [Dependency] attribute.")] public INetworkManager Network { get; protected set; } /// + [Obsolete("Use GetManager() or the [Dependency] attribute.")] public CommandManager Commands { get; protected set; } /// public event Action SessionLoading;