Compare commits

...

76 Commits

Author SHA1 Message Date
zznty
02bd9df059 fix because retarded exmaples 2022-11-06 21:00:24 +06:00
zznty
a5cc132151 add version string normalization 2022-11-06 20:57:56 +06:00
zznty
92dea1986c linux ci v5 2022-11-06 20:49:18 +06:00
zznty
c03eb79f81 linux ci v4 2022-11-06 20:46:54 +06:00
zznty
0ee9c5f97c test linux ci v3 2022-11-06 20:44:17 +06:00
zznty
c283059106 test linux ci v2 2022-11-06 20:41:26 +06:00
zznty
422963517f test building linux 2022-11-06 17:36:29 +03:00
zznty
d2ac0e44be correct whitelist for compiler 2022-10-16 02:09:44 +07:00
zznty
c5acf61f7c add auto-updates from github 2022-10-15 15:33:57 +07:00
zznty
197d04a661 fix steamcmd default directory 2022-10-15 15:33:09 +07:00
zznty
99ab7d0eea Merge remote-tracking branch 'pve/master' 2022-10-15 02:19:05 +07:00
zznty
17f97af52f add gslt login option (#515)
(cherry picked from commit c81f139fe6b5de0c9f7a005dc2cbe576f4ca8f67)
2022-10-15 02:09:09 +07:00
zznty
ed7c897bd2 Update README.md 2022-10-14 21:59:18 +03:00
zznty
e4d3c3987f fix ci changelog building 2022-10-14 21:48:43 +03:00
zznty
067d8802b6 change instance path resolving back to bin folder
i dont think we need to place our instance at game dir by default
2022-10-15 00:55:44 +07:00
zznty
8f32f64ede change script compilation manager lifetime
seems game loads faster than torch internals
2022-10-15 00:50:10 +07:00
zznty
90ff3f93f0 disable gc patch because conflicting with the same from plugin 2022-10-15 00:44:49 +07:00
zznty
ad28b302f9 add harmony logging 2022-10-15 00:42:23 +07:00
zznty
a0d0976a6a c# 10 and assembly unloading for in-game scripts 2022-10-15 00:16:00 +07:00
zznty
a9c9a0de68 refactor restart and config handling 2022-10-12 16:36:25 +07:00
zznty
9a967345b9 skip obfuscated assemblies from compatibility fixes 2022-10-10 20:31:59 +07:00
zznty
98a4be655f fix plugins download 2022-10-10 20:31:10 +07:00
zznty
76de8f3d0b fix ci zipping 2022-10-09 20:34:22 +07:00
zznty
6f886d4f7d fix for releases 2022-10-09 20:28:58 +07:00
zznty
a753abb8fd i love github and windows 2022-10-09 20:25:00 +07:00
zznty
452970d2fd add nuget source to ci 2022-10-09 20:14:25 +07:00
zznty
e28c38e04f update ci 2022-10-09 20:07:08 +07:00
zznty
5d9fe01c69 Update README.md 2022-10-09 15:58:28 +03:00
zznty
04227912b0 github ci 2022-10-09 19:55:22 +07:00
zznty
44dd0805e2 get rid of old ci files 2022-10-09 17:54:36 +07:00
zznty
c5c8e527da projects cleanup 2022-10-09 17:52:30 +07:00
zznty
49f68a8fcc allow selection for log lines 2022-10-09 17:51:45 +07:00
zznty
f8a3647308 seems to be configuration rework 2022-09-09 16:48:14 +07:00
zznty
0a40b1fe0f move some parts of main class to other
fix poorly implemented features
2022-06-05 15:50:03 +07:00
zznty
a1e9434444 remove compat for netframework 2022-06-05 15:43:15 +07:00
zznty
d2d96df8be fix csproj 2022-05-08 12:39:09 +07:00
zznty
b40f288827 Merge remote-tracking branch 'zznty/master'
# Conflicts:
#	Torch.API/Torch.API.csproj
#	Torch/Patches/ScriptCompilerPatch.cs
2022-05-08 12:36:22 +07:00
zznty
6267cffebe update packages 2022-05-08 12:30:05 +07:00
zznty
5fe25fb781 ship harmony with torch to prevent crashes due to runtime version mismatch
add warning if plugin is using harmony
update monomod package
2022-05-08 12:26:31 +07:00
zznty
d05e979ede remove unnecessary async lambda 2022-05-08 12:25:06 +07:00
zznty
1afb126cb5 fix plugin downloading on separated runtime directory 2022-05-08 12:24:38 +07:00
zznty
d52348149a fix scripts compilation 2022-05-08 11:44:12 +07:00
zznty
20eac508d3 clean up project files 2022-05-08 11:22:39 +07:00
zznty
8ba02a84d3 correct logging indent 2022-05-08 11:22:14 +07:00
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
95 changed files with 1673 additions and 3021 deletions

80
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Release
on:
push:
tags:
- '*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
TORCH_VERSION: ${{ github.ref_name }}
BUILD_CONFIGURATION: Release
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
steps:
- uses: actions/checkout@master
name: Checkout
- uses: actions/setup-dotnet@v3
name: Setup dotnet
with:
dotnet-version: '6.0.x'
- uses: actions/cache@v1
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Add Gh Packages Nuget Source
run: dotnet nuget add source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github
- name: Restore dependencies
run: dotnet restore Torch.Server/Torch.Server.csproj --use-lock-file
- uses: bhowell2/github-substring-action@v1.0.0
id: normalize_version
with:
value: ${{ env.TORCH_VERSION }}
index_of_str: "v"
- name: Build
run: dotnet build Torch.Server/Torch.Server.csproj --no-restore -c ${{ env.BUILD_CONFIGURATION }} /p:AssemblyVersion=${{ env.TORCH_VERSION }} /p:Version=${{ steps.normalize_version.outputs.substring }}
- name: Publish
run: dotnet publish Torch.Server/Torch.Server.csproj --no-build -r win-x64 --sc -c ${{ env.BUILD_CONFIGURATION }} -o ./publish
- uses: vimtor/action-zip@v1
name: Zip Release
with:
files: publish/
dest: release.zip
- name: Build Changelog
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commitMode: true
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.TORCH_VERSION }}
release_name: Release v${{ env.TORCH_VERSION }}
body: ${{ steps.github_release.outputs.changelog }}
draft: true
prerelease: false
- name: Upload release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: release.zip
asset_name: torch-server.zip
asset_content_type: application/zip
- name: Publish release
uses: StuYarrow/publish-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
id: ${{ steps.create_release.outputs.id }}

View File

@@ -1,33 +0,0 @@
# Torch 1.1.229.265
* Features
- Added more lenient version parsing for plugins (v#.# should work)
- Added countdown option to restart command (!restart [seconds])
* Fixes
- General fixes to work with the latest SE version
- Fixed config changes not saving
- (hopefully) Fixed issue causing crashes on servers using the Windows Classic theme
# Torch 1.1.207.7
* Notes
- This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options.
* Features
- Plugins, Torch, and the DS can now all update automatically
- Changed command prefix to !
- Added manual save command (thanks to Maldark)
- Added restart command
- Improved instance creation: now creates an entire skeleton instance with blank config
- Added instance name to console title
* Fixes
- Optimized UI so it's snappier and freezes less often
- Fixed NetworkManager.RaiseEvent overload that had an off-by-one bug
- Fixed chat window so it automatically scrolls down
# Torch 1.0.182.329
* Improved logging, logs now to go the Logs folder and aren't deleted on start
* Fixed chat tab not enabling with -autostart
* Fixed player list
* Watchdog time-out is now configurable in TorchConfig.xml
* Fixed infinario log spam
* Fixed crash when sending empty message from chat tab
* Fixed permissions on Torch commands
* Changed plugin StoragePath to the current instance path (per-instance configs)

View File

@@ -1,6 +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)
# What is Torch? # What is Torch?
Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features. Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features.
@@ -15,15 +12,25 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
### Fork Difference ### Fork Difference
* .NET 6.0 runtime * .NET 6.0 runtime
* Additional options & features * Optimized in-game scripts (also newer compiler & language versions)
* Better configuration via cli arguments, environment variables or xml config
* Designed to run multiple instance from same install directory without having you to waste disk space
* Mostly compatible with original torch's plugins
### 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.
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder. - If you already have a DS installed you can:
* Unzip the Torch files into the folder that contains the DedicatedServer64 folder.
* Pass path to game files using config parameter `gamePath`
# Building # Building
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
As a regular dotnet project with cli by running `dotnet build Torch.Server/Torch.Server.csproj`, with VS 2022 or higher, with JB Rider or Fleet.
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE) If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE)

View File

@@ -1,13 +0,0 @@
:: This script creates a symlink to the game binaries to account for different installation directories on different systems.
@echo off
set /p path="Please enter the folder location of your SpaceEngineersDedicated.exe: "
cd %~dp0
mklink /J GameBinaries "%path%"
if errorlevel 1 goto Error
echo Done! You can now open the Torch solution without issue.
goto End
:Error
echo An error occured creating the symlink.
:End
pause

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

@@ -16,30 +16,6 @@ namespace Torch.API
/// </summary> /// </summary>
public interface ITorchBase public interface ITorchBase
{ {
/// <summary>
/// Fired when the session begins loading.
/// </summary>
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionLoading;
/// <summary>
/// Fired when the session finishes loading.
/// </summary>
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionLoaded;
/// <summary>
/// Fires when the session begins unloading.
/// </summary>
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionUnloading;
/// <summary>
/// Fired when the session finishes unloading.
/// </summary>
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionUnloaded;
/// <summary> /// <summary>
/// Gets the currently running session instance, or null if none exists. /// Gets the currently running session instance, or null if none exists.
/// </summary> /// </summary>
@@ -68,6 +44,16 @@ namespace Torch.API
/// </summary> /// </summary>
Version TorchVersion { get; } Version TorchVersion { get; }
/// <summary>
/// Path of the dedicated instance folder.
/// </summary>
string InstancePath { get; }
/// <summary>
/// Name of the dedicated instance.
/// </summary>
string InstanceName { get; }
/// <summary> /// <summary>
/// Invoke an action on the game thread. /// Invoke an action on the game thread.
/// </summary> /// </summary>
@@ -146,16 +132,6 @@ namespace Torch.API
/// </summary> /// </summary>
ServerState State { get; } ServerState State { get; }
/// <summary>
/// Path of the dedicated instance folder.
/// </summary>
string InstancePath { get; }
/// <summary>
/// Name of the dedicated instance.
/// </summary>
string InstanceName { get; }
/// <summary> /// <summary>
/// Raised when the server's Init() method has completed. /// Raised when the server's Init() method has completed.
/// </summary> /// </summary>

View File

@@ -22,7 +22,6 @@ namespace Torch
bool ShouldUpdatePlugins { get; } bool ShouldUpdatePlugins { get; }
bool ShouldUpdateTorch { get; } bool ShouldUpdateTorch { get; }
int TickTimeout { get; set; } int TickTimeout { get; set; }
string WaitForPID { get; set; }
string ChatName { get; set; } string ChatName { get; set; }
string ChatColor { get; set; } string ChatColor { get; set; }
string TestPlugin { get; set; } string TestPlugin { get; set; }
@@ -32,6 +31,23 @@ namespace Torch
int FontSize { get; set; } int FontSize { get; set; }
UGCServiceType UgcServiceType { get; set; } UGCServiceType UgcServiceType { get; set; }
bool EntityManagerEnabled { get; set; } bool EntityManagerEnabled { get; set; }
string LoginToken { get; set; }
UpdateSource UpdateSource { get; set; }
void Save(string path = null); void Save(string path = null);
} }
public class UpdateSource
{
public UpdateSourceType SourceType { get; set; }
public string Url { get; set; }
public string Repository { get; set; }
public string Branch { get; set; }
}
public enum UpdateSourceType
{
Github,
Jenkins
}
} }

View File

