Compare commits

..

1 Commits

Author SHA1 Message Date
Westin Miller
bd9d549607 Auto-release 2017-08-31 19:01:10 -07:00
348 changed files with 7068 additions and 23703 deletions

View File

@@ -1,75 +0,0 @@
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
- name: Build
run: dotnet build Torch.Server/Torch.Server.csproj --no-restore -c ${{ env.BUILD_CONFIGURATION }} /p:AssemblyVersion=${{ env.TORCH_VERSION }} /p:Version=${{ env.TORCH_VERSION }}
- 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 }}

3
.gitignore vendored
View File

@@ -1,9 +1,6 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
#Rider directory
.idea/
# User-specific files
*.suo
*.user

33
CHANGELOG.md Normal file
View File

@@ -0,0 +1,33 @@
# 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,9 +1,9 @@
# Making a Pull Request
* Fork this repository and make sure your local **staging** branch is up to date with the main repository.
* Create a new branch from the **staging** branch for your addition with an appropriate name, e.g. **add-restart-command**
* Fork this repository and make sure your local **master** branch is up to date with the main repository.
* Create a new branch for your addition with an appropriate name, e.g. **add-restart-command**
* PRs work by submitting the *entire* branch, so this allows you to continue work without locking up your whole repository.
* Commit your changes to that branch, making sure that you **follow the code guidelines below**.
* Submit your branch as a PR to be reviewed, with Torch's **staging** branch as the base.
* Submit your branch as a PR to be reviewed.
## Naming Conventions
* Types: **PascalCase**

View File

@@ -0,0 +1,22 @@
pushd
$steamData = "C:/Steam/Data/"
$steamCMDPath = "C:/Steam/steamcmd/"
$steamCMDZip = "C:/Steam/steamcmd.zip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
if (!(Test-Path $steamData)) {
mkdir "$steamData"
}
if (!(Test-Path $steamCMDPath)) {
if (!(Test-Path $steamCMDZip)) {
(New-Object System.Net.WebClient).DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", "$steamCMDZip");
}
[System.IO.Compression.ZipFile]::ExtractToDirectory($steamCMDZip, $steamCMDPath)
}
cd "$steamData"
& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740" "+quit"
popd

52
Jenkins/release.ps1 Normal file
View File

@@ -0,0 +1,52 @@
param([string] $ApiBase, [string]$tagName, [string]$authinfo, [string[]] $assetPaths)
Add-Type -AssemblyName "System.Web"
$headers = @{
Authorization = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authinfo))
Accept = "application/vnd.github.v3+json"
}
try
{
Write-Output("Checking if release with tag " + $tagName + " already exists...")
$release = Invoke-RestMethod -Uri ($ApiBase+"releases/tags/$tagName") -Method "GET" -Headers $headers
Write-Output(" Using existing release " + $release.id + " at " + $release.html_url)
} catch {
Write-Output(" Doesn't exist")
$rel_arg = @{
tag_name=$tagName
name="Generated $tagName"
body=""
draft=$FALSE
prerelease=$tagName.Contains("alpha") -or $tagName.Contains("beta")
}
Write-Output("Creating new release " + $tagName + "...")
$release = Invoke-RestMethod -Uri ($ApiBase+"releases") -Method "POST" -Headers $headers -Body (ConvertTo-Json($rel_arg))
Write-Output(" Created new release " + $tagName + " at " + $release.html_url)
}
$assetsApiBase = $release.assets_url
Write-Output("Checking for existing assets...")
$existingAssets = Invoke-RestMethod -Uri ($assetsApiBase) -Method "GET" -Headers $headers
$assetLabels = ($assetPaths | ForEach-Object {[System.IO.Path]::GetFileName($_)})
foreach ($asset in $existingAssets) {
if ($assetLabels -contains $asset.name) {
$uri = $asset.url
Write-Output(" Deleting old asset " + $asset.name + " (id " + $asset.id + "); URI=" + $uri)
$result = Invoke-RestMethod -Uri $uri -Method "DELETE" -Headers $headers
}
}
Write-Output("Uploading assets...")
$uploadUrl = $release.upload_url.Substring(0, $release.upload_url.LastIndexOf('{'))
foreach ($asset in $assetPaths) {
$assetName = [System.IO.Path]::GetFileName($asset)
$assetType = [System.Web.MimeMapping]::GetMimeMapping($asset)
$assetData = [System.IO.File]::ReadAllBytes($asset)
$headerExtra = $headers + @{
"Content-Type" = $assetType
Name = $assetName
}
$uri = $uploadUrl + "?name=" + $assetName
Write-Output(" Uploading " + $asset + " as " + $assetType + "; URI=" + $uri)
$result = Invoke-RestMethod -Uri $uri -Method "POST" -Headers $headerExtra -Body $assetData
Write-Output(" ID=" + $result.id + ", found at=" + $result.browser_download_url)
}

