Compare commits

..

18 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
45 changed files with 552 additions and 609 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

@@ -3,6 +3,7 @@ using System.IO;
using NLog; using NLog;
using NLog.Config; using NLog.Config;
using NLog.Targets; using NLog.Targets;
using Torch.API;
using Torch.Utils; using Torch.Utils;
namespace Torch.Server namespace Torch.Server
@@ -12,112 +13,71 @@ namespace Torch.Server
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
var isService = Environment.GetEnvironmentVariable("TORCH_SERVICE") var context = CreateApplicationContext();
?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false;
//Ensures that all the files are downloaded in the Torch directory.
var workingDir = AppContext.BaseDirectory;
var binDir = Path.Combine(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir, "DedicatedServer64");
Directory.SetCurrentDirectory(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir);
if (!isService && Directory.Exists(binDir)) SetupLogging();
foreach (var file in Directory.GetFiles(binDir, "System.*.dll"))
{
File.Delete(file);
}
// Breaks on Windows Server 2019 var oldTorchCfg = Path.Combine(context.TorchDirectory.FullName, "Torch.cfg");
#if TORCH_SERVICE var torchCfg = Path.Combine(context.InstanceDirectory.FullName, "Torch.cfg");
if (!new ComputerInfo().OSFullName.Contains("Server 2019") && !Environment.UserInteractive)
{
using (var service = new TorchService(args))
ServiceBase.Run(service);
return;
}
#endif
var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance";
string instancePath;
if (Path.IsPathRooted(instanceName))
{
instancePath = instanceName;
instanceName = Path.GetDirectoryName(instanceName);
}
else
{
instancePath = Directory.CreateDirectory(instanceName).FullName;
}
var oldNlog = Path.Combine(workingDir, "NLog.config");
var newNlog = Path.Combine(instancePath, "NLog.config");
if (File.Exists(oldNlog))
File.Move(oldNlog, newNlog, true);
else if (!File.Exists(newNlog))
using (var f = File.Create(newNlog))
typeof(Program).Assembly.GetManifestResourceStream("Torch.Server.NLog.config")!.CopyTo(f);
var oldTorchCfg = Path.Combine(workingDir, "Torch.cfg");
var torchCfg = Path.Combine(instancePath, "Torch.cfg");
if (File.Exists(oldTorchCfg)) if (File.Exists(oldTorchCfg))
File.Move(oldTorchCfg, torchCfg, true); File.Move(oldTorchCfg, torchCfg);
var config = Persistent<TorchConfig>.Load(torchCfg); var config = Persistent<TorchConfig>.Load(torchCfg);
config.Data.InstanceName = instanceName; config.Data.InstanceName = context.InstanceName;
config.Data.InstancePath = instancePath; config.Data.InstancePath = context.InstanceDirectory.FullName;
if (!config.Data.Parse(args)) if (!config.Data.Parse(args))
{ {
Console.WriteLine("Invalid arguments"); Console.WriteLine("Invalid arguments");
Environment.Exit(1); Environment.Exit(1);
} }
var handler = new UnhandledExceptionHandler(config.Data, isService); var handler = new UnhandledExceptionHandler(config.Data);
AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException; AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
Target.Register<LogViewerTarget>(nameof(LogViewerTarget)); var initializer = new Initializer(config);
TorchLogManager.RegisterTargets(Environment.GetEnvironmentVariable("TORCH_LOG_EXTENSIONS_PATH") ??
Path.Combine(instancePath, "LoggingExtensions"));
TorchLogManager.SetConfiguration(new XmlLoggingConfiguration(newNlog));
var initializer = new Initializer(workingDir, config);
if (!initializer.Initialize(args)) if (!initializer.Initialize(args))
Environment.Exit(1); Environment.Exit(1);
TorchLauncher.Launch(workingDir, binDir); #if DEBUG
TorchLauncher.Launch(context.TorchDirectory.FullName, context.GameBinariesDirectory.FullName);
#else
TorchLauncher.Launch(context.TorchDirectory.FullName, Path.Combine(context.TorchDirectory.FullName, "torch64"),
context.GameBinariesDirectory.FullName);
#endif
CopyNative(binDir); CopyNative();
initializer.Run(isService, instanceName, instancePath); initializer.Run();
} }
private static void CopyNative(string binPath) private static void CopyNative()
{ {
var log = LogManager.GetLogger("TorchLauncher"); var log = LogManager.GetLogger("TorchLauncher");
var workingDir = new DirectoryInfo(Directory.GetCurrentDirectory()); if (ApplicationContext.Current.GameFilesDirectory.Attributes.HasFlag(FileAttributes.ReadOnly))
if (workingDir.Attributes.HasFlag(FileAttributes.ReadOnly))
{ {
log.Warn("Game directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually"); log.Warn("Torch directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually");
return; return;
} }
try try
{ {
var apiSource = Path.Combine(binPath, "steam_api64.dll"); var apiSource = Path.Combine(ApplicationContext.Current.GameBinariesDirectory.FullName, "steam_api64.dll");
var apiTarget = Path.Combine(workingDir.FullName, "steam_api64.dll"); var apiTarget = Path.Combine(ApplicationContext.Current.GameFilesDirectory.FullName, "steam_api64.dll");
if (!File.Exists(apiTarget)) if (!File.Exists(apiTarget))
{ {
File.Copy(apiSource, apiTarget); File.Copy(apiSource, apiTarget);
} }
else if (File.GetLastWriteTime(apiTarget) < File.GetLastWriteTime(binPath)) else if (File.GetLastWriteTime(apiTarget) < ApplicationContext.Current.GameBinariesDirectory.LastWriteTime)
{ {
File.Delete(apiTarget); File.Delete(apiTarget);
File.Copy(apiSource, apiTarget); File.Copy(apiSource, apiTarget);
} }
var havokSource = Path.Combine(binPath, "Havok.dll"); var havokSource = Path.Combine(ApplicationContext.Current.GameBinariesDirectory.FullName, "Havok.dll");
var havokTarget = Path.Combine(workingDir.FullName, "Havok.dll"); var havokTarget = Path.Combine(ApplicationContext.Current.GameFilesDirectory.FullName, "Havok.dll");
if (!File.Exists(havokTarget)) if (!File.Exists(havokTarget))
{ {
@@ -138,5 +98,49 @@ namespace Torch.Server
log.Error(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")
?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false;
var workingDir = AppContext.BaseDirectory;
var gamePath = Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir;
var binDir = Path.Combine(gamePath, "DedicatedServer64");
Directory.SetCurrentDirectory(gamePath);
var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance";
string instancePath;
if (Path.IsPathRooted(instanceName))
{
instancePath = instanceName;
instanceName = Path.GetDirectoryName(instanceName);
}
else
{
instancePath = Directory.CreateDirectory(instanceName).FullName;
}
return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
new(instancePath), instanceName, isService);
}
} }
} }

View File

@@ -1,17 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Torch Server")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Torch")]
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif

View File

@@ -2,11 +2,10 @@
<PropertyGroup> <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" />

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 {
StopInternal();
var config = (TorchConfig)Config; var config = (TorchConfig)Config;
LogManager.Flush(); LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe"); string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe");
#if NETFRAMEWORK
config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
#else
config.WaitForPID = Environment.ProcessId.ToString(); config.WaitForPID = Environment.ProcessId.ToString();
#endif
config.TempAutostart = true; config.TempAutostart = true;
Process.Start(exe, config.ToString()); 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,25 +374,17 @@ 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 runtime = target.ClrVersions[0].CreateRuntime();
var clrThread = runtime.Threads.First(b => b.ManagedThreadId == thread.ManagedThreadId); var clrThread = runtime.Threads.First(b => b.ManagedThreadId == thread.ManagedThreadId);
var sb = new StringBuilder(); var sb = new StringBuilder();
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()) foreach (var frame in clrThread.EnumerateStackTrace())
{ {
sb.Append('\t'); sb.Append('\t');
@@ -382,7 +406,6 @@ namespace Torch.Server
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,24 +50,14 @@ 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;
if (obj is { })
Task.Run(async () => Task.Run(async () =>
{ {
await obj.UpdateAllModInfosAsync(); await obj.UpdateAllModInfosAsync();
@@ -93,13 +66,6 @@ namespace Torch.Server.Views
}); });
} }
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

@@ -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,9 +1,10 @@
using System; #if !NETFRAMEWORK
using System.Collections; 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.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -13,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;
@@ -42,10 +44,14 @@ 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(
typeof(ValueType).Assembly.Location, Path.Combine(basePath, "netstandard.dll"),
Path.Combine(basePath, "mscorlib.dll"),
Path.Combine(basePath, "System.Runtime.dll"),
typeof(LinkedList<>).Assembly.Location, typeof(LinkedList<>).Assembly.Location,
typeof(Regex).Assembly.Location, typeof(Regex).Assembly.Location,
typeof(Enumerable).Assembly.Location, typeof(Enumerable).Assembly.Location,
@@ -53,8 +59,9 @@ namespace Torch.Patches
typeof(ImmutableArray).Assembly.Location, typeof(ImmutableArray).Assembly.Location,
typeof(PropertyChangedEventArgs).Assembly.Location, typeof(PropertyChangedEventArgs).Assembly.Location,
typeof(TypeConverter).Assembly.Location, typeof(TypeConverter).Assembly.Location,
typeof(System.Diagnostics.TraceSource).Assembly.Location, typeof(TraceSource).Assembly.Location,
typeof(ProtoBuf.Meta.RuntimeTypeModel).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"),
@@ -73,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;
} }
@@ -102,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)
{ {
AssemblyResolver = Resolver _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 = _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

@@ -2,7 +2,9 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
#if !NETFRAMEWORK
using System.Runtime.Loader; using System.Runtime.Loader;
#endif
using NLog; using NLog;
using NLog.Config; using NLog.Config;
using NLog.Targets; using NLog.Targets;
@@ -11,7 +13,9 @@ namespace Torch.Utils;
public static class TorchLogManager public static class TorchLogManager
{ {
#if !NETFRAMEWORK
private static AssemblyLoadContext LoadContext = new("TorchLog"); private static AssemblyLoadContext LoadContext = new("TorchLog");
#endif
public static LoggingConfiguration Configuration { get; private set; } public static LoggingConfiguration Configuration { get; private set; }
@@ -26,7 +30,12 @@ public static class TorchLogManager
{ {
if (!Directory.Exists(dir)) return; if (!Directory.Exists(dir)) return;
foreach (var type in Directory.EnumerateFiles(dir, "*.dll").Select(LoadContext.LoadFromAssemblyPath) foreach (var type in Directory.EnumerateFiles(dir, "*.dll")
#if NETFRAMEWORK
.Select(Assembly.LoadFile)
#else
.Select(LoadContext.LoadFromAssemblyPath)
#endif
.SelectMany(b => b.ExportedTypes) .SelectMany(b => b.ExportedTypes)
.Where(b => b.GetCustomAttribute<TargetAttribute>() is { })) .Where(b => b.GetCustomAttribute<TargetAttribute>() is { }))
{ {

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")]