@@ -14,7 +14,6 @@ public interface IInstanceManager : IManager
public interface IWorld public interface IWorld
{ {
string FolderName { get; }
string WorldPath { get; } string WorldPath { get; }
MyObjectBuilder_SessionSettings KeenSessionSettings { get; } MyObjectBuilder_SessionSettings KeenSessionSettings { get; }
MyObjectBuilder_Checkpoint KeenCheckpoint { get; } MyObjectBuilder_Checkpoint KeenCheckpoint { 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,105 +1,27 @@
<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> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Release'"> <PropertyGroup Condition="$(Configuration) == 'Release'">
<NoWarn>1591</NoWarn> <NoWarn>1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="JorgeSerrano.Json.JsonSnakeCaseNamingPolicy" Version="0.9.0" />
<PackageReference Include="SemanticVersioning" Version="2.0.0" /> <PackageReference Include="NLog" Version="5.0.4" />
</ItemGroup> <PackageReference Include="SemanticVersioning" Version="2.0.2" />
<ItemGroup> <PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13">
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64"> <PrivateAssets>all</PrivateAssets>
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath> <IncludeAssets>compile</IncludeAssets>
<Private>False</Private> </PackageReference>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Game, Version=0.1.6305.30774, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Graphics, Version=0.1.6305.30761, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="SpaceEngineers.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="SpaceEngineers.ObjectBuilders, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage">
<HintPath>..\GameBinaries\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Audio, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Dedicated">
<HintPath>..\GameBinaries\VRage.Dedicated.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Library">
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Render11, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Scripting, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using JorgeSerrano.Json;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI;
public class GithubQuery : IUpdateQuery
{
private readonly HttpClient _client;
public GithubQuery(string url)
{
if (url == null) throw new ArgumentNullException(nameof(url));
_client = new()
{
BaseAddress = new(url),
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
DefaultRequestHeaders =
{
{"User-Agent", "TorchAPI"}
}
};
}
public void Dispose()
{
_client?.Dispose();
}
public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{
var response = await _client.GetFromJsonAsync<Release>($"/repos/{repository}/releases/latest", new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy()
});
if (response is null)
throw new($"Unable to get latest release for {repository}");
return new(Version.Parse(response.TagName), response.Assets.First(b => b.Name == "torch-server.zip").BrowserDownloadUrl);
}
private record Asset(
string Url,
int Id,
string NodeId,
string Name,
string Label,
Uploader Uploader,
string ContentType,
string State,
int Size,
int DownloadCount,
DateTime CreatedAt,
DateTime UpdatedAt,
string BrowserDownloadUrl
);
private record Author(
string Login,
int Id,
string NodeId,
string AvatarUrl,
string GravatarId,
string Url,
string HtmlUrl,
string FollowersUrl,
string FollowingUrl,
string GistsUrl,
string StarredUrl,
string SubscriptionsUrl,
string OrganizationsUrl,
string ReposUrl,
string EventsUrl,
string ReceivedEventsUrl,
string Type,
bool SiteAdmin
);
private record Release(
string Url,
string AssetsUrl,
string UploadUrl,
string HtmlUrl,
int Id,
Author Author,
string NodeId,
string TagName,
string TargetCommitish,
string Name,
bool Draft,
bool Prerelease,
DateTime CreatedAt,
DateTime PublishedAt,
IReadOnlyList<Asset> Assets,
string TarballUrl,
string ZipballUrl,
string Body
);
private record Uploader(
string Login,
int Id,
string NodeId,
string AvatarUrl,
string GravatarId,
string Url,
string HtmlUrl,
string FollowersUrl,
string FollowingUrl,
string GistsUrl,
string StarredUrl,
string SubscriptionsUrl,
string OrganizationsUrl,
string ReposUrl,
string EventsUrl,
string ReceivedEventsUrl,
string Type,
bool SiteAdmin
);
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;
namespace Torch.API.WebAPI;
public interface IUpdateQuery : IDisposable
{
Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null);
}

View File

@@ -1,78 +1,52 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog;
using Torch.API.Utils; using Torch.API.Utils;
using Version = SemanticVersioning.Version; using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI namespace Torch.API.WebAPI
{ {
public class JenkinsQuery public class JenkinsQuery : IUpdateQuery
{ {
private const string BRANCH_QUERY = "http://136.243.80.164:2690/job/Torch/job/{0}/" + API_PATH; private const string ApiPath = "api/json";
private const string ARTIFACT_PATH = "artifact/bin/torch-server.zip"; private readonly HttpClient _client;
private const string API_PATH = "api/json";
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); public JenkinsQuery(string url)
private static JenkinsQuery _instance;
public static JenkinsQuery Instance => _instance ??= new JenkinsQuery();
private HttpClient _client;
private JenkinsQuery()
{ {
_client = new HttpClient(); if (url == null) throw new ArgumentNullException(nameof(url));
_client = new()
{
BaseAddress = new(url)
};
} }
public async Task<Job> GetLatestVersion(string branch) public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{ {
var h = await _client.GetAsync(string.Format(BRANCH_QUERY, branch)); branch ??= "master";
if (!h.IsSuccessStatusCode)
{ var response = await _client.GetFromJsonAsync<BranchResponse>($"/job/{repository}/job/{branch}/{ApiPath}");
Log.Error($"'{branch}' Branch query failed with code {h.StatusCode}");
if(h.StatusCode == HttpStatusCode.NotFound) if (response is null)
Log.Error("This likely means you're trying to update a branch that is not public on Jenkins. Sorry :("); throw new($"Unable to get latest release for {repository}");
return null;
var job = await _client.GetFromJsonAsync<Job>(
$"/job/{repository}/job/{branch}/{response.LastBuild.Number}/{ApiPath}");
if (job is null)
throw new($"Unable to get latest release for job {repository}/{response.LastBuild.Number}");
return new(job.Version, job.Url + job.Artifacts.First(b => b.FileName == "torch-server.zip").RelativePath);
} }
var branchResponse = await h.Content.ReadFromJsonAsync<BranchResponse>(); public void Dispose()
if (branchResponse is null)
{ {
Log.Error("Error reading branch response"); _client?.Dispose();
return null;
} }
h = await _client.GetAsync($"{branchResponse.LastStableBuild.Url}{API_PATH}");
if (h.IsSuccessStatusCode)
return await h.Content.ReadFromJsonAsync<Job>();
Log.Error($"Job query failed with code {h.StatusCode}");
return null;
}
public async Task<bool> DownloadRelease(Job job, string path)
{
var h = await _client.GetAsync(job.Url + ARTIFACT_PATH);
if (!h.IsSuccessStatusCode)
{
Log.Error($"Job download failed with code {h.StatusCode}");
return false;
}
var s = await h.Content.ReadAsStreamAsync();
await using var fs = new FileStream(path, FileMode.Create);
await s.CopyToAsync(fs);
return true;
}
} }
public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild); public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild);
@@ -80,5 +54,12 @@ namespace Torch.API.WebAPI
public record Build(int Number, string Url); public record Build(int Number, string Url);
public record Job(int Number, bool Building, string Description, string Result, string Url, public record Job(int Number, bool Building, string Description, string Result, string Url,
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version); [property: JsonConverter(typeof(SemanticVersionConverter))] Version Version,
IReadOnlyList<Artifact> Artifacts);
public record Artifact(
string DisplayPath,
string FileName,
string RelativePath
);
} }

View File