67
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,67 @@
node {
stage('Checkout') {
checkout scm
bat 'git pull --tags'
}
stage('Acquire SE') {
bat 'powershell -File Jenkins/jenkins-grab-se.ps1'
bat 'IF EXIST GameBinaries RMDIR GameBinaries'
bat 'mklink /J GameBinaries "C:/Steam/Data/DedicatedServer64/"'
}
stage('Acquire NuGet Packages') {
bat 'nuget restore Torch.sln'
}
stage('Build') {
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=Release /p:Platform=x64"
}
stage('Test') {
bat 'IF NOT EXIST reports MKDIR reports'
bat "\"packages/xunit.runner.console.2.2.0/tools/xunit.console.exe\" \"bin-test/x64/Release/Torch.Tests.dll\" \"bin-test/x64/Release/Torch.Server.Tests.dll\" \"bin-test/x64/Release/Torch.Client.Tests.dll\" -parallel none -xml \"reports/Torch.Tests.xml\""
step([
$class: 'XUnitBuilder',
thresholdMode: 1,
thresholds: [[$class: 'FailedThreshold', failureThreshold: '1']],
tools: [[
$class: 'XUnitDotNetTestType',
deleteOutputFiles: true,
failIfNotNew: true,
pattern: 'reports/*.xml',
skipNoTestFiles: false,
stopProcessingIfError: true
]]
])
}
stage('Archive') {
bat '''IF EXIST bin\\torch-server.zip DEL bin\\torch-server.zip
IF EXIST bin\\package-server RMDIR /S /Q bin\\package-server
xcopy bin\\x64\\Release bin\\package-server\\
del bin\\package-server\\Torch.Client*'''
bat "powershell -Command \"Add-Type -Assembly System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory(\\\"\$PWD\\bin\\package-server\\\", \\\"\$PWD\\bin\\torch-server.zip\\\")\""
archiveArtifacts artifacts: 'bin/torch-server.zip', caseSensitive: false, onlyIfSuccessful: true
bat '''IF EXIST bin\\torch-client.zip DEL bin\\torch-client.zip
IF EXIST bin\\package-client RMDIR /S /Q bin\\package-client
xcopy bin\\x64\\Release bin\\package-client\\
del bin\\package-client\\Torch.Server*'''
bat "powershell -Command \"Add-Type -Assembly System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory(\\\"\$PWD\\bin\\package-client\\\", \\\"\$PWD\\bin\\torch-client.zip\\\")\""
archiveArtifacts artifacts: 'bin/torch-client.zip', caseSensitive: false, onlyIfSuccessful: true
archiveArtifacts artifacts: 'bin/x64/Release/Torch*', caseSensitive: false, fingerprint: true, onlyIfSuccessful: true
}
gitVersion = bat(returnStdout: true, script: "@git describe --tags").trim()
gitSimpleVersion = bat(returnStdout: true, script: "@git describe --tags --abbrev=0").trim()
if (gitVersion == gitSimpleVersion) {
stage('Release') {
withCredentials([usernamePassword(credentialsId: 'e771beac-b3ee-4bc9-82b7-40a6d426d508', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
powershell "./Jenkins/release.ps1 \"https://api.github.com/repos/TorchAPI/Torch/\" \"$gitSimpleVersion\" \"$USERNAME:$PASSWORD\" @(\"bin/torch-server.zip\", \"bin/torch-client.zip\")"
}
}
}
}

View File

@@ -1,31 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
<variable name="logContent" value="${message:withException=true}"/>
<targets>
<default-wrapper xsi:type="AsyncWrapper" overflowAction="Block" optimizeBufferReuse="true" />
<target xsi:type="Null" name="null" formatMessage="false" />
<target xsi:type="File" keepFileOpen="true" concurrentWrites="false" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}"
fileName="Logs\Keen-${shortdate}.log" />
<target xsi:type="File" keepFileOpen="true" concurrentWrites="false" name="main" layout="${var:logStamp} ${logger}: ${var:logContent}"
fileName="Logs\Torch-${shortdate}.log" />
<target xsi:type="File" name="main" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" fileName="Logs\Torch-${shortdate}.log" />
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
<target xsi:type="ColoredConsole" name="console" layout="${var:logStamp} ${logger:shortName=true}: ${var:logContent}" />
<target xsi:type="File" name="patch" layout="${var:logContent}" fileName="Logs\patch.log"/>
<target xsi:type="LogViewerTarget" name="wpf" layout="[${level:uppercase=true}] ${logger:shortName=true}: ${var:logContent}" />
<target xsi:type="ColoredConsole" name="console" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" />
</targets>
<rules>
<!-- Do not define custom rules here. Use NLog-user.config -->
<logger name="Keen" minlevel="Warn" writeTo="main"/>
<logger name="Keen" minlevel="Info" writeTo="console, wpf"/>
<logger name="Keen" minlevel="Debug" writeTo="keen" final="true" />
<logger name="Keen" writeTo="null" final="true" />
<logger name="*" minlevel="Info" writeTo="main, console, wpf" />
<logger name="*" minlevel="Info" writeTo="main, console" />
<logger name="Chat" minlevel="Info" writeTo="chat" />
<!--<logger name="Torch.Managers.PatchManager.*" minlevel="Trace" writeTo="patch"/>-->
</rules>
</nlog>

View File

@@ -1,36 +1,30 @@
[![Discord](https://discordapp.com/api/guilds/230191591640268800/widget.png)](https://discord.gg/8uHZykr) [![Build Status](http://server.torchapi.net:8080/job/Torch/job/Torch/job/master/badge/icon)](http://server.torchapi.net:8080/job/Torch/job/Torch/job/master/)
# 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.Server
### Features
# Features
* WPF-based user interface
* Chat: interact with the game chat and run commands without having to join the game.
* Entity manager: realtime modification of ingame entities such as stopping grids and changing block settings without having to join the game
* Organized, easy to use configuration editor
* Extensible using the Torch plugin system
### Fork Difference
* .NET 6.0 runtime
* 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
* Get the latest Torch release here: https://github.com/TorchAPI/Torch/releases
* 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.
* Pass path to game files using config parameter `gamePath`
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
# 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.
In both cases you will need to set the InstancePath in TorchConfig.xml to an existing dedicated server instance as Torch can't fully generate it on its own yet.
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE)
# Official Plugins
Install plugins by unzipping them into the 'Plugins' folder which should be in the same location as the Torch files. If it doesn't exist you can simply create it.
* [Essentials](https://github.com/TorchAPI/Essentials): Adds a slew of chat commands and other tools to help manage your server.
* [Concealment](https://github.com/TorchAPI/Concealment): Adds game logic and physics optimizations that significantly improve sim speed.
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon.
[![Patreon](http://i.imgur.com/VzzIMgn.png)](https://www.patreon.com/bePatron?u=847269)!

View File

@@ -0,0 +1,13 @@
:: 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

@@ -1,28 +0,0 @@
using System;
namespace Torch.API.Event
{
/// <summary>
/// Attribute indicating that a method should be invoked when the event occurs.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class EventHandlerAttribute : Attribute
{
/// <summary>
/// Events are executed from low priority to high priority.
/// </summary>
/// <remarks>
/// While this may seem unintuitive this gives the high priority events the final say on changing/canceling events.
/// </remarks>
public int Priority { get; set; } = 0;
/// <summary>
/// Specifies if this handler should ignore a consumed event.
/// </summary>
/// <remarks>
/// If <see cref="SkipCancelled"/> is <em>true</em> and the event is cancelled by a lower priority handler this handler won't be invoked.
/// </remarks>
/// <seealso cref="IEvent.Cancelled"/>
public bool SkipCancelled { get; set; } = false;
}
}

View File

@@ -1,11 +0,0 @@
namespace Torch.API.Event
{
public interface IEvent
{
/// <summary>
/// An event that has been cancelled will no be processed in the default manner.
/// </summary>
/// <seealso cref="EventHandlerAttribute.SkipCancelled"/>
bool Cancelled { get; }
}
}

View File

@@ -1,9 +0,0 @@
namespace Torch.API.Event
{
/// <summary>
/// Interface used to tag an event handler. This does <b>not</b> register it with the event manager.
/// </summary>
public interface IEventHandler
{
}
}

View File

@@ -1,28 +0,0 @@
using System.Runtime.CompilerServices;
using Torch.API.Managers;
namespace Torch.API.Event
{
/// <summary>
/// Manager class responsible for registration of event handlers.
/// </summary>
public interface IEventManager : IManager
{
/// <summary>
/// Registers all event handler methods contained in the given instance
/// </summary>
/// <param name="handler">Instance to register</param>
/// <returns><b>true</b> if added, <b>false</b> otherwise</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
bool RegisterHandler(IEventHandler handler);
/// <summary>
/// Unregisters all event handler methods contained in the given instance
/// </summary>
/// <param name="handler">Instance to unregister</param>
/// <returns><b>true</b> if removed, <b>false</b> otherwise</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
bool UnregisterHandler(IEventHandler handler);
}
}

View File

@@ -1,30 +0,0 @@
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; }
}

31
Torch.API/IChatMessage.cs Normal file
View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API
{
public interface IChatMessage
{
/// <summary>
/// The time the message was created.
/// </summary>
DateTime Timestamp { get; }
/// <summary>
/// The SteamID of the message author.
/// </summary>
ulong SteamId { get; }
/// <summary>
/// The name of the message author.
/// </summary>
string Name { get; }
/// <summary>
/// The content of the message.
/// </summary>
string Message { get; }
}
}

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Torch.API.Managers;
using Torch.API.Session;
using VRage.Game.ModAPI;
using Version = SemanticVersioning.Version;
namespace Torch.API
{
@@ -16,6 +14,26 @@ namespace Torch.API
/// </summary>
public interface ITorchBase
{
/// <summary>
/// Fired when the session begins loading.
/// </summary>
event Action SessionLoading;
/// <summary>
/// Fired when the session finishes loading.
/// </summary>
event Action SessionLoaded;
/// <summary>
/// Fires when the session begins unloading.
/// </summary>
event Action SessionUnloading;
/// <summary>
/// Fired when the session finishes unloading.
/// </summary>
event Action SessionUnloaded;
/// <summary>
/// Gets the currently running session instance, or null if none exists.
/// </summary>
@@ -26,6 +44,10 @@ namespace Torch.API
/// </summary>
ITorchConfig Config { get; }
/// <inheritdoc cref="IMultiplayerManager"/>
[Obsolete]
IMultiplayerManager Multiplayer { get; }
/// <inheritdoc cref="IPluginManager"/>
[Obsolete]
IPluginManager Plugins { get; }
@@ -33,93 +55,52 @@ namespace Torch.API
/// <inheritdoc cref="IDependencyManager"/>
IDependencyManager Managers { get; }
[Obsolete("Prefer using Managers.GetManager for global managers")]
T GetManager<T>() where T : class, IManager;
[Obsolete("Prefer using Managers.AddManager for global managers")]
bool AddManager<T>(T manager) where T : class, IManager;
/// <summary>
/// The binary version of the current instance.
/// </summary>
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>
/// Invoke an action on the game thread.
/// </summary>
void Invoke(Action action, [CallerMemberName] string caller = "");
void Invoke(Action action);
/// <summary>
/// Invoke an action on the game thread and block until it has completed.
/// If this is called on the game thread the action will execute immediately.
/// </summary>
/// <param name="action">Action to execute</param>
/// <param name="caller">Caller of the invoke function</param>
/// <param name="timeoutMs">Timeout before <see cref="TimeoutException"/> is thrown, or -1 to never timeout</param>
/// <exception cref="TimeoutException">If the action times out</exception>
void InvokeBlocking(Action action, int timeoutMs = -1, [CallerMemberName] string caller = "");
void InvokeBlocking(Action action);
/// <summary>
/// Invoke an action on the game thread asynchronously.
/// </summary>
Task InvokeAsync(Action action, [CallerMemberName] string caller = "");
Task InvokeAsync(Action action);
/// <summary>
/// Invoke a function on the game thread asynchronously.
/// </summary>
Task<T> InvokeAsync<T>(Func<T> func, [CallerMemberName] string caller = "");
/// <summary>
/// Signals the torch instance to start, then blocks until it's started.
/// Start the Torch instance.
/// </summary>
void Start();
/// <summary>
/// Signals the torch instance to stop, then blocks until it's stopped.
/// Stop the Torch instance.
/// </summary>
void Stop();
/// <summary>
/// Restart the Torch instance, blocking until the restart has been performed.
/// Restart the Torch instance.
/// </summary>
void Restart(bool save = true);
void Restart();
/// <summary>
/// Initializes a save of the game.
/// </summary>
/// <param name="timeoutMs">timeout before the save is treated as failed, or -1 for no timeout</param>
/// <param name="exclusive">Only start saving if we aren't already saving</param>
/// <returns>Future result of the save, or null if one is in progress and in exclusive mode</returns>
Task<GameSaveResult> Save(int timeoutMs = -1, bool exclusive = false);
/// <param name="callerId">Id of the player who initiated the save.</param>
Task Save(long callerId);
/// <summary>
/// Initialize the Torch instance. Before this <see cref="Start"/> is invalid.
/// Initialize the Torch instance.
/// </summary>
void Init();
/// <summary>
/// Disposes the Torch instance. After this <see cref="Start"/> is invalid.
/// </summary>
void Destroy();
/// <summary>
/// The current state of the game this instance of torch is controlling.
/// </summary>
TorchGameState GameState { get; }
/// <summary>
/// Event raised when <see cref="GameState"/> changes.
/// </summary>
event TorchGameStateChangedDel GameStateChanged;
}
/// <summary>
@@ -128,16 +109,9 @@ namespace Torch.API
public interface ITorchServer : ITorchBase
{
/// <summary>
/// The current <see cref="ServerState"/>
/// Path of the dedicated instance folder.
/// </summary>
ServerState State { get; }
/// <summary>
/// Raised when the server's Init() method has completed.
/// </summary>
event Action<ITorchServer> Initialized;
TimeSpan ElapsedPlayTime { get; set; }
string InstancePath { get; }
}
/// <summary>
@@ -145,6 +119,6 @@ namespace Torch.API
/// </summary>
public interface ITorchClient : ITorchBase
{
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using Torch.API;
using System.Collections.Generic;
namespace Torch
{
@@ -10,44 +8,17 @@ namespace Torch
bool ForceUpdate { get; set; }
bool GetPluginUpdates { get; set; }
bool GetTorchUpdates { get; set; }
[Obsolete("Use Torch.InstanceName instead")]
string InstanceName { get; set; }
[Obsolete("Use Torch.InstancePath instead")]
string InstancePath { get; set; }
bool NoGui { get; set; }
bool NoUpdate { get; set; }
List<Guid> Plugins { get; set; }
bool LocalPlugins { get; set; }
List<string> Plugins { get; set; }
bool RestartOnCrash { get; set; }
bool ShouldUpdatePlugins { get; }
bool ShouldUpdateTorch { get; }
int TickTimeout { get; set; }
string ChatName { get; set; }
string ChatColor { get; set; }
string TestPlugin { get; set; }
bool DisconnectOnRestart { get; set; }
int WindowWidth { get; set; }
int WindowHeight { get; set; }
int FontSize { get; set; }
UGCServiceType UgcServiceType { get; set; }
bool EntityManagerEnabled { get; set; }
string LoginToken { get; set; }
UpdateSource UpdateSource { get; set; }
void Save(string path = null);
}
string WaitForPID { get; set; }
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
bool Save(string path = null);
}
}

View File

@@ -1,171 +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.Gui;
using Sandbox.Game.Multiplayer;
using Torch.Utils;
using VRage.Game;
using VRage.Network;
using VRage.Replication;
using VRageMath;
using VRageRender;
namespace Torch.API.Managers
{
/// <summary>
/// Represents a scripted or user chat message.
/// </summary>
public readonly struct TorchChatMessage
{
private const string DEFAULT_FONT = MyFontEnum.Blue;
#region Backwards compatibility
[Obsolete]
public TorchChatMessage(string author, string message, string font = DEFAULT_FONT)
: this(author, message, default, font) { }
[Obsolete]
public TorchChatMessage(string author, ulong authorSteamId, string message, ChatChannel channel, long target, string font = DEFAULT_FONT)
: this(author, authorSteamId, message, channel, target, default, font) { }
[Obsolete]
public TorchChatMessage(ulong authorSteamId, string message, ChatChannel channel, long target, string font = DEFAULT_FONT)
: this(authorSteamId, message, channel, target, default, font) { }
#endregion
/// <summary>
/// Creates a new torch chat message with the given author and message.
/// </summary>
/// <param name="author">Author's name</param>
/// <param name="message">Message</param>
/// <param name="font">Font</param>
public TorchChatMessage(string author, string message, Color color, string font = DEFAULT_FONT)
{
Timestamp = DateTime.Now;
AuthorSteamId = null;
Author = author;
Message = message;
Channel = ChatChannel.Global;
Target = 0;
Font = font;
Color = color == default ? ColorUtils.TranslateColor(font) : color;
}
/// <summary>
/// Creates a new torch chat message with the given author and message.
/// </summary>
/// <param name="author">Author's name</param>
/// <param name="authorSteamId">Author's steam ID</param>
/// <param name="message">Message</param>
/// <param name="font">Font</param>
public TorchChatMessage(string author, ulong authorSteamId, string message, ChatChannel channel, long target, Color color, string font = DEFAULT_FONT)
{
Timestamp = DateTime.Now;
AuthorSteamId = authorSteamId;
Author = author;
Message = message;
Channel = channel;
Target = target;
Font = font;
Color = color == default ? ColorUtils.TranslateColor(font) : color;
}
/// <summary>
/// Creates a new torch chat message with the given author and message.
/// </summary>
/// <param name="authorSteamId">Author's steam ID</param>
/// <param name="message">Message</param>
/// <param name="font">Font</param>
public TorchChatMessage(ulong authorSteamId, string message, ChatChannel channel, long target, Color color, string font = DEFAULT_FONT)
{
Timestamp = DateTime.Now;
AuthorSteamId = authorSteamId;
Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
Message = message;
Channel = channel;
Target = target;
Font = font;
Color = color == default ? ColorUtils.TranslateColor(font) : color;
}
/// <summary>
/// This message's timestamp.
/// </summary>
public readonly DateTime Timestamp;
/// <summary>
/// The author's steam ID, if available. Else, null.
/// </summary>
public readonly ulong? AuthorSteamId;
/// <summary>
/// The author's name, if available. Else, null.
/// </summary>
public readonly string Author;
/// <summary>
/// The message contents.
/// </summary>
public readonly string Message;
/// <summary>
/// The chat channel the message is part of.
/// </summary>
public readonly ChatChannel Channel;
/// <summary>
/// The intended recipient of the message.
/// </summary>
public readonly long Target;
/// <summary>
/// The font, or null if default.
/// </summary>
public readonly string Font;
/// <summary>
/// The chat message color.
/// </summary>
public readonly Color Color;
}
/// <summary>
/// Callback used to indicate that a messaage has been recieved.
/// </summary>
/// <param name="msg"></param>
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
public delegate void MessageRecievedDel(TorchChatMessage msg, ref bool consumed);
/// <summary>
/// Callback used to indicate the user is attempting to send a message locally.
/// </summary>
/// <param name="msg">Message the user is attempting to send</param>
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
public delegate void MessageSendingDel(string msg, ref bool consumed);
public interface IChatManagerClient : IManager
{
/// <summary>
/// Event that is raised when a message addressed to us is recieved. <see cref="MessageRecievedDel"/>
/// </summary>
event MessageRecievedDel MessageRecieved;
/// <summary>
/// Event that is raised when we are attempting to send a message. <see cref="MessageSendingDel"/>
/// </summary>
event MessageSendingDel MessageSending;
/// <summary>
/// Triggers the <see cref="MessageSending"/> event,
/// typically raised by the user entering text into the chat window.
/// </summary>
/// <param name="message">The message to send</param>
void SendMessageAsSelf(string message);
/// <summary>
/// Displays a message on the UI given an author name and a message.
/// </summary>
/// <param name="author">Author name</param>
/// <param name="message">Message content</param>
/// <param name="font">font to use</param>
void DisplayMessageOnSelf(string author, string message, string font = "Blue" );
}
}

View File

@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Collections;
using VRage.Game;
using VRage.Network;
using VRageMath;
namespace Torch.API.Managers
{
/// <summary>
/// Callback used to indicate the server has recieved a message to process and forward on to others.
/// </summary>
/// <param name="authorId">Steam ID of the user sending a message</param>
/// <param name="msg">Message the user is attempting to send</param>
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
public delegate void MessageProcessingDel(TorchChatMessage msg, ref bool consumed);
public interface IChatManagerServer : IChatManagerClient
{
/// <summary>
/// Event triggered when the server has recieved a message and should process it. <see cref="MessageProcessingDel"/>
/// </summary>
event MessageProcessingDel MessageProcessing;
/// <summary>
/// Sends a message with the given author and message to the given player, or all players by default.
/// </summary>
/// <param name="authorId">Author's steam ID</param>
/// <param name="message">The message to send</param>
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0);
[Obsolete("Use the other overload with a Color parameter.")]
void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0);
/// <summary>
/// Sends a scripted message with the given author and message to the given player, or all players by default.
/// </summary>
/// <param name="author">Author name</param>
/// <param name="message">The message to send</param>
/// <param name="color">Name color</param>
/// <param name="font">Font to use</param>
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
void SendMessageAsOther(string author, string message, Color color = default, ulong targetSteamId = 0, string font = MyFontEnum.White);
/// <summary>
/// Mute user from global chat.
/// </summary>
/// <param name="steamId"></param>
/// <returns></returns>
bool MuteUser(ulong steamId);
/// <summary>
/// Unmute user from global chat.
/// </summary>
/// <param name="steamId"></param>
/// <returns></returns>
bool UnmuteUser(ulong steamId);
/// <summary>
/// Users which are not allowed to chat.
/// </summary>
HashSetReader<ulong> MutedUsers { get; }
}
}

View File

@@ -1,20 +0,0 @@
using VRage.Game;
namespace Torch.API.Managers;
public interface IInstanceManager : IManager
{
IWorld SelectedWorld { get; }
void LoadInstance(string path, bool validate = true);
void SelectCreatedWorld(string worldPath);
void SelectWorld(string worldPath, bool modsOnly = true);
void ImportSelectedWorldConfig();
void SaveConfig();
}
public interface IWorld
{
string WorldPath { get; }
MyObjectBuilder_SessionSettings KeenSessionSettings { get; }
MyObjectBuilder_Checkpoint KeenCheckpoint { get; }
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using VRage.Game;
using VRage.Game.ModAPI;
namespace Torch.API.Managers
{
/// <summary>
/// Delegate for received messages.
/// </summary>
/// <param name="message">Message data.</param>
/// <param name="sendToOthers">Flag to broadcast message to other players.</param>
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
/// <summary>
/// API for multiplayer related functions.
/// </summary>
public interface IMultiplayerManager : IManager
{
/// <summary>
/// Fired when a player joins.
/// </summary>
event Action<IPlayer> PlayerJoined;
/// <summary>
/// Fired when a player disconnects.
/// </summary>
event Action<IPlayer> PlayerLeft;
/// <summary>
/// Fired when a chat message is received.
/// </summary>
event MessageReceivedDel MessageReceived;
/// <summary>
/// Send a chat message to all or one specific player.
/// </summary>
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
/// <summary>
/// Kicks the player from the game.
/// </summary>
void KickPlayer(ulong steamId);
/// <summary>
/// Bans or unbans a player from the game.
/// </summary>
void BanPlayer(ulong steamId, bool banned = true);
/// <summary>
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
/// </summary>
IMyPlayer GetPlayerBySteamId(ulong id);
/// <summary>
/// Gets a player by their display name or returns null if the player isn't found.
/// </summary>
IMyPlayer GetPlayerByName(string name);
}
}

View File

@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using VRage.Game;
using VRage.Game.ModAPI;
namespace Torch.API.Managers
{
/// <summary>
/// API for multiplayer related functions common to servers and clients.
/// </summary>
public interface IMultiplayerManagerBase : IManager
{
/// <summary>
/// Fired when a player joins.
/// </summary>
event Action<IPlayer> PlayerJoined;
/// <summary>
/// Fired when a player disconnects.
/// </summary>
event Action<IPlayer> PlayerLeft;
/// <summary>
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
/// </summary>
IMyPlayer GetPlayerBySteamId(ulong id);
/// <summary>
/// Gets a player by their display name or returns null if the player isn't found.
/// </summary>
IMyPlayer GetPlayerByName(string name);
/// <summary>
/// Gets the steam username of a member's steam ID
/// </summary>
/// <param name="steamId">steam ID</param>
/// <returns>steam username</returns>
string GetSteamUsername(ulong steamId);
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API.Managers
{
public interface IMultiplayerManagerClient : IMultiplayerManagerBase
{
}
}

View File

@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game.ModAPI;
namespace Torch.API.Managers
{
/// <summary>
/// API for multiplayer functions that exist on servers and lobbies
/// </summary>
public interface IMultiplayerManagerServer : IMultiplayerManagerBase
{
/// <summary>
/// Kicks the player from the game.
/// </summary>
void KickPlayer(ulong steamId);
/// <summary>
/// Bans or unbans a player from the game.
/// </summary>
void BanPlayer(ulong steamId, bool banned = true);
/// <summary>
/// Promotes user if possible.
/// </summary>
/// <param name="steamId"></param>
void PromoteUser(ulong steamId);
/// <summary>
/// Demotes user if possible.
/// </summary>
/// <param name="steamId"></param>
void DemoteUser(ulong steamId);
/// <summary>
/// Gets a user's promote level.
/// </summary>
/// <param name="steamId"></param>
/// <returns></returns>
MyPromoteLevel GetUserPromoteLevel(ulong steamId);
/// <summary>
/// List of the banned SteamID's
/// </summary>
IReadOnlyList<ulong> BannedPlayers { get; }
/// <summary>
/// Checks if the player with the given SteamID is banned.
/// </summary>
/// <param name="steamId">The SteamID of the player.</param>
/// <returns>True if the player is banned; otherwise false.</returns>
bool IsBanned(ulong steamId);
/// <summary>
/// Raised when a player is kicked. Passes with SteamID of kicked player.
/// </summary>
event Action<ulong> PlayerKicked;
/// <summary>
/// Raised when a player is banned or unbanned. Passes SteamID of player, and true if banned, false if unbanned.
/// </summary>
event Action<ulong, bool> PlayerBanned;
/// <summary>
/// Raised when a player is promoted or demoted. Passes SteamID of player, and new promote level.
/// </summary>
event Action<ulong, MyPromoteLevel> PlayerPromoted;
}
}

View File

@@ -18,12 +18,6 @@ namespace Torch.API.Managers
/// Register a network handler.
/// </summary>
void RegisterNetworkHandler(INetworkHandler handler);
/// <summary>
/// Unregister a network handler.
/// </summary>
/// <returns>true if the handler was unregistered, false if it wasn't registered to begin with</returns>
bool UnregisterNetworkHandler(INetworkHandler handler);
}
/// <summary>
@@ -39,7 +33,6 @@ namespace Torch.API.Managers
/// <summary>
/// Processes a network message.
/// </summary>
/// <returns>true if the message should be discarded</returns>
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
}
}

View File

@@ -14,12 +14,12 @@ namespace Torch.API.Managers
/// <summary>
/// Fired when plugins are loaded.
/// </summary>
event Action<IReadOnlyCollection<ITorchPlugin>> PluginsLoaded;
event Action<IList<ITorchPlugin>> PluginsLoaded;
/// <summary>
/// Collection of loaded plugins.
/// </summary>
IReadOnlyDictionary<Guid, ITorchPlugin> Plugins { get; }
IList<ITorchPlugin> Plugins { get; }
/// <summary>
/// Updates all loaded plugins.

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;

View File

@@ -17,7 +17,7 @@ namespace Torch.API.Plugins
/// <summary>
/// The version of the plugin.
/// </summary>
string Version { get; }
Version Version { get; }
/// <summary>
/// The name of the plugin.
@@ -34,22 +34,5 @@ namespace Torch.API.Plugins
/// This is called on the game thread after each tick.
/// </summary>
void Update();
/// <summary>
/// Plugin's enabled state. Mainly for UI niceness
/// </summary>
PluginState State { get; }
}
public enum PluginState
{
NotInitialized,
DisabledError,
DisabledUser,
UpdateRequired,
UninstallRequested,
NotInstalled,
MissingDependency,
Enabled
}
}

