Compare commits

...

36 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
z__
92db8994ef fixed ScriptCompilerPatch 2022-02-28 17:16:23 +07:00
z__
aee36661fd fixed keen log indent 2022-02-28 17:15:33 +07:00
z__
feda84fac8 Merge remote-tracking branch 'zznty/master' 2022-02-18 17:27:52 +07:00
z__
2503cd6372 fixed NRE in edit roles button 2022-02-18 17:27:40 +07:00
zznty
f321034eeb Update README.md 2022-02-16 10:08:20 +07:00
z__
7573684520 remove unnecessary information that breaks common suffixes 2022-02-12 02:02:09 +07:00
z__
223eaa9fd0 rebase online players counter to events 2022-02-12 01:58:00 +07:00
z__
d138a46f25 forced stop & restart in separate thread 2022-02-12 01:42:46 +07:00
z__
ce2bbd4a61 fix not implemented property 2022-02-12 01:40:38 +07:00
z__
85dd4b46b8 mods loading fixes 2022-02-12 00:40:10 +07:00
z__
166a9d1dbe logs limit 2022-02-12 00:37:00 +07:00
z__
dfc15354ca replace log config if it's from previous versions 2022-02-11 23:04:03 +07:00
z__
57c977deb4 remove load order display 2022-02-11 21:26:47 +07:00
z__
f6cdc2fe79 removed missing dockerfile in solution files 2022-02-11 15:35:53 +07:00
z__
f42b9c6674 dont forget to call 2022-02-09 20:26:37 +07:00
zznty
227557f421 Merge branch 'fixes' 2022-02-09 20:25:46 +07:00
z__
0632f68aaf it was needed, but with some checks 2022-02-09 20:25:09 +07:00
z__
0a9f299527 we dont need it anymore 2022-02-09 15:34:13 +07:00
z__
67f25ab20b transform ctor to extension method 2022-02-08 23:36:39 +07:00
z__
ad19a7dc9e better exception for invalid operand type 2022-02-08 23:26:25 +07:00
z__
dd854a159a ok high iq solutions is bad for compatibility 2022-02-04 15:11:49 +07:00
z__
879a373e6a added possibility to call SetConfiguration again if needed 2022-02-04 14:42:37 +07:00
z__
ec1b017946 added nlog custom targets assemblies loading 2022-02-04 14:33:32 +07:00
z__
cf75210304 fixed game analytics logger crash 2022-02-04 13:44:04 +07:00
z__
3696f18714 move nlog config to instance and move default to resources 2022-02-04 13:25:56 +07:00
z__
1f7e4e869d final fixes for warfare 2 2022-02-04 12:09:17 +07:00
z__
ba5b611994 add STA thread back, not being added automatically on local build 2022-02-04 10:38:47 +07:00
z__
2bcf79efdd to trigger autobuild to fix warfare 2 update 2022-02-04 09:47:22 +07:00
z__
d5c101bf19 i believe we're done 2022-02-03 19:49:16 +07:00
z__
e9841b4de1 now should work 2022-02-03 19:43:54 +07:00
z__
aea0011683 maybe 2022-02-03 19:41:13 +07:00
z__
d10528f9fe fixed RID 2022-02-03 19:39:07 +07:00
52 changed files with 810 additions and 770 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

