diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..35e3d93 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM mcr.microsoft.com/windows/servercore:ltsc2022 + +USER ContainerAdministrator +ADD https://aka.ms/highdpimfc2013x64enu vc_redist2013.exe +ADD https://aka.ms/vs/16/release/vc_redist.x64.exe vc_redist.exe + +RUN vc_redist2013.exe /passive /norestart +RUN vc_redist.exe /passive /norestart +RUN del vc_redist2013.exe && del vc_redist.exe + +USER ContainerUser +COPY . . +ENV TORCH_GAME_PATH="c:\dedi" +ENV TORCH_INSTANCE="c:\instance" +ENV TORCH_SERVICE="true" +ENTRYPOINT ["Torch.Server.exe"] +CMD ["-noupdate"] \ No newline at end of file diff --git a/Jenkins/jenkins-grab-se.ps1 b/Jenkins/jenkins-grab-se.ps1 deleted file mode 100644 index 0d9f43e..0000000 --- a/Jenkins/jenkins-grab-se.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -pushd - -$steamData = "C:/Steam/Data/" -$steamCMDPath = "C:/Steam/steamcmd/" -$steamCMDZip = "C:/Steam/steamcmd.zip" - -Add-Type -AssemblyName System.IO.Compression.FileSystem - -if (!(Test-Path $steamData)) { - mkdir "$steamData" -} -if (!(Test-Path $steamCMDPath)) { - if (!(Test-Path $steamCMDZip)) { - (New-Object System.Net.WebClient).DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", "$steamCMDZip"); - } - [System.IO.Compression.ZipFile]::ExtractToDirectory($steamCMDZip, $steamCMDPath) -} - -cd "$steamData" -& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740 validate" "+quit" - -popd diff --git a/Jenkins/release.ps1 b/Jenkins/release.ps1 deleted file mode 100644 index 81e5e63..0000000 --- a/Jenkins/release.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -param([string] $ApiBase, [string]$tagName, [string]$authinfo, [string[]] $assetPaths) -Add-Type -AssemblyName "System.Web" - -$headers = @{ - Authorization = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authinfo)) - Accept = "application/vnd.github.v3+json" -} -try -{ - Write-Output("Checking if release with tag " + $tagName + " already exists...") - $release = Invoke-RestMethod -Uri ($ApiBase+"releases/tags/$tagName") -Method "GET" -Headers $headers - Write-Output(" Using existing release " + $release.id + " at " + $release.html_url) -} catch { - Write-Output(" Doesn't exist") - $rel_arg = @{ - tag_name=$tagName - name="Generated $tagName" - body="" - draft=$TRUE - prerelease=$tagName.Contains("alpha") -or $tagName.Contains("beta") - } - Write-Output("Creating new release " + $tagName + "...") - $release = Invoke-RestMethod -Uri ($ApiBase+"releases") -Method "POST" -Headers $headers -Body (ConvertTo-Json($rel_arg)) - Write-Output(" Created new release " + $tagName + " at " + $release.html_url) -} - -$assetsApiBase = $release.assets_url -Write-Output("Checking for existing assets...") -$existingAssets = Invoke-RestMethod -Uri ($assetsApiBase) -Method "GET" -Headers $headers -$assetLabels = ($assetPaths | ForEach-Object {[System.IO.Path]::GetFileName($_)}) -foreach ($asset in $existingAssets) { - if ($assetLabels -contains $asset.name) { - $uri = $asset.url - Write-Output(" Deleting old asset " + $asset.name + " (id " + $asset.id + "); URI=" + $uri) - $result = Invoke-RestMethod -Uri $uri -Method "DELETE" -Headers $headers - } -} -Write-Output("Uploading assets...") -$uploadUrl = $release.upload_url.Substring(0, $release.upload_url.LastIndexOf('{')) -foreach ($asset in $assetPaths) { - $assetName = [System.IO.Path]::GetFileName($asset) - $assetType = [System.Web.MimeMapping]::GetMimeMapping($asset) - $assetData = [System.IO.File]::ReadAllBytes($asset) - $headerExtra = $headers + @{ - "Content-Type" = $assetType - Name = $assetName - } - $uri = $uploadUrl + "?name=" + $assetName - Write-Output(" Uploading " + $asset + " as " + $assetType + "; URI=" + $uri) - $result = Invoke-RestMethod -Uri $uri -Method "POST" -Headers $headerExtra -Body $assetData - Write-Output(" ID=" + $result.id + ", found at=" + $result.browser_download_url) -} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 3bc8a6a..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,77 +0,0 @@ -def packageAndArchive(buildMode, packageName) { - zipFile = "bin\\${packageName}.zip" - packageDir = "bin\\${packageName}\\" - - bat "IF EXIST ${zipFile} DEL ${zipFile}" - bat "IF EXIST ${packageDir} RMDIR /S /Q ${packageDir}" - - bat "xcopy bin\\x64\\${buildMode} ${packageDir}" - - bat "del ${packageDir}VRage.*" - bat "del ${packageDir}Sandbox.*" - bat "del ${packageDir}SpaceEngineers.*" - - powershell "Add-Type -Assembly System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"\$PWD\\${packageDir}\", \"\$PWD\\${zipFile}\")" - archiveArtifacts artifacts: zipFile, caseSensitive: false, onlyIfSuccessful: true -} - -node('windows') { - stage('Checkout') { - checkout scm - bat 'git pull https://github.com/TorchAPI/Torch/ master --tags' - } - - stage('Acquire SE') { - bat 'powershell -File Jenkins/jenkins-grab-se.ps1' - bat 'IF EXIST GameBinaries RMDIR GameBinaries' - bat 'mklink /J GameBinaries "C:/Steam/Data/DedicatedServer64/"' - bat 'dir GameBinaries' - } - - stage('Acquire NuGet Packages') { - bat 'cd C:\\Program Files\\Jenkins' - bat '"C:\\Program Files\\Jenkins\\nuget.exe" restore Torch.sln' - } - - stage('Build') { - currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim() - if (env.BRANCH_NAME == "master" || env.BRANCH_NAME == "Patron" || env.BRANCH_NAME == "publictest") { - buildMode = "Release" - } else { - buildMode = "Release" - } - bat "IF EXIST \"bin\" rmdir /Q /S \"bin\"" - bat "IF EXIST \"bin-test\" rmdir /Q /S \"bin-test\"" - bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64 /t:Clean" - bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64" - } - - stage('Archive') { - archiveArtifacts artifacts: "bin/x64/${buildMode}/Torch*", caseSensitive: false, fingerprint: true, onlyIfSuccessful: true - - packageAndArchive(buildMode, "torch-server") - - /*packageAndArchive(buildMode, "torch-client", "Torch.Server*")*/ - } - - /* Disabled because they fail builds more often than they detect actual problems - stage('Test') { - bat 'IF NOT EXIST reports MKDIR reports' - bat "\"packages/xunit.runner.console.2.2.0/tools/xunit.console.exe\" \"bin-test/x64/${buildMode}/Torch.Tests.dll\" \"bin-test/x64/${buildMode}/Torch.Server.Tests.dll\" \"bin-test/x64/${buildMode}/Torch.Client.Tests.dll\" -parallel none -xml \"reports/Torch.Tests.xml\"" - - step([ - $class: 'XUnitBuilder', - thresholdMode: 1, - thresholds: [[$class: 'FailedThreshold', failureThreshold: '1']], - tools: [[ - $class: 'XUnitDotNetTestType', - deleteOutputFiles: true, - failIfNotNew: true, - pattern: 'reports/*.xml', - skipNoTestFiles: false, - stopProcessingIfError: true - ]] - ]) - } - */ -} diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index 33e4481..4b5e41f 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -150,6 +150,11 @@ namespace Torch.API /// Path of the dedicated instance folder. /// string InstancePath { get; } + + /// + /// Name of the dedicated instance. + /// + string InstanceName { get; } /// /// Raised when the server's Init() method has completed. diff --git a/Torch.API/ITorchConfig.cs b/Torch.API/ITorchConfig.cs index 2156dfd..077d7b9 100644 --- a/Torch.API/ITorchConfig.cs +++ b/Torch.API/ITorchConfig.cs @@ -10,7 +10,9 @@ namespace Torch bool ForceUpdate { get; set; } bool GetPluginUpdates { get; set; } bool GetTorchUpdates { get; set; } + [Obsolete("Use Torch.InstanceName instead")] string InstanceName { get; set; } + [Obsolete("Use Torch.InstancePath instead")] string InstancePath { get; set; } bool NoGui { get; set; } bool NoUpdate { get; set; } diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj index f29f500..48dd83d 100644 --- a/Torch.API/Torch.API.csproj +++ b/Torch.API/Torch.API.csproj @@ -5,7 +5,7 @@ Torch Copyright © Torch API 2017 false - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + ..\bin\$(Platform)\$(Configuration)\ True False x64 diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs index fe61e95..f99d13f 100644 --- a/Torch.Server/Initializer.cs +++ b/Torch.Server/Initializer.cs @@ -22,40 +22,35 @@ namespace Torch.Server { public class Initializer { - [Obsolete("It's hack. Do not use it!")] internal static Initializer Instance { get; private set; } 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 static readonly string STEAMCMD_EXE = "steamcmd.exe"; + private static readonly string RUNSCRIPT_FILE = "runscript.txt"; private const string RUNSCRIPT = @"force_install_dir ../ login anonymous app_update 298740 quit"; private TorchServer _server; - private string _basePath; - internal Persistent ConfigPersistent { get; private set; } + internal Persistent ConfigPersistent { get; } public TorchConfig Config => ConfigPersistent?.Data; public TorchServer Server => _server; - public Initializer(string basePath) + public Initializer(string basePath, Persistent torchConfig) { - _basePath = basePath; Instance = this; + ConfigPersistent = torchConfig; } public bool Initialize(string[] args) { if (_init) return false; - - AppDomain.CurrentDomain.UnhandledException += HandleException; - #if DEBUG //enables logging debug messages when built in debug mode. Amazing. LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "main"); @@ -69,37 +64,6 @@ quit"; if (!Enumerable.Contains(args, "-noupdate")) RunSteamCmd(); - var basePath = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString(); - var apiSource = Path.Combine(basePath, "DedicatedServer64", "steam_api64.dll"); - var apiTarget = Path.Combine(basePath, "steam_api64.dll"); - - if (!File.Exists(apiTarget)) - { - File.Copy(apiSource, apiTarget); - } - else if (File.GetLastWriteTime(apiTarget) < File.GetLastWriteTime(apiSource)) - { - File.Delete(apiTarget); - File.Copy(apiSource, apiTarget); - } - - var havokSource = Path.Combine(basePath, "DedicatedServer64", "Havok.dll"); - var havokTarget = Path.Combine(basePath, "Havok.dll"); - - if (!File.Exists(havokTarget)) - { - File.Copy(havokSource, havokTarget); - } - else if (File.GetLastWriteTime(havokTarget) < File.GetLastWriteTime(havokSource)) - { - File.Delete(havokTarget); - File.Copy(havokSource, havokTarget); - } - - InitConfig(); - if (!Config.Parse(args)) - return false; - if (!string.IsNullOrEmpty(Config.WaitForPID)) { try @@ -114,9 +78,9 @@ quit"; Thread.Sleep(1000); } } - catch + catch (Exception e) { - // ignored + Log.Warn(e); } } @@ -124,11 +88,11 @@ quit"; return true; } - public void Run() + public void Run(bool isService, string instanceName, string instancePath) { - _server = new TorchServer(Config); + _server = new TorchServer(Config, instancePath, instanceName); - if (Config.NoGui) + if (isService || Config.NoGui) { _server.Init(); _server.Start(); @@ -164,35 +128,24 @@ quit"; ui.ShowDialog(); } } - - private void InitConfig() - { - var configName = "Torch.cfg"; - var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName); - if (File.Exists(configName)) - { - Log.Info($"Loading config {configName}"); - } - else - { - Log.Info($"Generating default config at {configPath}"); - } - ConfigPersistent = Persistent.Load(configPath); - } - + public static void RunSteamCmd() { var log = LogManager.GetLogger("SteamCMD"); - if (!Directory.Exists(STEAMCMD_DIR)) + var path = Environment.GetEnvironmentVariable("TORCH_STEAMCMD") ?? Path.GetFullPath(STEAMCMD_DIR); + + if (!Directory.Exists(path)) { - Directory.CreateDirectory(STEAMCMD_DIR); + Directory.CreateDirectory(path); } - if (!File.Exists(RUNSCRIPT_PATH)) - File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT); + var runScriptPath = Path.Combine(path, RUNSCRIPT_FILE); + if (!File.Exists(runScriptPath)) + File.WriteAllText(runScriptPath, RUNSCRIPT); - if (!File.Exists(STEAMCMD_PATH)) + var steamCmdExePath = Path.Combine(path, STEAMCMD_EXE); + if (!File.Exists(steamCmdExePath)) { try { @@ -201,22 +154,21 @@ quit"; using (var file = File.Create(STEAMCMD_ZIP)) client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result.CopyTo(file); - ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR); + ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path); File.Delete(STEAMCMD_ZIP); log.Info("SteamCMD downloaded successfully!"); } catch (Exception e) { - log.Error("Failed to download SteamCMD, unable to update the DS."); - log.Error(e); + log.Error(e, "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") + var steamCmdProc = new ProcessStartInfo(steamCmdExePath, "+runscript runscript.txt") { - WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR), + WorkingDirectory = path, UseShellExecute = false, RedirectStandardOutput = true, StandardOutputEncoding = Encoding.ASCII @@ -230,29 +182,5 @@ quit"; Thread.Sleep(100); } } - - private void HandleException(object sender, UnhandledExceptionEventArgs e) - { - _server.FatalException = true; - if (Debugger.IsAttached) - return; - var ex = (Exception)e.ExceptionObject; - Log.Fatal(ex.ToStringDemystified()); - LogManager.Flush(); - if (Config.RestartOnCrash) - { - Console.WriteLine("Restarting in 5 seconds."); - Thread.Sleep(5000); - var exe = typeof(Program).Assembly.Location; - Config.WaitForPID = Environment.ProcessId.ToString(); - Process.Start(exe, Config.ToString()); - } - else - { - MessageBox.Show("Torch encountered a fatal error and needs to close. Please check the logs for details."); - } - - Environment.Exit(1); - } } } diff --git a/Torch.Server/Managers/InstanceManager.cs b/Torch.Server/Managers/InstanceManager.cs index 05fbbdc..5e63b77 100644 --- a/Torch.Server/Managers/InstanceManager.cs +++ b/Torch.Server/Managers/InstanceManager.cs @@ -41,9 +41,11 @@ namespace Torch.Server.Managers [Dependency] private FilesystemManager _filesystemManager; - public InstanceManager(ITorchBase torchInstance) : base(torchInstance) + private new ITorchServer Torch { get; } + + public InstanceManager(ITorchServer torchInstance) : base(torchInstance) { - + Torch = torchInstance; } public IWorld SelectedWorld => DedicatedConfig.SelectedWorld; @@ -74,7 +76,7 @@ namespace Torch.Server.Managers DedicatedConfig = new ConfigDedicatedViewModel((MyConfigDedicated) MySandboxGame.ConfigDedicated); - var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves")); + var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.InstancePath, "Saves")); foreach (var f in worldFolders) { @@ -226,7 +228,7 @@ namespace Torch.Server.Managers { if (!((TorchServer)Torch).HasRun) { - DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME)); + DedicatedConfig.Save(Path.Combine(Torch.InstancePath, CONFIG_NAME)); Log.Info("Saved dedicated config."); } diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs index a51ff6e..179bf8d 100644 --- a/Torch.Server/Program.cs +++ b/Torch.Server/Program.cs @@ -1,17 +1,5 @@ using System; -using System.Diagnostics; using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.ServiceProcess; -using System.Text; -using System.Threading; -#if TORCH_SERVICE -using Microsoft.VisualBasic.Devices; -#endif -using NLog; -using NLog.Fluent; using NLog.Targets; using Torch.Utils; @@ -19,19 +7,17 @@ namespace Torch.Server { internal static class Program { - /// - /// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail. - /// - [STAThread] public static void Main(string[] args) { + var isService = Environment.GetEnvironmentVariable("TORCH_SERVICE") + ?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false; Target.Register(nameof(LogViewerTarget)); //Ensures that all the files are downloaded in the Torch directory. - var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory!.FullName; - var binDir = Path.Combine(workingDir, "DedicatedServer64"); - Directory.SetCurrentDirectory(workingDir); + var workingDir = AppContext.BaseDirectory; + var binDir = Path.Combine(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir, "DedicatedServer64"); + Directory.SetCurrentDirectory(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir); - if (Directory.Exists(binDir)) + if (!isService && Directory.Exists(binDir)) foreach (var file in Directory.GetFiles(binDir, "System.*.dll")) { File.Delete(file); @@ -49,11 +35,71 @@ namespace Torch.Server } #endif - var initializer = new Initializer(workingDir); - if (!initializer.Initialize(args)) - return; + var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance"; + string instancePath; + + if (Path.IsPathRooted(instanceName)) + { + instancePath = instanceName; + instanceName = Path.GetDirectoryName(instanceName); + } + else + { + instancePath = Path.GetFullPath(instanceName); + } - initializer.Run(); + var oldTorchCfg = Path.Combine(workingDir, "Torch.cfg"); + var torchCfg = Path.Combine(instancePath, "Torch.cfg"); + + if (File.Exists(oldTorchCfg)) + File.Move(oldTorchCfg, torchCfg, true); + + var config = Persistent.Load(torchCfg); + config.Data.InstanceName = instanceName; + config.Data.InstancePath = instancePath; + if (!config.Data.Parse(args)) + { + Console.WriteLine("Invalid arguments"); + Environment.Exit(1); + } + + var handler = new UnhandledExceptionHandler(config.Data, isService); + AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException; + + var initializer = new Initializer(workingDir, config); + if (!initializer.Initialize(args)) + Environment.Exit(1); + + CopyNative(binDir); + initializer.Run(isService, instanceName, instancePath); + } + + private static void CopyNative(string binPath) + { + var apiSource = Path.Combine(binPath, "steam_api64.dll"); + var apiTarget = Path.Combine(AppContext.BaseDirectory, "steam_api64.dll"); + if (!File.Exists(apiTarget)) + { + File.Copy(apiSource, apiTarget); + } + else if (File.GetLastWriteTime(apiTarget) < File.GetLastWriteTime(binPath)) + { + File.Delete(apiTarget); + File.Copy(apiSource, apiTarget); + } + + var havokSource = Path.Combine(binPath, "Havok.dll"); + var havokTarget = Path.Combine(AppContext.BaseDirectory, "Havok.dll"); + + if (!File.Exists(havokTarget)) + { + File.Copy(havokSource, havokTarget); + } + else if (File.GetLastWriteTime(havokTarget) < File.GetLastWriteTime(havokSource)) + { + File.Delete(havokTarget); + File.Copy(havokSource, havokTarget); + } } } } diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index d6e2bbc..ca88df6 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -5,31 +5,22 @@ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} true publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true 0 1.0.0.%2a - false false - true Torch Server Torch Copyright © Torch API 2017 false - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + ..\bin\$(Platform)\$(Configuration)\ true False en x64 Debug;Release AnyCPU + false + en Torch.Server.Program @@ -169,8 +160,7 @@ + + - - - \ No newline at end of file diff --git a/Torch.Server/Torch.Server.csproj.DotSettings b/Torch.Server/Torch.Server.csproj.DotSettings new file mode 100644 index 0000000..6e7fff8 --- /dev/null +++ b/Torch.Server/Torch.Server.csproj.DotSettings @@ -0,0 +1,2 @@ + + No \ No newline at end of file diff --git a/Torch.Server/TorchConfig.cs b/Torch.Server/TorchConfig.cs index fa0cc52..9068afe 100644 --- a/Torch.Server/TorchConfig.cs +++ b/Torch.Server/TorchConfig.cs @@ -17,9 +17,7 @@ namespace Torch.Server public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate; public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate; - - private string _instancePath = Path.GetFullPath("Instance"); - private string _instanceName = "Instance"; + private bool _autostart; private bool _restartOnCrash; private bool _noGui; @@ -40,21 +38,6 @@ namespace Torch.Server private UGCServiceType _ugcServiceType = UGCServiceType.Steam; private bool _entityManagerEnabled = true; - - /// - [Arg("instancename", "The name of the Torch instance.")] - [Display(Name = "Instance Name", Description = "The name of the Torch instance.", GroupName = "Server")] - public string InstanceName { get => _instanceName; set => Set(value, ref _instanceName); } - - /// - [Arg("instancepath", "Server data folder where saves and mods are stored.")] - [Display(Name = "Instance Path", Description = "Server data folder where saves and mods are stored.", GroupName = "Server")] - public string InstancePath - { - get => _instancePath; - set => Set(value, ref _instancePath); - } - /// [XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")] public bool NoUpdate { get; set; } @@ -81,6 +64,8 @@ namespace Torch.Server [Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")] public bool RestartOnCrash { get => _restartOnCrash; set => Set(value, ref _restartOnCrash); } + public string InstancePath { get; set; } + /// [Arg("nogui", "Do not show the Torch UI.")] [Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")] @@ -94,6 +79,8 @@ namespace Torch.Server [Display(Name = "Update Torch", Description = "Check every start for new versions of torch.", GroupName = "Server")] public bool GetTorchUpdates { get => _getTorchUpdates; set => Set(value, ref _getTorchUpdates); } + public string InstanceName { get; set; } + /// [Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.", GroupName = "Server")] public bool GetPluginUpdates { get => _getPluginUpdates; set => Set(value, ref _getPluginUpdates); } diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index e2d3ab1..47529fa 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -55,8 +55,10 @@ namespace Torch.Server //Here to trigger rebuild /// - public TorchServer(TorchConfig config) : base(config) + public TorchServer(ITorchConfig config, string instancePath, string instanceName) : base(config) { + InstancePath = instancePath; + InstanceName = instanceName; DedicatedInstance = new InstanceManager(this); AddManager(DedicatedInstance); if (config.EntityManagerEnabled) @@ -116,7 +118,7 @@ namespace Torch.Server public InstanceManager DedicatedInstance { get; } /// - public string InstanceName => Config?.InstanceName; + public string InstanceName { get; } /// protected override uint SteamAppId => 244850; @@ -130,7 +132,7 @@ namespace Torch.Server public event Action Initialized; /// - public string InstancePath => Config?.InstancePath; + public string InstancePath { get; } public int OnlinePlayers { get => _players; private set => SetValue(ref _players, value); } @@ -141,10 +143,10 @@ namespace Torch.Server MySandboxGame.IsDedicated = true; base.Init(); Managers.GetManager().SessionStateChanged += OnSessionStateChanged; - GetManager().LoadInstance(Config.InstancePath); + GetManager().LoadInstance(InstancePath); CanRun = true; Initialized?.Invoke(this); - Log.Info($"Initialized server '{Config.InstanceName}' at '{Config.InstancePath}'"); + Log.Info($"Initialized server '{InstanceName}' at '{InstancePath}'"); } /// diff --git a/Torch.Server/UnhandledExceptionHandler.cs b/Torch.Server/UnhandledExceptionHandler.cs new file mode 100644 index 0000000..86a831d --- /dev/null +++ b/Torch.Server/UnhandledExceptionHandler.cs @@ -0,0 +1,49 @@ +using System; +using System.Diagnostics; +using System.Threading; +using NLog; +using VRage; + +namespace Torch.Server; + +internal class UnhandledExceptionHandler +{ + private readonly TorchConfig _config; + private readonly bool _isService; + private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); + + public UnhandledExceptionHandler(TorchConfig config, bool isService) + { + _config = config; + _isService = isService; + } + + internal void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (Debugger.IsAttached) + return; + var ex = (Exception)e.ExceptionObject; + Log.Fatal(ex.ToStringDemystified()); + LogManager.Flush(); + + if (_isService) + Environment.Exit(1); + + if (_config.RestartOnCrash) + { + Console.WriteLine("Restarting in 5 seconds."); + Thread.Sleep(5000); + var exe = typeof(Program).Assembly.Location; + _config.WaitForPID = Environment.ProcessId.ToString(); + Process.Start(exe, _config.ToString()); + } + else + { + MyVRage.Platform.Windows.MessageBox( + "Torch encountered a fatal error and needs to close. Please check the logs for details.", + "Fatal exception", MessageBoxOptions.OkOnly); + } + + Environment.Exit(1); + } +} \ No newline at end of file diff --git a/Torch.sln b/Torch.sln index e44265d..b1cf872 100644 --- a/Torch.sln +++ b/Torch.sln @@ -12,6 +12,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}" ProjectSection(SolutionItems) = preProject NLog.config = NLog.config + Dockerfile = Dockerfile EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}" diff --git a/Torch/Patches/KeenLogPatch.cs b/Torch/Patches/KeenLogPatch.cs index c6e6e8c..4bfc23c 100644 --- a/Torch/Patches/KeenLogPatch.cs +++ b/Torch/Patches/KeenLogPatch.cs @@ -46,6 +46,9 @@ namespace Torch.Patches [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Init))] private static MethodInfo _logInit; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Close))] + private static MethodInfo _logClose; #pragma warning restore 649 @@ -64,6 +67,7 @@ namespace Torch.Patches context.GetPattern(_logWriteLineOptions).AddPrefix(nameof(PrefixWriteLineOptions)); context.GetPattern(_logInit).AddPrefix(nameof(PrefixInit)); + context.GetPattern(_logClose).AddPrefix(nameof(PrefixClose)); } [ReflectedMethod(Name = "GetIdentByThread")] @@ -81,6 +85,8 @@ namespace Torch.Patches return _getIndentByThread(MyLog.Default, Environment.CurrentManagedThreadId); } + private static bool PrefixClose() => false; + private static bool PrefixInit(MyLog __instance, StringBuilder appVersionString) { __instance.WriteLine("Log Started"); diff --git a/Torch/Persistent.cs b/Torch/Persistent.cs index 9bbd641..050c730 100644 --- a/Torch/Persistent.cs +++ b/Torch/Persistent.cs @@ -26,7 +26,7 @@ namespace Torch public T Data { get => _data; - private set + private init { if (_data is INotifyPropertyChanged npc1) npc1.PropertyChanged -= OnPropertyChanged; diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index a7d20f7..a5eac3c 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -6,7 +6,7 @@ Copyright © Torch API 2017 false true - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + ..\bin\$(Platform)\$(Configuration)\ True False x64 diff --git a/Versioning/version.ps1 b/Versioning/version.ps1 deleted file mode 100644 index 184a208..0000000 --- a/Versioning/version.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -$buildSalt = $Env:BUILD_NUMBER -$branchName = $Env:BRANCH_NAME -$gitSimpleVersion = git describe --tags --abbrev=0 -$simpleVersionStandard = echo $gitSimpleVersion | Select-String -Pattern "([0-9]+)\.([0-9]+)\.([0-9]+)" | % {$_.Matches} | %{$_.Groups[1].Value+"."+$_.Groups[2].Value+"."+$_.Groups[3].Value} -$dotNetVersion = "$simpleVersionStandard.$buildSalt" -$infoVersion = -join(("$gitSimpleVersion" -replace "([0-9]+)\.([0-9]+)\.([0-9]+)","$dotNetVersion"), "-", "$branchName") - -$fileContent = @" -using System.Reflection; - -[assembly: AssemblyVersion("$dotNetVersion")] -[assembly: AssemblyInformationalVersion("$infoVersion")] -"@ - -echo $fileContent | Set-Content "$PSScriptRoot/AssemblyVersion.cs" - -echo "$infoVersion" \ No newline at end of file