View File

@@ -10,7 +10,6 @@ namespace Torch.API.Plugins
/// <summary>
/// Indicates that the given type should be loaded by the plugin manager as a plugin.
/// </summary>
[Obsolete("All plugin meta-information is now defined in the manifest.xml.")]
[AttributeUsage(AttributeTargets.Class)]
public class PluginAttribute : Attribute
{

View File

@@ -0,0 +1,17 @@
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,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API.Session
{
/// <summary>
/// The result of a save operation
/// </summary>
public enum GameSaveResult
{
/// <summary>
/// Successfully saved
/// </summary>
Success = 0,
/// <summary>
/// The game wasn't ready to be saved
/// </summary>
GameNotReady = -1,
/// <summary>
/// Failed to take the snapshot of the current world state
/// </summary>
FailedToTakeSnapshot = -2,
/// <summary>
/// Failed to save the snapshot to disk
/// </summary>
FailedToSaveToDisk = -3,
/// <summary>
/// An unknown error occurred
/// </summary>
UnknownError = -4,
/// <summary>
/// The save operation timed out
/// </summary>
TimedOut = -5
}
}

View File

@@ -22,23 +22,8 @@ namespace Torch.API.Session
/// The Space Engineers game session this session is bound to.
/// </summary>
MySession KeenSession { get; }
/// <summary>
/// Currently running world
/// </summary>
IWorld World { get; }
/// <inheritdoc cref="IDependencyManager"/>
IDependencyManager Managers { get; }
/// <summary>
/// The current state of the session
/// </summary>
TorchSessionState State { get; }
/// <summary>
/// Event raised when the <see cref="State"/> changes.
/// </summary>
event TorchSessionStateChangedDel StateChanged;
}
}

View File

@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API.Managers;
using VRage.Game;
namespace Torch.API.Session
{
@@ -29,11 +27,6 @@ namespace Torch.API.Session
/// </summary>
ITorchSession CurrentSession { get; }
/// <summary>
/// Raised when any <see cref="ITorchSession"/> <see cref="ITorchSession.State"/> changes.
/// </summary>
event TorchSessionStateChangedDel SessionStateChanged;
/// <summary>
/// Adds the given factory as a supplier for session based managers
/// </summary>
@@ -49,29 +42,5 @@ namespace Torch.API.Session
/// <returns>true if removed, false if not present</returns>
/// <exception cref="ArgumentNullException">If the factory is null</exception>
bool RemoveFactory(SessionManagerFactoryDel factory);
/// <summary>
/// Add a mod to be injected into client's world download.
/// </summary>
/// <param name="modId"></param>
/// <returns></returns>
bool AddOverrideMod(ulong modId);
/// <summary>
/// Removes a mod from the injected mod list.
/// </summary>
/// <param name="modId"></param>
/// <returns></returns>
bool RemoveOverrideMod(ulong modId);
/// <summary>
/// List over mods that will be injected into client world downloads.
/// </summary>
IReadOnlyCollection<MyObjectBuilder_Checkpoint.ModItem> OverrideMods { get; }
/// <summary>
/// Event raised when injected mod list changes.
/// </summary>
event Action<CollectionChangeEventArgs> OverrideModsChanged;
}
}

View File

@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API.Session
{
/// <summary>
/// Represents the state of a <see cref="ITorchSession"/>
/// </summary>
public enum TorchSessionState
{
/// <summary>
/// The session has been created, and is now loading.
/// </summary>
Loading,
/// <summary>
/// The session has loaded, and is now running.
/// </summary>
Loaded,
/// <summary>
/// The session was running, and is now unloading.
/// </summary>
Unloading,
/// <summary>
/// The session was unloading, and is now unloaded and stopped.
/// </summary>
Unloaded
}
/// <summary>
/// Callback raised when a session's state changes
/// </summary>
/// <param name="session">The session who had a state change</param>
/// <param name="newState">The session's new state</param>
public delegate void TorchSessionStateChangedDel(ITorchSession session, TorchSessionState newState);
}

View File

@@ -1,27 +1,194 @@
<Project Sdk="Microsoft.NET.Sdk">
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch API</AssemblyTitle>
<Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWpf>True</UseWpf>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<ProjectGuid>{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Torch.API</RootNamespace>
<AssemblyName>Torch.API</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Release'">
<NoWarn>1591</NoWarn>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.API.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JorgeSerrano.Json.JsonSnakeCaseNamingPolicy" Version="0.9.0" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="SemanticVersioning" Version="2.0.2" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Game, Version=0.1.6305.30774, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Graphics, Version=0.1.6305.30761, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.ObjectBuilders, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.ObjectBuilders.XmlSerializers, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SteamSDK, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\SteamSDK.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<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">
<SpecificVersion>False</SpecificVersion>
<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.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game.XmlSerializers, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Game.XmlSerializers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<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, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Native, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Native.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.OpenVRWrapper, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.OpenVRWrapper.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render11, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Scripting, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="ConnectionState.cs" />
<Compile Include="IChatMessage.cs" />
<Compile Include="ITorchConfig.cs" />
<Compile Include="Managers\DependencyManagerExtensions.cs" />
<Compile Include="Managers\DependencyProviderExtensions.cs" />
<Compile Include="Managers\IDependencyManager.cs" />
<Compile Include="Managers\IDependencyProvider.cs" />
<Compile Include="Managers\IManager.cs" />
<Compile Include="Managers\IMultiplayerManager.cs" />
<Compile Include="IPlayer.cs" />
<Compile Include="Managers\INetworkManager.cs" />
<Compile Include="Managers\IPluginManager.cs" />
<Compile Include="Plugins\ITorchPlugin.cs" />
<Compile Include="IServerControls.cs" />
<Compile Include="ITorchBase.cs" />
<Compile Include="Plugins\IWpfPlugin.cs" />
<Compile Include="ModAPI\Ingame\GridExtensions.cs" />
<Compile Include="Plugins\PluginAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerState.cs" />
<Compile Include="ModAPI\TorchAPI.cs" />
<Compile Include="Session\ITorchSession.cs" />
<Compile Include="Session\ITorchSessionManager.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
</Project>

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox;
namespace Torch.API
{
/// <summary>
/// Represents the state of a <see cref="MySandboxGame"/>
/// </summary>
public enum TorchGameState
{
/// <summary>
/// The game is currently being created.
/// </summary>
Creating,
/// <summary>
/// The game has been created and is ready to begin loading.
/// </summary>
Created,
/// <summary>
/// The game is currently loading.
/// </summary>
Loading,
/// <summary>
/// The game is fully loaded and ready to start sessions
/// </summary>
Loaded,
/// <summary>
/// The game is beginning the unload sequence
/// </summary>
Unloading,
/// <summary>
/// The game has been shutdown and is no longer active
/// </summary>
Unloaded
}
/// <summary>
/// Callback raised when a game's state changes
/// </summary>
/// <param name="game">The game who had a state change</param>
/// <param name="newState">The game's new state</param>
public delegate void TorchGameStateChangedDel(MySandboxGame game, TorchGameState newState);
}

View File

@@ -1,8 +0,0 @@
namespace Torch.API
{
public enum UGCServiceType
{
Steam,
EOS
}
}

View File

@@ -1,40 +0,0 @@
using System.Windows.Media;
using VRage.Game;
using Color = VRageMath.Color;
namespace Torch.Utils
{
public static class ColorUtils
{
/// <summary>
/// Convert the old "font" or a RGB hex code to a Color.
/// </summary>
public static Color TranslateColor(string font)
{
if (StringUtils.IsFontEnum(font))
{
// RGB values copied from Fonts.sbc
switch (font)
{
case MyFontEnum.Blue:
return new Color(220, 244, 252);
case MyFontEnum.Red:
return new Color(227, 65, 65);
case MyFontEnum.Green:
return new Color(101, 182, 193);
case MyFontEnum.DarkBlue:
return new Color(94, 115, 127);
default:
return Color.White;
}
}
else
{
// VRage color doesn't have its own hex code parser and I don't want to write one
var conv = (System.Windows.Media.Color)(ColorConverter.ConvertFromString(font) ??
System.Windows.Media.Color.FromRgb(255, 255, 255));
return new Color(conv.R, conv.G, conv.B);
}
}
}
}

View File

@@ -1,21 +0,0 @@
#nullable enable
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Version = SemanticVersioning.Version;
namespace Torch.API.Utils;
public class SemanticVersionConverter : JsonConverter<Version>
{
public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Version.TryParse(reader.GetString(), out var ver);
return ver;
}
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}

View File

@@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Utils
{
/// <summary>
/// Utility methods for strings
/// </summary>
public static class StringUtils
{
/// <summary>
/// Determines a common prefix for the given set of strings
/// </summary>
/// <param name="set">Set of strings</param>
/// <returns>Common prefix</returns>
public static string CommonPrefix(IEnumerable<string> set)
{
StringBuilder builder = null;
foreach (string other in set)
{
if (builder == null)
builder = new StringBuilder(other);
if (builder.Length > other.Length)
builder.Remove(other.Length, builder.Length - other.Length);
for (var i = 0; i < builder.Length; i++)
if (builder[i] != other[i])
{
builder.Remove(i, builder.Length - i);
break;
}
}
return builder?.ToString() ?? "";
}
/// <summary>
/// Determines a common suffix for the given set of strings
/// </summary>
/// <param name="set">Set of strings</param>
/// <returns>Common suffix</returns>
public static string CommonSuffix(IEnumerable<string> set)
{
StringBuilder builder = null;
foreach (string other in set)
{
if (builder == null)
builder = new StringBuilder(other);
if (builder.Length > other.Length)
builder.Remove(0, builder.Length - other.Length);
for (var i = 0; i < builder.Length; i++)
{
if (builder[builder.Length - 1 - i] != other[other.Length - 1 - i])
{
builder.Remove(0, builder.Length - i);
break;
}
}
}
return builder?.ToString() ?? "";
}
private static string[] FontEnumValues => _fontEnumValues ?? (_fontEnumValues = typeof(VRage.Game.MyFontEnum).GetFields(BindingFlags.Public | BindingFlags.Static).Where(x => x.IsLiteral && !x.IsInitOnly).Select(x => (string)x.GetValue(null)).ToArray());
private static string[] _fontEnumValues;
public static bool IsFontEnum(string str)
{
return FontEnumValues.Contains(str);
}
}
}

View File

@@ -1,128 +0,0 @@
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