@@ -1,4 +1,3 @@
[![Discord](https://discordapp.com/api/guilds/929141809769226271/widget.png)](https://discord.gg/trK6sYdcNE)
[![Build status](https://ci.appveyor.com/api/projects/status/us64kmwshl50f5a3/branch/master?svg=true)](https://ci.appveyor.com/project/zznty/torch/branch/master) [![Build status](https://ci.appveyor.com/api/projects/status/us64kmwshl50f5a3/branch/master?svg=true)](https://ci.appveyor.com/project/zznty/torch/branch/master)
# What is Torch? # What is Torch?
@@ -17,6 +16,10 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
* .NET 6.0 runtime * .NET 6.0 runtime
* Additional options & features * Additional options & features
### Discord
If you have any questions or issues please join our [discord](https://discord.gg/UyYFSe3TyQ)
### Installation ### Installation
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files. * Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.

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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch API</AssemblyTitle> <AssemblyTitle>Torch API</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright> <Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath> <OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWpf>True</UseWpf> <UseWpf>True</UseWpf>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
@@ -19,6 +19,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="SemanticVersioning" Version="2.0.0" /> <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>
<ItemGroup> <ItemGroup>
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64"> <Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
@@ -99,7 +102,4 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
</ItemGroup>
</Project> </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; return false;
} }
var s = await h.Content.ReadAsStreamAsync(); var s = await h.Content.ReadAsStreamAsync();
#if !NETFRAMEWORK
await using var fs = new FileStream(path, FileMode.Create); await using var fs = new FileStream(path, FileMode.Create);
#else
using var fs = new FileStream(path, FileMode.Create);
#endif
await s.CopyToAsync(fs); await s.CopyToAsync(fs);
return true; return true;
} }

View File

@@ -78,7 +78,11 @@ namespace Torch.API.WebAPI
if(File.Exists(path)) if(File.Exists(path))
File.Delete(path); File.Delete(path);
#if NETFRAMEWORK
using var f = File.Create(path);
#else
await using var f = File.Create(path); await using var f = File.Create(path);
#endif
await s.CopyToAsync(f); await s.CopyToAsync(f);
} }
catch (Exception ex) 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<NoWarn>1591,0649</NoWarn> <NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Server Tests</AssemblyTitle> <AssemblyTitle>Torch Server Tests</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
@@ -27,9 +28,6 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" /> <ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch.Server\Torch.Server.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_DIR = "steamcmd";
private const string STEAMCMD_ZIP = "temp.zip"; private const string STEAMCMD_ZIP = "temp.zip";
private static readonly string STEAMCMD_EXE = "steamcmd.exe"; private static readonly string STEAMCMD_EXE = "steamcmd.exe";
private static readonly string RUNSCRIPT_FILE = "runscript.txt"; private const string STEAMCMD_ARGS = "+force_install_dir \"{0}\" +login anonymous +app_update 298740 +quit";
private const string RUNSCRIPT = @"force_install_dir ../
login anonymous
app_update 298740
quit";
private TorchServer _server; private TorchServer _server;
internal Persistent<TorchConfig> ConfigPersistent { get; } internal Persistent<TorchConfig> ConfigPersistent { get; }
public TorchConfig Config => ConfigPersistent?.Data; public TorchConfig Config => ConfigPersistent?.Data;
public TorchServer Server => _server; public TorchServer Server => _server;
public Initializer(string basePath, Persistent<TorchConfig> torchConfig) public Initializer(Persistent<TorchConfig> torchConfig)
{ {
Instance = this; Instance = this;
ConfigPersistent = torchConfig; ConfigPersistent = torchConfig;
@@ -88,11 +83,11 @@ quit";
return true; 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.Init();
_server.Start(); _server.Start();
@@ -140,10 +135,6 @@ quit";
Directory.CreateDirectory(path); 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); var steamCmdExePath = Path.Combine(path, STEAMCMD_EXE);
if (!File.Exists(steamCmdExePath)) if (!File.Exists(steamCmdExePath))
{ {
@@ -166,8 +157,9 @@ quit";
} }
log.Info("Checking for DS updates."); 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, WorkingDirectory = path,
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = true, RedirectStandardOutput = true,

View File

@@ -17,7 +17,7 @@ namespace Torch.Server
{ {
public IList<LogEntry> LogEntries { get; set; } public IList<LogEntry> LogEntries { get; set; }
public SynchronizationContext TargetContext { get; set; } public SynchronizationContext TargetContext { get; set; }
private readonly int _maxLines = 1000; private const int MAX_LINES = 1000;
/// <inheritdoc /> /// <inheritdoc />
protected override void Write(LogEventInfo logEvent) protected override void Write(LogEventInfo logEvent)
@@ -29,6 +29,11 @@ namespace Torch.Server
{ {
var logEvent = (LogEventInfo) state; var logEvent = (LogEventInfo) state;
LogEntries?.Add(new(logEvent.TimeStamp, Layout.Render(logEvent), LogLevelColors[logEvent.Level])); LogEntries?.Add(new(logEvent.TimeStamp, Layout.Render(logEvent), LogLevelColors[logEvent.Level]));
if (LogEntries is not {Count: > MAX_LINES}) return;
for (var i = 0; LogEntries.Count > MAX_LINES; i++)
{
LogEntries.RemoveAt(i);
}
} }
private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new() private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new()

View File

@@ -40,7 +40,13 @@ namespace Torch.Server.Managers
protected abstract EntityControlViewModel Create(EntityViewModel evm); 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); internal IEnumerable<EntityViewModel> Keys => _models.Select(b => b.Key);
#endif
internal EntityControlViewModel GetOrCreate(EntityViewModel evm) internal EntityControlViewModel GetOrCreate(EntityViewModel evm)
{ {

View File

@@ -234,9 +234,11 @@ namespace Torch.Server.Managers
try try
{ {
var world = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == DedicatedConfig.LoadWorld) ?? new WorldViewModel(DedicatedConfig.LoadWorld); var world = DedicatedConfig.SelectedWorld;
world.Checkpoint.SessionName = DedicatedConfig.WorldName; world.Checkpoint.SessionName = string.IsNullOrEmpty(DedicatedConfig.WorldName)
? Path.GetDirectoryName(DedicatedConfig.LoadWorld)
: DedicatedConfig.WorldName;
world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings; world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings;
world.WorldConfiguration.Mods.Clear(); world.WorldConfiguration.Mods.Clear();
@@ -268,7 +270,7 @@ namespace Torch.Server.Managers
private void ValidateInstance(string path) private void ValidateInstance(string path)
{ {
Directory.CreateDirectory(Path.Combine(path, "Saves")); Directory.CreateDirectory(Path.Combine(path, "Saves"));
Directory.CreateDirectory(Path.Combine(path, "Mods")); // Directory.CreateDirectory(Path.Combine(path, "Mods"));
var configPath = Path.Combine(path, CONFIG_NAME); var configPath = Path.Combine(path, CONFIG_NAME);
if (File.Exists(configPath)) if (File.Exists(configPath))
return; return;

View File

@@ -33,6 +33,9 @@ public static class CheckpointLoadPatch
return false; return false;
} }
world.KeenCheckpoint.Settings = world.WorldConfiguration.Settings;
world.KeenCheckpoint.Mods = world.WorldConfiguration.Mods;
__result = world.Checkpoint; __result = world.Checkpoint;
return false; return false;
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
@@ -8,6 +9,7 @@ using NLog;
using Sandbox; using Sandbox;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
namespace Torch.Patches namespace Torch.Patches
{ {
@@ -17,12 +19,14 @@ namespace Torch.Patches
[PatchShim] [PatchShim]
public static class WorldLoadExceptionPatch public static class WorldLoadExceptionPatch
{ {
private static readonly ILogger _log = LogManager.GetCurrentClassLogger(); private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
[ReflectedMethodInfo(typeof(MySandboxGame), "InitQuickLaunch")]
private static MethodInfo _quickLaunchMethod = null!;
public static void Patch(PatchContext ctx) public static void Patch(PatchContext ctx)
{ {
ctx.GetPattern(typeof(MySandboxGame).GetMethod("InitQuickLaunch", BindingFlags.Instance | BindingFlags.NonPublic)) ctx.GetPattern(_quickLaunchMethod).AddTranspiler(nameof(Transpile));
.Transpilers.Add(typeof(WorldLoadExceptionPatch).GetMethod(nameof(Transpile), BindingFlags.Static | BindingFlags.NonPublic));
} }
private static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> method) private static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> method)
@@ -30,19 +34,19 @@ namespace Torch.Patches
var msil = method.ToList(); var msil = method.ToList();
for (var i = 0; i < msil.Count; i++) for (var i = 0; i < msil.Count; i++)
{ {
if (msil[i].TryCatchOperations.All(x => x.Type != MsilTryCatchOperationType.BeginClauseBlock)) var instruction = msil[i];
continue; if (instruction.IsLocalStore() && instruction.Operand is MsilOperandInline.MsilOperandLocal {Value.Index: 19} operand)
for (; i < msil.Count; i++)
{ {
if (msil[i].OpCode != OpCodes.Leave) msil.InsertRange(i + 1, new []
continue; {
operand.Instruction.CopyWith(OpCodes.Ldloc_S),
msil[i] = new MsilInstruction(OpCodes.Rethrow); new MsilInstruction(OpCodes.Call).InlineValue(new Action<Exception>(LogFatal).Method)
break; });
} }
} }
return msil; return msil;
} }
private static void LogFatal(Exception e) => Log.Fatal(e.ToStringDemystified());
} }
} }

View File

@@ -1,39 +1,130 @@
using System; using System;
using System.IO; using System.IO;
using NLog;
using NLog.Config;
using NLog.Targets; using NLog.Targets;
using Torch.API;
using Torch.Utils; using Torch.Utils;
namespace Torch.Server namespace Torch.Server
{ {
internal static class Program internal static class Program
{ {
[STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{
var context = CreateApplicationContext();
SetupLogging();
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);
var config = Persistent<TorchConfig>.Load(torchCfg);
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);
AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
var initializer = new Initializer(config);
if (!initializer.Initialize(args))
Environment.Exit(1);
#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();
initializer.Run();
}
private static void CopyNative()
{
var log = LogManager.GetLogger("TorchLauncher");
if (ApplicationContext.Current.GameFilesDirectory.Attributes.HasFlag(FileAttributes.ReadOnly))
{
log.Warn("Torch directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually");
return;
}
try
{
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) < ApplicationContext.Current.GameBinariesDirectory.LastWriteTime)
{
File.Delete(apiTarget);
File.Copy(apiSource, apiTarget);
}
var havokSource = Path.Combine(ApplicationContext.Current.GameBinariesDirectory.FullName, "Havok.dll");
var havokTarget = Path.Combine(ApplicationContext.Current.GameFilesDirectory.FullName, "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);
}
}
catch (UnauthorizedAccessException)
{
// file is being used by another process, probably previous torch has not been closed yet
}
catch (Exception e)
{
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") var isService = Environment.GetEnvironmentVariable("TORCH_SERVICE")
?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false; ?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false;
Target.Register<LogViewerTarget>(nameof(LogViewerTarget));
//Ensures that all the files are downloaded in the Torch directory.
var workingDir = AppContext.BaseDirectory; var workingDir = AppContext.BaseDirectory;
var binDir = Path.Combine(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir, "DedicatedServer64"); var gamePath = Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir;
Directory.SetCurrentDirectory(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir); var binDir = Path.Combine(gamePath, "DedicatedServer64");
Directory.SetCurrentDirectory(gamePath);
if (!isService && Directory.Exists(binDir))
foreach (var file in Directory.GetFiles(binDir, "System.*.dll"))
{
File.Delete(file);
}
TorchLauncher.Launch(workingDir, binDir);
// 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"; var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance";
string instancePath; string instancePath;
@@ -45,61 +136,11 @@ namespace Torch.Server
} }
else else
{ {
instancePath = Path.GetFullPath(instanceName); instancePath = Directory.CreateDirectory(instanceName).FullName;
} }
var oldTorchCfg = Path.Combine(workingDir, "Torch.cfg"); return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
var torchCfg = Path.Combine(instancePath, "Torch.cfg"); new(instancePath), instanceName, isService);
if (File.Exists(oldTorchCfg))
File.Move(oldTorchCfg, torchCfg, true);
var config = Persistent<TorchConfig>.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);
}
} }
} }
} }

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

View File

