Compare commits

...

4 Commits

Author SHA1 Message Date
zznty
15be85b4f5 forced lang version to 10 (as ryo requested)
all libs are now packed into directory
jenkins file is back
some directives to get net48 supported too
2022-03-16 20:34:01 +07:00
zznty
cf5c00ce0e fixed steamcmd path does not follow TORCH_GAME_PATH env variable 2022-03-07 13:25:38 +07:00
zznty
9c185d5577 fixed mods not being properly cleared from bulk edit
fixed bulk edit crash if input format is invalid
2022-03-05 20:19:01 +07:00
LTP
8b6c401531 fixed plugin dependencies resolution 2022-03-03 22:30:55 +07:00
35 changed files with 440 additions and 283 deletions

5
Jenkins/get-version.ps1 Normal file
View File

@@ -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"

View File

@@ -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

55
Jenkinsfile vendored Normal file
View File

@@ -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
]]
])
}
*/
}

View File

@@ -0,0 +1,30 @@
using System.IO;
namespace Torch.API;
public interface IApplicationContext
{
/// <summary>
/// Directory contains torch binaries.
/// </summary>
public DirectoryInfo TorchDirectory { get; }
/// <summary>
/// Root directory for all game files.
/// </summary>
public DirectoryInfo GameFilesDirectory { get; }
/// <summary>
/// Directory contains game binaries.
/// </summary>
public DirectoryInfo GameBinariesDirectory { get; }
/// <summary>
/// Current instance directory.
/// </summary>
public DirectoryInfo InstanceDirectory { get; }
/// <summary>
/// Current instance name.
/// </summary>
public string InstanceName { get; }
/// <summary>
/// Application running in service mode.
/// </summary>
public bool IsService { get; }
}

View File

@@ -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

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch API</AssemblyTitle>
<Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWpf>True</UseWpf>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
@@ -19,6 +19,9 @@
<ItemGroup>
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="SemanticVersioning" Version="2.0.0" />
<PackageReference Include="System.Text.Json" Version="6.0.2" Condition="$(TargetFramework) == 'net48'" />
<PackageReference Include="System.Net.Http" Version="4.3.4" Condition="$(TargetFramework) == 'net48'" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" Condition="$(TargetFramework) == 'net48'" />
</ItemGroup>
<ItemGroup>
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
@@ -99,7 +102,4 @@
<SpecificVersion>False</SpecificVersion>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
#if NETFRAMEWORK
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
public class IsExternalInit
{
}
#endif

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Server Tests</AssemblyTitle>
<Product>Torch</Product>
@@ -27,9 +28,6 @@
<SpecificVersion>False</SpecificVersion>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch.Server\Torch.Server.csproj" />

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>

View File

@@ -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<TorchConfig> ConfigPersistent { get; }
public TorchConfig Config => ConfigPersistent?.Data;
public TorchServer Server => _server;
public Initializer(string basePath, Persistent<TorchConfig> torchConfig)
public Initializer(Persistent<TorchConfig> 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,

View File

@@ -40,7 +40,13 @@ namespace Torch.Server.Managers
protected abstract EntityControlViewModel Create(EntityViewModel evm);
#if NETFRAMEWORK
[ReflectedGetter(Name = "Keys")]
private static readonly Func<ConditionalWeakTable<EntityViewModel, EntityControlViewModel>, ICollection<EntityViewModel>> WeakTableKeys = null!;
internal IEnumerable<EntityViewModel> Keys => WeakTableKeys(_models);
#else
internal IEnumerable<EntityViewModel> Keys => _models.Select(b => b.Key);
#endif
internal EntityControlViewModel GetOrCreate(EntityViewModel evm)
{

View File

@@ -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);
var context = CreateApplicationContext();
if (!isService && Directory.Exists(binDir))
foreach (var file in Directory.GetFiles(binDir, "System.*.dll"))
{
File.Delete(file);
}
SetupLogging();
// 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
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<TorchConfig>.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<LogViewerTarget>(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<LogViewerTarget>(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);
}
}
}

View File

@@ -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

View File