@@ -1,9 +0,0 @@
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,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Torch.API.Utils;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI
{
public class JenkinsQuery : IUpdateQuery
{
private const string ApiPath = "api/json";
private readonly HttpClient _client;
public JenkinsQuery(string url)
{
if (url == null) throw new ArgumentNullException(nameof(url));
_client = new()
{
BaseAddress = new(url)
};
}
public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{
branch ??= "master";
var response = await _client.GetFromJsonAsync<BranchResponse>($"/job/{repository}/job/{branch}/{ApiPath}");
if (response is null)
throw new($"Unable to get latest release for {repository}");
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);
}
public void Dispose()
{
_client?.Dispose();
}
}
public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild);
public record Build(int Number, string Url);
public record Job(int Number, bool Building, string Description, string Result, string Url,
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version,
IReadOnlyList<Artifact> Artifacts);
public record Artifact(
string DisplayPath,
string FileName,
string RelativePath
);
}

View File

@@ -1,104 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using NLog;
namespace Torch.API.WebAPI
{
public class PluginQuery
{
private const string ALL_QUERY = "https://torchapi.com/api/plugins/";
private const string PLUGIN_QUERY = "https://torchapi.com/api/plugins/search/{0}";
private readonly HttpClient _client;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static PluginQuery _instance;
public static PluginQuery Instance => _instance ??= new();
private PluginQuery()
{
_client = new();
}
public async Task<PluginsResponse> QueryAll()
{
return (PluginsResponse) await _client.GetFromJsonAsync(ALL_QUERY, typeof(PluginsResponse), CancellationToken.None);
}
public Task<PluginItem> QueryOne(Guid guid)
{
return QueryOne(guid.ToString());
}
public async Task<PluginItem> QueryOne(string guid)
{
using var res = await _client.GetAsync(string.Format(PLUGIN_QUERY, guid));
if (!res.IsSuccessStatusCode)
return null;
return await res.Content.ReadFromJsonAsync<PluginItem>();
}
public Task<bool> DownloadPlugin(Guid guid, string path = null)
{
return DownloadPlugin(guid.ToString(), path);
}
public async Task<bool> DownloadPlugin(string guid, string path = null)
{
var item = await QueryOne(guid);
if (item is null) return false;
return await DownloadPlugin(item, path);
}
public async Task<bool> DownloadPlugin(PluginItem item, string path = null)
{
try
{
path ??= Path.Combine(AppContext.BaseDirectory, "Plugins", $"{item.Name}.zip");
if (item.Versions.Length == 0)
{
Log.Error($"Selected plugin {item.Name} does not have any versions to download!");
return false;
}
var version = item.Versions.FirstOrDefault(v => v.Version == item.LatestVersion);
if (version is null)
{
Log.Error($"Could not find latest version for selected plugin {item.Name}");
return false;
}
var s = await _client.GetStreamAsync(version.Url);
if(File.Exists(path))
File.Delete(path);
await using var f = File.Create(path);
await s.CopyToAsync(f);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to download plugin!");
}
return true;
}
}
public record PluginsResponse(PluginItem[] Plugins);
public record PluginItem(Guid Id, string Name, string Author, string Description, string LatestVersion,
VersionItem[] Versions)
{
[JsonIgnore]
public bool Installed { get; set; }
}
public record VersionItem(string Version, string Note, [property: JsonPropertyName("is_beta")] bool IsBeta,
string Url);
}

View File

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

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
</packages>

View File

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<ProjectGuid>{632E78C0-0DAC-4B71-B411-2F1B333CC310}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Torch.Client.Tests</RootNamespace>
<AssemblyName>Torch.Client.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<NoWarn>1591,0649</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Client.Tests.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TorchClientReflectionTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Client\Torch.Client.csproj">
<Project>{e36df745-260b-4956-a2e8-09f08b2e7161}</Project>
<Name>Torch.Client</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj">
<Project>{c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5}</Project>
<Name>Torch.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
<Name>Torch</Name>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
</Project>

View File

@@ -0,0 +1,67 @@
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;
#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));
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
<package id="xunit" version="2.2.0" targetFramework="net461" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
</packages>

181
Torch.Client/Program.cs Normal file
View File

@@ -0,0 +1,181 @@
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
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
// Early config: Resolve SE install directory.
if (!File.Exists(Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile)))
SetupSpaceEngInstallAlias();
using (new TorchAssemblyResolver(Path.Combine(SpaceEngineersInstallAlias, SpaceEngineersBinaries)))
{
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);
}
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

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <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

@@ -0,0 +1,117 @@
<?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

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <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

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

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<ProjectGuid>{E36DF745-260B-4956-A2E8-09F08B2E7161}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Torch.Client</RootNamespace>
<AssemblyName>Torch.Client</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Client.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>torchicon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<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, Version=0.1.6108.20417, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<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="System" />
<Reference Include="System.Data" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<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">
<SpecificVersion>False</SpecificVersion>
<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, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Math.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.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</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TorchClient.cs" />
<Compile Include="TorchClientConfig.cs" />
<Compile Include="TorchConsoleScreen.cs" />
<Compile Include="TorchMainMenuScreen.cs" />
<Compile Include="TorchSettingsScreen.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name>
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7E01635C-3B67-472E-BCD6-C5539564F214}</Project>
<Name>Torch</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Resource Include="torchicon.ico" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
<PropertyGroup>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
</PostBuildEvent>
</PropertyGroup>
</Project>

170
Torch.Client/TorchClient.cs Normal file
View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows;
using Sandbox;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Platform;
using Sandbox.Game;
using SpaceEngineers.Game;
using VRage.Steam;
using Torch.API;
using VRage;
using VRage.FileSystem;
using VRage.GameServices;
using VRageRender;
using VRageRender.ExternalApp;
namespace Torch.Client
{
public class TorchClient : TorchBase, ITorchClient
{
private MyCommonProgramStartup _startup;
private IMyRender _renderer;
private const uint APP_ID = 244850;
public TorchClient()
{
Config = new TorchClientConfig();
}
public override void Init()
{
Directory.SetCurrentDirectory(Program.SpaceEngineersInstallAlias);
MyFileSystem.ExePath = Path.Combine(Program.SpaceEngineersInstallAlias, Program.SpaceEngineersBinaries);
Log.Info("Initializing Torch Client");
base.Init();
SpaceEngineersGame.SetupBasicGameInfo();
_startup = new MyCommonProgramStartup(RunArgs);
if (_startup.PerformReporting())
throw new InvalidOperationException("Torch client won't launch when started in error reporting mode");
_startup.PerformAutoconnect();
if (!_startup.CheckSingleInstance())
throw new InvalidOperationException("Only one instance of Space Engineers can be running at a time.");
var appDataPath = _startup.GetAppDataPath();
MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath);
MyInitializer.InitCheckSum();
_startup.InitSplashScreen();
if (!_startup.Check64Bit())
throw new InvalidOperationException("Torch requires a 64bit operating system");
_startup.DetectSharpDxLeaksBeforeRun();
var steamService = new SteamService(Game.IsDedicated, APP_ID);
MyServiceManager.Instance.AddService<IMyGameService>(steamService);
_renderer = null;
SpaceEngineersGame.SetupPerGameSettings();
// I'm sorry, but it's what Keen does in SpaceEngineers.MyProgram
#pragma warning disable 612
SpaceEngineersGame.SetupRender();
#pragma warning restore 612
InitializeRender();
if (!_startup.CheckSteamRunning())
throw new InvalidOperationException("Space Engineers requires steam to be running");
if (!Game.IsDedicated)
MyFileSystem.InitUserSpecific(MyGameService.UserId.ToString());
}
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);
}
public override void Start()
{
using (var spaceEngineersGame = new SpaceEngineersGame(RunArgs))
{
Log.Info("Starting client");
OverrideMenus();
spaceEngineersGame.OnGameLoaded += SpaceEngineersGame_OnGameLoaded;
spaceEngineersGame.OnGameExit += Dispose;
spaceEngineersGame.Run(false, _startup.DisposeSplashScreen);
}
}
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;
});
}
private void SpaceEngineersGame_OnGameLoaded(object sender, EventArgs e)
{
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
}
public override void Dispose()
{
MyGameService.ShutDown();
_startup.DetectSharpDxLeaksAfterRun();
MyInitializer.InvokeAfterRun();
}
public override void Stop()
{
MySandboxGame.ExitThreadSafe();
}
private void InitializeRender()
{
try
{
if (Game.IsDedicated)
{
_renderer = new MyNullRender();
}
else
{
var graphicsRenderer = MySandboxGame.Config.GraphicsRenderer;
if (graphicsRenderer == MySandboxGame.DirectX11RendererKey)
{
_renderer = new MyDX11Render();
if (!_renderer.IsSupported)
{
MySandboxGame.Log.WriteLine("DirectX 11 renderer not supported. No renderer to revert back to.");
_renderer = null;
}
}
if (_renderer == null)
throw new MyRenderException("The current version of the game requires a Dx11 card. \\n For more information please see : http://blog.marekrosa.org/2016/02/space-engineers-news-full-source-code_26.html", MyRenderExceptionEnum.GpuNotSupported);
MySandboxGame.Config.GraphicsRenderer = graphicsRenderer;
}
MyRenderProxy.Initialize(_renderer);
MyRenderProxy.GetRenderProfiler().SetAutocommit(false);
MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Render Initialization Failed");
Environment.Exit(-1);
}
}
}
}

View File

@@ -0,0 +1,32 @@
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 bool Save(string path = null)
{
return true;
}
}
}

View File

@@ -0,0 +1,63 @@
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

@@ -0,0 +1,35 @@
#pragma warning disable 618
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Graphics;
using Sandbox.Graphics.GUI;
using Sandbox.Gui;
using SpaceEngineers.Game.GUI;
using VRage.Game;
using VRage.Utils;
using VRageMath;
namespace Torch.Client
{
public class TorchMainMenuScreen : MyGuiScreenMainMenu
{
/// <inheritdoc />
public override void RecreateControls(bool constructor)
{
base.RecreateControls(constructor);
var buttonSize = MyGuiControlButton.GetVisualStyle(MyGuiControlButtonStyleEnum.Default).NormalTexture.MinSizeGui;
Vector2 leftButtonPositionOrigin = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_BOTTOM) + new Vector2(buttonSize.X / 2f, 0f);
var btn = MakeButton(leftButtonPositionOrigin - 9 * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA, MyStringId.GetOrCompute("Torch"), TorchButtonClicked);
Controls.Add(btn);
}
private void TorchButtonClicked(MyGuiControlButton obj)
{
MyGuiSandbox.AddScreen(new TorchSettingsScreen());
}
}
}

View File

@@ -0,0 +1,38 @@
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

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
</packages>

BIN
Torch.Client/torchicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ProtoBuf;
using Sandbox.ModAPI;
namespace Torch.Mod.Messages
{
/// Dialogs are structured as follows
///
/// _____________________________________
/// | Title |
/// --------------------------------------
/// | Prefix Subtitle |
/// --------------------------------------
/// | ________________________________ |
/// | | Content | |
/// | --------------------------------- |
/// | ____________ |
/// | | ButtonText | |
/// | -------------- |
/// --------------------------------------
///
/// Button has a callback on click option,
/// but can't serialize that, so ¯\_(ツ)_/¯
[ProtoContract]
public class DialogMessage : MessageBase
{
[ProtoMember(201)]
public string Title;
[ProtoMember(202)]
public string Subtitle;
[ProtoMember(203)]
public string Prefix;
[ProtoMember(204)]
public string Content;
[ProtoMember(205)]
public string ButtonText;
public DialogMessage()
{ }
public DialogMessage(string title, string subtitle, string content)
{
Title = title;
Subtitle = subtitle;
Content = content;
Prefix = String.Empty;
}
public DialogMessage(string title = null, string prefix = null, string subtitle = null, string content = null, string buttonText = null)
{
Title = title;
Subtitle = subtitle;
Prefix = prefix ?? String.Empty;
Content = content;
ButtonText = buttonText;
}
public override void ProcessClient()
{
MyAPIGateway.Utilities.ShowMissionScreen(Title, Prefix, Subtitle, Content, null, ButtonText);
}
public override void ProcessServer()
{
throw new Exception();
}
}
}

View File

@@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Torch.Mod.Messages
{
/// <summary>
/// shim to store incoming message data
/// </summary>
internal class IncomingMessage : MessageBase
{
public IncomingMessage()
{
}
public override void ProcessClient()
{
throw new Exception();
}
public override void ProcessServer()
{
throw new Exception();
}
}
}

View File