@@ -107,7 +107,7 @@ namespace Torch.Server
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set => SetValue(ref _elapsedPlayTime, value); } public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set => SetValue(ref _elapsedPlayTime, value); }
/// <inheritdoc /> /// <inheritdoc />
public Thread GameThread { get; private set; } public Thread GameThread => MySandboxGame.Static?.UpdateThread;
/// <inheritdoc /> /// <inheritdoc />
public bool IsRunning { get => _isRunning; set => SetValue(ref _isRunning, value); } public bool IsRunning { get => _isRunning; set => SetValue(ref _isRunning, value); }
@@ -178,6 +178,17 @@ namespace Torch.Server
{ {
if (State == ServerState.Stopped) if (State == ServerState.Stopped)
Log.Error("Server is already stopped"); Log.Error("Server is already stopped");
if (Thread.CurrentThread == GameThread)
new Thread(StopInternal)
{
Name = "Stopping Thread"
}.Start();
else
StopInternal();
}
private void StopInternal()
{
Log.Info("Stopping server."); Log.Info("Stopping server.");
base.Stop(); base.Stop();
Log.Info("Server stopped."); Log.Info("Server stopped.");
@@ -185,6 +196,7 @@ namespace Torch.Server
State = ServerState.Stopped; State = ServerState.Stopped;
IsRunning = false; IsRunning = false;
CanRun = true; CanRun = true;
SimulationRatio = 0;
} }
/// <summary> /// <summary>
@@ -202,17 +214,26 @@ namespace Torch.Server
Log.Info("Ejected all players from server for restart."); Log.Info("Ejected all players from server for restart.");
} }
Stop(); new Thread(() =>
// TODO clone this {
var config = (TorchConfig)Config; StopInternal();
LogManager.Flush(); var config = (TorchConfig)Config;
LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe"); string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe");
config.WaitForPID = Environment.ProcessId.ToString(); #if NETFRAMEWORK
config.TempAutostart = true; config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
Process.Start(exe, config.ToString()); #else
config.WaitForPID = Environment.ProcessId.ToString();
#endif
config.TempAutostart = true;
Process.Start(exe, config.ToString());
Environment.Exit(0); Environment.Exit(0);
})
{
Name = "Restart thread"
}.Start();
} }
[SuppressPropertyChangedWarnings] [SuppressPropertyChangedWarnings]
@@ -228,11 +249,23 @@ namespace Torch.Server
if (newState == TorchSessionState.Loaded) if (newState == TorchSessionState.Loaded)
{ {
_multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>(); _multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>();
_multiplayerManagerDedicated.PlayerJoined += MultiplayerManagerDedicatedOnPlayerJoined;
_multiplayerManagerDedicated.PlayerLeft += MultiplayerManagerDedicatedOnPlayerLeft;
CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands)); CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands));
ModCommunication.Register(); ModCommunication.Register();
} }
} }
private void MultiplayerManagerDedicatedOnPlayerLeft(IPlayer player)
{
OnlinePlayers--;
}
private void MultiplayerManagerDedicatedOnPlayerJoined(IPlayer player)
{
OnlinePlayers++;
}
/// <inheritdoc /> /// <inheritdoc />
public override void Init(object gameInstance) public override void Init(object gameInstance)
{ {
@@ -251,7 +284,6 @@ namespace Torch.Server
SimulationRatio = Math.Min(Sync.ServerSimulationRatio, 1); SimulationRatio = Math.Min(Sync.ServerSimulationRatio, 1);
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds)); var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
ElapsedPlayTime = elapsed; ElapsedPlayTime = elapsed;
OnlinePlayers = _multiplayerManagerDedicated?.Players.Count ?? 0;
if (_watchdog == null && Config.TickTimeout > 0) if (_watchdog == null && Config.TickTimeout > 0)
{ {
@@ -342,46 +374,37 @@ namespace Torch.Server
// return stack.ToString(); // return stack.ToString();
// Modified from https://www.examplefiles.net/cs/579311 // 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(); sb.Append('\t');
switch (frame.Kind)
var clrThread = runtime.Threads.First(b => b.ManagedThreadId == thread.ManagedThreadId);
var sb = new StringBuilder();
sb.AppendFormat(
"ManagedThreadId: {0}, Name: {1}, OSThreadId: {2}, Thread: IsAlive: {3}, IsBackground: {4}, IsThreadPool: {5}",
thread.ManagedThreadId,
thread.Name,
clrThread.OSThreadId,
thread.IsAlive,
thread.IsBackground,
thread.IsThreadPoolThread)
.AppendLine();
sb.AppendLine("Stack trace:");
foreach (var frame in clrThread.EnumerateStackTrace())
{ {
sb.Append('\t'); case ClrStackFrameKind.Unknown:
switch (frame.Kind) sb.AppendLine("[Unknown]");
{ break;
case ClrStackFrameKind.Unknown: case ClrStackFrameKind.ManagedMethod:
sb.AppendLine("[Unknown]"); sb.AppendLine(frame.Method?.Signature ?? "[Unable to get method signature]");
break; break;
case ClrStackFrameKind.ManagedMethod: case ClrStackFrameKind.Runtime:
sb.AppendLine(frame.Method?.Signature ?? "[Unable to get method signature]"); sb.AppendLine("[CLR Runtime]");
break; break;
case ClrStackFrameKind.Runtime: default:
sb.AppendLine("[CLR Runtime]"); throw new ArgumentOutOfRangeException(nameof(frame.Kind), frame.Kind, "Incorrect value in EnumerateStackTrace");
break;
default:
throw new ArgumentOutOfRangeException(nameof(frame.Kind), frame.Kind, "Incorrect value in EnumerateStackTrace");
}
} }
return sb.ToString();
} }
return sb.ToString();
} }
#endregion #endregion

View File

@@ -9,13 +9,11 @@ namespace Torch.Server;
internal class UnhandledExceptionHandler internal class UnhandledExceptionHandler
{ {
private readonly TorchConfig _config; private readonly TorchConfig _config;
private readonly bool _isService;
private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
public UnhandledExceptionHandler(TorchConfig config, bool isService) public UnhandledExceptionHandler(TorchConfig config)
{ {
_config = config; _config = config;
_isService = isService;
} }
internal void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) internal void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
@@ -26,7 +24,7 @@ internal class UnhandledExceptionHandler
Log.Fatal(ex.ToStringDemystified()); Log.Fatal(ex.ToStringDemystified());
LogManager.Flush(); LogManager.Flush();
if (_isService) if (ApplicationContext.Current.IsService)
Environment.Exit(1); Environment.Exit(1);
if (_config.RestartOnCrash) if (_config.RestartOnCrash)
@@ -34,7 +32,11 @@ internal class UnhandledExceptionHandler
Console.WriteLine("Restarting in 5 seconds."); Console.WriteLine("Restarting in 5 seconds.");
Thread.Sleep(5000); Thread.Sleep(5000);
var exe = typeof(Program).Assembly.Location; var exe = typeof(Program).Assembly.Location;
#if NETFRAMEWORK
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
#else
_config.WaitForPID = Environment.ProcessId.ToString(); _config.WaitForPID = Environment.ProcessId.ToString();
#endif
Process.Start(exe, _config.ToString()); Process.Start(exe, _config.ToString());
} }
else else

View File

@@ -32,7 +32,7 @@ public class CommandSuggestionsProvider : ISuggestionProvider
{ {
if (_commandManager is null || !_commandManager.IsCommand(filter)) if (_commandManager is null || !_commandManager.IsCommand(filter))
yield break; yield break;
var args = filter[1..].Split(' ').ToList(); var args = filter.Substring(1).Split(' ').ToList();
var skip = _commandManager.Commands.GetNode(args, out var node); var skip = _commandManager.Commands.GetNode(args, out var node);
if (skip == -1) if (skip == -1)
yield break; yield break;
@@ -42,7 +42,7 @@ public class CommandSuggestionsProvider : ISuggestionProvider
{ {
if (lastArg != node.Name && !subcommandsKey.Contains(lastArg)) if (lastArg != node.Name && !subcommandsKey.Contains(lastArg))
continue; continue;
yield return $"!{string.Join(' ', node.GetPath())} {subcommandsKey}"; yield return $"!{string.Join(" ", node.GetPath())} {subcommandsKey}";
} }
} }
} }

View File

@@ -144,22 +144,17 @@ namespace Torch.Server.Views
{ {
//var w = new RoleEditor(_instanceManager.DedicatedConfig.SelectedWorld); //var w = new RoleEditor(_instanceManager.DedicatedConfig.SelectedWorld);
//w.Show(); //w.Show();
var d = new RoleEditor();
var w = _instanceManager.DedicatedConfig.SelectedWorld; var w = _instanceManager.DedicatedConfig.SelectedWorld;
if(w.Checkpoint.PromotedUsers == null) { if (w is null)
w.Checkpoint.PromotedUsers = new VRage.Serialization.SerializableDictionary<ulong, MyPromoteLevel>();
}
if (w == null)
{ {
MessageBox.Show("A world is not selected."); MessageBox.Show("A world is not selected.");
return; return;
} }
if (w.Checkpoint.PromotedUsers == null) w.Checkpoint.PromotedUsers ??= new();
w.Checkpoint.PromotedUsers = new SerializableDictionary<ulong, MyPromoteLevel>();
d.Edit(w.Checkpoint.PromotedUsers.Dictionary); new RoleEditor().Edit(w.Checkpoint.PromotedUsers.Dictionary);
_instanceManager.DedicatedConfig.Administrators = w.Checkpoint.PromotedUsers.Dictionary.Where(k => k.Value >= MyPromoteLevel.Admin).Select(k => k.Key.ToString()).ToList(); _instanceManager.DedicatedConfig.Administrators = w.Checkpoint.PromotedUsers.Dictionary.Where(k => k.Value >= MyPromoteLevel.Admin).Select(k => k.Key.ToString()).ToList();
} }
} }

