diff --git a/Jenkins/get-version.ps1 b/Jenkins/get-version.ps1
new file mode 100644
index 0000000..8650739
--- /dev/null
+++ b/Jenkins/get-version.ps1
@@ -0,0 +1,5 @@
+$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}
+Write-Host "$simpleVersionStandard.$buildSalt"
\ No newline at end of file
diff --git a/Jenkins/jenkins-grab-se.ps1 b/Jenkins/jenkins-grab-se.ps1
new file mode 100644
index 0000000..ea820af
--- /dev/null
+++ b/Jenkins/jenkins-grab-se.ps1
@@ -0,0 +1,26 @@
+pushd
+
+$steamData = "C:/Steam/Data/"
+$steamCMDPath = "C:/Steam/steamcmd/"
+$steamCMDZip = "C:/Steam/steamcmd.zip"
+
+if (!(Test-Path $steamData)) {
+ mkdir "$steamData"
+}
+if (!(Test-Path $steamCMDPath)) {
+ if (!(Test-Path $steamCMDZip)) {
+ Invoke-WebRequest -OutFile $steamCMDZip https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip
+ }
+ Expand-Archive $steamCMDZip -DestinationPath $steamCMDPath
+}
+& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740" "+quit"
+
+$dataPath = $steamData.Replace("/", "\");
+$contentPath = "$dataPath\Content";
+if (Test-Path $contentPath) {
+ Remove-Item -LiteralPath $contentPath -Force -Recurse
+}
+
+cmd /S /C mklink /J .\GameBinaries $dataPath\DedicatedServer64
+
+popd
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..39ae44a
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,55 @@
+def packageAndArchive(buildMode, packageName) {
+ zipFile = "bin\\${packageName}.zip"
+ packageDir = "publish"
+
+ bat 'powershell -Command { Compress-Archive -Path ${packageDir}\\* -DestinationPath ${zipFile} }'
+ archiveArtifacts artifacts: zipFile, caseSensitive: false, onlyIfSuccessful: true
+}
+
+node('windows') {
+ stage('Checkout') {
+ checkout scm
+ bat 'git pull https://github.com/TorchAPI/Torch/ ${env.BRANCH_NAME} --tags'
+ }
+
+ stage('Acquire SE') {
+ bat 'powershell -File Jenkins/jenkins-grab-se.ps1'
+ }
+
+ stage('Build') {
+ dotnetVersion = bat(returnStdout: true, script: '@powershell -NonInteractive -NoLogo -NoProfile -File Jenkins/get-version.ps1').trim()
+ infoVersion = "${dotnetVersion}-${env.BRANCH_NAME}"
+ currentBuild.description = infoVersion
+
+ bat 'dotnet publish .\\Torch.Server\\Torch.Server.csproj -p:PackageVersion=${dotnetVersion} -p:InformationalVersion=${infoVersion} --self-contained -f net6-windows -r win-x64 -c Release -o .\\publish\\'
+ }
+
+ 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/IApplicationContext.cs b/Torch.API/IApplicationContext.cs
new file mode 100644
index 0000000..5744592
--- /dev/null
+++ b/Torch.API/IApplicationContext.cs
@@ -0,0 +1,30 @@
+using System.IO;
+namespace Torch.API;
+
+public interface IApplicationContext
+{
+ ///
+ /// Directory contains torch binaries.
+ ///
+ public DirectoryInfo TorchDirectory { get; }
+ ///
+ /// Root directory for all game files.
+ ///
+ public DirectoryInfo GameFilesDirectory { get; }
+ ///
+ /// Directory contains game binaries.
+ ///
+ public DirectoryInfo GameBinariesDirectory { get; }
+ ///
+ /// Current instance directory.
+ ///
+ public DirectoryInfo InstanceDirectory { get; }
+ ///
+ /// Current instance name.
+ ///
+ public string InstanceName { get; }
+ ///
+ /// Application running in service mode.
+ ///
+ public bool IsService { get; }
+}
diff --git a/Torch.API/Properties/AssemblyInfo.cs b/Torch.API/Properties/AssemblyInfo.cs
deleted file mode 100644
index a2627e8..0000000
--- a/Torch.API/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Torch API")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Torch")]
-[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-#if DEBUG
-[assembly: AssemblyConfiguration("Debug")]
-#else
-[assembly: AssemblyConfiguration("Release")]
-#endif
\ No newline at end of file
diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj
index ae63a79..4ae358c 100644
--- a/Torch.API/Torch.API.csproj
+++ b/Torch.API/Torch.API.csproj
@@ -1,6 +1,7 @@
net6-windows
+ 10
Torch API
Torch
Copyright © Torch API 2017
@@ -19,6 +20,9 @@
+
+
+
diff --git a/Torch.API/Utils/IsExternalInit.cs b/Torch.API/Utils/IsExternalInit.cs
new file mode 100644
index 0000000..58e2c72
--- /dev/null
+++ b/Torch.API/Utils/IsExternalInit.cs
@@ -0,0 +1,8 @@
+#if NETFRAMEWORK
+// ReSharper disable once CheckNamespace
+namespace System.Runtime.CompilerServices;
+
+public class IsExternalInit
+{
+}
+#endif
diff --git a/Torch.API/WebAPI/JenkinsQuery.cs b/Torch.API/WebAPI/JenkinsQuery.cs
index a5be97f..d9d099b 100644
--- a/Torch.API/WebAPI/JenkinsQuery.cs
+++ b/Torch.API/WebAPI/JenkinsQuery.cs
@@ -68,7 +68,11 @@ namespace Torch.API.WebAPI
return false;
}
var s = await h.Content.ReadAsStreamAsync();
+#if !NETFRAMEWORK
await using var fs = new FileStream(path, FileMode.Create);
+#else
+ using var fs = new FileStream(path, FileMode.Create);
+#endif
await s.CopyToAsync(fs);
return true;
}
diff --git a/Torch.API/WebAPI/PluginQuery.cs b/Torch.API/WebAPI/PluginQuery.cs
index d095be3..4309dde 100644
--- a/Torch.API/WebAPI/PluginQuery.cs
+++ b/Torch.API/WebAPI/PluginQuery.cs
@@ -78,7 +78,11 @@ namespace Torch.API.WebAPI
if(File.Exists(path))
File.Delete(path);
+#if NETFRAMEWORK
+ using var f = File.Create(path);
+#else
await using var f = File.Create(path);
+#endif
await s.CopyToAsync(f);
}
catch (Exception ex)
diff --git a/Torch.Server.Tests/Properties/AssemblyInfo.cs b/Torch.Server.Tests/Properties/AssemblyInfo.cs
deleted file mode 100644
index 8891763..0000000
--- a/Torch.Server.Tests/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Torch Server Tests")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Torch")]
-[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-#if DEBUG
-[assembly: AssemblyConfiguration("Debug")]
-#else
-[assembly: AssemblyConfiguration("Release")]
-#endif
\ No newline at end of file
diff --git a/Torch.Server.Tests/Torch.Server.Tests.csproj b/Torch.Server.Tests/Torch.Server.Tests.csproj
index 3c3b379..9bfa78f 100644
--- a/Torch.Server.Tests/Torch.Server.Tests.csproj
+++ b/Torch.Server.Tests/Torch.Server.Tests.csproj
@@ -1,6 +1,7 @@
net6-windows
+ 10
1591,0649
Torch Server Tests
Torch
@@ -27,9 +28,6 @@
False
-
-
-
diff --git a/Torch.Server.Tests/app.config b/Torch.Server.Tests/app.config
deleted file mode 100644
index cf4f4e1..0000000
--- a/Torch.Server.Tests/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs
index f99d13f..83616e9 100644
--- a/Torch.Server/Initializer.cs
+++ b/Torch.Server/Initializer.cs
@@ -29,19 +29,14 @@ namespace Torch.Server
private const string STEAMCMD_DIR = "steamcmd";
private const string STEAMCMD_ZIP = "temp.zip";
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 const string STEAMCMD_ARGS = "+force_install_dir \"{0}\" +login anonymous +app_update 298740 +quit";
private TorchServer _server;
internal Persistent ConfigPersistent { get; }
public TorchConfig Config => ConfigPersistent?.Data;
public TorchServer Server => _server;
- public Initializer(string basePath, Persistent torchConfig)
+ public Initializer(Persistent torchConfig)
{
Instance = this;
ConfigPersistent = torchConfig;
@@ -88,11 +83,11 @@ quit";
return true;
}
- public void Run(bool isService, string instanceName, string instancePath)
+ public void Run()
{
- _server = new TorchServer(Config, instancePath, instanceName);
+ _server = new TorchServer(Config, ApplicationContext.Current.InstanceDirectory.FullName, ApplicationContext.Current.InstanceName);
- if (isService || Config.NoGui)
+ if (ApplicationContext.Current.IsService || Config.NoGui)
{
_server.Init();
_server.Start();
@@ -140,10 +135,6 @@ quit";
Directory.CreateDirectory(path);
}
- var runScriptPath = Path.Combine(path, RUNSCRIPT_FILE);
- if (!File.Exists(runScriptPath))
- File.WriteAllText(runScriptPath, RUNSCRIPT);
-
var steamCmdExePath = Path.Combine(path, STEAMCMD_EXE);
if (!File.Exists(steamCmdExePath))
{
@@ -166,8 +157,9 @@ quit";
}
log.Info("Checking for DS updates.");
- var steamCmdProc = new ProcessStartInfo(steamCmdExePath, "+runscript runscript.txt")
+ var steamCmdProc = new ProcessStartInfo(steamCmdExePath)
{
+ Arguments = string.Format(STEAMCMD_ARGS, Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? "../"),
WorkingDirectory = path,
UseShellExecute = false,
RedirectStandardOutput = true,
diff --git a/Torch.Server/Managers/EntityControlManager.cs b/Torch.Server/Managers/EntityControlManager.cs
index b121fdf..d5500ba 100644
--- a/Torch.Server/Managers/EntityControlManager.cs
+++ b/Torch.Server/Managers/EntityControlManager.cs
@@ -40,7 +40,13 @@ namespace Torch.Server.Managers
protected abstract EntityControlViewModel Create(EntityViewModel evm);
+#if NETFRAMEWORK
+ [ReflectedGetter(Name = "Keys")]
+ private static readonly Func, ICollection> WeakTableKeys = null!;
+ internal IEnumerable Keys => WeakTableKeys(_models);
+#else
internal IEnumerable Keys => _models.Select(b => b.Key);
+#endif
internal EntityControlViewModel GetOrCreate(EntityViewModel evm)
{
diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs
index 82c5ee5..74e14d1 100644
--- a/Torch.Server/Program.cs
+++ b/Torch.Server/Program.cs
@@ -3,6 +3,7 @@ using System.IO;
using NLog;
using NLog.Config;
using NLog.Targets;
+using Torch.API;
using Torch.Utils;
namespace Torch.Server
@@ -12,112 +13,71 @@ namespace Torch.Server
[STAThread]
public static void Main(string[] args)
{
- var isService = Environment.GetEnvironmentVariable("TORCH_SERVICE")
- ?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false;
- //Ensures that all the files are downloaded in the Torch directory.
- var workingDir = AppContext.BaseDirectory;
- var binDir = Path.Combine(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir, "DedicatedServer64");
- Directory.SetCurrentDirectory(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir);
-
- if (!isService && Directory.Exists(binDir))
- foreach (var file in Directory.GetFiles(binDir, "System.*.dll"))
- {
- File.Delete(file);
- }
+ var context = CreateApplicationContext();
- // Breaks on Windows Server 2019
-#if TORCH_SERVICE
- if (!new ComputerInfo().OSFullName.Contains("Server 2019") && !Environment.UserInteractive)
- {
- using (var service = new TorchService(args))
- ServiceBase.Run(service);
- return;
- }
-#endif
+ SetupLogging();
- var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance";
- string instancePath;
-
- if (Path.IsPathRooted(instanceName))
- {
- instancePath = instanceName;
- instanceName = Path.GetDirectoryName(instanceName);
- }
- else
- {
- instancePath = Directory.CreateDirectory(instanceName).FullName;
- }
-
- var oldNlog = Path.Combine(workingDir, "NLog.config");
- var newNlog = Path.Combine(instancePath, "NLog.config");
- if (File.Exists(oldNlog) && !File.ReadAllText(oldNlog).Contains("FlowDocument", StringComparison.Ordinal))
- File.Move(oldNlog, newNlog, true);
- else if (!File.Exists(newNlog))
- using (var f = File.Create(newNlog))
- typeof(Program).Assembly.GetManifestResourceStream("Torch.Server.NLog.config")!.CopyTo(f);
-
- var oldTorchCfg = Path.Combine(workingDir, "Torch.cfg");
- var torchCfg = Path.Combine(instancePath, "Torch.cfg");
+ var oldTorchCfg = Path.Combine(context.TorchDirectory.FullName, "Torch.cfg");
+ var torchCfg = Path.Combine(context.InstanceDirectory.FullName, "Torch.cfg");
if (File.Exists(oldTorchCfg))
- File.Move(oldTorchCfg, torchCfg, true);
+ File.Move(oldTorchCfg, torchCfg);
var config = Persistent.Load(torchCfg);
- config.Data.InstanceName = instanceName;
- config.Data.InstancePath = instancePath;
+ config.Data.InstanceName = context.InstanceName;
+ config.Data.InstancePath = context.InstanceDirectory.FullName;
+
if (!config.Data.Parse(args))
{
Console.WriteLine("Invalid arguments");
Environment.Exit(1);
}
- var handler = new UnhandledExceptionHandler(config.Data, isService);
+ var handler = new UnhandledExceptionHandler(config.Data);
AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
-
- Target.Register(nameof(LogViewerTarget));
- TorchLogManager.RegisterTargets(Environment.GetEnvironmentVariable("TORCH_LOG_EXTENSIONS_PATH") ??
- Path.Combine(instancePath, "LoggingExtensions"));
-
- TorchLogManager.SetConfiguration(new XmlLoggingConfiguration(newNlog));
- var initializer = new Initializer(workingDir, config);
+ var initializer = new Initializer(config);
if (!initializer.Initialize(args))
Environment.Exit(1);
- TorchLauncher.Launch(workingDir, binDir);
+#if DEBUG
+ TorchLauncher.Launch(context.TorchDirectory.FullName, context.GameBinariesDirectory.FullName);
+#else
+ TorchLauncher.Launch(context.TorchDirectory.FullName, Path.Combine(context.TorchDirectory.FullName, "torch64"),
+ context.GameBinariesDirectory.FullName);
+#endif
- CopyNative(binDir);
+ CopyNative();
- initializer.Run(isService, instanceName, instancePath);
+ initializer.Run();
}
- private static void CopyNative(string binPath)
+ private static void CopyNative()
{
var log = LogManager.GetLogger("TorchLauncher");
- var workingDir = new DirectoryInfo(Directory.GetCurrentDirectory());
- if (workingDir.Attributes.HasFlag(FileAttributes.ReadOnly))
+ if (ApplicationContext.Current.GameFilesDirectory.Attributes.HasFlag(FileAttributes.ReadOnly))
{
- log.Warn("Game directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually");
+ log.Warn("Torch directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually");
return;
}
try
{
- var apiSource = Path.Combine(binPath, "steam_api64.dll");
- var apiTarget = Path.Combine(workingDir.FullName, "steam_api64.dll");
+ var apiSource = Path.Combine(ApplicationContext.Current.GameBinariesDirectory.FullName, "steam_api64.dll");
+ var apiTarget = Path.Combine(ApplicationContext.Current.GameFilesDirectory.FullName, "steam_api64.dll");
if (!File.Exists(apiTarget))
{
File.Copy(apiSource, apiTarget);
}
- else if (File.GetLastWriteTime(apiTarget) < File.GetLastWriteTime(binPath))
+ else if (File.GetLastWriteTime(apiTarget) < ApplicationContext.Current.GameBinariesDirectory.LastWriteTime)
{
File.Delete(apiTarget);
File.Copy(apiSource, apiTarget);
}
- var havokSource = Path.Combine(binPath, "Havok.dll");
- var havokTarget = Path.Combine(workingDir.FullName, "Havok.dll");
+ var havokSource = Path.Combine(ApplicationContext.Current.GameBinariesDirectory.FullName, "Havok.dll");
+ var havokTarget = Path.Combine(ApplicationContext.Current.GameFilesDirectory.FullName, "Havok.dll");
if (!File.Exists(havokTarget))
{
@@ -138,5 +98,49 @@ namespace Torch.Server
log.Error(e);
}
}
+
+ private static void SetupLogging()
+ {
+ var oldNlog = Path.Combine(ApplicationContext.Current.TorchDirectory.FullName, "NLog.config");
+ var newNlog = Path.Combine(ApplicationContext.Current.InstanceDirectory.FullName, "NLog.config");
+ if (File.Exists(oldNlog) && !File.ReadAllText(oldNlog).Contains("FlowDocument"))
+ File.Move(oldNlog, newNlog);
+ else if (!File.Exists(newNlog))
+ using (var f = File.Create(newNlog))
+ typeof(Program).Assembly.GetManifestResourceStream("Torch.Server.NLog.config")!.CopyTo(f);
+
+ Target.Register(nameof(LogViewerTarget));
+ TorchLogManager.RegisterTargets(Environment.GetEnvironmentVariable("TORCH_LOG_EXTENSIONS_PATH") ??
+ Path.Combine(ApplicationContext.Current.InstanceDirectory.FullName, "LoggingExtensions"));
+
+ TorchLogManager.SetConfiguration(new XmlLoggingConfiguration(newNlog));
+ }
+
+ private static IApplicationContext CreateApplicationContext()
+ {
+ var isService = Environment.GetEnvironmentVariable("TORCH_SERVICE")
+ ?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false;
+
+ var workingDir = AppContext.BaseDirectory;
+ var gamePath = Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir;
+ var binDir = Path.Combine(gamePath, "DedicatedServer64");
+ Directory.SetCurrentDirectory(gamePath);
+
+ var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance";
+ string instancePath;
+
+ if (Path.IsPathRooted(instanceName))
+ {
+ instancePath = instanceName;
+ instanceName = Path.GetDirectoryName(instanceName);
+ }
+ else
+ {
+ instancePath = Directory.CreateDirectory(instanceName).FullName;
+ }
+
+ return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
+ new(instancePath), instanceName, isService);
+ }
}
}
diff --git a/Torch.Server/Properties/AssemblyInfo.cs b/Torch.Server/Properties/AssemblyInfo.cs
deleted file mode 100644
index 2256788..0000000
--- a/Torch.Server/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Torch Server")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Torch")]
-[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-#if DEBUG
-[assembly: AssemblyConfiguration("Debug")]
-#else
-[assembly: AssemblyConfiguration("Release")]
-#endif
\ No newline at end of file
diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj
index 0ff4c47..5b84758 100644
--- a/Torch.Server/Torch.Server.csproj
+++ b/Torch.Server/Torch.Server.csproj
@@ -2,11 +2,10 @@
Exe
net6-windows
+ 10
{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
true
publish\
- 0
- 1.0.0.%2a
false
Torch Server
Torch
@@ -14,13 +13,16 @@
false
..\bin\$(Platform)\$(Configuration)\
true
- False
en
x64
Debug;Release
AnyCPU
false
en
+
+ torch64
+ True
+ True
Torch.Server.Program
@@ -40,6 +42,7 @@
+
@@ -143,7 +146,6 @@
-
diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs
index 81a9d79..35ab007 100644
--- a/Torch.Server/TorchServer.cs
+++ b/Torch.Server/TorchServer.cs
@@ -221,7 +221,11 @@ namespace Torch.Server
LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe");
+#if NETFRAMEWORK
+ config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
+#else
config.WaitForPID = Environment.ProcessId.ToString();
+#endif
config.TempAutostart = true;
Process.Start(exe, config.ToString());
@@ -370,35 +374,37 @@ namespace Torch.Server
// return stack.ToString();
// Modified from https://www.examplefiles.net/cs/579311
- using (var target = DataTarget.CreateSnapshotAndAttach(Environment.ProcessId))
- {
- var runtime = target.ClrVersions[0].CreateRuntime();
+#if NETFRAMEWORK
+ using var target = DataTarget.CreateSnapshotAndAttach(Process.GetCurrentProcess().Id);
+#else
+ using var target = DataTarget.CreateSnapshotAndAttach(Environment.ProcessId);
+#endif
+ var runtime = target.ClrVersions[0].CreateRuntime();
- var clrThread = runtime.Threads.First(b => b.ManagedThreadId == thread.ManagedThreadId);
+ var clrThread = runtime.Threads.First(b => b.ManagedThreadId == thread.ManagedThreadId);
- var sb = new StringBuilder();
+ var sb = new StringBuilder();
- foreach (var frame in clrThread.EnumerateStackTrace())
+ foreach (var frame in clrThread.EnumerateStackTrace())
+ {
+ sb.Append('\t');
+ switch (frame.Kind)
{
- sb.Append('\t');
- switch (frame.Kind)
- {
- case ClrStackFrameKind.Unknown:
- sb.AppendLine("[Unknown]");
- break;
- case ClrStackFrameKind.ManagedMethod:
- sb.AppendLine(frame.Method?.Signature ?? "[Unable to get method signature]");
- break;
- case ClrStackFrameKind.Runtime:
- sb.AppendLine("[CLR Runtime]");
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(frame.Kind), frame.Kind, "Incorrect value in EnumerateStackTrace");
- }
+ case ClrStackFrameKind.Unknown:
+ sb.AppendLine("[Unknown]");
+ break;
+ case ClrStackFrameKind.ManagedMethod:
+ sb.AppendLine(frame.Method?.Signature ?? "[Unable to get method signature]");
+ break;
+ case ClrStackFrameKind.Runtime:
+ sb.AppendLine("[CLR Runtime]");
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(frame.Kind), frame.Kind, "Incorrect value in EnumerateStackTrace");
}
-
- return sb.ToString();
}
+
+ return sb.ToString();
}
#endregion
diff --git a/Torch.Server/UnhandledExceptionHandler.cs b/Torch.Server/UnhandledExceptionHandler.cs
index 86a831d..7353719 100644
--- a/Torch.Server/UnhandledExceptionHandler.cs
+++ b/Torch.Server/UnhandledExceptionHandler.cs
@@ -9,13 +9,11 @@ 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)
+ public UnhandledExceptionHandler(TorchConfig config)
{
_config = config;
- _isService = isService;
}
internal void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
@@ -26,7 +24,7 @@ internal class UnhandledExceptionHandler
Log.Fatal(ex.ToStringDemystified());
LogManager.Flush();
- if (_isService)
+ if (ApplicationContext.Current.IsService)
Environment.Exit(1);
if (_config.RestartOnCrash)
@@ -34,7 +32,11 @@ internal class UnhandledExceptionHandler
Console.WriteLine("Restarting in 5 seconds.");
Thread.Sleep(5000);
var exe = typeof(Program).Assembly.Location;
+#if NETFRAMEWORK
+ _config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
+#else
_config.WaitForPID = Environment.ProcessId.ToString();
+#endif
Process.Start(exe, _config.ToString());
}
else
diff --git a/Torch.Server/Views/CommandSuggestionsProvider.cs b/Torch.Server/Views/CommandSuggestionsProvider.cs
index 6c18a22..ad89d98 100644
--- a/Torch.Server/Views/CommandSuggestionsProvider.cs
+++ b/Torch.Server/Views/CommandSuggestionsProvider.cs
@@ -32,7 +32,7 @@ public class CommandSuggestionsProvider : ISuggestionProvider
{
if (_commandManager is null || !_commandManager.IsCommand(filter))
yield break;
- var args = filter[1..].Split(' ').ToList();
+ var args = filter.Substring(1).Split(' ').ToList();
var skip = _commandManager.Commands.GetNode(args, out var node);
if (skip == -1)
yield break;
@@ -42,7 +42,7 @@ public class CommandSuggestionsProvider : ISuggestionProvider
{
if (lastArg != node.Name && !subcommandsKey.Contains(lastArg))
continue;
- yield return $"!{string.Join(' ', node.GetPath())} {subcommandsKey}";
+ yield return $"!{string.Join(" ", node.GetPath())} {subcommandsKey}";
}
}
}
\ No newline at end of file
diff --git a/Torch.Server/Views/ModListControl.xaml.cs b/Torch.Server/Views/ModListControl.xaml.cs
index 2a456f1..0273b80 100644
--- a/Torch.Server/Views/ModListControl.xaml.cs
+++ b/Torch.Server/Views/ModListControl.xaml.cs
@@ -130,17 +130,17 @@ namespace Torch.Server.Views
//blocking
editor.Edit(idList, "Mods");
- modList.RemoveAll(m =>
- {
- var mod = m.ToString();
- return idList.Any(mod.Equals);
- });
+ modList.Clear();
modList.AddRange(idList.Select(id =>
{
- var info = new ModItemInfo(ModItemUtils.Create(id));
+ if (!ModItemUtils.TryParse(id, out var item))
+ return null;
+
+ var info = new ModItemInfo(item);
tasks.Add(Task.Run(info.UpdateModInfoAsync));
return info;
- }));
+ }).Where(b => b is not null));
+
_instanceManager.DedicatedConfig.Mods.Clear();
foreach (var mod in modList)
_instanceManager.DedicatedConfig.Mods.Add(mod);
diff --git a/Torch.Tests/Properties/AssemblyInfo.cs b/Torch.Tests/Properties/AssemblyInfo.cs
deleted file mode 100644
index 1551025..0000000
--- a/Torch.Tests/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Torch Tests")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Torch")]
-[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-#if DEBUG
-[assembly: AssemblyConfiguration("Debug")]
-#else
-[assembly: AssemblyConfiguration("Release")]
-#endif
\ No newline at end of file
diff --git a/Torch.Tests/Torch.Tests.csproj b/Torch.Tests/Torch.Tests.csproj
index be718c5..c333fe2 100644
--- a/Torch.Tests/Torch.Tests.csproj
+++ b/Torch.Tests/Torch.Tests.csproj
@@ -1,6 +1,7 @@
net6-windows
+ 10
1591,0649
Torch Tests
Torch
@@ -8,7 +9,6 @@
false
MinimumRecommendedRules.ruleset
$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\
- False
x64
Debug;Release
AnyCPU
@@ -27,13 +27,6 @@
-
-
-
-
-
-
-
diff --git a/Torch.Tests/app.config b/Torch.Tests/app.config
deleted file mode 100644
index cf4f4e1..0000000
--- a/Torch.Tests/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Torch.sln b/Torch.sln
index e44265d..093e776 100644
--- a/Torch.sln
+++ b/Torch.sln
@@ -12,17 +12,13 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}"
ProjectSection(SolutionItems) = preProject
NLog.config = NLog.config
+ Jenkinsfile = Jenkinsfile
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server.Tests", "Torch.Server.Tests\Torch.Server.Tests.csproj", "{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning", "{762F6A0D-55EF-4173-8CDE-309D183F40C4}"
- ProjectSection(SolutionItems) = preProject
- Versioning\AssemblyVersion.cs = Versioning\AssemblyVersion.cs
- EndProjectSection
-EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Torch.Mod", "Torch.Mod\Torch.Mod.shproj", "{3CE4D2E9-B461-4F19-8233-F87E0DFDDD74}"
EndProject
Global
@@ -56,7 +52,6 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {762F6A0D-55EF-4173-8CDE-309D183F40C4} = {7AD02A71-1D4C-48F9-A8C1-789A5512424F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E}
diff --git a/Torch/ApplicationContext.cs b/Torch/ApplicationContext.cs
new file mode 100644
index 0000000..e3a4b26
--- /dev/null
+++ b/Torch/ApplicationContext.cs
@@ -0,0 +1,32 @@
+using System.IO;
+using Torch.API;
+namespace Torch;
+
+public class ApplicationContext : IApplicationContext
+{
+ public static IApplicationContext Current { get; private set; }
+ public ApplicationContext(DirectoryInfo torchDirectory, DirectoryInfo gameFilesDirectory, DirectoryInfo gameBinariesDirectory,
+ DirectoryInfo instanceDirectory, string instanceName, bool isService)
+ {
+ TorchDirectory = torchDirectory;
+ GameFilesDirectory = gameFilesDirectory;
+ GameBinariesDirectory = gameBinariesDirectory;
+ InstanceDirectory = instanceDirectory;
+ InstanceName = instanceName;
+ IsService = isService;
+ Current = this;
+ }
+
+ ///
+ public DirectoryInfo TorchDirectory { get; }
+ ///
+ public DirectoryInfo GameFilesDirectory { get; }
+ ///
+ public DirectoryInfo GameBinariesDirectory { get; }
+ ///
+ public DirectoryInfo InstanceDirectory { get; }
+ ///
+ public string InstanceName { get; }
+ ///
+ public bool IsService { get; }
+}
diff --git a/Torch/Plugins/AssemblyRewriter.cs b/Torch/Plugins/AssemblyRewriter.cs
index 6721ec4..e2a006d 100644
--- a/Torch/Plugins/AssemblyRewriter.cs
+++ b/Torch/Plugins/AssemblyRewriter.cs
@@ -1,34 +1,52 @@
using System.Collections.Generic;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
-using NLog;
namespace Torch.Plugins;
internal static class AssemblyRewriter
{
- private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
-
- private static readonly IAssemblyResolver Resolver;
+ private static readonly ZipResolver _zipResolver;
+ private static readonly DefaultAssemblyResolver _defaultResolver;
static AssemblyRewriter()
{
- var resolver = new DefaultAssemblyResolver();
- Resolver = resolver;
- resolver.AddSearchDirectory(Directory.GetCurrentDirectory());
- resolver.AddSearchDirectory(Path.Combine(Directory.GetCurrentDirectory(), "DedicatedServer64"));
+ _defaultResolver = new();
+ _zipResolver = new(_defaultResolver);
+ _defaultResolver.AddSearchDirectory(Directory.GetCurrentDirectory());
+ _defaultResolver.AddSearchDirectory(Path.Combine(Directory.GetCurrentDirectory(), "DedicatedServer64"));
}
- public static Assembly ProcessWeavers(this Stream stream)
+ public static Assembly ProcessWeavers(this Stream stream, ZipArchive archive)
{
+ _zipResolver.Archive = archive;
using var assStream = new MemoryStream();
stream.CopyTo(assStream);
assStream.Position = 0;
- using var module = ModuleDefinition.ReadModule(assStream, new()
+ var ass = ProcessInternal(assStream, _zipResolver);
+ _zipResolver.Archive = null;
+ return ass;
+ }
+
+ public static Assembly ProcessWeavers(this Stream stream, string path)
+ {
+ _defaultResolver.AddSearchDirectory(path);
+ using var assStream = new MemoryStream();
+ stream.CopyTo(assStream);
+ assStream.Position = 0;
+ var ass = ProcessInternal(assStream, _defaultResolver);
+ _defaultResolver.RemoveSearchDirectory(path);
+ return ass;
+ }
+
+ private static Assembly ProcessInternal(Stream inputStream, IAssemblyResolver resolver)
+ {
+ using var module = ModuleDefinition.ReadModule(inputStream, new()
{
- AssemblyResolver = Resolver
+ AssemblyResolver = _zipResolver
});
foreach (var fieldDefinition in FindAllToRewrite(module))
{
@@ -47,4 +65,40 @@ internal static class AssemblyRewriter
private static bool HasValidAttributes(FieldDefinition definition) =>
definition.CustomAttributes.Any(b => b.AttributeType.Name.Contains("Reflected") || b.AttributeType.Name == "DependencyAttribute");
+
+ private class ZipResolver : IAssemblyResolver
+ {
+ private readonly IAssemblyResolver _fallbackResolver;
+ public ZipArchive Archive { get; set; }
+
+ public ZipResolver(IAssemblyResolver fallbackResolver)
+ {
+ _fallbackResolver = fallbackResolver;
+ }
+
+ public void Dispose()
+ {
+ _fallbackResolver.Dispose();
+ }
+
+ public AssemblyDefinition Resolve(AssemblyNameReference name)
+ {
+ return Resolve(name, new());
+ }
+
+ public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
+ {
+ var fileName = $"{name.Name}.dll";
+
+ if (Archive.Entries.FirstOrDefault(entry => entry.Name == fileName) is not { } archiveEntry)
+ return _fallbackResolver.Resolve(name, parameters);
+
+ using var stream = archiveEntry.Open();
+ using var memStream = new MemoryStream();
+ stream.CopyTo(memStream);
+ memStream.Position = 0;
+
+ return AssemblyDefinition.ReadAssembly(memStream, parameters);
+ }
+ }
}
\ No newline at end of file
diff --git a/Torch/Plugins/PluginManager.cs b/Torch/Plugins/PluginManager.cs
index f0d637b..8c2869a 100644
--- a/Torch/Plugins/PluginManager.cs
+++ b/Torch/Plugins/PluginManager.cs
@@ -360,7 +360,7 @@ namespace Torch.Managers
using var stream = entry.Open();
- assemblies.Add(stream.ProcessWeavers());
+ assemblies.Add(stream.ProcessWeavers(zipFile));
}
}
else
@@ -378,7 +378,7 @@ namespace Torch.Managers
// continue;
using var stream = File.OpenRead(file);
- assemblies.Add(stream.ProcessWeavers());
+ assemblies.Add(stream.ProcessWeavers(item.Path));
}
diff --git a/Torch/Properties/AssemblyInfo.cs b/Torch/Properties/AssemblyInfo.cs
index 21a30ba..e69de29 100644
--- a/Torch/Properties/AssemblyInfo.cs
+++ b/Torch/Properties/AssemblyInfo.cs
@@ -1,17 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Torch")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Torch")]
-[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-#if DEBUG
-[assembly: AssemblyConfiguration("Debug")]
-#else
-[assembly: AssemblyConfiguration("Release")]
-#endif
\ No newline at end of file
diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj
index b762b88..87199d8 100644
--- a/Torch/Torch.csproj
+++ b/Torch/Torch.csproj
@@ -1,6 +1,7 @@
net6-windows
+ 10
Torch
Torch
Copyright © Torch API 2017
@@ -8,7 +9,6 @@
true
..\bin\$(Platform)\$(Configuration)\
True
- False
x64
Debug;Release
AnyCPU
diff --git a/Torch/Utils/ModItemUtils.cs b/Torch/Utils/ModItemUtils.cs
index 14e6a62..4022094 100644
--- a/Torch/Utils/ModItemUtils.cs
+++ b/Torch/Utils/ModItemUtils.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using Sandbox.Engine.Networking;
using Torch.API;
@@ -18,6 +19,43 @@ namespace Torch.Utils
var arr = str.Split('-');
return new MyObjectBuilder_Checkpoint.ModItem(ulong.Parse(arr[0]), arr[1]);
}
+
+ public static bool TryParse(string str, out MyObjectBuilder_Checkpoint.ModItem item)
+ {
+ item = default;
+
+ var arr = str.Split('-');
+
+ if (arr.Length is 0 or > 2)
+ return false;
+
+ if (!ulong.TryParse(arr[0], out var id))
+ return false;
+
+ if (arr.Length == 1 || !TryParseServiceName(arr[1], out var serviceName))
+ serviceName = GetDefaultServiceName();
+
+ item = new(id, serviceName);
+ return true;
+ }
+
+ public static bool TryParseServiceName(string str, out string serviceName)
+ {
+ if (str.Equals("steam", StringComparison.OrdinalIgnoreCase))
+ {
+ serviceName = "Steam";
+ return true;
+ }
+ if (str.Equals("mod.io", StringComparison.OrdinalIgnoreCase) ||
+ str.Equals("eos", StringComparison.OrdinalIgnoreCase))
+ {
+ serviceName = "mod.io";
+ return true;
+ }
+
+ serviceName = null;
+ return false;
+ }
//because KEEEN!
public static string GetDefaultServiceName()
diff --git a/Torch/Utils/TorchLauncher.cs b/Torch/Utils/TorchLauncher.cs
index a4cc6a6..93a8fed 100644
--- a/Torch/Utils/TorchLauncher.cs
+++ b/Torch/Utils/TorchLauncher.cs
@@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Runtime.Loader;
-using System.Text;
-using System.Threading.Tasks;
-using ProtoBuf;
-using Torch.API;
namespace Torch.Utils
{
@@ -23,7 +17,13 @@ namespace Torch.Utils
try
{
var name = AssemblyName.GetAssemblyName(file);
- Assemblies.TryAdd(name.Name ?? name.FullName.Split(',')[0], file);
+ var key = name.Name ?? name.FullName.Split(',')[0];
+#if NETFRAMEWORK
+ if (!Assemblies.ContainsKey(key))
+ Assemblies.Add(key, file);
+#else
+ Assemblies.TryAdd(key, file);
+#endif
}
catch (BadImageFormatException)
{
@@ -37,7 +37,7 @@ namespace Torch.Utils
private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{
var name = args.Name;
- return Assemblies.TryGetValue(name[..name.IndexOf(',')], out var path) ? Assembly.LoadFrom(path) : null;
+ return Assemblies.TryGetValue(name.IndexOf(',') > 0 ? name.Substring(0, name.IndexOf(',')) : name, out var path) ? Assembly.LoadFrom(path) : null;
}
}
}
diff --git a/Torch/Utils/TorchLogManager.cs b/Torch/Utils/TorchLogManager.cs
index 5adea49..bfe67cb 100644
--- a/Torch/Utils/TorchLogManager.cs
+++ b/Torch/Utils/TorchLogManager.cs
@@ -2,7 +2,9 @@
using System.IO;
using System.Linq;
using System.Reflection;
+#if !NETFRAMEWORK
using System.Runtime.Loader;
+#endif
using NLog;
using NLog.Config;
using NLog.Targets;
@@ -11,7 +13,9 @@ namespace Torch.Utils;
public static class TorchLogManager
{
+#if !NETFRAMEWORK
private static AssemblyLoadContext LoadContext = new("TorchLog");
+#endif
public static LoggingConfiguration Configuration { get; private set; }
@@ -26,7 +30,12 @@ public static class TorchLogManager
{
if (!Directory.Exists(dir)) return;
- foreach (var type in Directory.EnumerateFiles(dir, "*.dll").Select(LoadContext.LoadFromAssemblyPath)
+ foreach (var type in Directory.EnumerateFiles(dir, "*.dll")
+#if NETFRAMEWORK
+ .Select(Assembly.LoadFile)
+#else
+ .Select(LoadContext.LoadFromAssemblyPath)
+#endif
.SelectMany(b => b.ExportedTypes)
.Where(b => b.GetCustomAttribute() is { }))
{
diff --git a/Versioning/AssemblyVersion.cs b/Versioning/AssemblyVersion.cs
deleted file mode 100644
index c663bea..0000000
--- a/Versioning/AssemblyVersion.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-using System.Reflection;
-
-[assembly: AssemblyVersion("0.0.0.0")]
-[assembly: AssemblyInformationalVersion("v0.0.0-dev")]