@@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using ProtoBuf;
using Sandbox.ModAPI;
namespace Torch.Mod.Messages
{
[ProtoContract]
public class JoinServerMessage : MessageBase
{
[ProtoMember(201)]
public int Delay;
[ProtoMember(202)]
public string Address;
private JoinServerMessage()
{
}
public JoinServerMessage(string address)
{
Address = address;
}
public JoinServerMessage(string address, int delay)
{
Address = address;
Delay = delay;
}
public override void ProcessClient()
{
if (TorchModCore.Debug)
{
MyAPIGateway.Utilities.ShowMessage("Torch", $"Joining server {Address} with delay {Delay}");
}
if (Delay <= 0)
{
MyAPIGateway.Multiplayer.JoinServer(Address);
return;
}
MyAPIGateway.Parallel.StartBackground(() =>
{
MyAPIGateway.Parallel.Sleep(Delay);
MyAPIGateway.Multiplayer.JoinServer(Address);
});
}
public override void ProcessServer()
{
}
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ProtoBuf;
namespace Torch.Mod.Messages
{
#region Includes
[ProtoInclude(1, typeof(DialogMessage))]
[ProtoInclude(2, typeof(NotificationMessage))]
[ProtoInclude(3, typeof(VoxelResetMessage))]
[ProtoInclude(4, typeof(JoinServerMessage))]
#endregion
[ProtoContract]
public abstract class MessageBase
{
[ProtoMember(101)]
public ulong SenderId;
public abstract void ProcessClient();
public abstract void ProcessServer();
//members below not serialized, they're just metadata about the intended target(s) of this message
internal MessageTarget TargetType;
internal ulong Target;
internal ulong[] Ignore;
internal byte[] CompressedData;
}
public enum MessageTarget
{
/// <summary>
/// Send to Target
/// </summary>
Single,
/// <summary>
/// Send to Server
/// </summary>
Server,
/// <summary>
/// Send to all Clients (only valid from server)
/// </summary>
AllClients,
/// <summary>
/// Send to all except those steam ID listed in Ignore
/// </summary>
AllExcept,
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using ProtoBuf;
using Sandbox.ModAPI;
namespace Torch.Mod.Messages
{
[ProtoContract]
public class NotificationMessage : MessageBase
{
[ProtoMember(201)]
public string Message;
[ProtoMember(202)]
public string Font;
[ProtoMember(203)]
public int DisappearTimeMs;
public NotificationMessage()
{ }
public NotificationMessage(string message, int disappearTimeMs, string font)
{
Message = message;
DisappearTimeMs = disappearTimeMs;
Font = font;
}
public override void ProcessClient()
{
MyAPIGateway.Utilities.ShowNotification(Message, DisappearTimeMs, Font);
}
public override void ProcessServer()
{
throw new Exception();
}
}
}

View File

@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using ProtoBuf;
using Sandbox.ModAPI;
using VRage.ModAPI;
using VRage.Voxels;
namespace Torch.Mod.Messages
{
[ProtoContract]
public class VoxelResetMessage : MessageBase
{
[ProtoMember(201)]
public long[] EntityId;
public VoxelResetMessage()
{ }
public VoxelResetMessage(long[] entityId)
{
EntityId = entityId;
}
public override void ProcessClient()
{
//MyAPIGateway.Parallel.ForEach(EntityId, id =>
foreach (var id in EntityId)
{
IMyEntity e;
if (!MyAPIGateway.Entities.TryGetEntityById(id, out e))
continue;
var v = e as IMyVoxelBase;
v?.Storage.Reset(MyStorageDataTypeFlags.All);
}
}
public override void ProcessServer()
{
throw new Exception();
}
}
}

View File

@@ -1,208 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Sandbox.ModAPI;
using Torch.Mod.Messages;
using VRage;
using VRage.Collections;
using VRage.Game.ModAPI;
using VRage.Network;
using VRage.Utils;
using Task = ParallelTasks.Task;
namespace Torch.Mod
{
public static class ModCommunication
{
public const ushort NET_ID = 4352;
private static bool _closing = false;
private static BlockingCollection<MessageBase> _processing;
private static MyConcurrentPool<IncomingMessage> _messagePool;
private static List<IMyPlayer> _playerCache;
public static void Register()
{
MyLog.Default.WriteLineAndConsole("TORCH MOD: Registering mod communication.");
_processing = new BlockingCollection<MessageBase>(new ConcurrentQueue<MessageBase>());
_playerCache = new List<IMyPlayer>();
_messagePool = new MyConcurrentPool<IncomingMessage>(8);
MyAPIGateway.Multiplayer.RegisterMessageHandler(NET_ID, MessageHandler);
//background thread to handle de/compression and processing
_closing = false;
MyAPIGateway.Parallel.StartBackground(DoProcessing);
MyLog.Default.WriteLineAndConsole("TORCH MOD: Mod communication registered successfully.");
}
public static void Unregister()
{
MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication.");
MyAPIGateway.Multiplayer?.UnregisterMessageHandler(NET_ID, MessageHandler);
_processing?.CompleteAdding();
_closing = true;
//_task.Wait();
}
private static void MessageHandler(byte[] bytes)
{
var m = _messagePool.Get();
m.CompressedData = bytes;
#if TORCH
m.SenderId = MyEventContext.Current.Sender.Value;
#endif
_processing.Add(m);
}
public static void DoProcessing()
{
while (!_closing)
{
try
{
MessageBase m;
try
{
m = _processing.Take();
}
catch
{
continue;
}
MyLog.Default.WriteLineAndConsole($"Processing message: {m.GetType().Name}");
if (m is IncomingMessage) //process incoming messages
{
MessageBase i;
try
{
var o = MyCompression.Decompress(m.CompressedData);
m.CompressedData = null;
_messagePool.Return((IncomingMessage)m);
i = MyAPIGateway.Utilities.SerializeFromBinary<MessageBase>(o);
}
catch (Exception ex)
{
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Failed to deserialize message! {ex}");
continue;
}
if (TorchModCore.Debug)
MyAPIGateway.Utilities.ShowMessage("Torch", $"Received message of type {i.GetType().Name}");
if (MyAPIGateway.Multiplayer.IsServer)
i.ProcessServer();
else
i.ProcessClient();
}
else //process outgoing messages
{
if (TorchModCore.Debug)
MyAPIGateway.Utilities.ShowMessage("Torch", $"Sending message of type {m.GetType().Name}");
var b = MyAPIGateway.Utilities.SerializeToBinary(m);
m.CompressedData = MyCompression.Compress(b);
switch (m.TargetType)
{
case MessageTarget.Single:
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, m.Target);
break;
case MessageTarget.Server:
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, m.CompressedData);
break;
case MessageTarget.AllClients:
MyAPIGateway.Players.GetPlayers(_playerCache);
foreach (var p in _playerCache)
{
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
continue;
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
}
break;
case MessageTarget.AllExcept:
MyAPIGateway.Players.GetPlayers(_playerCache);
foreach (var p in _playerCache)
{
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || m.Ignore.Contains(p.SteamUserId))
continue;
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
}
break;
default:
throw new Exception();
}
_playerCache.Clear();
}
}
catch (Exception ex)
{
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Exception occurred in communication thread! {ex}");
}
}
MyLog.Default.WriteLineAndConsole("TORCH MOD: INFO: Communication thread shut down successfully! THIS IS NOT AN ERROR");
//exit signal received. Clean everything and GTFO
_processing?.Dispose();
_processing = null;
_messagePool?.Clean();
_messagePool = null;
_playerCache = null;
}
public static void SendMessageTo(MessageBase message, ulong target)
{
if (!MyAPIGateway.Multiplayer.IsServer)
throw new Exception("Only server can send targeted messages");
if (_closing)
return;
message.Target = target;
message.TargetType = MessageTarget.Single;
_processing.Add(message);
}
public static void SendMessageToClients(MessageBase message)
{
if (!MyAPIGateway.Multiplayer.IsServer)
throw new Exception("Only server can send targeted messages");
if (_closing)
return;
message.TargetType = MessageTarget.AllClients;
_processing.Add(message);
}
public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers)
{
if (!MyAPIGateway.Multiplayer.IsServer)
throw new Exception("Only server can send targeted messages");
if (_closing)
return;
message.TargetType = MessageTarget.AllExcept;
message.Ignore = ignoredUsers;
_processing.Add(message);
}
public static void SendMessageToServer(MessageBase message)
{
if (_closing)
return;
message.TargetType = MessageTarget.Server;
_processing.Add(message);
}
}
}

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Torch.Mod</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Messages\IncomingMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\JoinServerMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\VoxelResetMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ModCommunication.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TorchModCore.cs" />
</ItemGroup>
</Project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<Import Project="Torch.Mod.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.ModAPI;
using VRage.Game.Components;
namespace Torch.Mod
{
[MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]
public class TorchModCore : MySessionComponentBase
{
public const ulong MOD_ID = 2722000298;
private static bool _init;
public static bool Debug;
public override void UpdateAfterSimulation()
{
if (_init)
return;
_init = true;
ModCommunication.Register();
MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered;
}
private void Utilities_MessageEntered(string messageText, ref bool sendToOthers)
{
if (messageText == "@!debug")
{
Debug = !Debug;
MyAPIGateway.Utilities.ShowMessage("Torch", $"Debug: {Debug}");
sendToOthers = false;
}
}
protected override void UnloadData()
{
try
{
MyAPIGateway.Utilities.MessageEntered -= Utilities_MessageEntered;
ModCommunication.Unregister();
}
catch
{
//session unloading, don't care
}
}
}
}

View File