View File

@@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Threading.Tasks;
using Torch.Server.ViewModels;
using NLog;
using Torch.Collections;
namespace Torch.Server.Views.Converters
{
/// <summary>
/// A converter to get the index of a ModItemInfo object within a collection of ModItemInfo objects
/// </summary>
public class ModToListIdConverter : IMultiValueConverter
{
/// <summary>
/// Converts a ModItemInfo object into its index within a Collection of ModItemInfo objects
/// </summary>
/// <param name="values">
/// Expected to contain a ModItemInfo object at index 0
/// and a Collection of ModItemInfo objects at index 1
/// </param>
/// <param name="targetType">This parameter will be ignored</param>
/// <param name="parameter">This parameter will be ignored</param>
/// <param name="culture"> This parameter will be ignored</param>
/// <returns>the index of the mod within the provided mod list.</returns>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//if (targetType != typeof(int))
// throw new NotSupportedException("ModToIdConverter can only convert mods into int values or vise versa!");
if (values[0] is ModItemInfo mod && values[1] is MtObservableList<ModItemInfo> modList)
{
return modList.IndexOf(mod);
}
else
{
return null;
}
}
/// <summary>
/// It is not supported to reverse this converter
/// </summary>
/// <param name="values"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns>Raises a NotSupportedException</returns>
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("ModToIdConverter can not convert back!");
}
}
}

View File