@@ -14,7 +14,7 @@ namespace Torch.API.WebAPI
public class PluginQuery public class PluginQuery
{ {
private const string ALL_QUERY = "https://torchapi.com/api/plugins/"; private const string ALL_QUERY = "https://torchapi.com/api/plugins/";
private const string PLUGIN_QUERY = "https://torchapi.com/api/plugins/?guid={0}"; private const string PLUGIN_QUERY = "https://torchapi.com/api/plugins/search/{0}";
private readonly HttpClient _client; private readonly HttpClient _client;
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static readonly Logger Log = LogManager.GetCurrentClassLogger();
@@ -60,7 +60,7 @@ namespace Torch.API.WebAPI
{ {
try try
{ {
path ??= Path.Combine(Directory.GetCurrentDirectory(), "Plugins", $"{item.Name}.zip"); path ??= Path.Combine(AppContext.BaseDirectory, "Plugins", $"{item.Name}.zip");
if (item.Versions.Length == 0) if (item.Versions.Length == 0)
{ {

View File

@@ -0,0 +1,5 @@
using SemanticVersioning;
namespace Torch.API.WebAPI;
public record UpdateRelease(Version Version, string ArtifactUrl);

View File

@@ -1,17 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Torch Client 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,51 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Client Tests</AssemblyTitle>
<Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<OutputPath>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<DebugType>pdbonly</DebugType>
<DocumentationFile>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\Torch.Client.Tests.xml</DocumentationFile>
</PropertyGroup>
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
<ItemGroup>
<PackageReference Include="Mono.TextTransform" Version="1.0.0" />
<PackageReference Include="NLog" Version="4.4.12" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.1" />
<PackageReference Include="xunit.assert" Version="2.2.0" />
<PackageReference Include="xunit.core" Version="2.2.0" />
<PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
<PackageReference Include="xunit.runner.console" Version="2.2.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch.Client\Torch.Client.csproj" />
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj" />
<ProjectReference Include="..\Torch\Torch.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="obj\x64\Debug\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
<Compile Remove="obj\x64\Release\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
</ItemGroup>
</Project>

View File

@@ -1,94 +0,0 @@
using System;
using System.Collections.Generic;
using Torch.Client;
using Torch.Tests;
using Torch.Utils;
using Xunit;
namespace Torch.Client.Tests
{
public class TorchClientReflectionTest
{
static TorchClientReflectionTest()
{
TestUtils.Init();
}
private static ReflectionTestManager _manager;
private static ReflectionTestManager Manager()
{
if (_manager != null)
return _manager;
return _manager = new ReflectionTestManager().Init(typeof(TorchClient).Assembly);
}
public static IEnumerable<object[]> Getters => Manager().Getters;
public static IEnumerable<object[]> Setters => Manager().Setters;
public static IEnumerable<object[]> Invokers => Manager().Invokers;
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
public static IEnumerable<object[]> Events => Manager().Events;
#region Binding
[Theory]
[MemberData(nameof(Getters))]
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
{
if (field.Field == null)
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
}
[Theory]
[MemberData(nameof(Setters))]
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
{
if (field.Field == null)
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
}
[Theory]
[MemberData(nameof(Invokers))]
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
{
if (field.Field == null)
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
}
[Theory]
[MemberData(nameof(MemberInfo))]
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
{
if (field.Field == null)
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
Assert.NotNull(field.Field.GetValue(null));
}
[Theory]
[MemberData(nameof(Events))]
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
{
if (field.Field == null)
return;
Assert.True(ReflectedManager.Process(field.Field));
if (field.Field.IsStatic)
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
}
#endregion
}
}

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>
</configuration>

View File

@@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Multiplayer;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
namespace Torch.Client.Manager
{
public class MultiplayerManagerClient : MultiplayerManagerBase, IMultiplayerManagerClient
{
/// <inheritdoc />
public MultiplayerManagerClient(ITorchBase torch) : base(torch) { }
/// <inheritdoc />
public override void Attach()
{
base.Attach();
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
}
/// <inheritdoc />
public override void Detach()
{
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
base.Detach();
}
}
}

View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.World;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using VRage.Game.ModAPI;
namespace Torch.Client.Manager
{
public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer
{
/// <inheritdoc />
public IReadOnlyList<ulong> BannedPlayers => new List<ulong>();
/// <inheritdoc />
public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { }
/// <inheritdoc />
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
/// <inheritdoc />
public void BanPlayer(ulong steamId, bool banned = true) => Torch.Invoke(() => MyMultiplayer.Static.BanClient(steamId, banned));
/// <inheritdoc />
public void PromoteUser(ulong steamId)
{
Torch.Invoke(() =>
{
var p = MySession.Static.GetUserPromoteLevel(steamId);
if (p < MyPromoteLevel.Admin) //cannot promote to owner by design
MySession.Static.SetUserPromoteLevel(steamId, p + 1);
});
}
/// <inheritdoc />
public void DemoteUser(ulong steamId)
{
Torch.Invoke(() =>
{
var p = MySession.Static.GetUserPromoteLevel(steamId);
if (p > MyPromoteLevel.None && p < MyPromoteLevel.Owner) //owner cannot be demoted by design
MySession.Static.SetUserPromoteLevel(steamId, p - 1);
});
}
/// <inheritdoc />
public MyPromoteLevel GetUserPromoteLevel(ulong steamId)
{
return MySession.Static.GetUserPromoteLevel(steamId);
}
/// <inheritdoc />
public bool IsBanned(ulong steamId) => false;
/// <inheritdoc />
public event Action<ulong> PlayerKicked
{
add => throw new NotImplementedException();
remove => throw new NotImplementedException();
}
/// <inheritdoc />
public event Action<ulong, bool> PlayerBanned
{
add => throw new NotImplementedException();
remove => throw new NotImplementedException();
}
/// <inheritdoc />
public event Action<ulong, MyPromoteLevel> PlayerPromoted
{
add => throw new NotImplementedException();
remove => throw new NotImplementedException();
}
/// <inheritdoc/>
public override void Attach()
{
base.Attach();
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
}
/// <inheritdoc/>
public override void Detach()
{
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
base.Detach();
}
}
}

View File

@@ -1,194 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using NLog;
using Torch.Utils;
using MessageBox = System.Windows.MessageBox;
namespace Torch.Client
{
public static class Program
{
public const string SpaceEngineersBinaries = "Bin64";
private static string _spaceEngInstallAlias = null;
public static string SpaceEngineersInstallAlias
{
get
{
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression
if (_spaceEngInstallAlias == null)
{
// ReSharper disable once AssignNullToNotNullAttribute
_spaceEngInstallAlias = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location),
"SpaceEngineersAlias");
}
return _spaceEngInstallAlias;
}
}
private const string _steamSpaceEngineersDirectory = @"steamapps\common\SpaceEngineers\";
private const string _spaceEngineersVerifyFile = SpaceEngineersBinaries + @"\SpaceEngineers.exe";
public const string ConfigName = "Torch.cfg";
private static Logger _log = LogManager.GetLogger("Torch");
#if DEBUG
[DllImport("kernel32.dll")]
private static extern void AllocConsole();
[DllImport("kernel32.dll")]
private static extern void FreeConsole();
#endif
public static void Main(string[] args)
{
#if DEBUG
try
{
AllocConsole();
#endif
if (!TorchLauncher.IsTorchWrapped())
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
// Early config: Resolve SE install directory.
if (!File.Exists(Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile)))
SetupSpaceEngInstallAlias();
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName, args,
Path.Combine(SpaceEngineersInstallAlias, SpaceEngineersBinaries));
return;
}
RunClient();
#if DEBUG
}
finally
{
FreeConsole();
}
#endif
}
private static void SetupSpaceEngInstallAlias()
{
string spaceEngineersDirectory = null;
// TODO look at Steam/config/Config.VDF? Has alternate directories.
var steamDir =
Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Valve\\Steam", "SteamPath",
null) as string;
if (steamDir != null)
{
spaceEngineersDirectory = Path.Combine(steamDir, _steamSpaceEngineersDirectory);
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (File.Exists(Path.Combine(spaceEngineersDirectory, _spaceEngineersVerifyFile)))
_log.Debug("Found Space Engineers in {0}", spaceEngineersDirectory);
else
{
_log.Debug("Couldn't find Space Engineers in {0}", spaceEngineersDirectory);
spaceEngineersDirectory = null;
}
}
if (spaceEngineersDirectory == null)
{
var dialog = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Please select the SpaceEngineers installation folder"
};
do
{
if (dialog.ShowDialog() != DialogResult.OK)
{
var ex = new FileNotFoundException(
"Unable to find the Space Engineers install directory, aborting");
_log.Fatal(ex);
LogManager.Flush();
throw ex;
}
spaceEngineersDirectory = dialog.SelectedPath;
if (File.Exists(Path.Combine(spaceEngineersDirectory, _spaceEngineersVerifyFile)))
break;
if (MessageBox.Show(
$"Unable to find {0} in {1}. Are you sure it's the Space Engineers install directory?",
"Invalid Space Engineers Directory", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
break;
} while (true); // Repeat until they confirm.
}
if (!JunctionLink(SpaceEngineersInstallAlias, spaceEngineersDirectory))
{
var ex = new IOException(
$"Failed to create junction link {SpaceEngineersInstallAlias} => {spaceEngineersDirectory}. Aborting.");
_log.Fatal(ex);
LogManager.Flush();
throw ex;
}
string junctionVerify = Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile);
if (!File.Exists(junctionVerify))
{
var ex = new FileNotFoundException(
$"Junction link is not working. File {junctionVerify} does not exist");
_log.Fatal(ex);
LogManager.Flush();
throw ex;
}
}
private static bool JunctionLink(string linkName, string targetDir)
{
var junctionLinkProc = new ProcessStartInfo("cmd.exe", $"/c mklink /J \"{linkName}\" \"{targetDir}\"")
{
WorkingDirectory = Directory.GetCurrentDirectory(),
UseShellExecute = false,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.ASCII
};
Process cmd = Process.Start(junctionLinkProc);
// ReSharper disable once PossibleNullReferenceException
while (!cmd.HasExited)
{
string line = cmd.StandardOutput.ReadLine();
if (!string.IsNullOrWhiteSpace(line))
_log.Info(line);
Thread.Sleep(100);
}
if (cmd.ExitCode != 0)
_log.Error("Unable to create junction link {0} => {1}", linkName, targetDir);
return cmd.ExitCode == 0;
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = (Exception) e.ExceptionObject;
_log.Error(ex);
LogManager.Flush();
MessageBox.Show(ex.StackTrace, ex.Message);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void RunClient()
{
var client = new TorchClient();
try
{
client.Init();
}
catch (Exception e)
{
_log.Fatal("Torch encountered an error trying to initialize the game.");
_log.Fatal(e);
return;
}
client.Start();
}
}
}

View File

@@ -1,17 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Torch Client")]
[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,63 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Torch.Client.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Torch.Client.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Torch.Client.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@@ -1,122 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net461</TargetFramework>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<AssemblyTitle>Torch Client</AssemblyTitle>
<Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<OutputPath>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutputPath>
<Prefer32Bit>true</Prefer32Bit>
<UseWPF>true</UseWPF>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
</PostBuildEvent>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
</PostBuildEvent>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
</PostBuildEvent>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<DebugType>pdbonly</DebugType>
<DocumentationFile>$(SolutionDir)\bin\$(Platform)\$(Configuration)\Torch.Client.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>torchicon.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
<ItemGroup>
<PackageReference Include="Mono.TextTransform" Version="1.0.0" />
<PackageReference Include="NLog" Version="4.4.12" />
</ItemGroup>
<ItemGroup>
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Game">
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Graphics, Version=0.1.6108.20417, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="SpaceEngineers.Game">
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml" />
<Reference Include="VRage">
<HintPath>..\GameBinaries\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game">
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game.XmlSerializers">
<HintPath>..\GameBinaries\VRage.Game.XmlSerializers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Library">
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Render">
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render11">
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Steam">
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch\Torch.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="torchicon.ico" />
</ItemGroup>
<ItemGroup>
<Compile Remove="obj\x64\Debug\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
<Compile Remove="obj\x64\Release\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
</ItemGroup>
</Project>

View File

@@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Platform;
using Sandbox.Game;
using SpaceEngineers.Game;
using VRage.Steam;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Client.Manager;
using Torch.Client.UI;
using Torch.Session;
using VRage;
using VRage.FileSystem;
using VRage.GameServices;
using VRageRender;
using VRageRender.ExternalApp;
namespace Torch.Client
{
public class TorchClient : TorchBase, ITorchClient
{
protected override uint SteamAppId => 244850;
protected override string SteamAppName => "SpaceEngineers";
public TorchClient()
{
Config = new TorchClientConfig();
var sessionManager = Managers.GetManager<ITorchSessionManager>();
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerLobby
? new MultiplayerManagerLobby(this)
: null);
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerClientBase
? new MultiplayerManagerClient(this)
: null);
}
public override void Init()
{
Directory.SetCurrentDirectory(Program.SpaceEngineersInstallAlias);
MyFileSystem.ExePath = Path.Combine(Program.SpaceEngineersInstallAlias, Program.SpaceEngineersBinaries);
Log.Info("Initializing Torch Client");
Config.InstancePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
SteamAppName);
base.Init();
OverrideMenus();
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
}
private void OverrideMenus()
{
var credits = new MyCreditsDepartment("Torch Developed By")
{
Persons = new List<MyCreditsPerson>
{
new MyCreditsPerson("THE TORCH TEAM"),
new MyCreditsPerson("http://github.com/TorchSE"),
}
};
MyPerGameSettings.Credits.Departments.Insert(0, credits);
MyPerGameSettings.GUI.MainMenu = typeof(TorchMainMenuScreen);
}
private void SetRenderWindowTitle(string title)
{
MyRenderThread renderThread = MySandboxGame.Static?.GameRenderComponent?.RenderThread;
if (renderThread == null)
return;
FieldInfo renderWindowField = typeof(MyRenderThread).GetField("m_renderWindow",
BindingFlags.Instance | BindingFlags.NonPublic);
if (renderWindowField == null)
return;
var window =
renderWindowField.GetValue(MySandboxGame.Static.GameRenderComponent.RenderThread) as
System.Windows.Forms.Form;
if (window != null)
renderThread.Invoke(() => { window.Text = title; });
}
public override void Restart()
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Client
{
public class TorchClientConfig : ITorchConfig
{
// How do we want to handle client side config? It's radically different than the server.
public bool GetPluginUpdates { get; set; } = false;
public bool GetTorchUpdates { get; set; } = false;
public string InstanceName { get; set; } = "TorchClient";
public string InstancePath { get; set; }
public bool NoUpdate { get; set; } = true;
public List<string> Plugins { get; set; }
public bool ShouldUpdatePlugins { get; } = false;
public bool ShouldUpdateTorch { get; } = false;
public int TickTimeout { get; set; }
public bool Autostart { get; set; } = false;
public bool ForceUpdate { get; set; } = false;
public bool NoGui { get; set; } = false;
public bool RestartOnCrash { get; set; } = false;
public string WaitForPID { get; set; } = null;
public string ChatName { get; set; }
public string ChatColor { get; set; }
public bool Save(string path = null)
{
return true;
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Sandbox.Graphics;
using Sandbox.Graphics.GUI;
using Sandbox.Gui;
using VRage.Utils;
using VRageMath;
namespace Torch.Client
{
public class TorchConsoleScreen : MyGuiScreenBase
{
private MyGuiControlTextbox _textBox;
public override string GetFriendlyName()
{
return "Torch Console";
}
public TorchConsoleScreen() : base(isTopMostScreen: true)
{
BackgroundColor = new Vector4(0, 0, 0, 0.5f);
Size = new Vector2(0.5f);
RecreateControls(true);
}
public sealed override void RecreateControls(bool constructor)
{
Elements.Clear();
Elements.Add(new MyGuiControlLabel
{
Text = "Torch Console",
OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_RIGHT_AND_VERTICAL_TOP,
Position = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_RIGHT_AND_VERTICAL_TOP)
});
Controls.Clear();
_textBox = new MyGuiControlTextbox
{
BorderEnabled = false,
Enabled = true,
OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP,
Position = new Vector2(-0.5f)
};
Controls.Add(_textBox);
var pistonBtn = new MyGuiControlImageButton
{
Name = "TorchButton",
Text = "Torch",
HighlightType = MyGuiControlHighlightType.WHEN_CURSOR_OVER,
Visible = true,
OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP
};
Controls.Add(pistonBtn);
}
}
}

View File

@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Graphics;
using Sandbox.Graphics.GUI;
using VRage.Utils;
using VRageMath;
namespace Torch.Client
{
public class TorchSettingsScreen : MyGuiScreenBase
{
public override string GetFriendlyName() => "Torch Settings";
public TorchSettingsScreen() : base(new Vector2(0.5f), null, Vector2.One, true)
{
RecreateControls(true);
}
public sealed override void RecreateControls(bool constructor)
{
base.RecreateControls(constructor);
AddCaption(MyStringId.GetOrCompute("Torch Settings"), null, new Vector2(0, 0), 1.2f);
var pluginList = new MyGuiControlListbox
{
VisibleRowsCount = 10,
};
foreach (var plugin in TorchBase.Instance.Plugins)
{
pluginList.Items.Add(new MyGuiControlListbox.Item(new StringBuilder(plugin.Name)));
}
Controls.Add(pluginList);
}
}
}

View File

@@ -1,56 +0,0 @@
#pragma warning disable 618
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Sandbox.Game;
using Sandbox.Game.Gui;
using Sandbox.Graphics;
using Sandbox.Graphics.GUI;
using Sandbox.Gui;
using SpaceEngineers.Game.GUI;
using Torch.Utils;
using VRage.Game;
using VRage.Utils;
using VRageMath;
namespace Torch.Client.UI
{
public class TorchMainMenuScreen : MyGuiScreenMainMenu
{
#pragma warning disable 169
[ReflectedGetter(Name = "m_elementGroup")]
private static Func<MyGuiScreenMainMenu, MyGuiControlElementGroup> _elementsGroup;
#pragma warning restore 169
public TorchMainMenuScreen() : this(false)
{
}
public TorchMainMenuScreen(bool pauseGame)
: base(pauseGame)
{
}
/// <inheritdoc />
public override void RecreateControls(bool constructor)
{
base.RecreateControls(constructor);
Vector2 minSizeGui = MyGuiControlButton.GetVisualStyle(MyGuiControlButtonStyleEnum.Default).NormalTexture.MinSizeGui;
Vector2 value = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_BOTTOM, 54, 54) + new Vector2(minSizeGui.X / 2f, 0f) + new Vector2(15f, 0f) / MyGuiConstants.GUI_OPTIMAL_SIZE;
MyGuiControlButton myGuiControlButton = MakeButton(value - 9 * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA,
MyStringId.GetOrCompute("Torch"), TorchButtonClicked, MyCommonTexts.ToolTipExitToWindows);
Controls.Add(myGuiControlButton);
_elementsGroup.Invoke(this).Add(myGuiControlButton);
}
private void TorchButtonClicked(MyGuiControlButton obj)
{
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen<TorchNavScreen>());
}
}
}

View File

@@ -1,49 +0,0 @@
using Sandbox;
using Sandbox.Game.Gui;
using Sandbox.Graphics.GUI;
using VRage;
using VRage.Game;
using VRage.Utils;
using VRageMath;
namespace Torch.Client.UI
{
public class TorchNavScreen : MyGuiScreenBase
{
private MyGuiControlElementGroup _elementGroup;
public TorchNavScreen() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR, new Vector2(0.35875f, 0.558333337f))
{
EnabledBackgroundFade = true;
RecreateControls(true);
}
public override void RecreateControls(bool constructor)
{
base.RecreateControls(constructor);
_elementGroup = new MyGuiControlElementGroup();
_elementGroup.HighlightChanged += ElementGroupHighlightChanged;
AddCaption(MyCommonTexts.ScreenCaptionOptions, null, null);
var value = new Vector2(0f, -m_size.Value.Y / 2f + 0.146f);
var num = 0;
var myGuiControlButton = new MyGuiControlButton(value + num++ * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA, MyGuiControlButtonStyleEnum.Default, null, null, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, null, MyTexts.Get(MyCommonTexts.ScreenOptionsButtonGame), 0.8f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, MyGuiControlHighlightType.WHEN_ACTIVE, delegate(MyGuiControlButton sender)
{
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen<TorchSettingsScreen>());
}, GuiSounds.MouseClick, 1f, null);
Controls.Add(myGuiControlButton);
_elementGroup.Add(myGuiControlButton);
CloseButtonEnabled = true;
}
private void ElementGroupHighlightChanged(MyGuiControlElementGroup obj)
{
foreach (MyGuiControlBase current in _elementGroup)
if (current.HasFocus && obj.SelectedElement != current)
FocusedControl = obj.SelectedElement;
}
public override string GetFriendlyName() => "Torch";
public void OnBackClick(MyGuiControlButton sender) => CloseScreen();
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Graphics.GUI;
using VRageMath;
namespace Torch.Client.UI
{
public class TorchSettingsScreen : MyGuiScreenBase
{
public TorchSettingsScreen() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR,
new Vector2(0.35875f, 0.558333337f))
{
EnabledBackgroundFade = true;
RecreateControls(true);
}
/// <inheritdoc />
public override string GetFriendlyName() => "Torch Settings";
public void OnBackClick(MyGuiControlButton sender) => CloseScreen();
}
}

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>
</configuration>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

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>
@@ -17,9 +18,9 @@
</PropertyGroup> </PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> --> <!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -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

@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
using Microsoft.Extensions.Configuration;
using NLog; using NLog;
using NLog.Targets; using NLog.Targets;
using Sandbox.Engine.Utils; using Sandbox.Engine.Utils;
@@ -29,25 +30,20 @@ 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;
} }
public bool Initialize(string[] args) public bool Initialize(IConfiguration configuration)
{ {
if (_init) if (_init)
return false; return false;
@@ -60,18 +56,17 @@ quit";
Log.Debug("Debug logging enabled."); Log.Debug("Debug logging enabled.");
#endif #endif
// This is what happens when Keen is bad and puts extensions into the System namespace. if (!configuration.GetValue("noupdate", false))
if (!Enumerable.Contains(args, "-noupdate")) RunSteamCmd(configuration);
RunSteamCmd();
if (!string.IsNullOrEmpty(Config.WaitForPID)) var processPid = configuration.GetValue<int>("waitForPid");
if (processPid != 0)
{ {
try try
{ {
var pid = int.Parse(Config.WaitForPID); var waitProc = Process.GetProcessById(processPid);
var waitProc = Process.GetProcessById(pid);
Log.Info("Continuing in 5 seconds."); Log.Info("Continuing in 5 seconds.");
Log.Warn($"Waiting for process {pid} to close"); Log.Warn($"Waiting for process {processPid} to close");
while (!waitProc.HasExited) while (!waitProc.HasExited)
{ {
Console.Write("."); Console.Write(".");
@@ -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();
@@ -129,21 +124,18 @@ quit";
} }
} }
public static void RunSteamCmd() public static void RunSteamCmd(IConfiguration configuration)
{ {
var log = LogManager.GetLogger("SteamCMD"); var log = LogManager.GetLogger("SteamCMD");
var path = Environment.GetEnvironmentVariable("TORCH_STEAMCMD") ?? Path.GetFullPath(STEAMCMD_DIR); var path = configuration.GetValue<string>("steamCmdPath") ?? ApplicationContext.Current.TorchDirectory
.CreateSubdirectory(STEAMCMD_DIR).FullName;
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
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))
{ {
@@ -152,7 +144,8 @@ quit";
log.Info("Downloading SteamCMD."); log.Info("Downloading SteamCMD.");
using (var client = new HttpClient()) using (var client = new HttpClient())
using (var file = File.Create(STEAMCMD_ZIP)) using (var file = File.Create(STEAMCMD_ZIP))
client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result.CopyTo(file); using (var stream = client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result)
stream.CopyTo(file);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path); ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path);
File.Delete(STEAMCMD_ZIP); File.Delete(STEAMCMD_ZIP);
@@ -166,8 +159,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, configuration.GetValue("gamePath", "../")),
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

@@ -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(world.Checkpoint.SessionName)
? Path.GetDirectoryName(DedicatedConfig.LoadWorld)
: world.Checkpoint.SessionName;
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;
@@ -282,7 +284,6 @@ namespace Torch.Server.Managers
{ {
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string FolderName { get; set; }
public string WorldPath { get; } public string WorldPath { get; }
public MyObjectBuilder_SessionSettings KeenSessionSettings => WorldConfiguration.Settings; public MyObjectBuilder_SessionSettings KeenSessionSettings => WorldConfiguration.Settings;
public MyObjectBuilder_Checkpoint KeenCheckpoint => Checkpoint; public MyObjectBuilder_Checkpoint KeenCheckpoint => Checkpoint;
@@ -311,7 +312,6 @@ namespace Torch.Server.Managers
WorldSizeKB = new DirectoryInfo(worldPath).GetFiles().Sum(x => x.Length) / 1024; WorldSizeKB = new DirectoryInfo(worldPath).GetFiles().Sum(x => x.Length) / 1024;
_checkpointPath = Path.Combine(WorldPath, "Sandbox.sbc"); _checkpointPath = Path.Combine(WorldPath, "Sandbox.sbc");
_worldConfigPath = Path.Combine(WorldPath, "Sandbox_config.sbc"); _worldConfigPath = Path.Combine(WorldPath, "Sandbox_config.sbc");
FolderName = Path.GetFileName(worldPath);
if (loadFiles) if (loadFiles)
LoadSandbox(); LoadSandbox();
} }

View File

@@ -12,7 +12,6 @@ using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking; using Sandbox.Engine.Networking;
using Sandbox.Game.Gui; using Sandbox.Game.Gui;
using Sandbox.Game.World; using Sandbox.Game.World;
using Steamworks;
using Torch.API; using Torch.API;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.Managers; using Torch.Managers;

View File

@@ -3,6 +3,7 @@ using NLog;
using Sandbox.Engine.Networking; using Sandbox.Engine.Networking;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Server;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.Utils; using Torch.Utils;
using VRage.Game; using VRage.Game;
@@ -25,6 +26,9 @@ public static class CheckpointLoadPatch
private static bool Prefix(ref MyObjectBuilder_Checkpoint __result) private static bool Prefix(ref MyObjectBuilder_Checkpoint __result)
{ {
#pragma warning disable CS0618 #pragma warning disable CS0618
if (!((TorchServer)TorchBase.Instance).HasRun)
return true;
var world = TorchBase.Instance.Managers.GetManager<InstanceManager>().DedicatedConfig.SelectedWorld; var world = TorchBase.Instance.Managers.GetManager<InstanceManager>().DedicatedConfig.SelectedWorld;
#pragma warning restore CS0618 #pragma warning restore CS0618
if (world is null) if (world is null)
@@ -33,6 +37,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

@@ -0,0 +1,35 @@
using System.Reflection;
using NLog;
using Steamworks;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches;
[PatchShim]
public static class SteamLoginPatch
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
[ReflectedMethodInfo(null, "LogOnAnonymous", TypeName = "VRage.Steam.MySteamGameServer, VRage.Steam")]
private static MethodInfo LoginMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(LoginMethod).AddPrefix();
}
private static bool Prefix()
{
#pragma warning disable CS0618
var token = TorchBase.Instance.Config.LoginToken;
#pragma warning restore CS0618
if (string.IsNullOrEmpty(token))
return true;
Log.Info("Logging in to Steam with GSLT");
SteamGameServer.LogOn(token);
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,41 +1,96 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Xml;
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 isService = Environment.GetEnvironmentVariable("TORCH_SERVICE") var configurationBuilder = new ConfigurationBuilder()
?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false; .AddEnvironmentVariables("TORCH")
Target.Register<LogViewerTarget>(nameof(LogViewerTarget)); .AddCommandLine(args);
//Ensures that all the files are downloaded in the Torch directory. var configuration = configurationBuilder.Build();
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)) var context = CreateApplicationContext(configuration);
foreach (var file in Directory.GetFiles(binDir, "System.*.dll"))
{
File.Delete(file);
}
TorchLauncher.Launch(workingDir, binDir); SetupLogging(context, configuration);
var config = SetupConfiguration(context, configurationBuilder, out configuration);
// Breaks on Windows Server 2019 var handler = new UnhandledExceptionHandler(config.Data);
#if TORCH_SERVICE AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
if (!new ComputerInfo().OSFullName.Contains("Server 2019") && !Environment.UserInteractive)
{ var initializer = new Initializer(config);
using (var service = new TorchService(args)) if (!initializer.Initialize(configuration))
ServiceBase.Run(service); Environment.Exit(1);
return;
} #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 #endif
var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance"; initializer.Run();
}
private static void SetupLogging(IApplicationContext context, IConfiguration configuration)
{
var oldNlog = Path.Combine(context.TorchDirectory.FullName, "NLog.config");
var newNlog = configuration.GetValue("loggingConfigPath", Path.Combine(context.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(configuration.GetValue("loggingExtensionsPath",
Path.Combine(
context.InstanceDirectory.FullName,
"LoggingExtensions")));
TorchLogManager.SetConfiguration(new XmlLoggingConfiguration(newNlog));
}
private static Persistent<TorchConfig> SetupConfiguration(IApplicationContext context,
IConfigurationBuilder builder,
out IConfigurationRoot configuration)
{
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);
builder.AddXmlFile(torchCfg, true, true);
configuration = builder.Build();
var config = new Persistent<TorchConfig>(torchCfg, configuration.Get<TorchConfig>() ?? new());
config.Data.InstanceName = context.InstanceName;
config.Data.InstancePath = context.InstanceDirectory.FullName;
return config;
}
private static IApplicationContext CreateApplicationContext(IConfiguration configuration)
{
var isService = configuration.GetValue("service", false);
var workingDir = AppContext.BaseDirectory;
var gamePath = configuration.GetValue("gamePath", workingDir);
var binDir = Path.Combine(gamePath, "DedicatedServer64");
var instanceName = configuration.GetValue("instanceName", "Instance");
string instancePath; string instancePath;
if (Path.IsPathRooted(instanceName)) if (Path.IsPathRooted(instanceName))
@@ -45,61 +100,13 @@ namespace Torch.Server
} }
else else
{ {
instancePath = Path.GetFullPath(instanceName); instancePath = Directory.CreateDirectory(instanceName!).FullName;
} }
var oldTorchCfg = Path.Combine(workingDir, "Torch.cfg"); Directory.SetCurrentDirectory(gamePath);
var torchCfg = Path.Combine(instancePath, "Torch.cfg");
if (File.Exists(oldTorchCfg)) return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
File.Move(oldTorchCfg, torchCfg, true); new(instancePath), instanceName, isService);
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

@@ -3,7 +3,7 @@
"profiles": { "profiles": {
"Torch.Server": { "Torch.Server": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "-noupdate", "commandLineArgs": "--noupdate true --gamepath \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SpaceEngineersDedicatedServer\"",
"use64Bit": true, "use64Bit": true,
"hotReloadEnabled": false "hotReloadEnabled": false
} }

View File

@@ -2,11 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net6-windows</TargetFramework>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<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,152 +13,68 @@
<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>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
<IsPackable>false</IsPackable>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<BeautyLibsDir>torch64</BeautyLibsDir>
<NoBeautyFlag>True</NoBeautyFlag>
<ForceBeauty>True</ForceBeauty>
</PropertyGroup>
<PropertyGroup> <PropertyGroup>
<StartupObject>Torch.Server.Program</StartupObject> <StartupObject>Torch.Server.Program</StartupObject>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>torchicon.ico</ApplicationIcon> <ApplicationIcon>torchicon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoCompleteTextBox" Version="1.3.0" /> <PackageReference Include="AutoCompleteTextBox" Version="1.6.0" />
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="ControlzEx" Version="5.0.1" /> <PackageReference Include="ControlzEx" Version="5.0.1" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" /> <PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MdXaml" Version="1.12.0" /> <PackageReference Include="MdXaml" Version="1.16.0" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.0.226801" /> <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.2.332302" />
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Xml" Version="6.0.0" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.3" PrivateAssets="all" />
<PackageReference Include="Steamworks.NET" Version="20.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
<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" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="HavokWrapper, Version=1.0.6051.28726, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Microsoft.CodeAnalysis, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Game, Version=0.1.6305.30774, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Sandbox.Graphics, Version=0.1.6305.30761, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="SpaceEngineers.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Steamworks.NET">
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
</Reference>
<Reference Include="VRage, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Audio, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Dedicated, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Dedicated.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Library, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Platform.Windows, Culture=neutral, PublicKeyToken=null">
<HintPath>..\GameBinaries\VRage.Platform.Windows.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Render11, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Scripting, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="VRage.Steam">
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
<Private>False</Private>
</Reference>
</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" />
<ProjectReference Include="..\Torch\Torch.csproj" /> <ProjectReference Include="..\Torch\Torch.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="torchicon.ico" /> <Resource Include="torchicon.ico" />
</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" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,171 +1,124 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Xml.Serialization; using System.Xml.Serialization;
using NLog;
using Torch.API; using Torch.API;
using Torch.Views; using Torch.Views;
namespace Torch.Server namespace Torch.Server;
{
// TODO: redesign this gerbage
public class TorchConfig : CommandLine, ITorchConfig, INotifyPropertyChanged
{
private static Logger _log = LogManager.GetLogger("Config");
public class TorchConfig : ViewModel, ITorchConfig
{
public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate; public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate;
public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate; public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate;
private bool _autostart;
private bool _restartOnCrash;
private bool _noGui;
private bool _getPluginUpdates = true;
private bool _getTorchUpdates = true;
private int _tickTimeout = 60;
private bool _localPlugins;
private bool _disconnectOnRestart;
private string _chatName = "Server";
private string _chatColor = "Red";
private bool _enableWhitelist = false;
private List<ulong> _whitelist = new List<ulong>();
private int _windowWidth = 980;
private int _windowHeight = 588;
private bool _independentConsole = false;
private bool _enableAsserts = false;
private int _fontSize = 16;
private UGCServiceType _ugcServiceType = UGCServiceType.Steam;
private bool _entityManagerEnabled = true;
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")] [XmlIgnore]
public bool NoUpdate { get; set; } public bool NoUpdate { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("forceupdate", "Manually check for and install updates.")] [XmlIgnore]
public bool ForceUpdate { get; set; } public bool ForceUpdate { get; set; }
/// <summary> /// <summary>
/// Permanent flag to ALWAYS automatically start the server /// Permanent flag to ALWAYS automatically start the server
/// </summary> /// </summary>
[Display(Name = "Auto Start", Description = "Permanent flag to ALWAYS automatically start the server.", GroupName = "Server")] [Display(Name = "Auto Start", Description = "Permanent flag to ALWAYS automatically start the server.", GroupName = "Server")]
public bool Autostart { get => _autostart; set => Set(value, ref _autostart); } public bool Autostart { get; set; }
/// <summary> /// <summary>
/// Temporary flag to automatically start the server only on the next run /// Temporary flag to automatically start the server only on the next run
/// </summary> /// </summary>
[Arg("autostart", "Start the server immediately.")]
[XmlIgnore] [XmlIgnore]
public bool TempAutostart { get; set; } public bool TempAutostart { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
[Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")] [Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")]
public bool RestartOnCrash { get => _restartOnCrash; set => Set(value, ref _restartOnCrash); } public bool RestartOnCrash { get; set; }
public string InstancePath { get; set; } public string InstancePath { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Arg("nogui", "Do not show the Torch UI.")]
[Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")] [Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")]
public bool NoGui { get => _noGui; set => Set(value, ref _noGui); } public bool NoGui { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("waitforpid", "Makes Torch wait for another process to exit.")] [Display(Name = "Update Torch", Description = "Check every start for new versions of torch.",
public string WaitForPID { get; set; } GroupName = "Server")]
public bool GetTorchUpdates { get; set; } = true;
/// <inheritdoc />
[Display(Name = "Update Torch", Description = "Check every start for new versions of torch.", GroupName = "Server")]
public bool GetTorchUpdates { get => _getTorchUpdates; set => Set(value, ref _getTorchUpdates); }
public string InstanceName { get; set; } public string InstanceName { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.", GroupName = "Server")] [Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.",
public bool GetPluginUpdates { get => _getPluginUpdates; set => Set(value, ref _getPluginUpdates); } GroupName = "Server")]
public bool GetPluginUpdates { get; set; } = true;
/// <inheritdoc /> /// <inheritdoc />
[Display(Name = "Watchdog Timeout", Description = "Watchdog timeout (in seconds).", GroupName = "Server")] [Display(Name = "Watchdog Timeout", Description = "Watchdog timeout (in seconds).", GroupName = "Server")]
public int TickTimeout { get => _tickTimeout; set => Set(value, ref _tickTimeout); } public int TickTimeout { get; set; } = 60;
/// <inheritdoc /> /// <inheritdoc />
[Arg("plugins", "Starts Torch with the given plugin GUIDs (space delimited).")] public List<Guid> Plugins { get; set; } = new();
public List<Guid> Plugins { get; set; } = new List<Guid>();
[Arg("localplugins", "Loads all pluhins from disk, ignores the plugins defined in config.")]
[Display(Name = "Local Plugins", Description = "Loads all pluhins from disk, ignores the plugins defined in config.", GroupName = "In-Game")] [Display(Name = "Local Plugins", Description = "Loads all pluhins from disk, ignores the plugins defined in config.", GroupName = "In-Game")]
public bool LocalPlugins { get => _localPlugins; set => Set(value, ref _localPlugins); } public bool LocalPlugins { get; set; }
[Arg("disconnect", "When server restarts, all clients are rejected to main menu to prevent auto rejoin.")]
[Display(Name = "Auto Disconnect", Description = "When server restarts, all clients are rejected to main menu to prevent auto rejoin.", GroupName = "In-Game")] [Display(Name = "Auto Disconnect", Description = "When server restarts, all clients are rejected to main menu to prevent auto rejoin.", GroupName = "In-Game")]
public bool DisconnectOnRestart { get => _disconnectOnRestart; set => Set(value, ref _disconnectOnRestart); } public bool DisconnectOnRestart { get; set; }
[Display(Name = "Chat Name", Description = "Default name for chat from gui, broadcasts etc..", GroupName = "In-Game")] [Display(Name = "Chat Name", Description = "Default name for chat from gui, broadcasts etc..",
public string ChatName { get => _chatName; set => Set(value, ref _chatName); } GroupName = "In-Game")]
public string ChatName { get; set; } = "Server";
[Display(Name = "Chat Color", Description = "Default color for chat from gui, broadcasts etc.. (Red, Blue, White, Green)", GroupName = "In-Game")] [Display(Name = "Chat Color",
public string ChatColor { get => _chatColor; set => Set(value, ref _chatColor); } Description = "Default color for chat from gui, broadcasts etc.. (Red, Blue, White, Green)",
GroupName = "In-Game")]
public string ChatColor { get; set; } = "Red";
[Display(Name = "Enable Whitelist", Description = "Enable Whitelist to prevent random players join while maintance, tests or other.", GroupName = "In-Game")] [Display(Name = "Enable Whitelist", Description = "Enable Whitelist to prevent random players join while maintance, tests or other.", GroupName = "In-Game")]
public bool EnableWhitelist { get => _enableWhitelist; set => Set(value, ref _enableWhitelist); } public bool EnableWhitelist { get; set; }
[Display(Name = "Whitelist", Description = "Collection of whitelisted steam ids.", GroupName = "In-Game")] [Display(Name = "Whitelist", Description = "Collection of whitelisted steam ids.", GroupName = "In-Game")]
public List<ulong> Whitelist { get => _whitelist; set => Set(value, ref _whitelist); } public List<ulong> Whitelist { get; set; } = new();
[Display(Name = "Width", Description = "Default window width.", GroupName = "Window")] [Display(Name = "Width", Description = "Default window width.", GroupName = "Window")]
public int WindowWidth { get => _windowWidth; set => Set(value, ref _windowWidth); } public int WindowWidth { get; set; } = 980;
[Display(Name = "Height", Description = "Default window height", GroupName = "Window")] [Display(Name = "Height", Description = "Default window height", GroupName = "Window")]
public int WindowHeight { get => _windowHeight; set => Set(value, ref _windowHeight); } public int WindowHeight { get; set; } = 588;
[Display(Name = "Font Size", Description = "Font size for logging text box. (default is 16)", GroupName = "Window")] [Display(Name = "Font Size", Description = "Font size for logging text box. (default is 16)",
public int FontSize { get => _fontSize; set => Set(value, ref _fontSize); } GroupName = "Window")]
public int FontSize { get; set; } = 16;
[Display(Name = "UGC Service Type", Description = "Service for downloading mods", GroupName = "Server")] [Display(Name = "UGC Service Type", Description = "Service for downloading mods", GroupName = "Server")]
public UGCServiceType UgcServiceType public UGCServiceType UgcServiceType { get; set; } = UGCServiceType.Steam;
{
get => _ugcServiceType;
set => Set(value, ref _ugcServiceType);
}
public string LastUsedTheme { get; set; } = "Torch Theme"; public string LastUsedTheme { get; set; } = "Torch Theme";
//Prevent reserved players being written to disk, but allow it to be read
//remove this when ReservedPlayers is removed
private bool ShouldSerializeReservedPlayers() => false;
[Arg("console", "Keeps a separate console window open after the main UI loads.")]
[Display(Name = "Independent Console", Description = "Keeps a separate console window open after the main UI loads.", GroupName = "Window")] [Display(Name = "Independent Console", Description = "Keeps a separate console window open after the main UI loads.", GroupName = "Window")]
public bool IndependentConsole { get => _independentConsole; set => Set(value, ref _independentConsole); } public bool IndependentConsole { get; set; }
[XmlIgnore] [XmlIgnore]
[Arg("testplugin", "Path to a plugin to debug. For development use only.")]
public string TestPlugin { get; set; } public string TestPlugin { get; set; }
[Arg("asserts", "Enable Keen's assert logging.")]
[Display(Name = "Enable Asserts", Description = "Enable Keen's assert logging.", GroupName = "Server")] [Display(Name = "Enable Asserts", Description = "Enable Keen's assert logging.", GroupName = "Server")]
public bool EnableAsserts { get => _enableAsserts; set => Set(value, ref _enableAsserts); } public bool EnableAsserts { get; set; }
[Display(Name = "Enable Entity Manager", Description = "Enable Entity Manager tab. (can affect performance)", [Display(Name = "Enable Entity Manager", Description = "Enable Entity Manager tab. (can affect performance)",
GroupName = "Server")] GroupName = "Server")]
public bool EntityManagerEnabled public bool EntityManagerEnabled { get; set; } = true;
[Display(Name = "Login Token", Description = "Steam GSLT (can be used if you have dynamic ip)", GroupName = "Server")]
public string LoginToken { get; set; }
public UpdateSource UpdateSource { get; set; } = new()
{ {
get => _entityManagerEnabled; Repository = "PveTeam/Torch",
set => Set(value, ref _entityManagerEnabled); Url = "https://api.github.com",
} SourceType = UpdateSourceType.Github
};
public event PropertyChangedEventHandler PropertyChanged;
public TorchConfig() { }
protected void Set<T>(T value, ref T field, [CallerMemberName] string callerName = default)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerName));
}
// for backward compatibility // for backward compatibility
public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path); public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path);
} }
}