@@ -0,0 +1,17 @@
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,37 +1,93 @@
<Project Sdk="Microsoft.NET.Sdk">
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<TargetFramework>net6-windows</TargetFramework>
<LangVersion>10</LangVersion>
<ProjectGuid>{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Torch.Server.Tests</RootNamespace>
<AssemblyName>Torch.Server.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Server 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'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Release'">
<DocumentationFile>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\Torch.Server.Tests.xml</DocumentationFile>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Server.Tests.xml</DocumentationFile>
</PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup>
<ItemGroup>
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
<ProjectReference Include="..\Torch.Server\Torch.Server.csproj" />
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj" />
<ProjectReference Include="..\Torch\Torch.csproj" />
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TorchServerReflectionTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Server\Torch.Server.csproj">
<Project>{ca50886b-7b22-4cd8-93a0-c06f38d4f77d}</Project>
<Name>Torch.Server</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj">
<Project>{c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5}</Project>
<Name>Torch.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
<Name>Torch</Name>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
</Project>

View File

@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Torch.Tests;
using Torch.Utils;
using Xunit;
namespace Torch.Server.Tests
{
#warning Disabled reflection tests because of seemingly random failures
public class TorchServerReflectionTest
{
static TorchServerReflectionTest()
@@ -30,12 +28,8 @@ namespace Torch.Server.Tests
public static IEnumerable<object[]> Invokers => Manager().Invokers;
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
public static IEnumerable<object[]> Events => Manager().Events;
#region Binding
//[Theory]
[Theory]
[MemberData(nameof(Getters))]
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
{
@@ -46,7 +40,7 @@ namespace Torch.Server.Tests
Assert.NotNull(field.Field.GetValue(null));
}
//[Theory]
[Theory]
[MemberData(nameof(Setters))]
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
{
@@ -57,7 +51,7 @@ namespace Torch.Server.Tests
Assert.NotNull(field.Field.GetValue(null));
}
//[Theory]
[Theory]
[MemberData(nameof(Invokers))]
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
{
@@ -67,17 +61,6 @@ namespace Torch.Server.Tests
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,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Torch.Server.ViewModels;
using VRage.Game;
using Xunit;
using System.ComponentModel.DataAnnotations;
namespace Torch.Server.Tests
{
public class TorchServerSessionSettingsTest
{
public static PropertyInfo[] ViewModelProperties = typeof(SessionSettingsViewModel).GetProperties(BindingFlags.Public | BindingFlags.Instance);
public static IEnumerable<object[]> ModelFields = typeof(MyObjectBuilder_SessionSettings).GetFields(BindingFlags.Public | BindingFlags.Instance).Select(x => new object[] { x });
[Theory]
[MemberData(nameof(ModelFields))]
public void MissingPropertyTest(FieldInfo modelField)
{
// Ignore fields that aren't applicable to SE
if (modelField.GetCustomAttribute<GameRelationAttribute>()?.RelatedTo == Game.MedievalEngineers)
return;
if (string.IsNullOrEmpty(modelField.GetCustomAttribute<DisplayAttribute>()?.Name))
return;
var match = ViewModelProperties.FirstOrDefault(p => p.Name.Equals(modelField.Name, StringComparison.InvariantCultureIgnoreCase));
Assert.NotNull(match);
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
<package id="xunit" version="2.2.0" targetFramework="net461" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
</packages>

View File

@@ -1,66 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.Commands;
namespace Torch.Server.Commands
{
[Category("whitelist")]
public class WhitelistCommands : CommandModule
{
private TorchConfig Config => (TorchConfig)Context.Torch.Config;
[Command("on", "Enables the whitelist.")]
public void On()
{
if (!Config.EnableWhitelist)
{
Config.EnableWhitelist = true;
Context.Respond("Whitelist enabled.");
Config.Save();
}
else
Context.Respond("Whitelist is already enabled.");
}
[Command("off", "Disables the whitelist")]
public void Off()
{
if (Config.EnableWhitelist)
{
Config.EnableWhitelist = false;
Context.Respond("Whitelist disabled.");
Config.Save();
}
else
Context.Respond("Whitelist is already disabled.");
}
[Command("add", "Add a Steam ID to the whitelist.")]
public void Add(ulong steamId)
{
if (!Config.Whitelist.Contains(steamId))
{
Config.Whitelist.Add(steamId);
Context.Respond($"Added {steamId} to the whitelist.");
Config.Save();
}
else
Context.Respond($"{steamId} is already whitelisted.");
}
[Command("remove", "Remove a Steam ID from the whitelist.")]
public void Remove(ulong steamId)
{
if (Config.Whitelist.Remove(steamId))
{
Context.Respond($"Removed {steamId} from the whitelist.");
Config.Save();
}
else
Context.Respond($"{steamId} is not whitelisted.");
}
}
}

View File

@@ -1,3 +0,0 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EventInvokerNames" type="xs:string">
<xs:annotation>
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEquality" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressWarnings" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -5,77 +5,73 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Microsoft.Extensions.Configuration;
using NLog;
using NLog.Targets;
using Sandbox.Engine.Utils;
using Torch.Utils;
using VRage.FileSystem;
namespace Torch.Server
{
public class Initializer
{
internal static Initializer Instance { get; private set; }
private static readonly Logger Log = LogManager.GetLogger(nameof(Initializer));
private bool _init;
private const string STEAMCMD_DIR = "steamcmd";
private const string STEAMCMD_ZIP = "temp.zip";
private static readonly string STEAMCMD_EXE = "steamcmd.exe";
private const string STEAMCMD_ARGS = "+force_install_dir \"{0}\" +login anonymous +app_update 298740 +quit";
private TorchServer _server;
private static readonly string STEAMCMD_PATH = $"{STEAMCMD_DIR}\\steamcmd.exe";
private static readonly string RUNSCRIPT_PATH = $"{STEAMCMD_DIR}\\runscript.txt";
private const string RUNSCRIPT = @"force_install_dir ../
login anonymous
app_update 298740
quit";
internal Persistent<TorchConfig> ConfigPersistent { get; }
public TorchConfig Config => ConfigPersistent?.Data;
private TorchAssemblyResolver _resolver;
private TorchConfig _config;
private TorchServer _server;
private string _basePath;
public TorchConfig Config => _config;
public TorchServer Server => _server;
public Initializer(Persistent<TorchConfig> torchConfig)
public Initializer(string basePath)
{
Instance = this;
ConfigPersistent = torchConfig;
_basePath = basePath;
}
public bool Initialize(IConfiguration configuration)
public bool Initialize(string[] args)
{
if (_init)
return false;
#if DEBUG
//enables logging debug messages when built in debug mode. Amazing.
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "main");
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "console");
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "wpf");
LogManager.ReconfigExistingLoggers();
Log.Debug("Debug logging enabled.");
#endif
if (!configuration.GetValue("noupdate", false))
RunSteamCmd(configuration);
var processPid = configuration.GetValue<int>("waitForPid");
if (processPid != 0)
AppDomain.CurrentDomain.UnhandledException += HandleException;
if (!args.Contains("-noupdate"))
RunSteamCmd();
_resolver = new TorchAssemblyResolver(Path.Combine(_basePath, "DedicatedServer64"));
_config = InitConfig();
if (!_config.Parse(args))
return false;
if (!string.IsNullOrEmpty(_config.WaitForPID))
{
try
{
var waitProc = Process.GetProcessById(processPid);
var pid = int.Parse(_config.WaitForPID);
var waitProc = Process.GetProcessById(pid);
Log.Info("Continuing in 5 seconds.");
Log.Warn($"Waiting for process {processPid} to close");
while (!waitProc.HasExited)
Thread.Sleep(5000);
if (!waitProc.HasExited)
{
Console.Write(".");
Thread.Sleep(1000);
Log.Warn($"Killing old process {pid}.");
waitProc.Kill();
}
}
catch (Exception e)
catch
{
Log.Warn(e);
// ignored
}
}
@@ -85,90 +81,81 @@ namespace Torch.Server
public void Run()
{
_server = new TorchServer(Config, ApplicationContext.Current.InstanceDirectory.FullName, ApplicationContext.Current.InstanceName);
_server = new TorchServer(_config);
_server.Init();
if (ApplicationContext.Current.IsService || Config.NoGui)
if (_config.NoGui || _config.Autostart)
{
_server.Init();
_server.Start();
new Thread(_server.Start).Start();
}
if (!_config.NoGui)
{
new TorchUI(_server).ShowDialog();
}
_resolver?.Dispose();
}
private TorchConfig InitConfig()
{
var configName = "Torch.cfg";
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
if (File.Exists(configName))
{
Log.Info($"Loading config {configPath}");
return TorchConfig.LoadFrom(configPath);
}
else
{
#if !DEBUG
if (!Config.IndependentConsole)
{
Console.SetOut(TextWriter.Null);
NativeMethods.FreeConsole();
}
#endif
var gameThread = new Thread(() =>
{
_server.Init();
if (Config.Autostart || Config.TempAutostart)
{
Config.TempAutostart = false;
_server.Start();
}
});
gameThread.Start();
var ui = new TorchUI(_server);
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
ui.ShowDialog();
Log.Info($"Generating default config at {configPath}");
var config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
config.Save(configPath);
return config;
}
}
public static void RunSteamCmd(IConfiguration configuration)
private static void RunSteamCmd()
{
var log = LogManager.GetLogger("SteamCMD");
var path = configuration.GetValue<string>("steamCmdPath") ?? ApplicationContext.Current.TorchDirectory
.CreateSubdirectory(STEAMCMD_DIR).FullName;
if (!Directory.Exists(path))
if (!Directory.Exists(STEAMCMD_DIR))
{
Directory.CreateDirectory(path);
Directory.CreateDirectory(STEAMCMD_DIR);
}
var steamCmdExePath = Path.Combine(path, STEAMCMD_EXE);
if (!File.Exists(steamCmdExePath))
if (!File.Exists(RUNSCRIPT_PATH))
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
if (!File.Exists(STEAMCMD_PATH))
{
try
{
log.Info("Downloading SteamCMD.");
using (var client = new HttpClient())
using (var file = File.Create(STEAMCMD_ZIP))
using (var stream = client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result)
stream.CopyTo(file);
using (var client = new WebClient())
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
File.Delete(STEAMCMD_ZIP);
log.Info("SteamCMD downloaded successfully!");
}
catch (Exception e)
catch
{
log.Error(e, "Failed to download SteamCMD, unable to update the DS.");
log.Error("Failed to download SteamCMD, unable to update the DS.");
return;
}
}
log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(steamCmdExePath)
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
{
Arguments = string.Format(STEAMCMD_ARGS, configuration.GetValue("gamePath", "../")),
WorkingDirectory = path,
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
UseShellExecute = false,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.ASCII
};
var cmd = Process.Start(steamCmdProc);
// ReSharper disable once PossibleNullReferenceException
while (!cmd.HasExited)
{
@@ -176,5 +163,22 @@ namespace Torch.Server
Thread.Sleep(100);
}
}
private void HandleException(object sender, UnhandledExceptionEventArgs e)
{
var ex = (Exception)e.ExceptionObject;
Log.Fatal(ex);
Console.WriteLine("Exiting in 5 seconds.");
Thread.Sleep(5000);
if (_config.RestartOnCrash)
{
var exe = typeof(Program).Assembly.Location;
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
Process.Start(exe, _config.ToString());
}
//1627 = Function failed during execution.
Environment.Exit(1627);
}
}
}

View File

@@ -1,56 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Windows.Media;
using System.Windows.Threading;
using NLog;
using NLog.Targets;
using Torch.Server.ViewModels;
using Torch.Server.Views;
namespace Torch.Server
{
/// <summary>
/// NLog target that writes to a <see cref="LogViewerControl"/>.
/// </summary>
[Target("logViewer")]
public sealed class LogViewerTarget : TargetWithLayout
{
public IList<LogEntry> LogEntries { get; set; }
public SynchronizationContext TargetContext { get; set; }
private const int MAX_LINES = 1000;
/// <inheritdoc />
protected override void Write(LogEventInfo logEvent)
{
TargetContext?.Post(_sendOrPostCallback, logEvent);
}
private void WriteCallback(object state)
{
var logEvent = (LogEventInfo) state;
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()
{
[LogLevel.Trace] = new SolidColorBrush(Colors.DimGray),
[LogLevel.Debug] = new SolidColorBrush(Colors.DarkGray),
[LogLevel.Info] = new SolidColorBrush(Colors.White),
[LogLevel.Warn] = new SolidColorBrush(Colors.Magenta),
[LogLevel.Error] = new SolidColorBrush(Colors.Yellow),
[LogLevel.Fatal] = new SolidColorBrush(Colors.Red),
};
private readonly SendOrPostCallback _sendOrPostCallback;
public LogViewerTarget()
{
_sendOrPostCallback = WriteCallback;
}
}
}

View File

@@ -1,257 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using NLog;
using NLog.Fluent;
using Torch.API;
using Torch.Collections;
using Torch.Managers;
using Torch.Server.ViewModels.Entities;
using Torch.Utils;
namespace Torch.Server.Managers
{
/// <summary>
/// Manager that lets users bind random view models to entities in Torch's Entity Manager
/// </summary>
public class EntityControlManager : Manager
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
/// <summary>
/// Creates an entity control manager for the given instance of torch
/// </summary>
/// <param name="torchInstance">Torch instance</param>
internal EntityControlManager(ITorchBase torchInstance) : base(torchInstance)
{
}
private abstract class ModelFactory
{
private readonly ConditionalWeakTable<EntityViewModel, EntityControlViewModel> _models = new ConditionalWeakTable<EntityViewModel, EntityControlViewModel>();
public abstract Delegate Delegate { get; }
protected abstract EntityControlViewModel Create(EntityViewModel evm);
internal IEnumerable<EntityViewModel> Keys => _models.Select(b => b.Key);
internal EntityControlViewModel GetOrCreate(EntityViewModel evm)
{
return _models.GetValue(evm, Create);
}
internal bool TryGet(EntityViewModel evm, out EntityControlViewModel res)
{
return _models.TryGetValue(evm, out res);
}
}
private class ModelFactory<T> : ModelFactory where T : EntityViewModel
{
private readonly Func<T, EntityControlViewModel> _factory;
public override Delegate Delegate => _factory;
internal ModelFactory(Func<T, EntityControlViewModel> factory)
{
_factory = factory;
}
protected override EntityControlViewModel Create(EntityViewModel evm)
{
if (evm is T m)
{
var result = _factory(m);
_log.Trace($"Model factory {_factory.Method} created {result} for {evm}");
return result;
}
return null;
}
}
private readonly List<ModelFactory> _modelFactories = new List<ModelFactory>();
private readonly List<Delegate> _controlFactories = new List<Delegate>();
private readonly List<WeakReference<EntityViewModel>> _boundEntityViewModels = new List<WeakReference<EntityViewModel>>();
private readonly ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>> _boundViewModels = new ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>>();
/// <summary>
/// This factory will be used to create component models for matching entity models.
/// </summary>
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
/// <param name="modelFactory">Method to create component model from entity model.</param>
public void RegisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
where TEntityBaseModel : EntityViewModel
{
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
lock (this)
{
var factory = new ModelFactory<TEntityBaseModel>(modelFactory);
_modelFactories.Add(factory);
var i = 0;
while (i < _boundEntityViewModels.Count)
{
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
{
if (target is TEntityBaseModel tent)
UpdateBinding(target, components);
i++;
}
else
_boundEntityViewModels.RemoveAtFast(i);
}
}
}
/// <summary>
/// Unregisters a factory registered with <see cref="RegisterModelFactory{TEntityBaseModel}"/>
/// </summary>
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
/// <param name="modelFactory">Method to create component model from entity model.</param>
public void UnregisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
where TEntityBaseModel : EntityViewModel
{
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
lock (this)
{
for (var i = 0; i < _modelFactories.Count; i++)
{
if (_modelFactories[i].Delegate == (Delegate)modelFactory)
{
foreach (var entry in _modelFactories[i].Keys)
if (_modelFactories[i].TryGet(entry, out EntityControlViewModel ecvm) && ecvm != null
&& _boundViewModels.TryGetValue(entry, out var binding))
binding.Remove(ecvm);
_modelFactories.RemoveAt(i);
break;
}
}
}
}
/// <summary>
/// This factory will be used to create controls for matching view models.
/// </summary>
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
/// <param name="controlFactory">Method to create control from component model</param>
public void RegisterControlFactory<TEntityComponentModel>(
Func<TEntityComponentModel, Control> controlFactory)
where TEntityComponentModel : EntityControlViewModel
{
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
lock (this)
{
_controlFactories.Add(controlFactory);
RefreshControls<TEntityComponentModel>();
}
}
///<summary>
/// Unregisters a factory registered with <see cref="RegisterControlFactory{TEntityComponentModel}"/>
/// </summary>
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
/// <param name="controlFactory">Method to create control from component model</param>
public void UnregisterControlFactory<TEntityComponentModel>(
Func<TEntityComponentModel, Control> controlFactory)
where TEntityComponentModel : EntityControlViewModel
{
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
lock (this)
{
_controlFactories.Remove(controlFactory);
RefreshControls<TEntityComponentModel>();
}
}
private void RefreshControls<TEntityComponentModel>() where TEntityComponentModel : EntityControlViewModel
{
var i = 0;
while (i < _boundEntityViewModels.Count)
{
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
{
foreach (EntityControlViewModel component in components)
if (component is TEntityComponentModel)
component.InvalidateControl();
i++;
}
else
_boundEntityViewModels.RemoveAtFast(i);
}
}
/// <summary>
/// Gets the models bound to the given entity view model.
/// </summary>
/// <param name="entity">view model to query</param>
/// <returns></returns>
public MtObservableList<EntityControlViewModel> BoundModels(EntityViewModel entity)
{
return _boundViewModels.GetValue(entity, CreateFreshBinding);
}
/// <summary>
/// Gets a control for the given view model type.
/// </summary>
/// <param name="model">model to create a control for</param>
/// <returns>control, or null if none</returns>
public Control CreateControl(EntityControlViewModel model)
{
lock (this)
foreach (Delegate factory in _controlFactories)
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
factory.DynamicInvoke(model) is Control result)
{
_log.Trace($"Control factory {factory.Method} created {result}");
return result;
}
_log.Warn($"No control created for {model}");
return null;
}
private MtObservableList<EntityControlViewModel> CreateFreshBinding(EntityViewModel key)
{
var binding = new MtObservableList<EntityControlViewModel>();
lock (this)
{
_boundEntityViewModels.Add(new WeakReference<EntityViewModel>(key));
}
binding.PropertyChanged += (x, args) =>
{
if (nameof(binding.IsObserved).Equals(args.PropertyName))
UpdateBinding(key, binding);
};
return binding;
}
private void UpdateBinding(EntityViewModel key, MtObservableList<EntityControlViewModel> binding)
{
if (!binding.IsObserved)
return;
lock (this)
{
foreach (ModelFactory factory in _modelFactories)
{
var result = factory.GetOrCreate(key);
if (result != null && !binding.Contains(result))
binding.Add(result);
}
}
}
}
}

View File

@@ -1,192 +1,96 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Havok;
using NLog;
using Sandbox;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Sandbox.Game;
using Sandbox.Game.Gui;
using Torch.API;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Managers;
using Torch.Mod;
using Torch.Server.ViewModels;
using Torch.Utils;
using VRage;
using VRage.FileSystem;
using VRage.Game;
using VRage.Game.ObjectBuilder;
using VRage.ObjectBuilders;
using VRage.Plugins;
namespace Torch.Server.Managers
{
public class InstanceManager : Manager, IInstanceManager
public class InstanceManager : Manager
{
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
public event Action<ConfigDedicatedViewModel> InstanceLoaded;
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
[Dependency]
private FilesystemManager _filesystemManager;
private new ITorchServer Torch { get; }
public InstanceManager(ITorchServer torchInstance) : base(torchInstance)
public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
{
Torch = torchInstance;
}
public IWorld SelectedWorld => DedicatedConfig.SelectedWorld;
/// <inheritdoc />
public override void Attach()
{
MyFileSystem.ExePath = Path.Combine(_filesystemManager.TorchDirectory, "DedicatedServer64");
MyFileSystem.Init("Content", Torch.Config.InstancePath);
//Initializes saves path. Why this isn't in Init() we may never know.
MyFileSystem.InitUserSpecific(null);
}
public void LoadInstance(string path, bool validate = true)
{
Log.Info($"Loading instance {path}");
if (validate)
ValidateInstance(path);
MyFileSystem.Reset();
MyFileSystem.ExePath = Path.Combine(_filesystemManager.TorchDirectory, "DedicatedServer64");
MyFileSystem.Init("Content", path);
//Initializes saves path. Why this isn't in Init() we may never know.
MyFileSystem.InitUserSpecific(null);
// why?....
// var configPath = Path.Combine(path, CONFIG_NAME);
// if (!File.Exists(configPath))
// {
// Log.Error($"Failed to load dedicated config at {path}");
// return;
// }
// var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
// config.Load(configPath);
DedicatedConfig = new ConfigDedicatedViewModel((MyConfigDedicated<MyObjectBuilder_SessionSettings>) MySandboxGame.ConfigDedicated);
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.InstancePath, "Saves"));
foreach (var f in worldFolders)
var configPath = Path.Combine(path, CONFIG_NAME);
if (!File.Exists(configPath))
{
try
{
if (!string.IsNullOrEmpty(f) && File.Exists(Path.Combine(f, "Sandbox.sbc")))
{
var worldViewModel = new WorldViewModel(f, DedicatedConfig.LoadWorld == f);
DedicatedConfig.Worlds.Add(worldViewModel);
}
}
catch (Exception ex)
{
Log.Error("Failed to load world at path: " + f);
continue;
}
Log.Error($"Failed to load dedicated config at {path}");
return;
}
if (DedicatedConfig.Worlds.Count == 0)
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
config.Load(configPath);
DedicatedConfig = new ConfigDedicatedViewModel(config);
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves"));
foreach (var f in worldFolders)
DedicatedConfig.WorldPaths.Add(f);
if (DedicatedConfig.WorldPaths.Count == 0)
{
Log.Warn($"No worlds found in the current instance {path}.");
return;
}
SelectWorld(DedicatedConfig.LoadWorld ?? DedicatedConfig.Worlds.First().WorldPath, false);
ImportWorldConfig();
InstanceLoaded?.Invoke(DedicatedConfig);
}
public void SelectCreatedWorld(string worldPath)
{
WorldViewModel worldViewModel;
try
/*
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
{
worldViewModel = new(worldPath);
DedicatedConfig.Worlds.Add(worldViewModel);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to load world at path: " + worldPath);
return;
}
SelectWorld(worldViewModel, false);
Log.Warn("No world specified, importing first available world.");
SelectWorld(DedicatedConfig.WorldPaths[0], false);
}*/
}
public void SelectWorld(string worldPath, bool modsOnly = true)
{
DedicatedConfig.LoadWorld = worldPath;
var worldInfo = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == worldPath);
try
{
if (worldInfo?.Checkpoint == null)
{
worldInfo = new WorldViewModel(worldPath);
DedicatedConfig.Worlds.Add(worldInfo);
}
}
catch (Exception ex)
{
Log.Error("Failed to load world at path: " + worldPath);
DedicatedConfig.LoadWorld = null;
return;
}
DedicatedConfig.SelectedWorld = worldInfo;
if (DedicatedConfig.SelectedWorld?.Checkpoint != null)
{
DedicatedConfig.Mods.Clear();
//remove the Torch mod to avoid running multiple copies of it
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
DedicatedConfig.Mods.Add(new ModItemInfo(m));
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
}
ImportWorldConfig(modsOnly);
}
public void SelectWorld(WorldViewModel world, bool modsOnly = true)
{
DedicatedConfig.LoadWorld = world.WorldPath;
DedicatedConfig.SelectedWorld = world;
if (DedicatedConfig.SelectedWorld?.Checkpoint != null)
{
DedicatedConfig.Mods.Clear();
//remove the Torch mod to avoid running multiple copies of it
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
DedicatedConfig.Mods.Add(new ModItemInfo(m));
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
}
}
public void ImportSelectedWorldConfig()
{
ImportWorldConfig(DedicatedConfig.SelectedWorld, false);
}
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
{
var mods = new MtObservableList<ModItemInfo>();
foreach (var mod in world.WorldConfiguration.Mods)
mods.Add(new ModItemInfo(mod));
DedicatedConfig.Mods = mods;
Log.Debug("Loaded mod list from world");
if (!modsOnly)
DedicatedConfig.SessionSettings = world.WorldConfiguration.Settings;
}
private void ImportWorldConfig(bool modsOnly = true)
{
@@ -203,14 +107,15 @@ namespace Torch.Server.Managers
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null)
{
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {Torch.Config.InstancePath})");
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
return;
}
var mods = new MtObservableList<ModItemInfo>();
var sb = new StringBuilder();
foreach (var mod in checkpoint.Mods)
mods.Add(new ModItemInfo(mod));
DedicatedConfig.Mods = mods;
sb.AppendLine(mod.PublishedFileId.ToString());
DedicatedConfig.Mods = sb.ToString();
Log.Debug("Loaded mod list from world");
@@ -226,40 +131,28 @@ namespace Torch.Server.Managers
public void SaveConfig()
{
if (!((TorchServer)Torch).HasRun)
{
DedicatedConfig.Save(Path.Combine(Torch.InstancePath, CONFIG_NAME));
Log.Info("Saved dedicated config.");
}
DedicatedConfig.Save();
Log.Info("Saved dedicated config.");
try
{
var world = DedicatedConfig.SelectedWorld;
world.Checkpoint.SessionName = string.IsNullOrEmpty(world.Checkpoint.SessionName)
? Path.GetDirectoryName(DedicatedConfig.LoadWorld)
: world.Checkpoint.SessionName;
world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings;
world.WorldConfiguration.Mods.Clear();
foreach (var mod in DedicatedConfig.Mods)
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null)
{
var savedMod = ModItemUtils.Create(mod.PublishedFileId);
savedMod.IsDependency = mod.IsDependency;
savedMod.Name = mod.Name;
savedMod.FriendlyName = mod.FriendlyName;
world.WorldConfiguration.Mods.Add(savedMod);
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
return;
}
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
world.SaveSandbox();
checkpoint.Settings = DedicatedConfig.SessionSettings;
checkpoint.Mods.Clear();
foreach (var modId in DedicatedConfig.Model.Mods)
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
MyLocalCache.SaveCheckpoint(checkpoint, DedicatedConfig.LoadWorld);
Log.Info("Saved world config.");
}
catch (Exception e)
{
Log.Error("Failed to write sandbox config");
Log.Error("Failed to write sandbox config, changes will not appear on server");
Log.Error(e);
}
}
@@ -270,7 +163,7 @@ namespace Torch.Server.Managers
private void ValidateInstance(string path)
{
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);
if (File.Exists(configPath))
return;
@@ -279,82 +172,4 @@ namespace Torch.Server.Managers
config.Save(configPath);
}
}
public class WorldViewModel : ViewModel, IWorld
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string WorldPath { get; }
public MyObjectBuilder_SessionSettings KeenSessionSettings => WorldConfiguration.Settings;
public MyObjectBuilder_Checkpoint KeenCheckpoint => Checkpoint;
public long WorldSizeKB { get; }
private string _checkpointPath;
private string _worldConfigPath;
private CheckpointViewModel _checkpoint;
public CheckpointViewModel Checkpoint
{
get
{
if (_checkpoint is null) LoadSandbox();
return _checkpoint;
}
private set => _checkpoint = value;
}
public WorldConfigurationViewModel WorldConfiguration { get; private set; }
public WorldViewModel(string worldPath, bool loadFiles = true)
{
try
{
WorldPath = worldPath;
WorldSizeKB = new DirectoryInfo(worldPath).GetFiles().Sum(x => x.Length) / 1024;
_checkpointPath = Path.Combine(WorldPath, "Sandbox.sbc");
_worldConfigPath = Path.Combine(WorldPath, "Sandbox_config.sbc");
if (loadFiles)
LoadSandbox();
}
catch (ArgumentException ex)
{
Log.Error($"World view model failed to load the path: {worldPath} Please ensure this is a valid path.");
throw; //rethrow to be handled further up the stack
}
}
public void SaveSandbox()
{
using (var f = File.Open(_checkpointPath, FileMode.Create))
MyObjectBuilderSerializer.SerializeXML(f, Checkpoint);
using (var f = File.Open(_worldConfigPath, FileMode.Create))
MyObjectBuilderSerializer.SerializeXML(f, WorldConfiguration);
}
public void LoadSandbox()
{
if (!MyObjectBuilderSerializer.DeserializeXML(_checkpointPath, out MyObjectBuilder_Checkpoint checkpoint))
throw new SerializationException("Error reading checkpoint, see keen log for details");
Checkpoint = new CheckpointViewModel(checkpoint);
// migrate old saves
if (File.Exists(_worldConfigPath))
{
if (!MyObjectBuilderSerializer.DeserializeXML(_worldConfigPath, out MyObjectBuilder_WorldConfiguration worldConfig))
throw new SerializationException("Error reading settings, see keen log for details");
WorldConfiguration = new WorldConfigurationViewModel(worldConfig);
}
else
{
WorldConfiguration = new WorldConfigurationViewModel(new MyObjectBuilder_WorldConfiguration
{
Mods = checkpoint.Mods,
Settings = checkpoint.Settings
});
checkpoint.Mods = null;
checkpoint.Settings = null;
}
}
}
}