@@ -5,8 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels" xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800">
MouseMove="UserControl_MouseMove">
<!--<UserControl.DataContext> <!--<UserControl.DataContext>
<viewModels:ConfigDedicatedViewModel /> <viewModels:ConfigDedicatedViewModel />
</UserControl.DataContext>--> </UserControl.DataContext>-->
@@ -18,7 +17,7 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<Style TargetType="Grid" x:Key="RootGridStyle"> <Style TargetType="Grid" x:Key="RootGridStyle">
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged, BindingGroupName=RootEnabledBinding}" Value="{x:Null}"> <DataTrigger Binding="{Binding Mode=OneWay, BindingGroupName=RootEnabledBinding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/> <Setter Property="IsEnabled" Value="False"/>
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>
@@ -36,38 +35,25 @@
<RowDefinition Height="80px"/> <RowDefinition Height="80px"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DataGrid Name="ModList" Grid.Column="0" Grid.ColumnSpan="1" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}" <DataGrid Name="ModList" Grid.Column="0" ItemsSource="{Binding Mods}"
Sorting="ModList_Sorting"
SelectionMode="Single" SelectionMode="Single"
SelectionUnit="FullRow" SelectionUnit="FullRow"
AllowDrop="True" AllowDrop="True"
CanUserReorderColumns="False" CanUserReorderColumns="False"
CanUserSortColumns="True" CanUserSortColumns="True"
PreviewMouseLeftButtonDown="ModList_MouseLeftButtonDown"
MouseLeftButtonUp="ModList_MouseLeftButtonUp"
SelectedCellsChanged="ModList_Selected" SelectedCellsChanged="ModList_Selected"
AutoGenerateColumns="False"> AutoGenerateColumns="False">
<!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">--> <!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">-->
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="Load Order"
Width="Auto"
IsReadOnly="True">
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource ModToListIdConverter}" StringFormat="{}{0}">
<Binding />
<Binding ElementName="ModList" Path="DataContext"></Binding>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="Workshop Id" <DataGridTextColumn Header="Workshop Id"
IsReadOnly="True" IsReadOnly="True"
Binding="{Binding PublishedFileId, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}"> Binding="{Binding PublishedFileId}">
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="Name" <DataGridTextColumn Header="Name"
Width="*" Width="*"
IsReadOnly="True" IsReadOnly="True"
Binding="{Binding FriendlyName, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}"> Binding="{Binding FriendlyName}">
</DataGridTextColumn> </DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
<DataGrid.ItemContainerStyle> <DataGrid.ItemContainerStyle>
@@ -88,12 +74,12 @@
</Style> </Style>
</DataGrid.ItemContainerStyle> </DataGrid.ItemContainerStyle>
</DataGrid> </DataGrid>
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838"> <ScrollViewer Grid.Row="0" Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838">
<TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px" <TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px"
Text="{Binding ElementName=ModList, Path=SelectedItem.Description}"> Text="{Binding ElementName=ModList, Path=SelectedItem.Description}">
</TextBlock> </TextBlock>
</ScrollViewer> </ScrollViewer>
<Grid Grid.Row="2" Margin="0 0 0 6px"> <Grid Grid.Row="1" Grid.Column="0" Margin="0 0 0 6px">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -108,10 +94,10 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center" <CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center"
HorizontalAlignment="Left" Margin="6px 0" Grid.Column="0" Grid.Row="0"/> HorizontalAlignment="Left" Margin="6px 0" Grid.Column="0" Grid.Row="0"/>
<Label Content="Show Dependency Mods" Padding="0" Margin="6px 0" Grid.Column="1" VerticalAlignment="Center"/> <Label Content="Show Dependency Mods" Padding="0" Margin="6px 0" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"/>
<Label Content="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left" <Label Content="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left"
VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/> VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
<TextBox Name="AddModIDTextBox" Grid.Column="1" VerticalContentAlignment="Center" <TextBox Name="AddModIdTextBox" Grid.Column="1" VerticalContentAlignment="Center"
HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/> HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/>
<ComboBox Grid.Column="2" Grid.Row="1" x:Name="UgcServiceTypeBox" SelectionChanged="UgcServiceTypeBox_OnSelectionChanged" SelectedValuePath="Value" DisplayMemberPath="Key"/> <ComboBox Grid.Column="2" Grid.Row="1" x:Name="UgcServiceTypeBox" SelectionChanged="UgcServiceTypeBox_OnSelectionChanged" SelectedValuePath="Value" DisplayMemberPath="Key"/>
<Button Content="Add" Grid.Column="3" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/> <Button Content="Add" Grid.Column="3" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/>
@@ -120,6 +106,6 @@
<Button Content="Bulk Edit" Grid.Column="5" Margin="6px 0" Width="60px" Height="40px" Click="BulkButton_OnClick" Grid.Row="1"/> <Button Content="Bulk Edit" Grid.Column="5" Margin="6px 0" Width="60px" Height="40px" Click="BulkButton_OnClick" Grid.Row="1"/>
</Grid> </Grid>
<Button Content="Save Config" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/> <Button Content="Save Config" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -1,32 +1,15 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.CompilerServices;
using System.Windows.Threading;
using VRage.Game;
using NLog; using NLog;
using Sandbox.Engine.Networking;
using Torch.API; using Torch.API;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
using Torch.Server.Annotations;
using Torch.Collections;
using Torch.Utils; using Torch.Utils;
using Torch.Views; using Torch.Views;
@@ -35,14 +18,12 @@ namespace Torch.Server.Views
/// <summary> /// <summary>
/// Interaction logic for ModListControl.xaml /// Interaction logic for ModListControl.xaml
/// </summary> /// </summary>
public partial class ModListControl : UserControl, INotifyPropertyChanged public partial class ModListControl : UserControl
{ {
private static Logger Log = LogManager.GetLogger(nameof(ModListControl)); private static Logger Log = LogManager.GetLogger(nameof(ModListControl));
private InstanceManager _instanceManager; private InstanceManager _instanceManager;
ModItemInfo _draggedMod;
bool _hasOrderChanged = false;
bool _isSortedByLoadOrder = true;
private readonly ITorchConfig _config; private readonly ITorchConfig _config;
private ConfigDedicatedViewModel _viewModel;
//private List<BindingExpression> _bindingExpressions = new List<BindingExpression>(); //private List<BindingExpression> _bindingExpressions = new List<BindingExpression>();
/// <summary> /// <summary>
@@ -51,9 +32,11 @@ namespace Torch.Server.Views
public ModListControl() public ModListControl()
{ {
InitializeComponent(); InitializeComponent();
#pragma warning disable CS0618
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>(); _instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
_config = TorchBase.Instance.Config; _config = TorchBase.Instance.Config;
#pragma warning restore CS0618
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
//var mods = _instanceManager.DedicatedConfig?.Mods; //var mods = _instanceManager.DedicatedConfig?.Mods;
//if( mods != null) //if( mods != null)
// DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>(); // DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>();
@@ -67,39 +50,22 @@ namespace Torch.Server.Views
//Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles)); //Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles));
} }
private void ModListControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
throw new NotImplementedException();
}
private void ResetSorting()
{
CollectionViewSource.GetDefaultView(ModList.ItemsSource).SortDescriptions.Clear();
}
private void _instanceManager_InstanceLoaded(ConfigDedicatedViewModel obj) private void _instanceManager_InstanceLoaded(ConfigDedicatedViewModel obj)
{ {
Dispatcher.Invoke(() => { Dispatcher.InvokeAsync(() =>
DataContext = obj?.Mods ?? new MtObservableList<ModItemInfo>(); {
_viewModel = obj;
DataContext = obj;
UpdateLayout(); UpdateLayout();
((MtObservableList<ModItemInfo>)DataContext).CollectionChanged += OnModlistUpdate; Task.Run(async () =>
if (obj is { }) {
Task.Run(async () => await obj.UpdateAllModInfosAsync();
{ Log.Info("Instance loaded.");
await obj.UpdateAllModInfosAsync(); });
Log.Info("Instance loaded.");
});
}); });
} }
private void OnModlistUpdate(object sender, NotifyCollectionChangedEventArgs e)
{
ModList.Items.Refresh();
//if (e.Action == NotifyCollectionChangedAction.Remove)
// _instanceManager.SaveConfig();
}
private void SaveBtn_OnClick(object sender, RoutedEventArgs e) private void SaveBtn_OnClick(object sender, RoutedEventArgs e)
{ {
_instanceManager.SaveConfig(); _instanceManager.SaveConfig();
@@ -108,31 +74,28 @@ namespace Torch.Server.Views
private void AddBtn_OnClick(object sender, RoutedEventArgs e) private void AddBtn_OnClick(object sender, RoutedEventArgs e)
{ {
if (TryExtractId(AddModIDTextBox.Text, out ulong id)) if (TryExtractId(AddModIdTextBox.Text, out ulong id))
{ {
var mod = new ModItemInfo(ModItemUtils.Create(id, UgcServiceTypeBox.SelectedValue?.ToString())); var mod = new ModItemInfo(ModItemUtils.Create(id, UgcServiceTypeBox.SelectedValue?.ToString()));
_instanceManager.DedicatedConfig.Mods.Add(mod); _instanceManager.DedicatedConfig.Mods.Add(mod);
Task.Run(mod.UpdateModInfoAsync) Task.Run(mod.UpdateModInfoAsync)
.ContinueWith((t) => .ContinueWith(_ =>
{ {
Dispatcher.Invoke(() => _instanceManager.DedicatedConfig.Save();
{
_instanceManager.DedicatedConfig.Save();
});
}); });
AddModIDTextBox.Text = ""; AddModIdTextBox.Text = "";
} }
else else
{ {
AddModIDTextBox.BorderBrush = Brushes.Red; AddModIdTextBox.BorderBrush = Brushes.Red;
Log.Warn("Invalid mod id!"); Log.Warn("Invalid mod id!");
MessageBox.Show("Invalid mod id!"); MessageBox.Show("Invalid mod id!");
} }
} }
private void RemoveBtn_OnClick(object sender, RoutedEventArgs e) private void RemoveBtn_OnClick(object sender, RoutedEventArgs e)
{ {
var modList = ((MtObservableList<ModItemInfo>)DataContext); var modList = _viewModel.Mods;
if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod)) if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod))
modList.Remove(mod); modList.Remove(mod);
} }
@@ -150,113 +113,9 @@ namespace Torch.Server.Views
return success; return success;
} }
private void ModList_Sorting(object sender, DataGridSortingEventArgs e)
{
Log.Info($"Sorting by '{e.Column.Header}'");
if (e.Column == ModList.Columns[0])
{
var dataView = CollectionViewSource.GetDefaultView(ModList.ItemsSource);
dataView.SortDescriptions.Clear();
dataView.Refresh();
_isSortedByLoadOrder = true;
}
else
_isSortedByLoadOrder = false;
}
private void ModList_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//return;
_draggedMod = (ModItemInfo) TryFindRowAtPoint((UIElement) sender, e.GetPosition(ModList))?.DataContext;
//DraggedMod = (ModItemInfo) ModList.SelectedItem;
}
private static DataGridRow TryFindRowAtPoint(UIElement reference, Point point)
{
var element = reference.InputHitTest(point) as DependencyObject;
if (element == null)
return null;
if (element is DataGridRow row)
return row;
else
return TryFindParent<DataGridRow>(element);
}
private static T TryFindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject parent;
if (child == null)
return null;
if (child is ContentElement contentElement)
{
parent = ContentOperations.GetParent(contentElement);
if (parent == null && child is FrameworkContentElement fce)
parent = fce.Parent;
}
else
{
parent = VisualTreeHelper.GetParent(child);
}
if (parent is T result)
return result;
else
return TryFindParent<T>(parent);
}
private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (_draggedMod == null)
return;
if (!_isSortedByLoadOrder)
return;
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
if( targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
{
_hasOrderChanged = true;
var modList = (MtObservableList<ModItemInfo>)DataContext;
modList.Move(modList.IndexOf(targetMod), _draggedMod);
//modList.RemoveAt(modList.IndexOf(_draggedMod));
//modList.Insert(modList.IndexOf(targetMod), _draggedMod);
ModList.Items.Refresh();
ModList.SelectedItem = _draggedMod;
}
}
private void ModList_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!_isSortedByLoadOrder)
{
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
if (targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
{
var msg = "Drag and drop is only available when sorted by load order!";
Log.Warn(msg);
MessageBox.Show(msg);
}
}
//if (DraggedMod != null && HasOrderChanged)
//Log.Info("Dragging over, saving...");
//_instanceManager.SaveConfig();
_draggedMod = null;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ModList_Selected(object sender, SelectedCellsChangedEventArgs e) private void ModList_Selected(object sender, SelectedCellsChangedEventArgs e)
{ {
if (_draggedMod != null) if( e.AddedCells.Count > 0)
ModList.SelectedItem = _draggedMod;
else if( e.AddedCells.Count > 0)
ModList.SelectedItem = e.AddedCells[0].Item; ModList.SelectedItem = e.AddedCells[0].Item;
} }
@@ -265,23 +124,23 @@ namespace Torch.Server.Views
var editor = new CollectionEditor(); var editor = new CollectionEditor();
//let's see just how poorly we can do this //let's see just how poorly we can do this
var modList = ((MtObservableList<ModItemInfo>)DataContext).ToList(); var modList = _viewModel.Mods.ToList();
var idList = modList.Select(m => m.ToString()).ToList(); var idList = modList.Select(m => m.ToString()).ToList();
var tasks = new List<Task>(); var tasks = new List<Task>();
//blocking //blocking
editor.Edit<string>(idList, "Mods"); editor.Edit<string>(idList, "Mods");
modList.RemoveAll(m => modList.Clear();
{
var mod = m.ToString();
return idList.Any(mod.Equals);
});
modList.AddRange(idList.Select(id => 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)); tasks.Add(Task.Run(info.UpdateModInfoAsync));
return info; return info;
})); }).Where(b => b is not null));
_instanceManager.DedicatedConfig.Mods.Clear(); _instanceManager.DedicatedConfig.Mods.Clear();
foreach (var mod in modList) foreach (var mod in modList)
_instanceManager.DedicatedConfig.Mods.Add(mod); _instanceManager.DedicatedConfig.Mods.Add(mod);

View File

@@ -18,7 +18,6 @@
</Style> </Style>
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/> <converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/> <converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
<converters:ModToListIdConverter x:Key="ModToListIdConverter"/>
<converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/> <converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/>
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/> <converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
</ResourceDictionary> </ResourceDictionary>

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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<NoWarn>1591,0649</NoWarn> <NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Tests</AssemblyTitle> <AssemblyTitle>Torch Tests</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
@@ -8,7 +9,6 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<OutputPath>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\</OutputPath> <OutputPath>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\</OutputPath>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
@@ -27,13 +27,6 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </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> <ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" /> <ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch\Torch.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,18 +12,13 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
NLog.config = NLog.config NLog.config = NLog.config
Dockerfile = Dockerfile Jenkinsfile = Jenkinsfile
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server.Tests", "Torch.Server.Tests\Torch.Server.Tests.csproj", "{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server.Tests", "Torch.Server.Tests\Torch.Server.Tests.csproj", "{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}"
EndProject 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}" Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Torch.Mod", "Torch.Mod\Torch.Mod.shproj", "{3CE4D2E9-B461-4F19-8233-F87E0DFDDD74}"
EndProject EndProject
Global Global
@@ -57,7 +52,6 @@ Global
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{762F6A0D-55EF-4173-8CDE-309D183F40C4} = {7AD02A71-1D4C-48F9-A8C1-789A5512424F}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E} 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