View File

@@ -37,15 +37,9 @@ namespace Torch.Server
{ {
public class TorchServer : TorchBase, ITorchServer public class TorchServer : TorchBase, ITorchServer
{ {
private bool _hasRun;
private bool _canRun;
private TimeSpan _elapsedPlayTime;
private bool _isRunning;
private float _simRatio; private float _simRatio;
private ServerState _state;
private Stopwatch _uptime; private Stopwatch _uptime;
private Timer _watchdog; private Timer _watchdog;
private int _players;
private MultiplayerManagerDedicated _multiplayerManagerDedicated; private MultiplayerManagerDedicated _multiplayerManagerDedicated;
internal bool FatalException { get; set; } internal bool FatalException { get; set; }
@@ -67,11 +61,13 @@ namespace Torch.Server
var sessionManager = Managers.GetManager<ITorchSessionManager>(); var sessionManager = Managers.GetManager<ITorchSessionManager>();
sessionManager.AddFactory(x => new MultiplayerManagerDedicated(this)); sessionManager.AddFactory(x => new MultiplayerManagerDedicated(this));
sessionManager.SessionStateChanged += OnSessionStateChanged;
// Needs to be done at some point after MyVRageWindows.Init // Needs to be done at some point after MyVRageWindows.Init
// where the debug listeners are registered // where the debug listeners are registered
if (!((TorchConfig)Config).EnableAsserts) if (!((TorchConfig)Config).EnableAsserts)
MyDebug.Listeners.Clear(); MyDebug.Listeners.Clear();
_simUpdateTimer.Elapsed += SimUpdateElapsed; _simUpdateTimer.Elapsed += SimUpdateElapsed;
_simUpdateTimer.Start(); _simUpdateTimer.Start();
} }
@@ -85,7 +81,7 @@ namespace Torch.Server
} }
} }
public bool HasRun { get => _hasRun; set => SetValue(ref _hasRun, value); } public bool HasRun { get; set; }
/// <inheritdoc /> /// <inheritdoc />
@@ -104,22 +100,19 @@ namespace Torch.Server
} }
/// <inheritdoc /> /// <inheritdoc />
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set => SetValue(ref _elapsedPlayTime, value); } public TimeSpan ElapsedPlayTime { get; set; }
/// <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; set; }
public bool CanRun { get => _canRun; set => SetValue(ref _canRun, value); } public bool CanRun { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public InstanceManager DedicatedInstance { get; } public InstanceManager DedicatedInstance { get; }
/// <inheritdoc />
public string InstanceName { get; }
/// <inheritdoc /> /// <inheritdoc />
protected override uint SteamAppId => 244850; protected override uint SteamAppId => 244850;
@@ -127,22 +120,17 @@ namespace Torch.Server
protected override string SteamAppName => "SpaceEngineersDedicated"; protected override string SteamAppName => "SpaceEngineersDedicated";
/// <inheritdoc /> /// <inheritdoc />
public ServerState State { get => _state; private set => SetValue(ref _state, value); } public ServerState State { get; private set; }
public event Action<ITorchServer> Initialized; public event Action<ITorchServer> Initialized;
/// <inheritdoc /> public int OnlinePlayers { get; private set; }
public string InstancePath { get; }
public int OnlinePlayers { get => _players; private set => SetValue(ref _players, value); }
/// <inheritdoc /> /// <inheritdoc />
public override void Init() public override void Init()
{ {
Log.Info("Initializing server"); Log.Info("Initializing server");
MySandboxGame.IsDedicated = true;
base.Init(); base.Init();
Managers.GetManager<ITorchSessionManager>().SessionStateChanged += OnSessionStateChanged;
GetManager<InstanceManager>().LoadInstance(InstancePath); GetManager<InstanceManager>().LoadInstance(InstancePath);
CanRun = true; CanRun = true;
Initialized?.Invoke(this); Initialized?.Invoke(this);
@@ -178,6 +166,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 +184,7 @@ namespace Torch.Server
State = ServerState.Stopped; State = ServerState.Stopped;
IsRunning = false; IsRunning = false;
CanRun = true; CanRun = true;
SimulationRatio = 0;
} }
/// <summary> /// <summary>
@@ -202,35 +202,73 @@ 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(); LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe"); #if DEBUG
config.WaitForPID = Environment.ProcessId.ToString();
config.TempAutostart = true;
Process.Start(exe, config.ToString());
Environment.Exit(0); Environment.Exit(0);
#endif
var exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe");
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length; i++)
{
if (args[i].Contains(' '))
args[i] = $"\"{args[i]}\"";
if (!args[i].Contains("--tempAutostart", StringComparison.InvariantCultureIgnoreCase) &&
!args[i].Contains("--waitForPid", StringComparison.InvariantCultureIgnoreCase))
continue;
args[i] = string.Empty;
args[++i] = string.Empty;
}
Process.Start(exe, $"--waitForPid {Environment.ProcessId} --tempAutostart true {string.Join(" ", args)}");
})
{
Name = "Restart thread"
}.Start();
} }
[SuppressPropertyChangedWarnings] [SuppressPropertyChangedWarnings]
private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState) private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
{ {
if (newState == TorchSessionState.Unloading || newState == TorchSessionState.Unloaded) switch (newState)
{ {
case TorchSessionState.Unloading:
_watchdog?.Dispose(); _watchdog?.Dispose();
_watchdog = null; _watchdog = null;
ModCommunication.Unregister(); ModCommunication.Unregister();
} break;
case 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();
break;
case TorchSessionState.Loading:
case TorchSessionState.Unloaded:
break;
default:
throw new ArgumentOutOfRangeException(nameof(newState), newState, null);
} }
}
private void MultiplayerManagerDedicatedOnPlayerLeft(IPlayer player)
{
OnlinePlayers--;
}
private void MultiplayerManagerDedicatedOnPlayerJoined(IPlayer player)
{
OnlinePlayers++;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -251,7 +289,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 +379,14 @@ 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)) using var target = DataTarget.CreateSnapshotAndAttach(Environment.ProcessId);
{
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 +408,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,8 +32,8 @@ 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;
_config.WaitForPID = Environment.ProcessId.ToString();
Process.Start(exe, _config.ToString()); Process.Start(exe, $"-waitForPid {Environment.ProcessId} {_config}");
} }
else else
{ {

View File

@@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Steamworks;
namespace Torch.Server.ViewModels
{
public class SteamUserViewModel : ViewModel
{
public string Name { get; }
public ulong SteamId { get; }
public SteamUserViewModel(ulong id)
{
SteamId = id;
Name = SteamFriends.GetFriendPersonaName(new CSteamID(id));
}
public SteamUserViewModel() : this(0) { }
}
}

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

@@ -15,7 +15,8 @@
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate> <ControlTemplate>
<ScrollViewer CanContentScroll="True" ScrollChanged="ScrollViewer_OnScrollChanged" Loaded="ScrollViewer_OnLoaded" Unloaded="ScrollViewer_OnUnloaded"> <ScrollViewer CanContentScroll="True" ScrollChanged="ScrollViewer_OnScrollChanged"
Loaded="ScrollViewer_OnLoaded" Unloaded="ScrollViewer_OnUnloaded">
<ItemsPresenter /> <ItemsPresenter />
</ScrollViewer> </ScrollViewer>
</ControlTemplate> </ControlTemplate>
@@ -31,10 +32,6 @@
</Setter> </Setter>
</Style> </Style>
<Style TargetType="{x:Type TextBlock}" x:Key="TextBlockBaseStyle">
<Setter Property="FontFamily" Value="Consolas" />
</Style>
<DataTemplate DataType="{x:Type viewModels:LogEntry}"> <DataTemplate DataType="{x:Type viewModels:LogEntry}">
<Grid IsSharedSizeScope="True"> <Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -43,10 +40,16 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="{Binding Timestamp, StringFormat=HH:mm:ss.fff}" Grid.Column="0" <TextBlock Text="{Binding Timestamp, StringFormat=HH:mm:ss.fff}" Grid.Column="0"
FontWeight="Bold" Style="{StaticResource TextBlockBaseStyle}" Foreground="{Binding Color}" Margin="5,0,5,0" /> FontWeight="Bold" FontFamily="Consolas" Foreground="{Binding Color}" Margin="5,0,5,0" />
<TextBlock Text="{Binding Message}" Grid.Column="1" <TextBox Grid.Column="1"
TextWrapping="Wrap" Style="{StaticResource TextBlockBaseStyle}" Foreground="{Binding Color}" /> Text="{Binding Message, Mode=OneWay}"
TextWrapping="Wrap"
FontFamily="Consolas"
Background="Transparent"
BorderThickness="0"
IsReadOnly="True"
Foreground="{Binding Color}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</UserControl.Resources> </UserControl.Resources>

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

@@ -50,7 +50,7 @@ namespace Torch.Server.Views
var PercentChangeOnDownload = 100 / PluginsToDownload.Count; var PercentChangeOnDownload = 100 / PluginsToDownload.Count;
foreach (PluginItem PluginItem in PluginsToDownload) { foreach (PluginItem PluginItem in PluginsToDownload) {
if (!Task.Run(async () => await PluginQuery.Instance.DownloadPlugin(PluginItem.Id)).Result) { if (!PluginQuery.Instance.DownloadPlugin(PluginItem.Id).Result) {
failedDownloads++; failedDownloads++;
DownloadProgress += PercentChangeOnDownload; DownloadProgress += PercentChangeOnDownload;
(sender as BackgroundWorker).ReportProgress(DownloadProgress); (sender as BackgroundWorker).ReportProgress(DownloadProgress);

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>
@@ -18,22 +18,15 @@
</PropertyGroup> </PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> --> <!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<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,20 +12,19 @@ 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
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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI/CD workflows", "CI/CD workflows", "{DFC9AB6D-BD21-4748-8CE8-57097DC673AF}"
ProjectSection(SolutionItems) = preProject
.github\workflows\release.yaml = .github\workflows\release.yaml
EndProjectSection
EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
Torch.Mod\Torch.Mod.projitems*{3ce4d2e9-b461-4f19-8233-f87e0dfddd74}*SharedItemsImports = 13 Torch.Mod\Torch.Mod.projitems*{3ce4d2e9-b461-4f19-8233-f87e0dfddd74}*SharedItemsImports = 13
@@ -57,7 +56,7 @@ Global
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{762F6A0D-55EF-4173-8CDE-309D183F40C4} = {7AD02A71-1D4C-48F9-A8C1-789A5512424F} {DFC9AB6D-BD21-4748-8CE8-57097DC673AF} = {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

@@ -1,138 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NLog;
namespace Torch
{
/// <summary>
/// Base class that adds tools for setting type properties through the command line.
/// </summary>
public abstract class CommandLine
{
private readonly string _argPrefix;
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
private readonly Logger _log = LogManager.GetCurrentClassLogger();
protected CommandLine(string argPrefix = "-")
{
_argPrefix = argPrefix;
foreach (var prop in GetType().GetProperties())
{
var attr = prop.GetCustomAttribute<ArgAttribute>();
if (attr == null)
continue;
_args.Add(attr, prop);
}
}
public string GetHelp()
{
var sb = new StringBuilder();
foreach (var property in _args)
{
var attr = property.Key;
sb.AppendLine($"{_argPrefix}{attr.Name.PadRight(24)}{attr.Description}");
}
return sb.ToString();
}
public override string ToString()
{
var args = new List<string>();
foreach (var prop in _args)
{
var attr = prop.Key;
if (prop.Value.PropertyType == typeof(bool) && (bool)prop.Value.GetValue(this))
{
args.Add($"{_argPrefix}{attr.Name}");
}
else if (prop.Value.PropertyType == typeof(string))
{
var str = (string)prop.Value.GetValue(this);
if (string.IsNullOrEmpty(str))
continue;
args.Add($"{_argPrefix}{attr.Name} \"{str}\"");
}
}
return string.Join(" ", args);
}
public bool Parse(string[] args)
{
if (args.Length == 0)
return true;
if (args[0] == $"{_argPrefix}help")
{
Console.WriteLine(GetHelp());
return false;
}
for (var i = 0; i < args.Length; i++)
{
if (!args[i].StartsWith(_argPrefix))
continue;
foreach (var property in _args)
{
var argName = property.Key.Name;
if (argName == null)
continue;
try
{
if (string.Compare(argName, 0, args[i], 1, argName.Length, StringComparison.InvariantCultureIgnoreCase) == 0)
{
if (property.Value.PropertyType == typeof(bool))
property.Value.SetValue(this, true);
if (property.Value.PropertyType == typeof(string))
property.Value.SetValue(this, args[++i]);
if (property.Value.PropertyType == typeof(List<Guid>))
{
i++;
var l = new List<Guid>(16);
while (i < args.Length && !args[i].StartsWith(_argPrefix))
{
if (Guid.TryParse(args[i], out Guid g))
{
l.Add(g);
_log.Info($"added plugin {g}");
}
else
_log.Warn($"Failed to parse GUID {args[i]}");
i++;
}
property.Value.SetValue(this, l);
}
}
}
catch
{
Console.WriteLine($"Error parsing arg {argName}");
}
}
}
return true;
}
public class ArgAttribute : Attribute
{
public string Name { get; }
public string Description { get; }
public ArgAttribute(string name, string description)
{
Name = name;
Description = description;
}
}
}
}

View File

@@ -11,7 +11,6 @@ using System.Timers;
using NLog; using NLog;
using Sandbox.Game.Multiplayer; using Sandbox.Game.Multiplayer;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Steamworks;
using Torch; using Torch;
using Torch.API; using Torch.API;
using Torch.API.Managers; using Torch.API.Managers;

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

@@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Emit;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.EntityComponents;
using Sandbox.ModAPI;
using Sandbox.ModAPI.Ingame;
using Torch.API;
using Torch.Utils;
using VRage;
using VRage.ModAPI;
using VRage.Scripting;
namespace Torch.Managers;
public class ScriptCompilationManager : Manager
{
[ReflectedSetter(Name = "m_terminationReason")]
private static Action<MyProgrammableBlock, MyProgrammableBlock.ScriptTerminationReason> TerminationReasonSetter = null!;
[ReflectedGetter(Name = "ScriptComponent")]
private static Func<MyProgrammableBlock, MyIngameScriptComponent> ScriptComponentGetter = null!;
[ReflectedMethod]
private static Action<MyProgrammableBlock, string> SetDetailedInfo = null!;
[ReflectedSetter(Name = "m_instance")]
private static Action<MyProgrammableBlock, IMyGridProgram> InstanceSetter = null!;
[ReflectedSetter(Name = "m_assembly")]
private static Action<MyProgrammableBlock, Assembly> AssemblySetter = null!;
[ReflectedMethod]
private static Func<MyProgrammableBlock, Assembly, IEnumerable<string>, string, bool> CreateInstance = null!;
[ReflectedGetter(Name = "m_compilerErrors")]
private static Func<MyProgrammableBlock, List<string>> CompilerErrorsGetter = null!;
[ReflectedGetter(Name = "m_modApiWhitelistDiagnosticAnalyzer")]
private static Func<MyScriptCompiler, DiagnosticAnalyzer> ModWhitelistAnalyzer = null!;
[ReflectedGetter(Name = "m_ingameWhitelistDiagnosticAnalyzer")]
private static Func<MyScriptCompiler, DiagnosticAnalyzer> ScriptWhitelistAnalyzer = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CSharpCompilation, SyntaxTree, int, SyntaxTree> InjectMod = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CSharpCompilation, SyntaxTree, SyntaxTree> InjectInstructionCounter = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CompilationWithAnalyzers, EmitResult, List<Message>, bool, Task<bool>> EmitDiagnostics = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, MyApiTarget, string, IList<SyntaxTree>, string, Task> WriteDiagnostics = null!;
[ReflectedMethod(Name = "WriteDiagnostics")]
private static Func<MyScriptCompiler, MyApiTarget, string, IEnumerable<Message>, bool, Task> WriteDiagnostics2 = null!;
[ReflectedGetter(Name = "m_metadataReferences")]
private static Func<MyScriptCompiler, List<MetadataReference>> MetadataReferencesGetter = null!;
private readonly ConditionalWeakTable<MyProgrammableBlock, AssemblyLoadContext> _contexts = new();
public ScriptCompilationManager(ITorchBase torchInstance) : base(torchInstance)
{
}
public async void CompileAsync(MyProgrammableBlock block, string program, string storage, bool instantiate)
{
TerminationReasonSetter(block, MyProgrammableBlock.ScriptTerminationReason.None);
var component = ScriptComponentGetter(block);
component.NextUpdate = UpdateType.None;
component.NeedsUpdate = MyEntityUpdateEnum.NONE;
try
{
if (_contexts.TryGetValue(block, out var context))
{
InstanceSetter(block, null);
AssemblySetter(block, null);
context!.Unload();
}
_contexts.AddOrUpdate(block, context = new AssemblyLoadContext(null, true));
var messages = new List<Message>();
var assembly = await CompileAsync(context, MyApiTarget.Ingame,
$"pb_{block.EntityId}_{Random.Shared.NextInt64()}",
new[]
{
MyVRage.Platform.Scripting.GetIngameScript(
program, "Program", nameof(MyGridProgram))
}, messages, $"PB: {block.DisplayName} ({block.EntityId})");
AssemblySetter(block, assembly);
var errors = CompilerErrorsGetter(block);
errors.Clear();
errors.AddRange(messages.Select(b => b.Text));
if (instantiate)
await Torch.InvokeAsync(() => CreateInstance(block, assembly, errors, storage));
}
catch (Exception e)
{
await Torch.InvokeAsync(() => SetDetailedInfo(block, e.ToString()));
throw;
}
}
public async Task<Assembly> CompileAsync(AssemblyLoadContext context, MyApiTarget target, string assemblyName, IEnumerable<Script> scripts, List<Message> messages, string friendlyName, bool enableDebugInformation = false)
{
friendlyName ??= "<No Name>";
Func<CSharpCompilation, SyntaxTree, SyntaxTree> syntaxTreeInjector;
DiagnosticAnalyzer whitelistAnalyzer;
switch (target)
{
case MyApiTarget.None:
whitelistAnalyzer = null;
syntaxTreeInjector = null;
break;
case MyApiTarget.Mod:
{
var modId = MyModWatchdog.AllocateModId(friendlyName);
whitelistAnalyzer = ModWhitelistAnalyzer(MyScriptCompiler.Static);
syntaxTreeInjector = (c, st) => InjectMod(MyScriptCompiler.Static, c, st, modId);
break;
}
case MyApiTarget.Ingame:
syntaxTreeInjector = (c, t) => InjectInstructionCounter(MyScriptCompiler.Static, c, t);
whitelistAnalyzer = ScriptWhitelistAnalyzer(MyScriptCompiler.Static);
break;
default:
throw new ArgumentOutOfRangeException(nameof(target), target, "Invalid compilation target");
}
var compilation = CreateCompilation(assemblyName, scripts);
await WriteDiagnostics(MyScriptCompiler.Static, target, assemblyName, compilation.SyntaxTrees, null).ConfigureAwait(false);
var injectionFailed = false;
var compilationWithoutInjection = compilation;
if (syntaxTreeInjector != null)
{
SyntaxTree[] newSyntaxTrees = null;
try
{
var syntaxTrees = compilation.SyntaxTrees;
if (syntaxTrees.Length == 1)
{
newSyntaxTrees = new[] { syntaxTreeInjector(compilation, syntaxTrees[0]) };
}
else
{
newSyntaxTrees = await Task
.WhenAll(syntaxTrees.Select(
x => Task.Run(() => syntaxTreeInjector(compilation, x))))
.ConfigureAwait(false);
}
}
catch
{
injectionFailed = true;
}
if (!injectionFailed)
{
await WriteDiagnostics(MyScriptCompiler.Static, target, assemblyName, newSyntaxTrees, ".injected").ConfigureAwait(false);
compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSyntaxTrees);
}
}
CompilationWithAnalyzers analyticCompilation = null;
if (whitelistAnalyzer != null)
{
analyticCompilation = compilation.WithAnalyzers(ImmutableArray.Create(whitelistAnalyzer));
compilation = (CSharpCompilation)analyticCompilation.Compilation;
}
using var assemblyStream = new MemoryStream();
var emitResult = compilation.Emit(assemblyStream);
var success = emitResult.Success;
var myBlacklistSyntaxVisitor = new MyBlacklistSyntaxVisitor();
foreach (var syntaxTree in compilation.SyntaxTrees)
{
myBlacklistSyntaxVisitor.SetSemanticModel(compilation.GetSemanticModel(syntaxTree, false));
myBlacklistSyntaxVisitor.Visit(await syntaxTree.GetRootAsync());
}
if (myBlacklistSyntaxVisitor.HasAnyResult())
{
myBlacklistSyntaxVisitor.GetResultMessages(messages);
}
else
{
success = await EmitDiagnostics(MyScriptCompiler.Static, analyticCompilation, emitResult, messages, success).ConfigureAwait(false);
await WriteDiagnostics2(MyScriptCompiler.Static, target, assemblyName, messages, success).ConfigureAwait(false);
assemblyStream.Seek(0, SeekOrigin.Begin);
if (injectionFailed) return null;
if (success)
return context.LoadFromStream(assemblyStream);
await EmitDiagnostics(MyScriptCompiler.Static, analyticCompilation, compilationWithoutInjection.Emit(assemblyStream), messages, false).ConfigureAwait(false);
}
return null;
}
private readonly CSharpCompilationOptions _compilationOptions = new(OutputKind.DynamicallyLinkedLibrary);
private readonly CSharpParseOptions _parseOptions = new(LanguageVersion.CSharp10, DocumentationMode.None);
private CSharpCompilation CreateCompilation(string assemblyFileName, IEnumerable<Script> scripts)
{
if (scripts == null)
return CSharpCompilation.Create(assemblyFileName, null, MetadataReferencesGetter(MyScriptCompiler.Static),
_compilationOptions);
var parseOptions = _parseOptions.WithPreprocessorSymbols(MyScriptCompiler.Static.ConditionalCompilationSymbols);
var enumerable = scripts.Select(s => CSharpSyntaxTree.ParseText(s.Code, parseOptions, s.Name, Encoding.UTF8));
return CSharpCompilation.Create(assemblyFileName, enumerable, MetadataReferencesGetter(MyScriptCompiler.Static), _compilationOptions);
}
}

View File

@@ -5,6 +5,7 @@ using System.IO.Compression;
using System.IO.Packaging; using System.IO.Packaging;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@@ -21,8 +22,9 @@ namespace Torch.Managers
public class UpdateManager : Manager public class UpdateManager : Manager
{ {
private readonly Timer _updatePollTimer; private readonly Timer _updatePollTimer;
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName; private readonly string _torchDir = ApplicationContext.Current.TorchDirectory.FullName;
private Logger _log = LogManager.GetCurrentClassLogger(); private readonly Logger _log = LogManager.GetCurrentClassLogger();
[Dependency] [Dependency]
private FilesystemManager _fsManager = null!; private FilesystemManager _fsManager = null!;
@@ -50,26 +52,22 @@ namespace Torch.Managers
try try
{ {
var job = await JenkinsQuery.Instance.GetLatestVersion(Torch.TorchVersion.Build); var updateSource = Torch.Config.UpdateSource;
if (job == null)
{
_log.Info("Failed to fetch latest version.");
return;
}
if (job.Version > Torch.TorchVersion) IUpdateQuery source = updateSource.SourceType switch
{ {
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {job.Version}"); UpdateSourceType.Github => new GithubQuery(updateSource.Url),
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip"); UpdateSourceType.Jenkins => new JenkinsQuery(updateSource.Url),
//new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName); _ => throw new ArgumentOutOfRangeException()
if (!await JenkinsQuery.Instance.DownloadRelease(job, updateName)) };
var release = await source.GetLatestReleaseAsync(updateSource.Repository, updateSource.Branch);
if (release.Version > Torch.TorchVersion)
{ {
_log.Warn("Failed to download new release!"); _log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {release.Version}");
return; await UpdateAsync(release, _torchDir);
} _log.Warn($"Torch version {release.Version} has been installed, please restart Torch to finish the process.");
UpdateFromZip(updateName, _torchDir);
File.Delete(updateName);
_log.Warn($"Torch version {job.Version} has been installed, please restart Torch to finish the process.");
} }
else else
{ {
@@ -83,10 +81,17 @@ namespace Torch.Managers
} }
} }
private void UpdateFromZip(string zipFile, string extractPath) private async Task UpdateAsync(UpdateRelease release, string extractPath)
{ {
using (var zip = ZipFile.OpenRead(zipFile)) using var client = new HttpClient();
await using var stream = await client.GetStreamAsync(release.ArtifactUrl);
UpdateFromZip(stream, extractPath);
}
private void UpdateFromZip(Stream zipStream, string extractPath)
{ {
using var zip = new ZipArchive(zipStream, ZipArchiveMode.Read, true);
foreach (var file in zip.Entries) foreach (var file in zip.Entries)
{ {
if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName))) if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName)))
@@ -100,7 +105,6 @@ namespace Torch.Managers
//zip.ExtractToDirectory(extractPath); //throws exceptions sometimes? //zip.ExtractToDirectory(extractPath); //throws exceptions sometimes?
} }
}
/// <inheritdoc /> /// <inheritdoc />
public override void Detach() public override void Detach()

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

@@ -1,4 +1,5 @@
using System; #if false
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
@@ -17,6 +18,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)),
@@ -50,3 +52,4 @@ internal static class GcCollectPatch
} }
} }
} }
#endif

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

@@ -0,0 +1,33 @@
using System;
using System.Reflection;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.World;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches;
[PatchShim]
public static class ProgramableBlockPatch
{
[ReflectedMethodInfo(typeof(MyProgrammableBlock), "Compile")]
private static MethodInfo CompileMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(CompileMethod).AddPrefix();
}
private static bool Prefix(MyProgrammableBlock __instance, string program, string storage, bool instantiate)
{
if (!MySession.Static.EnableIngameScripts || __instance.CubeGrid is {IsPreview: true} or {CreatePhysics: false})
return false;
#pragma warning disable CS0618
TorchBase.Instance.CurrentSession.Managers.GetManager<ScriptCompilationManager>().CompileAsync(__instance, program, storage, instantiate);
#pragma warning restore CS0618
return false;
}
}

View File

@@ -1,7 +1,9 @@
using System; using System;
using System.Collections;
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.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -10,6 +12,7 @@ using System.Reflection.Emit;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using ParallelTasks;
using ProtoBuf; using ProtoBuf;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
@@ -42,14 +45,23 @@ namespace Torch.Patches
private static void WhitelistCtorPrefix(MyScriptCompiler scriptCompiler) private static void WhitelistCtorPrefix(MyScriptCompiler scriptCompiler)
{ {
var baseDir = new FileInfo(typeof(Type).Assembly.Location).DirectoryName!;
scriptCompiler.AddReferencedAssemblies( scriptCompiler.AddReferencedAssemblies(
typeof(Type).Assembly.Location,
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(TypeConverter).Assembly.Location,
typeof(System.Diagnostics.TraceSource).Assembly.Location, typeof(System.Diagnostics.TraceSource).Assembly.Location,
typeof(System.Security.Policy.Evidence).Assembly.Location,
typeof(ProtoBuf.Meta.RuntimeTypeModel).Assembly.Location, typeof(ProtoBuf.Meta.RuntimeTypeModel).Assembly.Location,
typeof(ProtoContractAttribute).Assembly.Location,
Path.Combine(baseDir, "netstandard.dll"),
Path.Combine(baseDir, "System.Runtime.dll"),
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,18 @@ 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();
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(ConcurrentQueue<>));
batch.AllowNamespaceOfTypes(MyWhitelistTarget.Both, typeof(ImmutableArray), typeof(ArrayExtensions));
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(ProtoContractAttribute).Assembly.GetExportedTypes()
.Where(b => b.Namespace == "ProtoBuf" && b.Name.Contains("Attribute"))
.Concat(new[]
{
typeof(DataFormat), typeof(MemberSerializationOptions), typeof(ImplicitFields)
}).ToArray());
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(WorkData));
return false; return false;
} }

View File

@@ -20,6 +20,7 @@ namespace Torch
public sealed class Persistent<T> : IDisposable where T : new() public sealed class Persistent<T> : IDisposable where T : new()
{ {
private static readonly XmlSerializer Serializer = new(typeof(T)); private static readonly XmlSerializer Serializer = new(typeof(T));
private static readonly XmlSerializerNamespaces Namespaces = new(new[] {new XmlQualifiedName("", "")});
private static Logger _log = LogManager.GetCurrentClassLogger(); private static Logger _log = LogManager.GetCurrentClassLogger();
public string Path { get; set; } public string Path { get; set; }
private T _data; private T _data;
@@ -69,9 +70,10 @@ namespace Torch
using var f = File.Create(path); using var f = File.Create(path);
using var writer = new XmlTextWriter(f, Encoding.UTF8) using var writer = new XmlTextWriter(f, Encoding.UTF8)
{ {
Formatting = Formatting.Indented Formatting = Formatting.Indented,
Namespaces = false
}; };
Serializer.Serialize(writer, Data); Serializer.Serialize(writer, Data, Namespaces);
} }
public static Persistent<T> Load(string path, bool saveIfNew = true) public static Persistent<T> Load(string path, bool saveIfNew = true)

View File

@@ -1,5 +1,7 @@
using System.Collections.Generic; using System;
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;
@@ -10,25 +12,57 @@ namespace Torch.Plugins;
internal static class AssemblyRewriter internal static class AssemblyRewriter
{ {
private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private static readonly ZipResolver _zipResolver;
private static readonly IAssemblyResolver Resolver; private static readonly DefaultAssemblyResolver _defaultResolver;
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()
try
{ {
AssemblyResolver = Resolver var ass = ProcessInternal(assStream, _zipResolver);
return ass;
}
catch (Exception e)
{
Log.Error(e, "Unable to process assembly, skipping");
return Assembly.Load(assStream.ToArray());
}
finally
{
_zipResolver.Archive = null;
}
}
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 = _zipResolver
}); });
foreach (var fieldDefinition in FindAllToRewrite(module)) foreach (var fieldDefinition in FindAllToRewrite(module))
{ {
@@ -47,4 +81,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,12 +378,19 @@ 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));
} }
} }
var harmonyAssembly = assemblies.FirstOrDefault(b => b.FullName?.StartsWith("0Harmony") == true);
if (harmonyAssembly is { })
{
_log.Warn($"Plugin {item.Manifest.Name} is using harmony library, logic collision between plugins could be encountered!");
assemblies.Remove(harmonyAssembly);
}
RegisterAllAssemblies(assemblies); RegisterAllAssemblies(assemblies);
InstantiatePlugin(item.Manifest, assemblies); InstantiatePlugin(item.Manifest, assemblies);
} }
@@ -412,12 +419,15 @@ namespace Torch.Managers
AppDomain.CurrentDomain.AssemblyResolve += ResolveDependentAssembly; AppDomain.CurrentDomain.AssemblyResolve += ResolveDependentAssembly;
foreach (Assembly asm in assemblies) foreach (Assembly asm in assemblies)
{ {
TorchBase.RegisterAuxAssembly(asm); TorchLauncher.RegisterAssembly(asm);
} }
} }
private static bool IsAssemblyCompatible(AssemblyName a, AssemblyName b) private static bool IsAssemblyCompatible(AssemblyName a, AssemblyName b)
{ {
if (a.Version is null || b.Version is null)
return a.Name == b.Name;
return a.Name == b.Name && a.Version.Major == b.Version.Major && a.Version.Minor == b.Version.Minor; return a.Name == b.Name && a.Version.Major == b.Version.Major && a.Version.Minor == b.Version.Minor;
} }

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