View File

@@ -1,372 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using NLog;
using NLog.Fluent;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking;
using Sandbox.Game.Gui;
using Sandbox.Game.World;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Utils;
using Torch.ViewModels;
using VRage.Game;
using VRage.Game.ModAPI;
using VRage.GameServices;
using VRage.Network;
using VRage.Steam;
namespace Torch.Server.Managers
{
public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerManagerServer
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
#pragma warning disable 649
[ReflectedGetter(Name = "m_members")]
private static Func<MyDedicatedServerBase, List<ulong>> _members;
[ReflectedGetter(Name = "m_waitingForGroup")]
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
#pragma warning restore 649
/// <inheritdoc />
public IReadOnlyList<ulong> BannedPlayers => MySandboxGame.ConfigDedicated.Banned;
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
[Dependency]
private InstanceManager _instanceManager;
/// <inheritdoc />
public MultiplayerManagerDedicated(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);
MyGuiScreenPlayers.PromoteImplementation(steamId, true);
});
}
/// <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);
MyGuiScreenPlayers.PromoteImplementation(steamId, false);
});
}
/// <inheritdoc />
public MyPromoteLevel GetUserPromoteLevel(ulong steamId)
{
return MySession.Static.GetUserPromoteLevel(steamId);
}
internal void RaiseClientBanned(ulong user, bool banned)
{
PlayerBanned?.Invoke(user, banned);
Torch.Invoke(() =>
{
if(_gameOwnerIds.TryGetValue(user, out ulong owner))
MyMultiplayer.Static.BanClient(owner, banned);
});
}
internal void RaiseClientKicked(ulong user)
{
PlayerKicked?.Invoke(user);
}
/// <inheritdoc />
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) ||
MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
public bool IsProfiling(ulong steamId) => _Profiling.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
/// <inheritdoc />
public event Action<ulong> PlayerKicked;
/// <inheritdoc />
public event Action<ulong, bool> PlayerBanned;
/// <inheritdoc />
public event Action<ulong, MyPromoteLevel> PlayerPromoted;
internal void RaisePromoteChanged(ulong steamId, MyPromoteLevel level)
{
PlayerPromoted?.Invoke(steamId, level);
}
/// <inheritdoc/>
public override void Attach()
{
base.Attach();
if (Torch.Config.UgcServiceType == UGCServiceType.Steam)
{
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
}
else
{
_gameServerValidateAuthTicketReplacer = _eosServerValidateAuthTicketFactory.Invoke();
_gameServerUserGroupStatusReplacer = _eosServerUserGroupStatusFactory.Invoke();
}
_gameServerValidateAuthTicketReplacer.Replace(
new Action<ulong, JoinResult, ulong, string>(ValidateAuthTicketResponse), MyGameService.GameServer);
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse),
MyGameService.GameServer);
_log.Info("Inserted authentication intercept");
}
/// <inheritdoc/>
public override void Detach()
{
if (_gameServerValidateAuthTicketReplacer != null && _gameServerValidateAuthTicketReplacer.Replaced)
_gameServerValidateAuthTicketReplacer.Restore(MyGameService.GameServer);
if (_gameServerUserGroupStatusReplacer != null && _gameServerUserGroupStatusReplacer.Replaced)
_gameServerUserGroupStatusReplacer.Restore(MyGameService.GameServer);
_log.Info("Removed authentication intercept");
base.Detach();
}
#pragma warning disable 649
[ReflectedEventReplace("VRage.Steam.MySteamGameServer, VRage.Steam", "ValidateAuthTicketResponse",
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
[ReflectedEventReplace("VRage.Steam.MySteamGameServer, VRage.Steam", "UserGroupStatusResponse",
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
[ReflectedEventReplace("VRage.EOS.MyEOSGameServer, VRage.EOS", "ValidateAuthTicketResponse",
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
private static Func<ReflectedEventReplacer> _eosServerValidateAuthTicketFactory;
[ReflectedEventReplace("VRage.EOS.MyEOSGameServer, VRage.EOS", "UserGroupStatusResponse",
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
private static Func<ReflectedEventReplacer> _eosServerUserGroupStatusFactory;
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
#pragma warning restore 649
#region CustomAuth
#pragma warning disable 649
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
private static Func<ulong, string> _convertSteamIDFrom64;
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
[ReflectedMethod(Name = "ClientIsProfiling")]
private static Func<MyDedicatedServerBase, ulong, bool> _Profiling;
[ReflectedMethod(Name = "UserAccepted")]
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
[ReflectedMethod(Name = "UserRejected")]
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
[ReflectedMethod(Name = "IsClientBanned")]
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
[ReflectedMethod(Name = "IsClientKicked")]
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
[ReflectedMethod(Name = "RaiseClientKicked")]
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
#pragma warning restore 649
private const int _waitListSize = 32;
private readonly List<WaitingForGroup> _waitingForGroupLocal = new List<WaitingForGroup>(_waitListSize);
private struct WaitingForGroup
{
public readonly ulong SteamId;
public readonly JoinResult Response;
public readonly ulong SteamOwner;
public WaitingForGroup(ulong id, JoinResult response, ulong owner)
{
SteamId = id;
Response = response;
SteamOwner = owner;
}
}
//Largely copied from SE
private void ValidateAuthTicketResponse(ulong steamId, JoinResult response, ulong steamOwner, string serviceName)
{
var state = new MyP2PSessionState();
Sandbox.Engine.Networking.MyGameService.Peer2Peer.GetSessionState(steamId, ref state);
var ip = new IPAddress(BitConverter.GetBytes(state.RemoteIP).Reverse().ToArray());
Torch.CurrentSession.KeenSession.PromotedUsers.TryGetValue(steamId, out MyPromoteLevel promoteLevel);
_log.Debug($"ValidateAuthTicketResponse(user={steamId}, response={response}, owner={steamOwner}, permissions={promoteLevel})");
_log.Info($"Connection attempt by {steamId} from {ip}");
if (IsProfiling(steamId))
{
_log.Warn($"Rejecting user {steamId} for using Profiler/ModSDK!");
UserRejected(steamId, JoinResult.ProfilingNotAllowed);
}
else if (Torch.CurrentSession.KeenSession.OnlineMode == MyOnlineModeEnum.OFFLINE &&
promoteLevel < MyPromoteLevel.Admin)
{
_log.Warn($"Rejecting user {steamId}, world is set to offline and user is not admin.");
UserRejected(steamId, JoinResult.TicketCanceled);
}
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
RunEvent(new ValidateAuthTicketEvent(steamId, steamOwner, response, 0, true, false));
else if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
UserRejected(steamId, JoinResult.GroupIdInvalid);
else if (MyGameService.GameServer.RequestGroupStatus(steamId, MySandboxGame.ConfigDedicated.GroupID))
lock (_waitingForGroupLocal)
{
if (_waitingForGroupLocal.Count >= _waitListSize)
_waitingForGroupLocal.RemoveAt(0);
_waitingForGroupLocal.Add(new WaitingForGroup(steamId, response, steamOwner));
}
else
UserRejected(steamId, JoinResult.SteamServersOffline);
}
private void RunEvent(ValidateAuthTicketEvent info)
{
JoinResult internalAuth;
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
internalAuth = JoinResult.BannedByAdmins;
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
internalAuth = JoinResult.KickedRecently;
else if (info.SteamResponse == JoinResult.OK)
{
var config = (TorchConfig) Torch.Config;
if (config.EnableWhitelist && !config.Whitelist.Contains(info.SteamID))
{
_log.Warn($"Rejecting user {info.SteamID} because they are not whitelisted in Torch.cfg.");
internalAuth = JoinResult.NotInGroup;
}
else if (MySandboxGame.ConfigDedicated.Reserved.Contains(info.SteamID))
internalAuth = JoinResult.OK;
//Admins can bypass member limit
else if (MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
internalAuth = JoinResult.OK;
//Server counts as a client, so subtract 1 from MemberCount
else if (MyMultiplayer.Static.MemberLimit > 0 &&
MyMultiplayer.Static.MemberCount - 1 >= MyMultiplayer.Static.MemberLimit)
internalAuth = JoinResult.ServerFull;
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
internalAuth = JoinResult.OK;
else
{
if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
internalAuth = JoinResult.OK;
else
internalAuth = JoinResult.NotInGroup;
}
}
else
internalAuth = info.SteamResponse;
info.FutureVerdict = Task.FromResult(internalAuth);
MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
info.FutureVerdict.ContinueWith((task) =>
{
JoinResult verdict;
if (task.IsFaulted)
{
_log.Error(task.Exception, $"Future validation verdict faulted");
verdict = JoinResult.TicketCanceled;
}
else if (Players.ContainsKey(info.SteamID))
{
_log.Warn($"Player {info.SteamID} has already joined!");
verdict = JoinResult.AlreadyJoined;
}
else
verdict = task.Result;
Torch.Invoke(() => { CommitVerdict(info.SteamID, verdict); });
});
}
private void CommitVerdict(ulong steamId, JoinResult verdict)
{
if (verdict == JoinResult.OK)
UserAccepted(steamId);
else
UserRejected(steamId, verdict);
}
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
{
lock (_waitingForGroupLocal)
for (var j = 0; j < _waitingForGroupLocal.Count; j++)
{
var wait = _waitingForGroupLocal[j];
if (wait.SteamId == userId)
{
RunEvent(new ValidateAuthTicketEvent(wait.SteamId, wait.SteamOwner, wait.Response, groupId,
member, officer));
_waitingForGroupLocal.RemoveAt(j);
break;
}
}
}
private void UserRejected(ulong steamId, JoinResult reason)
{
_userRejected.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId, reason);
}
private void UserAccepted(ulong steamId)
{
_userAcceptedImpl.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId);
base.RaiseClientJoined(steamId);
}
#endregion
}
}

View File

@@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox;
using Torch.API.Event;
using Torch.Event;
using VRage.Network;
namespace Torch.Server.Managers
{
[EventShim]
internal static class MultiplayerManagerDedicatedEventShim
{
private static readonly EventList<ValidateAuthTicketEvent> _eventValidateAuthTicket =
new EventList<ValidateAuthTicketEvent>();
internal static void RaiseValidateAuthTicket(ref ValidateAuthTicketEvent info)
{
_eventValidateAuthTicket?.RaiseEvent(ref info);
}
}
/// <summary>
/// Event that occurs when a player tries to connect to a dedicated server.
/// Use these values to choose a <see cref="ValidateAuthTicketEvent.FutureVerdict"/>,
/// or leave it unset to allow the default logic to handle the request.
/// </summary>
public struct ValidateAuthTicketEvent : IEvent
{
/// <summary>
/// SteamID of the player
/// </summary>
public readonly ulong SteamID;
/// <summary>
/// SteamID of the game owner
/// </summary>
public readonly ulong SteamOwner;
/// <summary>
/// The response from steam
/// </summary>
public readonly JoinResult SteamResponse;
/// <summary>
/// ID of the queried group, or <c>0</c> if no group.
/// </summary>
public readonly ulong Group;
/// <summary>
/// Is this person a member of <see cref="Group"/>. If no group this is true.
/// </summary>
public readonly bool Member;
/// <summary>
/// Is this person an officer of <see cref="Group"/>. If no group this is false.
/// </summary>
public readonly bool Officer;
/// <summary>
/// A future verdict on this authorization request. If null, let the default logic choose. If not async use <see cref="Task.FromResult{TResult}(TResult)"/>
/// </summary>
public Task<JoinResult> FutureVerdict;
internal ValidateAuthTicketEvent(ulong steamId, ulong steamOwner, JoinResult steamResponse,
ulong serverGroup, bool member, bool officer)
{
SteamID = steamId;
SteamOwner = steamOwner;
SteamResponse = steamResponse;
Group = serverGroup;
Member = member;
Officer = officer;
FutureVerdict = null;
}
/// <inheritdoc/>
public bool Cancelled => FutureVerdict != null;
}
}

View File

@@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox.Engine.Multiplayer;
using Torch.Managers.PatchManager;
using Torch.API.Managers;
namespace Torch.Server.Managers
{
[PatchShim]
internal static class MultiplayerManagerDedicatedPatchShim
{
public static void Patch(PatchContext ctx)
{
ctx.GetPattern(typeof(MyDedicatedServerBase).GetMethod(nameof(MyDedicatedServerBase.BanClient))).Prefixes.Add(typeof(MultiplayerManagerDedicatedPatchShim).GetMethod(nameof(BanPrefix)));
ctx.GetPattern(typeof(MyDedicatedServerBase).GetMethod(nameof(MyDedicatedServerBase.KickClient))).Prefixes.Add(typeof(MultiplayerManagerDedicatedPatchShim).GetMethod(nameof(KickPrefix)));
}
public static void BanPrefix(ulong userId, bool banned)
{
TorchBase.Instance.CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>().RaiseClientBanned(userId, banned);
}
public static void KickPrefix(ulong userId)
{
TorchBase.Instance.CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>().RaiseClientKicked(userId);
}
}
}

View File

@@ -1,40 +0,0 @@
using NLog;
using Sandbox;
using Torch.API;
using Torch.Managers;
using VRage.Dedicated.RemoteAPI;
namespace Torch.Server.Managers
{
public class RemoteAPIManager : Manager
{
/// <inheritdoc />
public RemoteAPIManager(ITorchBase torchInstance) : base(torchInstance)
{
}
/// <inheritdoc />
public override void Attach()
{
Torch.GameStateChanged += TorchOnGameStateChanged;
base.Attach();
}
/// <inheritdoc />
public override void Detach()
{
Torch.GameStateChanged -= TorchOnGameStateChanged;
base.Detach();
}
private void TorchOnGameStateChanged(MySandboxGame game, TorchGameState newstate)
{
if (newstate == TorchGameState.Loading && MySandboxGame.ConfigDedicated.RemoteApiEnabled && !string.IsNullOrEmpty(MySandboxGame.ConfigDedicated.RemoteSecurityKey))
{
var myRemoteServer = new MyRemoteServer(MySandboxGame.ConfigDedicated.RemoteApiPort, MySandboxGame.ConfigDedicated.RemoteSecurityKey);
LogManager.GetCurrentClassLogger().Info($"Remote API started on port {myRemoteServer.Port}");
}
}
}
}

View File

@@ -1,49 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Torch.Server
{
public class MultiTextWriter : TextWriter
{
private IEnumerable<TextWriter> writers;
public MultiTextWriter(IEnumerable<TextWriter> writers)
{
this.writers = writers.ToList();
}
public MultiTextWriter(params TextWriter[] writers)
{
this.writers = writers;
}
public override void Write(char value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Write(string value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Flush()
{
foreach (var writer in writers)
writer.Flush();
}
public override void Close()
{
foreach (var writer in writers)
writer.Close();
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
}

View File

@@ -1,46 +0,0 @@
using System.Reflection;
using NLog;
using Sandbox.Engine.Networking;
using Torch.API.Managers;
using Torch.Managers.PatchManager;
using Torch.Server;
using Torch.Server.Managers;
using Torch.Utils;
using VRage.Game;
namespace Torch.Patches;
[PatchShim]
public static class CheckpointLoadPatch
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
[ReflectedMethodInfo(typeof(MyLocalCache), "LoadCheckpoint")]
private static MethodInfo LoadCheckpointMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(LoadCheckpointMethod).AddPrefix();
}
private static bool Prefix(ref MyObjectBuilder_Checkpoint __result)
{
#pragma warning disable CS0618
if (!((TorchServer)TorchBase.Instance).HasRun)
return true;
var world = TorchBase.Instance.Managers.GetManager<InstanceManager>().DedicatedConfig.SelectedWorld;
#pragma warning restore CS0618
if (world is null)
{
Log.Error("Selected world is null");
return false;
}
world.KeenCheckpoint.Settings = world.WorldConfiguration.Settings;
world.KeenCheckpoint.Mods = world.WorldConfiguration.Mods;
__result = world.Checkpoint;
return false;
}
}

View File

@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox.Game.World;
using Torch.Managers.PatchManager;
using VRage.Game.ModAPI;
using Torch.API.Managers;
using Torch.Server.Managers;
namespace Torch.Patches
{
[PatchShim]
internal static class PromotePatch
{
private static Logger _log = LogManager.GetCurrentClassLogger();
private static IMultiplayerManagerServer _backing;
private static IMultiplayerManagerServer ServerManager => _backing ?? (_backing = TorchBase.Instance?.CurrentSession?.Managers.GetManager<IMultiplayerManagerServer>());
public static void Patch(PatchContext ctx)
{
_log.Info("patching promote");
ctx.GetPattern(typeof(MySession).GetMethod("OnPromoteLevelSet", BindingFlags.NonPublic | BindingFlags.Static)).Prefixes.Add(typeof(PromotePatch).GetMethod(nameof(PromotePrefix)));
}
public static void PromotePrefix(ulong steamId, MyPromoteLevel level)
{
if (ServerManager is MultiplayerManagerDedicated d)
d.RaisePromoteChanged(steamId, level);
else
throw new NotSupportedException();
}
}
}

View File

@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using NLog;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.World;
using Torch.API.Managers;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Server.Managers;
using VRage.Game.ModAPI;
namespace Torch.Patches
{
[PatchShim]
public static class ServerResponsePatch
{
private static Logger _log = LogManager.GetCurrentClassLogger();
public static void Patch(PatchContext ctx)
{
var transpiler = typeof(ServerResponsePatch).GetMethod(nameof(Transpile), BindingFlags.Public | BindingFlags.Static);
ctx.GetPattern(typeof(MyDedicatedServerBase).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Instance))
.Transpilers.Add(transpiler);
_log.Info("Patching Steam response polling");
}
public static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> instructions)
{
// Reduce response timeout from 100 seconds to 5 seconds.
foreach (var instruction in instructions)
{
if (instruction.OpCode == OpCodes.Ldc_I4 && instruction.Operand is MsilOperandInline.MsilOperandInt32 inlineI32 && inlineI32.Value == 1000)
{
_log.Info("Patching Steam response timeout to 5 seconds");
inlineI32.Value = 50;
}
yield return instruction;
}
}
}
}

View File

@@ -1,35 +0,0 @@
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,52 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using NLog;
using Sandbox;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
namespace Torch.Patches
{
/// <summary>
/// Patches MySandboxGame.InitQuickLaunch to rethrow exceptions caught during session load.
/// </summary>
[PatchShim]
public static class WorldLoadExceptionPatch
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
[ReflectedMethodInfo(typeof(MySandboxGame), "InitQuickLaunch")]
private static MethodInfo _quickLaunchMethod = null!;
public static void Patch(PatchContext ctx)
{
ctx.GetPattern(_quickLaunchMethod).AddTranspiler(nameof(Transpile));
}
private static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> method)
{
var msil = method.ToList();
for (var i = 0; i < msil.Count; i++)
{
var instruction = msil[i];
if (instruction.IsLocalStore() && instruction.Operand is MsilOperandInline.MsilOperandLocal {Value.Index: 19} operand)
{
msil.InsertRange(i + 1, new []
{
operand.Instruction.CopyWith(OpCodes.Ldloc_S),
new MsilInstruction(OpCodes.Call).InlineValue(new Action<Exception>(LogFatal).Method)
});
}
}
return msil;
}
private static void LogFatal(Exception e) => Log.Fatal(e.ToStringDemystified());
}
}

View File

@@ -1,112 +1,64 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Xml;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using NLog;
using NLog.Config;
using NLog.Targets;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using Torch;
using Torch.API;
using Torch.Server.Views;
using VRage.Game.ModAPI;
using System.IO.Compression;
using System.Net;
using System.Security.Policy;
using Torch.Server.Managers;
using Torch.Utils;
using VRage.FileSystem;
using VRageRender;
namespace Torch.Server
{
internal static class Program
{
/// <remarks>
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
/// </remarks>
[STAThread]
public static void Main(string[] args)
{
var configurationBuilder = new ConfigurationBuilder()
.AddEnvironmentVariables("TORCH")
.AddCommandLine(args);
var configuration = configurationBuilder.Build();
var context = CreateApplicationContext(configuration);
//Ensures that all the files are downloaded in the Torch directory.
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
var binDir = Path.Combine(workingDir, "DedicatedServer64");
Directory.SetCurrentDirectory(workingDir);
SetupLogging(context, configuration);
var config = SetupConfiguration(context, configurationBuilder, out configuration);
var handler = new UnhandledExceptionHandler(config.Data);
AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
if (!Environment.UserInteractive)
{
using (var service = new TorchService())
using (new TorchAssemblyResolver(binDir))
{
ServiceBase.Run(service);
}
return;
}
var initializer = new Initializer(config);
if (!initializer.Initialize(configuration))
Environment.Exit(1);
#if DEBUG
TorchLauncher.Launch(context.TorchDirectory.FullName, context.GameBinariesDirectory.FullName);
#else
TorchLauncher.Launch(context.TorchDirectory.FullName, Path.Combine(context.TorchDirectory.FullName, "torch64"),
context.GameBinariesDirectory.FullName);
#endif
var initializer = new Initializer(workingDir);
if (!initializer.Initialize(args))
return;
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;
if (Path.IsPathRooted(instanceName))
{
instancePath = instanceName;
instanceName = Path.GetDirectoryName(instanceName);
}
else
{
instancePath = Directory.CreateDirectory(instanceName!).FullName;
}
Directory.SetCurrentDirectory(gamePath);
return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
new(instancePath), instanceName, isService);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
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

Some files were not shown because too many files have changed in this diff Show More