@@ -197,7 +197,7 @@ namespace Torch.Managers.PatchManager
lock (_log) lock (_log)
{ {
var instructions = context.Body.Instructions var instructions = context.Body.Instructions
.Select(b => new MsilInstruction(b)).ToList(); .Select(b => b.ToMsilInstruction()).ToList();
LogTarget(PrintModeEnum.Patched, false, "========== Patched method =========="); LogTarget(PrintModeEnum.Patched, false, "========== Patched method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), instructions, true); MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), instructions, true);
LogTarget(PrintModeEnum.Patched, false, gap); LogTarget(PrintModeEnum.Patched, false, gap);

View File

@@ -0,0 +1,136 @@
using System;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
using Torch.Managers.PatchManager.Transpile;
using OperandType = System.Reflection.Emit.OperandType;
namespace Torch.Managers.PatchManager.MSIL;
internal static class InstructionExtensions
{
public static MsilInstruction ToMsilInstruction(this Instruction instruction)
{
static System.Reflection.Emit.Label CreateLabel(int pos)
{
var instance = Activator.CreateInstance(typeof(System.Reflection.Emit.Label),
BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {pos}, null);
if (instance == null)
return default;
return (System.Reflection.Emit.Label) instance;
}
var systemOpCode = MethodContext.OpCodeLookup[instruction.OpCode.Value];
var msil = new MsilInstruction(systemOpCode);
if (instruction.Operand is null || instruction.OpCode.OperandType == Mono.Cecil.Cil.OperandType.InlineNone)
return msil;
var opType = systemOpCode.OperandType;
switch (instruction.Operand)
{
case Instruction targetInstruction when opType is OperandType.InlineBrTarget or OperandType.ShortInlineBrTarget:
msil.Operand = new MsilOperandBrTarget(msil)
{
Target = new MsilLabel(CreateLabel(targetInstruction.Offset))
};
break;
case FieldReference reference when opType == OperandType.InlineField:
msil.Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(msil)
{
Value = reference.ResolveReflection()
};
break;
case int int32 when opType is OperandType.InlineI or OperandType.ShortInlineI:
msil.Operand = new MsilOperandInline.MsilOperandInt32(msil)
{
Value = int32
};
break;
case long int64 when opType is OperandType.InlineI8:
msil.Operand = new MsilOperandInline.MsilOperandInt64(msil)
{
Value = int64
};
break;
case MethodReference methodReference when opType is OperandType.InlineMethod:
msil.Operand = new MsilOperandInline.MsilOperandReflected<MethodBase>(msil)
{
Value = methodReference.ResolveReflection()
};
break;
case double @double when opType is OperandType.InlineR:
msil.Operand = new MsilOperandInline.MsilOperandDouble(msil)
{
Value = @double
};
break;
case null when opType is OperandType.InlineSig:
throw new NotSupportedException("InlineSignature is not supported by instruction converter");
case string @string when opType == OperandType.InlineString:
msil.Operand = new MsilOperandInline.MsilOperandString(msil)
{
Value = @string
};
break;
case Instruction[] targetInstructions when opType is OperandType.InlineSwitch:
msil.Operand = new MsilOperandSwitch(msil)
{
Labels = targetInstructions.Select(b => new MsilLabel(CreateLabel(b.Offset))).ToArray()
};
break;
case MemberReference memberReference when opType is OperandType.InlineTok:
msil.Operand = new MsilOperandInline.MsilOperandReflected<MemberInfo>(msil)
{
Value = memberReference.ResolveReflection()
};
break;
case TypeReference typeReference when opType is OperandType.InlineType:
msil.Operand = new MsilOperandInline.MsilOperandReflected<Type>(msil)
{
Value = typeReference.ResolveReflection()
};
break;
case VariableDefinition variableDefinition when opType is OperandType.InlineVar or OperandType.ShortInlineVar:
if (systemOpCode.IsLocalStore() || systemOpCode.IsLocalLoad() || systemOpCode.IsLocalLoadByRef())
msil.Operand = new MsilOperandInline.MsilOperandLocal(msil)
{
Value = new MsilLocal(variableDefinition.Index)
};
else
msil.Operand = new MsilOperandInline.MsilOperandArgument(msil)
{
Value = new MsilArgument(variableDefinition.Index)
};
break;
case ParameterDefinition parameterDefinition when opType is OperandType.InlineVar or OperandType.ShortInlineVar:
if (systemOpCode.IsLocalStore() || systemOpCode.IsLocalLoad() || systemOpCode.IsLocalLoadByRef())
msil.Operand = new MsilOperandInline.MsilOperandLocal(msil)
{
Value = new MsilLocal(parameterDefinition.Index)
};
else
msil.Operand = new MsilOperandInline.MsilOperandArgument(msil)
{
Value = new MsilArgument(parameterDefinition.Index)
};
break;
case float @float when opType == OperandType.ShortInlineR:
msil.Operand = new MsilOperandInline.MsilOperandSingle(msil)
{
Value = @float
};
break;
#pragma warning disable 618
case null when opType == OperandType.InlinePhi:
#pragma warning restore 618
default:
throw new ArgumentOutOfRangeException(nameof(instruction.Operand), instruction.Operand, "Invalid operand type");
}
return msil;
}
}

View File

@@ -12,6 +12,7 @@ using Mono.Cecil.Cil;
using MonoMod.Utils; using MonoMod.Utils;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
using Torch.Utils; using Torch.Utils;
using VRage.Game.VisualScripting;
using OpCode = System.Reflection.Emit.OpCode; using OpCode = System.Reflection.Emit.OpCode;
using OpCodes = System.Reflection.Emit.OpCodes; using OpCodes = System.Reflection.Emit.OpCodes;
using OperandType = System.Reflection.Emit.OperandType; using OperandType = System.Reflection.Emit.OperandType;
@@ -88,132 +89,6 @@ namespace Torch.Managers.PatchManager.MSIL
} }
} }
public MsilInstruction(Instruction instruction)
{
Label CreateLabel(int pos)
{
var instance = Activator.CreateInstance(typeof(Label),
BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {pos}, null);
if (instance == null)
return default;
return (Label) instance;
}
if (!MethodContext.OpCodeLookup.TryGetValue(instruction.OpCode.Value, out var opCode))
return;
OpCode = opCode;
var opType = opCode.OperandType;
if (opType == OperandType.InlineNone)
{
Operand = null;
return;
}
switch (instruction.Operand)
{
case OperandType.InlineNone:
break;
case Instruction targetInstruction when opType == OperandType.InlineBrTarget || opType == OperandType.ShortInlineBrTarget:
Operand = new MsilOperandBrTarget(this)
{
Target = new MsilLabel(CreateLabel(targetInstruction.Offset))
};
break;
case FieldReference reference when opType == OperandType.InlineField:
Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(this)
{
Value = reference.ResolveReflection()
};
break;
case int int32 when opType == OperandType.InlineI || opType == OperandType.ShortInlineI:
Operand = new MsilOperandInline.MsilOperandInt32(this)
{
Value = int32
};
break;
case long int64 when opType == OperandType.InlineI8:
Operand = new MsilOperandInline.MsilOperandInt64(this)
{
Value = int64
};
break;
case MethodReference methodReference when opType == OperandType.InlineMethod:
Operand = new MsilOperandInline.MsilOperandReflected<MethodBase>(this)
{
Value = methodReference.ResolveReflection()
};
break;
case double @double when opType == OperandType.InlineR:
Operand = new MsilOperandInline.MsilOperandDouble(this)
{
Value = @double
};
break;
case null when opType == OperandType.InlineSig:
throw new NotSupportedException("InlineSignature is not supported by instruction converter");
case string @string when opType == OperandType.InlineString:
Operand = new MsilOperandInline.MsilOperandString(this)
{
Value = @string
};
break;
case Instruction[] targetInstructions when opType == OperandType.InlineSwitch:
Operand = new MsilOperandSwitch(this)
{
Labels = targetInstructions.Select(b => new MsilLabel(CreateLabel(b.Offset))).ToArray()
};
break;
case MemberReference memberReference when opType == OperandType.InlineTok:
Operand = new MsilOperandInline.MsilOperandReflected<MemberInfo>(this)
{
Value = memberReference.ResolveReflection()
};
break;
case TypeReference typeReference when opType == OperandType.InlineType:
Operand = new MsilOperandInline.MsilOperandReflected<Type>(this)
{
Value = typeReference.ResolveReflection()
};
break;
case VariableDefinition variableDefinition when opType == OperandType.InlineVar || opType == OperandType.ShortInlineVar:
if (OpCode.IsLocalStore() || OpCode.IsLocalLoad() || OpCode.IsLocalLoadByRef())
Operand = new MsilOperandInline.MsilOperandLocal(this)
{
Value = new MsilLocal(variableDefinition.Index)
};
else
Operand = new MsilOperandInline.MsilOperandArgument(this)
{
Value = new MsilArgument(variableDefinition.Index)
};
break;
case ParameterDefinition parameterDefinition when opType == OperandType.InlineVar || opType == OperandType.ShortInlineVar:
if (OpCode.IsLocalStore() || OpCode.IsLocalLoad() || OpCode.IsLocalLoadByRef())
Operand = new MsilOperandInline.MsilOperandLocal(this)
{
Value = new MsilLocal(parameterDefinition.Index)
};
else
Operand = new MsilOperandInline.MsilOperandArgument(this)
{
Value = new MsilArgument(parameterDefinition.Index)
};
break;
case float @float when opType == OperandType.ShortInlineR:
Operand = new MsilOperandInline.MsilOperandSingle(this)
{
Value = @float
};
break;
#pragma warning disable 618
case null when opType == OperandType.InlinePhi:
#pragma warning restore 618
default:
throw new ArgumentOutOfRangeException(nameof(instruction.Operand), instruction.Operand, "Invalid operand type");
}
}
/// <summary> /// <summary>
/// Opcode of this instruction /// Opcode of this instruction
/// </summary> /// </summary>
@@ -227,7 +102,7 @@ namespace Torch.Managers.PatchManager.MSIL
/// <summary> /// <summary>
/// The operand for this instruction, or null. /// The operand for this instruction, or null.
/// </summary> /// </summary>
public MsilOperand Operand { get; } public MsilOperand Operand { get; internal set; }
/// <summary> /// <summary>
/// Labels pointing to this instruction. /// Labels pointing to this instruction.
@@ -284,7 +159,11 @@ namespace Torch.Managers.PatchManager.MSIL
type = type.BaseType; type = type.BaseType;
} }
((MsilOperandInline<T>) Operand).Value = o; if (Operand is not MsilOperandInline<T> operandInline)
throw new InvalidOperationException(
$"Type {typeof(T).FullName} is not valid operand for {Operand?.GetType().Signature()}");
operandInline.Value = o;
return this; return this;
} }