@@ -2,11 +2,10 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<PublishUrl>publish\</PublishUrl>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<AssemblyTitle>Torch Server</AssemblyTitle>
<Product>Torch</Product>
@@ -14,13 +13,16 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWPF>true</UseWPF>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
<IsPackable>false</IsPackable>
<NeutralLanguage>en</NeutralLanguage>
<BeautyLibsDir>torch64</BeautyLibsDir>
<NoBeautyFlag>True</NoBeautyFlag>
<ForceBeauty>True</ForceBeauty>
</PropertyGroup>
<PropertyGroup>
<StartupObject>Torch.Server.Program</StartupObject>
@@ -40,6 +42,7 @@
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" />
<PackageReference Include="nulastudio.NetCoreBeauty" Version="1.2.9.3" />
</ItemGroup>
<ItemGroup>
<Reference Include="HavokWrapper, Version=1.0.6051.28726, Culture=neutral, processorArchitecture=AMD64">
@@ -143,7 +146,6 @@
<Compile Remove="ServerManager.cs" />
<Compile Remove="ViewModels\SessionSettingsViewModel1.cs" />
<Compile Remove="Views\WorldSelectControl.xaml.cs" />
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />

View File

@@ -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))
#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 sb = new StringBuilder();
foreach (var frame in clrThread.EnumerateStackTrace())
{
var runtime = target.ClrVersions[0].CreateRuntime();
var clrThread = runtime.Threads.First(b => b.ManagedThreadId == thread.ManagedThreadId);
var sb = new StringBuilder();
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

View File

@@ -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

View File

@@ -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}";
}
}
}

View File

@@ -130,17 +130,17 @@ namespace Torch.Server.Views
//blocking
editor.Edit<string>(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);

View File

@@ -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

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Tests</AssemblyTitle>
<Product>Torch</Product>
@@ -8,7 +9,6 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<OutputPath>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\</OutputPath>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
@@ -27,13 +27,6 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch\Torch.csproj" />

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>

View File

@@ -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}

View File

@@ -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;
}
/// <inheritdoc />
public DirectoryInfo TorchDirectory { get; }
/// <inheritdoc />
public DirectoryInfo GameFilesDirectory { get; }
/// <inheritdoc />
public DirectoryInfo GameBinariesDirectory { get; }
/// <inheritdoc />
public DirectoryInfo InstanceDirectory { get; }
/// <inheritdoc />
public string InstanceName { get; }
/// <inheritdoc />
public bool IsService { get; }
}

View File

@@ -1,8 +1,10 @@
using System;
#if !NETFRAMEWORK
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -11,6 +13,8 @@ using System.Reflection.Emit;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.CodeAnalysis;
using ProtoBuf;
using ProtoBuf.Meta;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
@@ -55,9 +59,9 @@ namespace Torch.Patches
typeof(ImmutableArray).Assembly.Location,
typeof(PropertyChangedEventArgs).Assembly.Location,
typeof(TypeConverter).Assembly.Location,
typeof(System.Diagnostics.TraceSource).Assembly.Location,
typeof(ProtoBuf.Meta.RuntimeTypeModel).Assembly.Location,
typeof(ProtoBuf.ProtoMemberAttribute).Assembly.Location,
typeof(TraceSource).Assembly.Location,
typeof(RuntimeTypeModel).Assembly.Location,
typeof(ProtoMemberAttribute).Assembly.Location,
Path.Combine(MyFileSystem.ExePath, "Sandbox.Game.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Common.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Graphics.dll"),
@@ -108,3 +112,4 @@ namespace Torch.Patches
}
}
}
#endif

View File

@@ -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);
}
}
}

View File

@@ -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));
}

View File

@@ -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

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch</AssemblyTitle>
<Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright>
@@ -8,7 +9,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWpf>True</UseWpf>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>

View File

@@ -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;
@@ -19,6 +20,43 @@ namespace Torch.Utils
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()
{

View File

@@ -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;
}
}
}

View File

@@ -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<TargetAttribute>() is { }))
{

View File

@@ -1,4 +0,0 @@
using System.Reflection;
[assembly: AssemblyVersion("0.0.0.0")]
[assembly: AssemblyInformationalVersion("v0.0.0-dev")]