@@ -113,7 +113,7 @@ namespace Torch.Session
_currentSession.Detach(); _currentSession.Detach();
} }
_log.Info($"Starting new torch session for {_instanceManager.SelectedWorld.FolderName}"); _log.Info($"Starting new torch session for {_instanceManager.SelectedWorld.KeenCheckpoint.SessionName}");
_currentSession = new TorchSession(Torch, MySession.Static, _instanceManager.SelectedWorld); _currentSession = new TorchSession(Torch, MySession.Static, _instanceManager.SelectedWorld);
SetState(TorchSessionState.Loading); SetState(TorchSessionState.Loading);
@@ -139,7 +139,7 @@ namespace Torch.Session
CurrentSession.Managers.AddManager(manager); CurrentSession.Managers.AddManager(manager);
} }
(CurrentSession as TorchSession)?.Attach(); (CurrentSession as TorchSession)?.Attach();
_log.Info($"Loaded torch session for {CurrentSession.World.FolderName}"); _log.Info($"Loaded torch session for {CurrentSession.World.KeenCheckpoint.SessionName}");
SetState(TorchSessionState.Loaded); SetState(TorchSessionState.Loaded);
} }
catch (Exception e) catch (Exception e)
@@ -156,7 +156,7 @@ namespace Torch.Session
if (_currentSession is null) if (_currentSession is null)
throw new InvalidOperationException("Session loaded event occurred when we don't have a session."); throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
_log.Info($"Unloading torch session for {_currentSession.World.FolderName}"); _log.Info($"Unloading torch session for {_currentSession.World.KeenCheckpoint.SessionName}");
SetState(TorchSessionState.Unloading); SetState(TorchSessionState.Unloading);
_currentSession.Detach(); _currentSession.Detach();
} }
@@ -174,7 +174,7 @@ namespace Torch.Session
if (_currentSession is null) if (_currentSession is null)
throw new InvalidOperationException("Session loaded event occurred when we don't have a session."); throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
_log.Info($"Unloaded torch session for {_currentSession.World.FolderName}"); _log.Info($"Unloaded torch session for {_currentSession.World.KeenCheckpoint.SessionName}");
SetState(TorchSessionState.Unloaded); SetState(TorchSessionState.Unloaded);
_currentSession = null; _currentSession = null;
} }

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,146 +9,39 @@
<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>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Release'"> <PropertyGroup Condition="$(Configuration) == 'Release'">
<NoWarn>1591</NoWarn> <NoWarn>1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<Import Project="..\Torch.Mod\Torch.Mod.projitems" Label="Shared" /> <Import Project="..\Torch.Mod\Torch.Mod.projitems" Label="Shared" />
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="ControlzEx" Version="5.0.1" /> <PackageReference Include="ControlzEx" Version="5.0.1" />
<PackageReference Include="InfoOf.Fody" Version="2.1.0" PrivateAssets="all" /> <PackageReference Include="HarmonyX" Version="2.10.0" />
<PackageReference Include="InfoOf.Fody" Version="2.1.1" PrivateAssets="all" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" /> <PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.1.4.3" /> <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.3.1" />
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" /> <PackageReference Include="MonoMod.RuntimeDetour" Version="22.5.1.1" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.3" PrivateAssets="all" />
<PackageReference Include="protobuf-net" Version="2.4.7" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" /> <PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Microsoft.CodeAnalysis, Version=2.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ProtoBuf.Net, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\GameBinaries\ProtoBuf.Net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ProtoBuf.Net.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\GameBinaries\ProtoBuf.Net.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Common">
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Game">
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Graphics">
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.Game">
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Steamworks.NET">
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
</Reference>
<Reference Include="VRage">
<HintPath>..\GameBinaries\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Audio">
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Dedicated">
<HintPath>..\GameBinaries\VRage.Dedicated.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.EOS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\GameBinaries\VRage.EOS.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game">
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Input">
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Library">
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math">
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Mod.Io, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\GameBinaries\VRage.Mod.Io.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Platform.Windows, Culture=neutral, PublicKeyToken=null">
<HintPath>..\GameBinaries\VRage.Platform.Windows.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render">
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render11">
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Scripting">
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Steam">
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
<Private>False</Private>
</Reference>
</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>
</Project> </Project>

View File

@@ -37,17 +37,16 @@ namespace Torch
{ {
static TorchBase() static TorchBase()
{ {
MyVRageWindows.Init("SpaceEngineersDedicated", MySandboxGame.Log, null, false); TorchLauncher.RegisterAssembly(typeof(ITorchBase).Assembly);
ReflectedManager.Process(typeof(TorchBase).Assembly); TorchLauncher.RegisterAssembly(typeof(TorchBase).Assembly);
ReflectedManager.Process(typeof(ITorchBase).Assembly); TorchLauncher.RegisterAssembly(Assembly.GetEntryAssembly());
PatchManager.AddPatchShim(typeof(GameStatePatchShim)); PatchManager.AddPatchShim(typeof(GameStatePatchShim));
PatchManager.AddPatchShim(typeof(GameAnalyticsPatch)); PatchManager.AddPatchShim(typeof(GameAnalyticsPatch));
PatchManager.AddPatchShim(typeof(KeenLogPatch)); PatchManager.AddPatchShim(typeof(KeenLogPatch));
PatchManager.AddPatchShim(typeof(XmlRootWriterPatch)); PatchManager.AddPatchShim(typeof(XmlRootWriterPatch));
PatchManager.CommitInternal(); PatchManager.CommitInternal();
RegisterCoreAssembly(typeof(ITorchBase).Assembly);
RegisterCoreAssembly(typeof(TorchBase).Assembly);
RegisterCoreAssembly(Assembly.GetEntryAssembly());
//exceptions in English, please //exceptions in English, please
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
} }
@@ -64,6 +63,8 @@ namespace Torch
/// <inheritdoc /> /// <inheritdoc />
public SemanticVersioning.Version TorchVersion { get; } public SemanticVersioning.Version TorchVersion { get; }
public string InstancePath { get; protected init;}
public string InstanceName { get; protected init; }
/// <inheritdoc /> /// <inheritdoc />
public Version GameVersion { get; private set; } public Version GameVersion { get; private set; }
@@ -78,18 +79,6 @@ namespace Torch
/// <inheritdoc /> /// <inheritdoc />
public ITorchSession CurrentSession => Managers?.GetManager<ITorchSessionManager>()?.CurrentSession; public ITorchSession CurrentSession => Managers?.GetManager<ITorchSessionManager>()?.CurrentSession;
/// <inheritdoc />
public event Action SessionLoading;
/// <inheritdoc />
public event Action SessionLoaded;
/// <inheritdoc />
public event Action SessionUnloading;
/// <inheritdoc />
public event Action SessionUnloaded;
/// <summary> /// <summary>
/// Common log for the Torch instance. /// Common log for the Torch instance.
/// </summary> /// </summary>
@@ -103,10 +92,10 @@ namespace Torch
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception> /// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception>
protected TorchBase(ITorchConfig config) protected TorchBase(ITorchConfig config)
{ {
RegisterCoreAssembly(GetType().Assembly);
#pragma warning disable CS0618 #pragma warning disable CS0618
if (Instance != null) if (Instance != null)
throw new InvalidOperationException("A TorchBase instance already exists."); throw new InvalidOperationException("A TorchBase instance already exists.");
@@ -115,14 +104,8 @@ namespace Torch
#pragma warning restore CS0618 #pragma warning restore CS0618
Config = config; Config = config;
var versionString = GetType().Assembly var assemblyVersion = GetType().Assembly.GetName().Version ?? new Version();
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()! TorchVersion = new(assemblyVersion.Major, assemblyVersion.Minor, assemblyVersion.Build);
.InformationalVersion;
if (!SemanticVersioning.Version.TryParse(versionString, out var version))
throw new TypeLoadException("Unable to parse the Torch version from the assembly.");
TorchVersion = version;
RunArgs = Array.Empty<string>(); RunArgs = Array.Empty<string>();
@@ -140,11 +123,12 @@ namespace Torch
Managers.AddManager(sessionManager); Managers.AddManager(sessionManager);
Managers.AddManager(new PatchManager(this)); Managers.AddManager(new PatchManager(this));
Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new FilesystemManager(this));
// Managers.AddManager(new UpdateManager(this)); Managers.AddManager(new UpdateManager(this));
Managers.AddManager(new EventManager(this)); Managers.AddManager(new EventManager(this));
#pragma warning disable CS0618 #pragma warning disable CS0618
Managers.AddManager(Plugins); Managers.AddManager(Plugins);
#pragma warning restore CS0618 #pragma warning restore CS0618
Managers.AddManager(new ScriptCompilationManager(this));
TorchAPI.Instance = this; TorchAPI.Instance = this;
GameStateChanged += (game, state) => GameStateChanged += (game, state) =>
@@ -159,26 +143,19 @@ namespace Torch
} }
}; };
sessionManager.SessionStateChanged += (session, state) => var harmonyLog = LogManager.GetLogger("HarmonyX");
HarmonyLib.Tools.Logger.ChannelFilter = HarmonyLib.Tools.Logger.LogChannel.Debug;
HarmonyLib.Tools.Logger.MessageReceived += (_, args) => harmonyLog.Log(args.LogChannel switch
{ {
switch (state) HarmonyLib.Tools.Logger.LogChannel.None => LogLevel.Off,
{ HarmonyLib.Tools.Logger.LogChannel.Info => LogLevel.Info,
case TorchSessionState.Loading: HarmonyLib.Tools.Logger.LogChannel.IL => LogLevel.Trace,
SessionLoading?.Invoke(); HarmonyLib.Tools.Logger.LogChannel.Warn => LogLevel.Warn,
break; HarmonyLib.Tools.Logger.LogChannel.Error => LogLevel.Error,
case TorchSessionState.Loaded: HarmonyLib.Tools.Logger.LogChannel.Debug => LogLevel.Debug,
SessionLoaded?.Invoke(); HarmonyLib.Tools.Logger.LogChannel.All => LogLevel.Debug,
break; _ => throw new ArgumentOutOfRangeException()
case TorchSessionState.Unloading: }, args.Message);
SessionUnloading?.Invoke();
break;
case TorchSessionState.Unloaded:
SessionUnloaded?.Invoke();
break;
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
}
};
} }
[Obsolete("Prefer using Managers.GetManager for global managers")] [Obsolete("Prefer using Managers.GetManager for global managers")]
@@ -193,11 +170,6 @@ namespace Torch
return Managers.AddManager(manager); return Managers.AddManager(manager);
} }
public bool IsOnGameThread()
{
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
}
#region Game Actions #region Game Actions
/// <summary> /// <summary>
@@ -288,16 +260,15 @@ namespace Torch
public virtual void Init() public virtual void Init()
{ {
Debug.Assert(!_init, "Torch instance is already initialized."); Debug.Assert(!_init, "Torch instance is already initialized.");
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
ObjectFactoryInitPatch.ForceRegisterAssemblies(); ObjectFactoryInitPatch.ForceRegisterAssemblies();
VRageGame.SetupVersionInfo();
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null"); Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
GameVersion = new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value); GameVersion = new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value);
try try
{ {
Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; Console.Title = $"{InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
} }
catch catch
{ {
@@ -311,17 +282,17 @@ namespace Torch
#endif #endif
Log.Info($"Torch Version: {TorchVersion}"); Log.Info($"Torch Version: {TorchVersion}");
Log.Info($"Game Version: {GameVersion}"); Log.Info($"Game Version: {GameVersion}");
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}"); Log.Info($"Executing assembly: {Assembly.GetEntryAssembly()?.FullName}");
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}"); Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
Managers.GetManager<PluginManager>().LoadPlugins(); Managers.GetManager<PluginManager>().LoadPlugins();
Game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs); Game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, InstancePath, RunArgs);
if (!Game.WaitFor(VRageGame.GameState.Stopped)) if (!Game.WaitFor(VRageGame.GameState.Stopped))
Log.Warn("Failed to wait for game to be initialized"); Log.Warn("Failed to wait for game to be initialized");
Managers.Attach(); Managers.Attach();
_init = true; _init = true;
if (GameState >= TorchGameState.Created && GameState < TorchGameState.Unloading) if (GameState is >= TorchGameState.Created and < TorchGameState.Unloading)
// safe to commit here; all important static ctors have run // safe to commit here; all important static ctors have run
PatchManager.CommitInternal(); PatchManager.CommitInternal();
} }
@@ -435,41 +406,5 @@ namespace Torch
/// <inheritdoc/> /// <inheritdoc/>
public event TorchGameStateChangedDel GameStateChanged; public event TorchGameStateChangedDel GameStateChanged;
private static readonly HashSet<Assembly> _registeredCoreAssemblies = new HashSet<Assembly>();
/// <summary>
/// Registers a core (Torch) assembly with the system, including its
/// <see cref="EventManager"/> shims, <see cref="PatchManager"/> shims, and <see cref="ReflectedManager"/> components.
/// </summary>
/// <param name="asm">Assembly to register</param>
internal static void RegisterCoreAssembly(Assembly asm)
{
lock (_registeredCoreAssemblies)
if (_registeredCoreAssemblies.Add(asm))
{
ReflectedManager.Process(asm);
EventManager.AddDispatchShims(asm);
PatchManager.AddPatchShims(asm);
}
}
private static readonly HashSet<Assembly> _registeredAuxAssemblies = new HashSet<Assembly>();
private static readonly TimeSpan _gameStateChangeTimeout = TimeSpan.FromMinutes(1);
/// <summary>
/// Registers an auxillary (plugin) assembly with the system, including its
/// <see cref="ReflectedManager"/> related components.
/// </summary>
/// <param name="asm">Assembly to register</param>
internal static void RegisterAuxAssembly(Assembly asm)
{
lock (_registeredAuxAssemblies)
if (_registeredAuxAssemblies.Add(asm))
{
ReflectedManager.Process(asm);
PatchManager.AddPatchShims(asm);
}
}
} }
} }

View File

@@ -6,7 +6,6 @@ using System.Threading;
using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Entities; using Sandbox.Game.Entities;
using Sandbox.Game.World; using Sandbox.Game.World;
using Steamworks;
namespace Torch.Utils namespace Torch.Utils
{ {
@@ -54,12 +53,6 @@ namespace Torch.Utils
return result; return result;
} }
public static IPAddress GetRemoteIP(this P2PSessionState_t state)
{
// What is endianness anyway?
return new IPAddress(BitConverter.GetBytes(state.m_nRemoteIP).Reverse().ToArray());
}
public static string GetGridOwnerName(this MyCubeGrid grid) public static string GetGridOwnerName(this MyCubeGrid grid)
{ {
if (grid.BigOwners.Count == 0 || grid.BigOwners[0] == 0) if (grid.BigOwners.Count == 0 || grid.BigOwners[0] == 0)

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,29 +1,31 @@
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 NLog;
using System.Text; using Torch.Event;
using System.Threading.Tasks; using Torch.Managers.PatchManager;
using ProtoBuf;
using Torch.API;
namespace Torch.Utils namespace Torch.Utils
{ {
public class TorchLauncher public static class TorchLauncher
{ {
private static readonly Dictionary<string, string> Assemblies = new Dictionary<string, string>(); private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private static readonly HashSet<Assembly> RegisteredAssemblies = new();
private static readonly Dictionary<string, string> Assemblies = new();
public static void Launch(params string[] binaryPaths) public static void Launch(params string[] binaryPaths)
{ {
CopyNative();
foreach (var file in binaryPaths.SelectMany(path => Directory.EnumerateFiles(path, "*.dll"))) foreach (var file in binaryPaths.SelectMany(path => Directory.EnumerateFiles(path, "*.dll")))
{ {
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[..','];
Assemblies.TryAdd(key, file);
} }
catch (BadImageFormatException) catch (BadImageFormatException)
{ {
@@ -34,10 +36,64 @@ namespace Torch.Utils
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
} }
public static void RegisterAssembly(Assembly assembly)
{
if (!RegisteredAssemblies.Add(assembly))
return;
ReflectedManager.Process(assembly);
EventManager.AddDispatchShims(assembly);
PatchManager.AddPatchShims(assembly);
}
private static void CopyNative()
{
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 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[..name.IndexOf(',')] : name, out var path) ? Assembly.LoadFrom(path) : null;
} }
} }
} }

View File

@@ -0,0 +1,44 @@
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
{
private static readonly AssemblyLoadContext LoadContext = new("TorchLog");
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")
.Select(LoadContext.LoadFromAssemblyPath)
.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

@@ -22,7 +22,6 @@ using Sandbox.Game.World;
using Sandbox.Graphics.GUI; using Sandbox.Graphics.GUI;
using SpaceEngineers.Game; using SpaceEngineers.Game;
using SpaceEngineers.Game.GUI; using SpaceEngineers.Game.GUI;
using Steamworks;
using Torch.API; using Torch.API;
using Torch.Utils; using Torch.Utils;
using VRage; using VRage;
@@ -35,11 +34,13 @@ using VRage.Game.ObjectBuilder;
using VRage.Game.SessionComponents; using VRage.Game.SessionComponents;
using VRage.GameServices; using VRage.GameServices;
using VRage.Mod.Io; using VRage.Mod.Io;
using VRage.Platform.Windows;
using VRage.Plugins; using VRage.Plugins;
using VRage.Scripting; using VRage.Scripting;
using VRage.Steam; using VRage.Steam;
using VRage.Utils; using VRage.Utils;
using VRageRender; using VRageRender;
using Game = Sandbox.Engine.Platform.Game;
using MyRenderProfiler = VRage.Profiler.MyRenderProfiler; using MyRenderProfiler = VRage.Profiler.MyRenderProfiler;
namespace Torch namespace Torch
@@ -138,13 +139,19 @@ namespace Torch
} }
} }
internal static void SetupVersionInfo()
{
Game.IsDedicated = true;
SpaceEngineersGame.SetupBasicGameInfo();
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
}
private void Create() private void Create()
{ {
bool dedicated = true; var dedicated = Game.IsDedicated;
Environment.SetEnvironmentVariable("SteamAppId", _appSteamId.ToString()); Environment.SetEnvironmentVariable("SteamAppId", _appSteamId.ToString());
SpaceEngineersGame.SetupBasicGameInfo(); MyVRageWindows.Init("SpaceEngineersDedicated", MySandboxGame.Log, null, false);
SpaceEngineersGame.SetupPerGameSettings(); SpaceEngineersGame.SetupPerGameSettings();
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
MySessionComponentExtDebug.ForceDisable = true; MySessionComponentExtDebug.ForceDisable = true;
MyPerGameSettings.SendLogToKeen = false; MyPerGameSettings.SendLogToKeen = false;
// SpaceEngineersGame.SetupAnalytics(); // SpaceEngineersGame.SetupAnalytics();
@@ -472,7 +479,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,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="TransformOnBuild">
<ItemGroup>
<TemplatePaths Include="@(None)" Condition="'%(None.Generator)' == 'TextTemplatingFileGenerator'" />
</ItemGroup>
<Exec Command="&quot;$(SolutionDir)\packages\Mono.TextTransform.1.0.0\tools\TextTransform.exe&quot; &quot;%(TemplatePaths.FullPath)&quot;" Condition="'%(TemplatePaths.Identity)' != ''"/>
</Target>
</Project>

View File

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

View File

@@ -1,65 +0,0 @@
version: 1.0.{build}-{branch}
pull_requests:
do_not_increment_build_number: true
branches:
only:
- master
skip_tags: true
image: Visual Studio 2022
configuration: Release
platform: x64
shallow_clone: true
assembly_info:
patch: true
file: '**\AssemblyVersion.*'
assembly_version: '{version}'
assembly_file_version: '{version}'
assembly_informational_version: v{version}
install:
- pwsh: >-
$steamData = "c:/steam/dedi/"
$steamCMDPath = "c:/steam/cmd/"
$steamCMDZip = "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
cache:
- c:\steam\dedi\
- c:\steam\cmd\
build_script:
- pwsh: >-
dotnet publish .\Torch.Server\Torch.Server.csproj --self-contained -f net6-windows -r win-x64 -c Release -o .\publish\
Compress-Archive -Path .\publish\* -DestinationPath .\torch-server.zip
artifacts:
- path: torch-server.zip
deploy:
- provider: GitHub
tag: v$(appveyor_build_version)
release: Torch - v$(appveyor_build_version)
auth_token:
secure: IOhwzgIpj85iHitQBiH4chKPL9fXAinEnqUcbZk0ANhfTmdUFh74ko7Bt7LoSJFy
artifact: torch-server.zip
on:
branch: master