View File

@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using NLog; using NLog;
using NLog.Config; using NLog.Config;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches namespace Torch.Patches
{ {
@@ -36,16 +37,13 @@ namespace Torch.Patches
_log.Warn("GALogger constructor is unknown. Logging may not function."); _log.Warn("GALogger constructor is unknown. Logging may not function.");
return; return;
} }
ctx.GetPattern(ctor).Prefixes.Add(typeof(GameAnalyticsPatch).GetMethod(nameof(PatchLogger), ctx.GetPattern(ctor).AddPrefix(nameof(PatchLogger));
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
} }
private static void FixLogging() private static void FixLogging()
{ {
TorchLogManager.RestoreGlobalConfiguration();
_setLogger(null, LogManager.GetLogger("GameAnalytics")); _setLogger(null, LogManager.GetLogger("GameAnalytics"));
if (!(LogManager.Configuration is XmlLoggingConfiguration))
LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? Environment.CurrentDirectory, "NLog.config"));
} }
private static bool PatchLogger() private static bool PatchLogger()

View File

@@ -17,6 +17,7 @@ internal static class GcCollectPatch
{ {
// FUCK YO KEEN // FUCK YO KEEN
// every call results in freeze for seconds // every call results in freeze for seconds
private static readonly MethodBase[] _targets = private static readonly MethodBase[] _targets =
{ {
Info.OfMethod<MyPlanetTextureMapProvider>(nameof(MyPlanetTextureMapProvider.GetHeightmap)), Info.OfMethod<MyPlanetTextureMapProvider>(nameof(MyPlanetTextureMapProvider.GetHeightmap)),

View File

@@ -103,27 +103,27 @@ namespace Torch.Patches
private static bool PrefixWriteLine(MyLog __instance, string msg) private static bool PrefixWriteLine(MyLog __instance, string msg)
{ {
if (__instance.LogEnabled && _log.IsDebugEnabled) if (__instance.LogEnabled && _log.IsDebugEnabled)
_log.Debug($"{" ".PadRight(3 * GetIndentByCurrentThread())}{msg}"); _log.Debug($"{string.Empty.PadRight(3 * GetIndentByCurrentThread(), ' ')}{msg}");
return false; return false;
} }
private static bool PrefixWriteLineConsole(MyLog __instance, string msg) private static bool PrefixWriteLineConsole(MyLog __instance, string msg)
{ {
if (__instance.LogEnabled && _log.IsInfoEnabled) if (__instance.LogEnabled && _log.IsInfoEnabled)
_log.Info($"{" ".PadRight(3 * GetIndentByCurrentThread())}{msg}"); _log.Info($"{string.Empty.PadRight(3 * GetIndentByCurrentThread(), ' ')}{msg}");
return false; return false;
} }
private static bool PrefixAppendToClosedLog(MyLog __instance, string text) private static bool PrefixAppendToClosedLog(MyLog __instance, string text)
{ {
if (__instance.LogEnabled && _log.IsDebugEnabled) if (__instance.LogEnabled && _log.IsDebugEnabled)
_log.Debug($"{" ".PadRight(3 * GetIndentByCurrentThread())}{text}"); _log.Debug($"{string.Empty.PadRight(3 * GetIndentByCurrentThread(), ' ')}{text}");
return false; return false;
} }
private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option) private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option)
{ {
if (__instance.LogEnabled && __instance.LogFlag(option) && _log.IsDebugEnabled) if (__instance.LogEnabled && __instance.LogFlag(option) && _log.IsDebugEnabled)
_log.Info($"{" ".PadRight(3 * GetIndentByCurrentThread())}{message}"); _log.Info($"{string.Empty.PadRight(3 * GetIndentByCurrentThread(), ' ')}{message}");
return false; return false;
} }
@@ -145,7 +145,7 @@ namespace Torch.Patches
return false; return false;
// ReSharper disable once TemplateIsNotCompileTimeConstantProblem // ReSharper disable once TemplateIsNotCompileTimeConstantProblem
_log.Log(new(LogLevelFor(severity), _log.Name, $"{" ".PadRight(3 * GetIndentByCurrentThread())}{string.Format(format, args)}")); _log.Log(new(LogLevelFor(severity), _log.Name, $"{string.Empty.PadRight(3 * GetIndentByCurrentThread(), ' ')}{string.Format(format, args)}"));
return false; return false;
} }

View File

@@ -1,7 +1,10 @@
using System; #if !NETFRAMEWORK
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -11,6 +14,7 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using ProtoBuf; using ProtoBuf;
using ProtoBuf.Meta;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
using Torch.Utils; using Torch.Utils;
@@ -40,16 +44,24 @@ namespace Torch.Patches
context.GetPattern(Register2Method).AddTranspiler(nameof(RegisterTranspiler)); context.GetPattern(Register2Method).AddTranspiler(nameof(RegisterTranspiler));
} }
private static void WhitelistCtorPrefix(MyScriptCompiler scriptCompiler) private static void WhitelistCtorPrefix(MyScriptCompiler scriptCompiler, MyScriptWhitelist __instance)
{ {
var basePath = new FileInfo(typeof(object).Assembly.Location).DirectoryName!;
scriptCompiler.AddReferencedAssemblies( scriptCompiler.AddReferencedAssemblies(
Path.Combine(basePath, "netstandard.dll"),
Path.Combine(basePath, "mscorlib.dll"),
Path.Combine(basePath, "System.Runtime.dll"),
typeof(LinkedList<>).Assembly.Location,
typeof(Regex).Assembly.Location, typeof(Regex).Assembly.Location,
typeof(Enumerable).Assembly.Location, typeof(Enumerable).Assembly.Location,
typeof(ConcurrentBag<>).Assembly.Location, typeof(ConcurrentBag<>).Assembly.Location,
typeof(ImmutableArray).Assembly.Location, typeof(ImmutableArray).Assembly.Location,
typeof(System.ComponentModel.TypeConverter).Assembly.Location, typeof(PropertyChangedEventArgs).Assembly.Location,
typeof(System.Diagnostics.TraceSource).Assembly.Location, typeof(TypeConverter).Assembly.Location,
typeof(ProtoBuf.Meta.RuntimeTypeModel).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.Game.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Common.dll"), Path.Combine(MyFileSystem.ExePath, "Sandbox.Common.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Graphics.dll"), Path.Combine(MyFileSystem.ExePath, "Sandbox.Graphics.dll"),
@@ -68,6 +80,9 @@ namespace Torch.Patches
MyModWatchdog.Init(updateThread); MyModWatchdog.Init(updateThread);
MyScriptCompiler.Static.AddImplicitIngameNamespacesFromTypes(referencedTypes); MyScriptCompiler.Static.AddImplicitIngameNamespacesFromTypes(referencedTypes);
MyScriptCompiler.Static.AddConditionalCompilationSymbols(symbols); MyScriptCompiler.Static.AddConditionalCompilationSymbols(symbols);
using var batch = MyScriptCompiler.Static.Whitelist.OpenBatch();
// Dict and queue in different assemblies, microsoft being microsoft
batch.AllowNamespaceOfTypes(MyWhitelistTarget.ModApi, typeof(ConcurrentQueue<>));
return false; return false;
} }
@@ -97,3 +112,4 @@ namespace Torch.Patches
} }
} }
} }
#endif

View File

@@ -1,34 +1,52 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Mono.Cecil; using Mono.Cecil;
using NLog;
namespace Torch.Plugins; namespace Torch.Plugins;
internal static class AssemblyRewriter internal static class AssemblyRewriter
{ {
private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); private static readonly ZipResolver _zipResolver;
private static readonly DefaultAssemblyResolver _defaultResolver;
private static readonly IAssemblyResolver Resolver;
static AssemblyRewriter() static AssemblyRewriter()
{ {
var resolver = new DefaultAssemblyResolver(); _defaultResolver = new();
Resolver = resolver; _zipResolver = new(_defaultResolver);
resolver.AddSearchDirectory(Directory.GetCurrentDirectory()); _defaultResolver.AddSearchDirectory(Directory.GetCurrentDirectory());
resolver.AddSearchDirectory(Path.Combine(Directory.GetCurrentDirectory(), "DedicatedServer64")); _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(); using var assStream = new MemoryStream();
stream.CopyTo(assStream); stream.CopyTo(assStream);
assStream.Position = 0; 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)) foreach (var fieldDefinition in FindAllToRewrite(module))
{ {
@@ -47,4 +65,40 @@ internal static class AssemblyRewriter
private static bool HasValidAttributes(FieldDefinition definition) => private static bool HasValidAttributes(FieldDefinition definition) =>
definition.CustomAttributes.Any(b => b.AttributeType.Name.Contains("Reflected") || b.AttributeType.Name == "DependencyAttribute"); 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(); using var stream = entry.Open();
assemblies.Add(stream.ProcessWeavers()); assemblies.Add(stream.ProcessWeavers(zipFile));
} }
} }
else else
@@ -378,7 +378,7 @@ namespace Torch.Managers
// continue; // continue;
using var stream = File.OpenRead(file); 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch</AssemblyTitle> <AssemblyTitle>Torch</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright> <Copyright>Copyright © Torch API 2017</Copyright>
@@ -8,7 +9,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath> <OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWpf>True</UseWpf> <UseWpf>True</UseWpf>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
@@ -122,31 +122,6 @@
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Views\CollectionEditor.xaml.cs">
<DependentUpon>CollectionEditor.xaml</DependentUpon>
</Compile>
<Compile Update="Views\DictionaryEditor.xaml.cs">
<DependentUpon>DictionaryEditor.xaml</DependentUpon>
</Compile>
<Compile Update="Views\EmbeddedCollectionEditor.xaml.cs">
<DependentUpon>EmbeddedCollectionEditor.xaml</DependentUpon>
</Compile>
<Compile Update="Views\FlagsEditor.xaml.cs">
<DependentUpon>FlagsEditor.xaml</DependentUpon>
</Compile>
<Compile Update="Views\ObjectCollectionEditor.xaml.cs">
<DependentUpon>ObjectCollectionEditor.xaml</DependentUpon>
</Compile>
<Compile Update="Views\ObjectEditor.xaml.cs">
<DependentUpon>ObjectEditor.xaml</DependentUpon>
</Compile>
<Compile Update="Views\PropertyGrid.xaml.cs">
<DependentUpon>PropertyGrid.xaml</DependentUpon>
</Compile>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
<Compile Remove="Commands\Permissions\PermissionManager.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" /> <ProjectReference Include="..\Torch.API\Torch.API.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sandbox.Engine.Networking; using Sandbox.Engine.Networking;
using Torch.API; using Torch.API;
@@ -19,6 +20,43 @@ namespace Torch.Utils
return new MyObjectBuilder_Checkpoint.ModItem(ulong.Parse(arr[0]), arr[1]); 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! //because KEEEN!
public static string GetDefaultServiceName() public static string GetDefaultServiceName()
{ {

View File

@@ -1,14 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using ProtoBuf;
using Torch.API;
namespace Torch.Utils namespace Torch.Utils
{ {
@@ -23,7 +17,13 @@ namespace Torch.Utils
try try
{ {
var name = AssemblyName.GetAssemblyName(file); 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) catch (BadImageFormatException)
{ {
@@ -37,7 +37,7 @@ namespace Torch.Utils
private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{ {
var name = args.Name; 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

@@ -0,0 +1,50 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
#if !NETFRAMEWORK
using System.Runtime.Loader;
#endif
using NLog;
using NLog.Config;
using NLog.Targets;
namespace Torch.Utils;
public static class TorchLogManager
{
#if !NETFRAMEWORK
private static AssemblyLoadContext LoadContext = new("TorchLog");
#endif
public static LoggingConfiguration Configuration { get; private set; }
public static void SetConfiguration(LoggingConfiguration configuration)
{
Configuration = configuration;
LogManager.Configuration = configuration;
LogManager.ReconfigExistingLoggers();
}
public static void RegisterTargets(string dir)
{
if (!Directory.Exists(dir)) return;
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 { }))
{
Target.Register(type.GetCustomAttribute<TargetAttribute>()!.Name, type);
}
}
public static void RestoreGlobalConfiguration()
{
SetConfiguration(Configuration);
}
}

View File

@@ -472,7 +472,8 @@ namespace Torch
{ {
// Kinda icky, but we can't block the update and expect the state to change. // Kinda icky, but we can't block the update and expect the state to change.
if (Thread.CurrentThread == _updateThread) if (Thread.CurrentThread == _updateThread)
return _state == state; throw new InvalidOperationException(
"Waiting for game state is not possible from update thread (deadlock)");
DateTime? end = timeout.HasValue ? (DateTime?) (DateTime.Now + timeout.Value) : null; DateTime? end = timeout.HasValue ? (DateTime?) (DateTime.Now + timeout.Value) : null;
while (_state != state && (!end.HasValue || end > DateTime.Now + TimeSpan.FromSeconds(1))) while (_state != state && (!end.HasValue || end > DateTime.Now + TimeSpan.FromSeconds(1)))

View File

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

View File

@@ -47,14 +47,11 @@ install:
cache: cache:
- c:\steam\dedi\ - c:\steam\dedi\
- c:\steam\cmd\ - c:\steam\cmd\
before_build:
- pwsh: dotnet restore .\Torch.Server\Torch.Server.csproj
build_script: build_script:
- pwsh: >- - pwsh: >-
dotnet publish .\Torch.Server\Torch.Server.csproj --self-contained -c Release -o .\publish\ dotnet publish .\Torch.Server\Torch.Server.csproj --self-contained -f net6-windows -r win-x64 -c Release -o .\publish\
cmd /c "rmdir GameBinaries" Compress-Archive -Path .\publish\* -DestinationPath .\torch-server.zip
Compress-Archive -Path .\publish\* -DestinationPath torch-server.zip
artifacts: artifacts:
- path: torch-server.zip - path: torch-server.zip
deploy: deploy: