Compare commits
76 Commits
1.1.229.26
...
v1.2.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9471d83a45 | ||
![]() |
52b225d944 | ||
![]() |
c8f0a61209 | ||
![]() |
59c3e9eb54 | ||
![]() |
d20d68b831 | ||
![]() |
cfda1f8eef | ||
![]() |
2fb222125a | ||
![]() |
c7c3c00783 | ||
![]() |
dcc130e2cf | ||
![]() |
491f3d3af4 | ||
![]() |
178fcb8164 | ||
![]() |
3d6806b63a | ||
![]() |
22e3019610 | ||
![]() |
7937cbd122 | ||
![]() |
2bef34ee5b | ||
![]() |
efe7236d31 | ||
![]() |
67dba9c820 | ||
![]() |
52c509aba0 | ||
![]() |
437c7d293e | ||
![]() |
2b6ce4f25b | ||
![]() |
64f123abe9 | ||
![]() |
599a98bceb | ||
![]() |
2cd1b8bd4e | ||
![]() |
c0be9c25da | ||
![]() |
5cea66374f | ||
![]() |
ee1c270c68 | ||
![]() |
4ab08e2faf | ||
![]() |
dd094edb88 | ||
![]() |
56e45236d8 | ||
![]() |
be9a8c5839 | ||
![]() |
8c11baf3b9 | ||
![]() |
6ce679bd83 | ||
![]() |
a4b1b9bb96 | ||
![]() |
91ad78e6a2 | ||
![]() |
4a68d66ab0 | ||
![]() |
4cb50b556f | ||
![]() |
b5f73a99cc | ||
![]() |
e9476a59e8 | ||
![]() |
f48f23c2eb | ||
![]() |
ddf465d8c9 | ||
![]() |
7149287b8e | ||
![]() |
cc709c6bb3 | ||
![]() |
8d101c4c11 | ||
![]() |
64eef6cd8e | ||
![]() |
f8ae3c0dd1 | ||
![]() |
589205edc3 | ||
![]() |
48b212faaf | ||
![]() |
0554dbc608 | ||
![]() |
3564eb805c | ||
![]() |
62a8064edd | ||
![]() |
55ed45190b | ||
![]() |
a6ae96093f | ||
![]() |
5b1afe6d50 | ||
![]() |
60df71a74c | ||
![]() |
a6d5da861f | ||
![]() |
afc10911f7 | ||
![]() |
0686e95c72 | ||
![]() |
234754fd49 | ||
![]() |
efb8d0f226 | ||
![]() |
3f881f7d67 | ||
![]() |
64d38abc99 | ||
![]() |
4a39362702 | ||
![]() |
db2d3794ae | ||
![]() |
526ff6fff0 | ||
![]() |
eebc0e428e | ||
![]() |
80aca514b2 | ||
![]() |
3e8068e82d | ||
![]() |
601fbcd176 | ||
![]() |
40eab15d69 | ||
![]() |
ceb272c0b4 | ||
![]() |
80d4f62694 | ||
![]() |
42f58a8649 | ||
![]() |
6b9af71967 | ||
![]() |
dbd98a09c5 | ||
![]() |
c6a6363163 | ||
![]() |
0a38eb770d |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -252,3 +252,6 @@ ModelManifest.xml
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
GameBinaries
|
||||
|
||||
# Generated Files
|
||||
**Gen.cs
|
||||
|
@@ -1,9 +1,9 @@
|
||||
# Making a Pull Request
|
||||
* 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**
|
||||
* 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**
|
||||
* 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.
|
||||
* Submit your branch as a PR to be reviewed, with Torch's **staging** branch as the base.
|
||||
|
||||
## Naming Conventions
|
||||
* Types: **PascalCase**
|
||||
|
22
Jenkins/jenkins-grab-se.ps1
Normal file
22
Jenkins/jenkins-grab-se.ps1
Normal 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
52
Jenkins/release.ps1
Normal 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=$TRUE
|
||||
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
67
Jenkinsfile
vendored
Normal 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: 'torch-github', 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\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
Discord: [](https://discord.gg/8uHZykr)
|
||||
[](https://discord.gg/8uHZykr) [](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.
|
||||
|
@@ -3,7 +3,7 @@
|
||||
@echo off
|
||||
set /p path="Please enter the folder location of your SpaceEngineersDedicated.exe: "
|
||||
cd %~dp0
|
||||
mklink /D GameBinaries %path%
|
||||
mklink /J GameBinaries "%path%"
|
||||
if errorlevel 1 goto Error
|
||||
echo Done! You can now open the Torch solution without issue.
|
||||
goto End
|
||||
|
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.API
|
||||
@@ -33,17 +34,27 @@ namespace Torch.API
|
||||
/// </summary>
|
||||
event Action SessionUnloaded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently running session instance, or null if none exists.
|
||||
/// </summary>
|
||||
ITorchSession CurrentSession { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the current instance.
|
||||
/// </summary>
|
||||
ITorchConfig Config { get; }
|
||||
|
||||
/// <inheritdoc cref="IMultiplayerManager"/>
|
||||
[Obsolete]
|
||||
IMultiplayerManager Multiplayer { get; }
|
||||
|
||||
/// <inheritdoc cref="IPluginManager"/>
|
||||
[Obsolete]
|
||||
IPluginManager Plugins { get; }
|
||||
|
||||
/// <inheritdoc cref="IDependencyManager"/>
|
||||
IDependencyManager Managers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The binary version of the current instance.
|
||||
/// </summary>
|
||||
@@ -90,12 +101,6 @@ namespace Torch.API
|
||||
/// Initialize the Torch instance.
|
||||
/// </summary>
|
||||
void Init();
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="IManager"/> that is part of the Torch instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Manager type</typeparam>
|
||||
T GetManager<T>() where T : class, IManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
30
Torch.API/Managers/DependencyManagerExtensions.cs
Normal file
30
Torch.API/Managers/DependencyManagerExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public static class DependencyManagerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Removes a single manager from this dependency manager.
|
||||
/// </summary>
|
||||
/// <param name="managerType">The dependency type to remove</param>
|
||||
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
public static IManager RemoveManager(this IDependencyManager depManager, Type managerType)
|
||||
{
|
||||
IManager mgr = depManager.GetManager(managerType);
|
||||
return depManager.RemoveManager(mgr) ? mgr : null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a single manager from this dependency manager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The dependency type to remove</typeparam>
|
||||
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
public static IManager RemoveManager<T>(this IDependencyManager depManager)
|
||||
{
|
||||
return depManager.RemoveManager(typeof(T));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
15
Torch.API/Managers/DependencyProviderExtensions.cs
Normal file
15
Torch.API/Managers/DependencyProviderExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public static class DependencyProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the manager that provides the given type. If there is no such manager, returns null.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of manager</typeparam>
|
||||
/// <returns>manager, or null if none exists</returns>
|
||||
public static T GetManager<T>(this IDependencyProvider depProvider) where T : class, IManager
|
||||
{
|
||||
return (T)depProvider.GetManager(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
62
Torch.API/Managers/IDependencyManager.cs
Normal file
62
Torch.API/Managers/IDependencyManager.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages a set of <see cref="IManager"/> and the dependencies between them.
|
||||
/// </summary>
|
||||
public interface IDependencyManager : IDependencyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the given manager into the dependency system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method only returns false when there is already a manager registered with a type derived from this given manager,
|
||||
/// or when the given manager is derived from an already existing manager.
|
||||
/// </remarks>
|
||||
/// <param name="manager">Manager to register</param>
|
||||
/// <exception cref="InvalidOperationException">When adding a new manager to an initialized dependency manager</exception>
|
||||
/// <returns>true if added, false if not</returns>
|
||||
bool AddManager(IManager manager);
|
||||
|
||||
/// <summary>
|
||||
/// Clears all managers registered with this dependency manager
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
void ClearManagers();
|
||||
|
||||
/// <summary>
|
||||
/// Removes a single manager from this dependency manager.
|
||||
/// </summary>
|
||||
/// <param name="manager">The manager to remove</param>
|
||||
/// <returns>true if successful, false if the manager wasn't found</returns>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
bool RemoveManager(IManager manager);
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the dependency manager, then attaches all its registered managers in <see cref="AttachOrder" />
|
||||
/// </summary>
|
||||
void Attach();
|
||||
|
||||
/// <summary>
|
||||
/// Detaches all registered managers in <see cref="DetachOrder"/>
|
||||
/// </summary>
|
||||
void Detach();
|
||||
|
||||
/// <summary>
|
||||
/// The order that managers should be attached in. (Dependencies, then dependents)
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">When trying to determine load order before this dependency manager is initialized</exception>
|
||||
IEnumerable<IManager> AttachOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The order that managers should be detached in. (Dependents, then dependencies)
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">When trying to determine unload order before this dependency manager is initialized</exception>
|
||||
IEnumerable<IManager> DetachOrder { get; }
|
||||
}
|
||||
}
|
18
Torch.API/Managers/IDependencyProvider.cs
Normal file
18
Torch.API/Managers/IDependencyProvider.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public interface IDependencyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the manager that provides the given type. If there is no such manager, returns null.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of manager</param>
|
||||
/// <returns>manager, or null if none exists</returns>
|
||||
IManager GetManager(Type type);
|
||||
}
|
||||
}
|
@@ -12,8 +12,13 @@ namespace Torch.API.Managers
|
||||
public interface IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the manager. Called after Torch is initialized.
|
||||
/// Attaches the manager to the session. Called once this manager's dependencies have been attached.
|
||||
/// </summary>
|
||||
void Init();
|
||||
void Attach();
|
||||
|
||||
/// <summary>
|
||||
/// Detaches the manager from the session. Called before this manager's dependencies are detached.
|
||||
/// </summary>
|
||||
void Detach();
|
||||
}
|
||||
}
|
||||
|
@@ -26,11 +26,6 @@ namespace Torch.API.Managers
|
||||
/// </summary>
|
||||
void UpdatePlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all loaded plugins.
|
||||
/// </summary>
|
||||
void DisposePlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Load plugins.
|
||||
/// </summary>
|
||||
|
@@ -1,22 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the given type should be loaded by the plugin manager as a plugin.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class PluginAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The display name of the plugin
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// The version of the plugin
|
||||
/// </summary>
|
||||
public Version Version { get; }
|
||||
/// <summary>
|
||||
/// The GUID of the plugin
|
||||
/// </summary>
|
||||
public Guid Guid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new plugin attribute with the given attributes
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="version"></param>
|
||||
/// <param name="guid"></param>
|
||||
public PluginAttribute(string name, string version, string guid)
|
||||
{
|
||||
Name = name;
|
||||
Version = Version.Parse(version);
|
||||
Guid = Guid.Parse(guid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new plugin attribute with the given attributes. Version is computed as the version of the assembly containing the given type.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="versionSupplier">Version is this type's assembly's version</param>
|
||||
/// <param name="guid"></param>
|
||||
public PluginAttribute(string name, Type versionSupplier, string guid)
|
||||
{
|
||||
Name = name;
|
||||
Version = versionSupplier.Assembly.GetName().Version;
|
||||
Guid = Guid.Parse(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("TorchAPI")]
|
||||
[assembly: AssemblyTitle("Torch API")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("TorchAPI")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("fba5d932-6254-4a1e-baf4-e229fa94e3c2")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
29
Torch.API/Session/ITorchSession.cs
Normal file
29
Torch.API/Session/ITorchSession.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.API.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Torch code working with a single game session
|
||||
/// </summary>
|
||||
public interface ITorchSession
|
||||
{
|
||||
/// <summary>
|
||||
/// The Torch instance this session is bound to
|
||||
/// </summary>
|
||||
ITorchBase Torch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Space Engineers game session this session is bound to.
|
||||
/// </summary>
|
||||
MySession KeenSession { get; }
|
||||
|
||||
/// <inheritdoc cref="IDependencyManager"/>
|
||||
IDependencyManager Managers { get; }
|
||||
}
|
||||
}
|
46
Torch.API/Session/ITorchSessionManager.cs
Normal file
46
Torch.API/Session/ITorchSessionManager.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.API.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a manager for the given session if applicable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is for creating managers that will live inside the session, not the manager that controls sesssions.
|
||||
/// </remarks>
|
||||
/// <param name="session">The session to construct a bound manager for</param>
|
||||
/// <returns>The manager that will live in the session, or null if none.</returns>
|
||||
public delegate IManager SessionManagerFactoryDel(ITorchSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Manages the creation and destruction of <see cref="ITorchSession"/> instances for each <see cref="Sandbox.Game.World.MySession"/> created by Space Engineers.
|
||||
/// </summary>
|
||||
public interface ITorchSessionManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently running session
|
||||
/// </summary>
|
||||
ITorchSession CurrentSession { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given factory as a supplier for session based managers
|
||||
/// </summary>
|
||||
/// <param name="factory">Session based manager supplier</param>
|
||||
/// <returns>true if added, false if already present</returns>
|
||||
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||
bool AddFactory(SessionManagerFactoryDel factory);
|
||||
|
||||
/// <summary>
|
||||
/// Remove the given factory from the suppliers for session based managers
|
||||
/// </summary>
|
||||
/// <param name="factory">Session based manager supplier</param>
|
||||
/// <returns>true if removed, false if not present</returns>
|
||||
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||
bool RemoveFactory(SessionManagerFactoryDel factory);
|
||||
}
|
||||
}
|
@@ -2,8 +2,6 @@
|
||||
<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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -12,10 +10,12 @@
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -23,14 +23,14 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<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>bin\x64\Release\Torch.API.xml</DocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.API.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||
@@ -38,8 +38,8 @@
|
||||
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog">
|
||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||
<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" />
|
||||
@@ -156,9 +156,16 @@
|
||||
</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" />
|
||||
@@ -173,16 +180,15 @@
|
||||
<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" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
@@ -1,12 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Torch Server")]
|
||||
[assembly: AssemblyTitle("Torch Client Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[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
|
96
Torch.Client.Tests/Torch.Client.Tests.csproj
Normal file
96
Torch.Client.Tests/Torch.Client.Tests.csproj
Normal 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>
|
67
Torch.Client.Tests/TorchClientReflectionTest.cs
Normal file
67
Torch.Client.Tests/TorchClientReflectionTest.cs
Normal 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
|
||||
}
|
||||
}
|
12
Torch.Client.Tests/packages.config
Normal file
12
Torch.Client.Tests/packages.config
Normal 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>
|
@@ -1,17 +1,167 @@
|
||||
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)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
#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
|
||||
@@ -27,11 +177,5 @@ namespace Torch.Client
|
||||
|
||||
client.Start();
|
||||
}
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
MessageBox.Show(ex.StackTrace, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.229.265")]
|
||||
[assembly: AssemblyFileVersion("1.0.229.265")]
|
||||
[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
|
@@ -1,16 +0,0 @@
|
||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".cs" #>
|
||||
using System.Reflection;
|
||||
|
||||
<# var dt = DateTime.Now;
|
||||
int major = 1;
|
||||
int minor = 0;
|
||||
int build = dt.DayOfYear;
|
||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
||||
#>
|
||||
[assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
||||
[assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
@@ -2,8 +2,6 @@
|
||||
<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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{E36DF745-260B-4956-A2E8-09F08B2E7161}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -15,10 +13,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -27,7 +27,7 @@
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@@ -35,19 +35,20 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<DocumentationFile>bin\x64\Release\Torch.Client.xml</DocumentationFile>
|
||||
<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.1\lib\net45\NLog.dll</HintPath>
|
||||
<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>
|
||||
@@ -64,6 +65,7 @@
|
||||
</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" />
|
||||
@@ -81,6 +83,10 @@
|
||||
<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>
|
||||
@@ -104,20 +110,20 @@
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="VRage.Steam">
|
||||
<HintPath>..\..\..\..\..\..\..\steamcmd\steamapps\common\SpaceEngineersDedicatedServer\DedicatedServer64\VRage.Steam.dll</HintPath>
|
||||
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<DependentUpon>AssemblyInfo.tt</DependentUpon>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo1.cs" />
|
||||
<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" />
|
||||
@@ -147,35 +153,24 @@
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7E01635C-3B67-472E-BCD6-C5539564F214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="torchicon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\AssemblyInfo.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
||||
</Content>
|
||||
</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>
|
||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
@@ -1,20 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Engine.Platform;
|
||||
using Sandbox.Engine.Utils;
|
||||
using Sandbox.Game;
|
||||
using Sandbox.ModAPI;
|
||||
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
|
||||
{
|
||||
@@ -24,47 +24,49 @@ namespace Torch.Client
|
||||
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();
|
||||
|
||||
if (!File.Exists("steam_appid.txt"))
|
||||
{
|
||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(typeof(VRage.FastResourceLock).Assembly.Location) + "\\..");
|
||||
}
|
||||
|
||||
SpaceEngineersGame.SetupBasicGameInfo();
|
||||
_startup = new MyCommonProgramStartup(RunArgs);
|
||||
if (_startup.PerformReporting())
|
||||
return;
|
||||
throw new InvalidOperationException("Torch client won't launch when started in error reporting mode");
|
||||
|
||||
_startup.PerformAutoconnect();
|
||||
if (!_startup.CheckSingleInstance())
|
||||
return;
|
||||
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())
|
||||
return;
|
||||
throw new InvalidOperationException("Torch requires a 64bit operating system");
|
||||
|
||||
_startup.DetectSharpDxLeaksBeforeRun();
|
||||
using (var mySteamService = new SteamService(Game.IsDedicated, APP_ID))
|
||||
{
|
||||
_renderer = null;
|
||||
SpaceEngineersGame.SetupPerGameSettings();
|
||||
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");
|
||||
|
||||
OverrideMenus();
|
||||
|
||||
InitializeRender();
|
||||
|
||||
if (!Game.IsDedicated)
|
||||
MyFileSystem.InitUserSpecific(mySteamService.UserId.ToString());
|
||||
}
|
||||
|
||||
_startup.DetectSharpDxLeaksAfterRun();
|
||||
MyInitializer.InvokeAfterRun();
|
||||
if (!Game.IsDedicated)
|
||||
MyFileSystem.InitUserSpecific(MyGameService.UserId.ToString());
|
||||
}
|
||||
|
||||
private void OverrideMenus()
|
||||
@@ -87,13 +89,40 @@ namespace Torch.Client
|
||||
using (var spaceEngineersGame = new SpaceEngineersGame(RunArgs))
|
||||
{
|
||||
Log.Info("Starting client");
|
||||
OverrideMenus();
|
||||
spaceEngineersGame.OnGameLoaded += SpaceEngineersGame_OnGameLoaded;
|
||||
spaceEngineersGame.Run();
|
||||
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()
|
||||
|
32
Torch.Client/TorchClientConfig.cs
Normal file
32
Torch.Client/TorchClientConfig.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
17
Torch.Server.Tests/Properties/AssemblyInfo.cs
Normal file
17
Torch.Server.Tests/Properties/AssemblyInfo.cs
Normal 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
|
93
Torch.Server.Tests/Torch.Server.Tests.csproj
Normal file
93
Torch.Server.Tests/Torch.Server.Tests.csproj
Normal file
@@ -0,0 +1,93 @@
|
||||
<?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>{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>
|
||||
</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.Server.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="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>
|
66
Torch.Server.Tests/TorchServerReflectionTest.cs
Normal file
66
Torch.Server.Tests/TorchServerReflectionTest.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using Torch.Tests;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Server.Tests
|
||||
{
|
||||
public class TorchServerReflectionTest
|
||||
{
|
||||
static TorchServerReflectionTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager;
|
||||
|
||||
private static ReflectionTestManager Manager()
|
||||
{
|
||||
if (_manager != null)
|
||||
return _manager;
|
||||
|
||||
return _manager = new ReflectionTestManager().Init(typeof(TorchServer).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
|
||||
}
|
||||
}
|
12
Torch.Server.Tests/packages.config
Normal file
12
Torch.Server.Tests/packages.config
Normal 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>
|
185
Torch.Server/Initializer.cs
Normal file
185
Torch.Server/Initializer.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public class Initializer
|
||||
{
|
||||
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_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";
|
||||
|
||||
private TorchAssemblyResolver _resolver;
|
||||
private TorchConfig _config;
|
||||
private TorchServer _server;
|
||||
private string _basePath;
|
||||
|
||||
public TorchConfig Config => _config;
|
||||
public TorchServer Server => _server;
|
||||
|
||||
public Initializer(string basePath)
|
||||
{
|
||||
_basePath = basePath;
|
||||
}
|
||||
|
||||
public bool Initialize(string[] args)
|
||||
{
|
||||
if (_init)
|
||||
return false;
|
||||
|
||||
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 pid = int.Parse(_config.WaitForPID);
|
||||
var waitProc = Process.GetProcessById(pid);
|
||||
Log.Info("Continuing in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
if (!waitProc.HasExited)
|
||||
{
|
||||
Log.Warn($"Killing old process {pid}.");
|
||||
waitProc.Kill();
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_server = new TorchServer(_config);
|
||||
_server.Init();
|
||||
|
||||
if (!_config.NoGui)
|
||||
{
|
||||
var ui = new TorchUI(_server);
|
||||
if (_config.Autostart)
|
||||
new Thread(_server.Start).Start();
|
||||
ui.ShowDialog();
|
||||
}
|
||||
else
|
||||
_server.Start();
|
||||
|
||||
_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
|
||||
{
|
||||
Log.Info($"Generating default config at {configPath}");
|
||||
var config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
|
||||
config.Save(configPath);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunSteamCmd()
|
||||
{
|
||||
var log = LogManager.GetLogger("SteamCMD");
|
||||
|
||||
if (!Directory.Exists(STEAMCMD_DIR))
|
||||
{
|
||||
Directory.CreateDirectory(STEAMCMD_DIR);
|
||||
}
|
||||
|
||||
if (!File.Exists(RUNSCRIPT_PATH))
|
||||
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
|
||||
|
||||
if (!File.Exists(STEAMCMD_PATH))
|
||||
{
|
||||
try
|
||||
{
|
||||
log.Info("Downloading SteamCMD.");
|
||||
using (var client = new WebClient())
|
||||
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
|
||||
|
||||
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
|
||||
File.Delete(STEAMCMD_ZIP);
|
||||
log.Info("SteamCMD downloaded successfully!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Checking for DS updates.");
|
||||
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
|
||||
{
|
||||
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)
|
||||
{
|
||||
log.Info(cmd.StandardOutput.ReadLine());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,6 +25,8 @@ namespace Torch.Server.Managers
|
||||
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
||||
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
|
||||
[Dependency]
|
||||
private FilesystemManager _filesystemManager;
|
||||
|
||||
public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
@@ -32,9 +34,9 @@ namespace Torch.Server.Managers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
|
||||
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);
|
||||
@@ -46,7 +48,7 @@ namespace Torch.Server.Managers
|
||||
ValidateInstance(path);
|
||||
|
||||
MyFileSystem.Reset();
|
||||
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
|
||||
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);
|
||||
@@ -129,7 +131,7 @@ namespace Torch.Server.Managers
|
||||
|
||||
public void SaveConfig()
|
||||
{
|
||||
DedicatedConfig.Save();
|
||||
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
|
||||
Log.Info("Saved dedicated config.");
|
||||
|
||||
try
|
||||
|
@@ -25,6 +25,7 @@ using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Security.Policy;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage.FileSystem;
|
||||
using VRageRender;
|
||||
|
||||
@@ -32,268 +33,32 @@ namespace Torch.Server
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private static ITorchServer _server;
|
||||
private static Logger _log = LogManager.GetLogger("Torch");
|
||||
private static bool _restartOnCrash;
|
||||
private static TorchConfig _config;
|
||||
private static bool _steamCmdDone;
|
||||
|
||||
/// <summary>
|
||||
/// <remarks>
|
||||
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
|
||||
/// </summary>
|
||||
/// </remarks>
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
//Ensures that all the files are downloaded in the Torch directory.
|
||||
Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString());
|
||||
|
||||
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.old"))
|
||||
File.Delete(file);
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
|
||||
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
||||
Directory.SetCurrentDirectory(workingDir);
|
||||
|
||||
if (!Environment.UserInteractive)
|
||||
{
|
||||
using (var service = new TorchService())
|
||||
using (new TorchAssemblyResolver(binDir))
|
||||
{
|
||||
ServiceBase.Run(service);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//CommandLine reflection triggers assembly loading, so DS update must be completely separated.
|
||||
if (!args.Contains("-noupdate"))
|
||||
{
|
||||
if (!Directory.Exists("DedicatedServer64"))
|
||||
{
|
||||
_log.Error("Game libraries not found. Press the Enter key to install the dedicated server.");
|
||||
Console.ReadLine();
|
||||
}
|
||||
RunSteamCmd();
|
||||
}
|
||||
|
||||
InitConfig();
|
||||
|
||||
if (!_config.Parse(args))
|
||||
var initializer = new Initializer(workingDir);
|
||||
if (!initializer.Initialize(args))
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
||||
{
|
||||
try
|
||||
{
|
||||
var pid = int.Parse(_config.WaitForPID);
|
||||
var waitProc = Process.GetProcessById(pid);
|
||||
_log.Info("Continuing in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
if (!waitProc.HasExited)
|
||||
{
|
||||
_log.Warn($"Killing old process {pid}.");
|
||||
waitProc.Kill();
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
_restartOnCrash = _config.RestartOnCrash;
|
||||
RunServer(_config);
|
||||
}
|
||||
|
||||
public static void InitConfig()
|
||||
{
|
||||
var configName = "Torch.cfg";
|
||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
||||
if (File.Exists(configName))
|
||||
{
|
||||
_log.Info($"Loading config {configPath}");
|
||||
_config = TorchConfig.LoadFrom(configPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info($"Generating default config at {configPath}");
|
||||
_config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
|
||||
_config.Save(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
private const string STEAMCMD_DIR = "steamcmd";
|
||||
private const string STEAMCMD_ZIP = "temp.zip";
|
||||
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";
|
||||
|
||||
public static void RunSteamCmd()
|
||||
{
|
||||
if (_steamCmdDone)
|
||||
return;
|
||||
|
||||
var log = LogManager.GetLogger("SteamCMD");
|
||||
|
||||
if (!Directory.Exists(STEAMCMD_DIR))
|
||||
{
|
||||
Directory.CreateDirectory(STEAMCMD_DIR);
|
||||
}
|
||||
|
||||
if (!File.Exists(RUNSCRIPT_PATH))
|
||||
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
|
||||
|
||||
if (!File.Exists(STEAMCMD_PATH))
|
||||
{
|
||||
try
|
||||
{
|
||||
log.Info("Downloading SteamCMD.");
|
||||
using (var client = new WebClient())
|
||||
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
|
||||
|
||||
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
|
||||
File.Delete(STEAMCMD_ZIP);
|
||||
log.Info("SteamCMD downloaded successfully!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Checking for DS updates.");
|
||||
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
|
||||
{
|
||||
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)
|
||||
{
|
||||
log.Info(cmd.StandardOutput.ReadLine());
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
_steamCmdDone = true;
|
||||
}
|
||||
|
||||
public static void RunServer(TorchConfig config)
|
||||
{
|
||||
|
||||
|
||||
/*
|
||||
if (!parser.ParseArguments(args, config))
|
||||
{
|
||||
_log.Error($"Parsing arguments failed: {string.Join(" ", args)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(config.Config) && File.Exists(config.Config))
|
||||
{
|
||||
config = ServerConfig.LoadFrom(config.Config);
|
||||
parser.ParseArguments(args, config);
|
||||
}*/
|
||||
|
||||
//RestartOnCrash autostart autosave=15
|
||||
//gamepath ="C:\Program Files\Space Engineers DS" instance="Hydro Survival" instancepath="C:\ProgramData\SpaceEngineersDedicated\Hydro Survival"
|
||||
|
||||
/*
|
||||
if (config.InstallService)
|
||||
{
|
||||
var serviceName = $"\"Torch - {config.InstanceName}\"";
|
||||
// Working on installing the service properly instead of with sc.exe
|
||||
_log.Info($"Installing service '{serviceName}");
|
||||
var exePath = $"\"{Assembly.GetExecutingAssembly().Location}\"";
|
||||
var createInfo = new ServiceCreateInfo
|
||||
{
|
||||
Name = config.InstanceName,
|
||||
BinaryPath = exePath,
|
||||
};
|
||||
_log.Info("Service Installed");
|
||||
|
||||
var runArgs = string.Join(" ", args.Skip(1));
|
||||
_log.Info($"Installing Torch as a service with arguments '{runArgs}'");
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc.exe",
|
||||
Arguments = $"create Torch binPath=\"{Assembly.GetExecutingAssembly().Location} {runArgs}\"",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas"
|
||||
};
|
||||
Process.Start(startInfo).WaitForExit();
|
||||
_log.Info("Torch service installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.UninstallService)
|
||||
{
|
||||
_log.Info("Uninstalling Torch service");
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc.exe",
|
||||
Arguments = "delete Torch",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas"
|
||||
};
|
||||
Process.Start(startInfo).WaitForExit();
|
||||
_log.Info("Torch service uninstalled");
|
||||
return;
|
||||
}*/
|
||||
|
||||
_server = new TorchServer(config);
|
||||
|
||||
_server.Init();
|
||||
if (config.NoGui || config.Autostart)
|
||||
{
|
||||
new Thread(() => _server.Start()).Start();
|
||||
}
|
||||
|
||||
if (!config.NoGui)
|
||||
{
|
||||
var ui = new TorchUI((TorchServer)_server);
|
||||
ui.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var basePath = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "DedicatedServer64");
|
||||
string asmPath = Path.Combine(basePath, new AssemblyName(args.Name).Name + ".dll");
|
||||
if (File.Exists(asmPath))
|
||||
return Assembly.LoadFrom(asmPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
_log.Fatal(ex);
|
||||
Console.WriteLine("Exiting in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
if (_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);
|
||||
initializer.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyVersion("1.1.229.265")]
|
||||
[assembly: AssemblyFileVersion("1.1.229.265")]
|
||||
[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
|
@@ -1,16 +0,0 @@
|
||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".cs" #>
|
||||
using System.Reflection;
|
||||
|
||||
<# var dt = DateTime.Now;
|
||||
int major = 1;
|
||||
int minor = 1;
|
||||
int build = dt.DayOfYear;
|
||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
||||
#>
|
||||
[assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
||||
[assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
@@ -2,8 +2,6 @@
|
||||
<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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -15,10 +13,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -27,7 +27,7 @@
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@@ -35,7 +35,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<DocumentationFile>bin\x64\Release\Torch.Server.xml</DocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Server.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Torch.Server.Program</StartupObject>
|
||||
@@ -63,7 +63,8 @@
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath>
|
||||
<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>
|
||||
@@ -124,6 +125,7 @@
|
||||
<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, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
@@ -185,15 +187,14 @@
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ListBoxExtensions.cs" />
|
||||
<Compile Include="Managers\InstanceManager.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AssemblyInfo.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo1.cs" />
|
||||
<Compile Include="Initializer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ServerStatistics.cs" />
|
||||
<Compile Include="TorchConfig.cs" />
|
||||
<Compile Include="TorchService.cs">
|
||||
@@ -290,12 +291,12 @@
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -358,24 +359,10 @@
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\AssemblyInfo.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>cd "$(TargetDir)"
|
||||
copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||
"Torch Server Release.bat"</PostBuildEvent>
|
||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
@@ -8,6 +8,7 @@ using NLog;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
// TODO: redesign this gerbage
|
||||
public class TorchConfig : CommandLine, ITorchConfig
|
||||
{
|
||||
private static Logger _log = LogManager.GetLogger("Config");
|
||||
|
@@ -19,6 +19,7 @@ using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage.Dedicated;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
@@ -29,6 +30,7 @@ using VRage.Library;
|
||||
using VRage.ObjectBuilders;
|
||||
using VRage.Plugins;
|
||||
using VRage.Utils;
|
||||
|
||||
#pragma warning disable 618
|
||||
|
||||
namespace Torch.Server
|
||||
@@ -121,6 +123,9 @@ namespace Torch.Server
|
||||
MySandboxGame.Config.Load();
|
||||
}
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(DedicatedServer), Name = "RunInternal")]
|
||||
private static Action _dsRunInternal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Start()
|
||||
{
|
||||
@@ -134,17 +139,23 @@ namespace Torch.Server
|
||||
State = ServerState.Starting;
|
||||
Log.Info("Starting server.");
|
||||
|
||||
var runInternal = typeof(DedicatedServer).GetMethod("RunInternal", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
MySandboxGame.IsDedicated = true;
|
||||
Environment.SetEnvironmentVariable("SteamAppId", MyPerServerSettings.AppId.ToString());
|
||||
|
||||
VRage.Service.ExitListenerSTA.OnExit += delegate { MySandboxGame.Static?.Exit(); };
|
||||
|
||||
base.Start();
|
||||
//Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager.
|
||||
// Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager.
|
||||
MySandboxGame.IsReloading = true;
|
||||
runInternal.Invoke(null, null);
|
||||
try
|
||||
{
|
||||
_dsRunInternal.Invoke();
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
// Makes log formatting a little nicer.
|
||||
throw e.InnerException ?? e;
|
||||
}
|
||||
|
||||
MySandboxGame.Log.Close();
|
||||
State = ServerState.Stopped;
|
||||
@@ -180,7 +191,8 @@ namespace Torch.Server
|
||||
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||
{
|
||||
var mainThread = MySandboxGame.Static.UpdateThread;
|
||||
mainThread.Suspend();
|
||||
if (mainThread.IsAlive)
|
||||
mainThread.Suspend();
|
||||
var stackTrace = new StackTrace(mainThread, true);
|
||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}");
|
||||
}
|
||||
|
@@ -14,13 +14,15 @@ namespace Torch.Server
|
||||
{
|
||||
public const string Name = "Torch (SEDS)";
|
||||
private TorchServer _server;
|
||||
private static Logger _log = LogManager.GetLogger("Torch");
|
||||
private Initializer _initializer;
|
||||
|
||||
public TorchService()
|
||||
{
|
||||
ServiceName = Name;
|
||||
var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString();
|
||||
Directory.SetCurrentDirectory(workingDir);
|
||||
_initializer = new Initializer(workingDir);
|
||||
|
||||
CanHandlePowerEvent = true;
|
||||
ServiceName = Name;
|
||||
CanHandleSessionChangeEvent = false;
|
||||
CanPauseAndContinue = false;
|
||||
CanStop = true;
|
||||
@@ -31,17 +33,8 @@ namespace Torch.Server
|
||||
{
|
||||
base.OnStart(args);
|
||||
|
||||
string configName = args.Length > 0 ? args[0] : "TorchConfig.xml";
|
||||
var options = new TorchConfig("Torch");
|
||||
if (File.Exists(configName))
|
||||
options = TorchConfig.LoadFrom(configName);
|
||||
else
|
||||
options.Save(configName);
|
||||
|
||||
_server = new TorchServer(options);
|
||||
_server.Init();
|
||||
_server.RunArgs = args;
|
||||
Task.Run(() => _server.Start());
|
||||
_initializer.Initialize(args);
|
||||
_initializer.Run();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,17 +43,5 @@ namespace Torch.Server
|
||||
_server.Stop();
|
||||
base.OnStop();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShutdown()
|
||||
{
|
||||
base.OnShutdown();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
|
||||
{
|
||||
return base.OnPowerEvent(powerStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,20 +10,9 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListView Grid.Row="0" x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled"/>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<WrapPanel>
|
||||
<TextBlock Text="{Binding Timestamp}"/>
|
||||
<TextBlock Text=" "/>
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
|
||||
<TextBlock Text=": "/>
|
||||
<TextBlock Text="{Binding Message}"/>
|
||||
</WrapPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<ScrollViewer x:Name="ChatScroller" Grid.Row="0" Margin="5,5,5,5" HorizontalScrollBarVisibility="Disabled">
|
||||
<TextBlock x:Name="ChatItems" />
|
||||
</ScrollViewer>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
|
@@ -41,23 +41,32 @@ namespace Torch.Server
|
||||
{
|
||||
_server = (TorchBase)server;
|
||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
||||
ChatItems.Items.Clear();
|
||||
DataContext = _multiplayer;
|
||||
|
||||
ChatItems.Inlines.Clear();
|
||||
_multiplayer.ChatHistory.ForEach(InsertMessage);
|
||||
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
||||
ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
ChatScroller.ScrollToBottom();
|
||||
}
|
||||
|
||||
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
ChatItems.ScrollToItem(ChatItems.Items.Count - 1);
|
||||
/*
|
||||
if (VisualTreeHelper.GetChildrenCount(ChatItems) > 0)
|
||||
{
|
||||
foreach (IChatMessage msg in e.NewItems)
|
||||
InsertMessage(msg);
|
||||
}
|
||||
|
||||
Border border = (Border)VisualTreeHelper.GetChild(ChatItems, 0);
|
||||
ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
|
||||
scrollViewer.ScrollToBottom();
|
||||
}*/
|
||||
private void InsertMessage(IChatMessage msg)
|
||||
{
|
||||
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
|
||||
var span = new Span();
|
||||
span.Inlines.Add($"{msg.Timestamp} ");
|
||||
span.Inlines.Add(new Run(msg.Name) { Foreground = msg.Name == "Server" ? Brushes.DarkBlue : Brushes.Blue });
|
||||
span.Inlines.Add($": {msg.Message}");
|
||||
span.Inlines.Add(new LineBreak());
|
||||
ChatItems.Inlines.Add(span);
|
||||
if (atBottom)
|
||||
ChatScroller.ScrollToBottom();
|
||||
}
|
||||
|
||||
private void SendButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -81,7 +90,7 @@ namespace Torch.Server
|
||||
var commands = _server.Commands;
|
||||
if (commands.IsCommand(text))
|
||||
{
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
_server.Invoke(() =>
|
||||
{
|
||||
var response = commands.HandleCommandFromServer(text);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Server.ViewModels;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace Torch.Server.Views
|
||||
public ConfigControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
_instanceManager = TorchBase.Instance.GetManager<InstanceManager>();
|
||||
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
|
||||
DataContext = _instanceManager.DedicatedConfig;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.11" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
@@ -1,12 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Torch Client")]
|
||||
[assembly: AssemblyTitle("Torch Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[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
|
221
Torch.Tests/ReflectionSystemTest.cs
Normal file
221
Torch.Tests/ReflectionSystemTest.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public class ReflectionSystemTest
|
||||
{
|
||||
static ReflectionSystemTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding));
|
||||
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
|
||||
|
||||
#region Results
|
||||
#region Dummy
|
||||
private class ReflectionTestTarget
|
||||
{
|
||||
public int TestField;
|
||||
public int TestProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Return true when greater or equal than 0
|
||||
/// </summary>
|
||||
public bool TestCall(int k)
|
||||
{
|
||||
return k >= 0;
|
||||
}
|
||||
|
||||
public static int TestFieldStatic;
|
||||
public static int TestPropertyStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Return true when greater or equal than 0
|
||||
/// </summary>
|
||||
public static bool TestCallStatic(int k)
|
||||
{
|
||||
return k >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReflectionTestBinding
|
||||
{
|
||||
[ReflectedGetter(Name = "TestField")]
|
||||
public static Func<ReflectionTestTarget, int> TestFieldGetter;
|
||||
[ReflectedSetter(Name = "TestField")]
|
||||
public static Action<ReflectionTestTarget, int> TestFieldSetter;
|
||||
|
||||
[ReflectedGetter(Name = "TestProperty")]
|
||||
public static Func<ReflectionTestTarget, int> TestPropertyGetter;
|
||||
[ReflectedSetter(Name = "TestProperty")]
|
||||
public static Action<ReflectionTestTarget, int> TestPropertySetter;
|
||||
|
||||
[ReflectedMethod]
|
||||
public static Func<ReflectionTestTarget, int, bool> TestCall;
|
||||
|
||||
|
||||
[ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int> TestStaticFieldGetter;
|
||||
[ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Action<int> TestStaticFieldSetter;
|
||||
|
||||
[ReflectedGetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int> TestStaticPropertyGetter;
|
||||
[ReflectedSetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Action<int> TestStaticPropertySetter;
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int, bool> TestCallStatic;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly Random _rand = new Random();
|
||||
private int AcquireRandomNum()
|
||||
{
|
||||
return _rand.Next();
|
||||
}
|
||||
|
||||
#region Instance
|
||||
[Fact]
|
||||
public void TestInstanceFieldGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget
|
||||
{
|
||||
TestField = testNumber
|
||||
};
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestFieldGetter.Invoke(target));
|
||||
}
|
||||
[Fact]
|
||||
public void TestInstanceFieldSet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget();
|
||||
ReflectionTestBinding.TestFieldSetter.Invoke(target, testNumber);
|
||||
Assert.Equal(testNumber, target.TestField);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstancePropertyGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget
|
||||
{
|
||||
TestProperty = testNumber
|
||||
};
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestPropertyGetter.Invoke(target));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstancePropertySet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget();
|
||||
ReflectionTestBinding.TestPropertySetter.Invoke(target, testNumber);
|
||||
Assert.Equal(testNumber, target.TestProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstanceInvoke()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
var target = new ReflectionTestTarget();
|
||||
Assert.True(ReflectionTestBinding.TestCall.Invoke(target, 1));
|
||||
Assert.False(ReflectionTestBinding.TestCall.Invoke(target, -1));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Static
|
||||
[Fact]
|
||||
public void TestStaticFieldGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestTarget.TestFieldStatic = testNumber;
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestStaticFieldGetter.Invoke());
|
||||
}
|
||||
[Fact]
|
||||
public void TestStaticFieldSet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestBinding.TestStaticFieldSetter.Invoke(testNumber);
|
||||
Assert.Equal(testNumber, ReflectionTestTarget.TestFieldStatic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStaticPropertyGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestTarget.TestPropertyStatic = testNumber;
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestStaticPropertyGetter.Invoke());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStaticPropertySet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestBinding.TestStaticPropertySetter.Invoke(testNumber);
|
||||
Assert.Equal(testNumber, ReflectionTestTarget.TestPropertyStatic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStaticInvoke()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1));
|
||||
Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1));
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
72
Torch.Tests/ReflectionTestManager.cs
Normal file
72
Torch.Tests/ReflectionTestManager.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public class ReflectionTestManager
|
||||
{
|
||||
#region FieldProvider
|
||||
public struct FieldRef
|
||||
{
|
||||
public FieldInfo Field;
|
||||
|
||||
public FieldRef(FieldInfo f)
|
||||
{
|
||||
Field = f;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Field == null)
|
||||
return "Ignored";
|
||||
return Field.DeclaringType?.FullName + "." + Field.Name;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<object[]> _getters = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _setters = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _invokers = new HashSet<object[]>();
|
||||
|
||||
public ReflectionTestManager()
|
||||
{
|
||||
_getters.Add(new object[] { new FieldRef(null) });
|
||||
_setters.Add(new object[] { new FieldRef(null) });
|
||||
_invokers.Add(new object[] { new FieldRef(null) });
|
||||
}
|
||||
|
||||
public ReflectionTestManager Init(Assembly asm)
|
||||
{
|
||||
foreach (Type type in asm.GetTypes())
|
||||
Init(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReflectionTestManager Init(Type type)
|
||||
{
|
||||
foreach (FieldInfo field in type.GetFields(BindingFlags.Static |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic))
|
||||
{
|
||||
if (field.GetCustomAttribute<ReflectedMethodAttribute>() != null)
|
||||
_invokers.Add(new object[] { new FieldRef(field) });
|
||||
if (field.GetCustomAttribute<ReflectedGetterAttribute>() != null)
|
||||
_getters.Add(new object[] { new FieldRef(field) });
|
||||
if (field.GetCustomAttribute<ReflectedSetterAttribute>() != null)
|
||||
_setters.Add(new object[] { new FieldRef(field) });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerable<object[]> Getters => _getters;
|
||||
|
||||
public IEnumerable<object[]> Setters => _setters;
|
||||
|
||||
public IEnumerable<object[]> Invokers => _invokers;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
35
Torch.Tests/TestUtils.cs
Normal file
35
Torch.Tests/TestUtils.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public sealed class TestUtils
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
if (_torchResolver == null)
|
||||
_torchResolver = new TorchAssemblyResolver(GetGameBinaries());
|
||||
}
|
||||
|
||||
private static string GetGameBinaries()
|
||||
{
|
||||
string dir = Environment.CurrentDirectory;
|
||||
while (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
string gameBin = Path.Combine(dir, "GameBinaries");
|
||||
if (Directory.Exists(gameBin))
|
||||
return gameBin;
|
||||
|
||||
dir = Path.GetDirectoryName(dir);
|
||||
}
|
||||
throw new Exception("GetGameBinaries failed to find a folder named GameBinaries in the directory tree");
|
||||
}
|
||||
|
||||
private static TorchAssemblyResolver _torchResolver;
|
||||
}
|
||||
}
|
91
Torch.Tests/Torch.Tests.csproj
Normal file
91
Torch.Tests/Torch.Tests.csproj
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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>{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Torch.Tests</RootNamespace>
|
||||
<AssemblyName>Torch.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.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="ReflectionTestManager.cs" />
|
||||
<Compile Include="ReflectionSystemTest.cs" />
|
||||
<Compile Include="TestUtils.cs" />
|
||||
<Compile Include="TorchReflectionTest.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\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>
|
65
Torch.Tests/TorchReflectionTest.cs
Normal file
65
Torch.Tests/TorchReflectionTest.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public class TorchReflectionTest
|
||||
{
|
||||
static TorchReflectionTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager;
|
||||
|
||||
private static ReflectionTestManager Manager()
|
||||
{
|
||||
if (_manager != null)
|
||||
return _manager;
|
||||
|
||||
return _manager = new ReflectionTestManager().Init(typeof(TorchBase).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
|
||||
}
|
||||
}
|
12
Torch.Tests/packages.config
Normal file
12
Torch.Tests/packages.config
Normal 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>
|
31
Torch.sln
31
Torch.sln
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.14
|
||||
VisualStudioVersion = 15.0.26730.8
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch", "Torch\Torch.csproj", "{7E01635C-3B67-472E-BCD6-C5539564F214}"
|
||||
EndProject
|
||||
@@ -16,6 +16,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
NLog.config = NLog.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server.Tests", "Torch.Server.Tests\Torch.Server.Tests.csproj", "{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Client.Tests", "Torch.Client.Tests\Torch.Client.Tests.csproj", "{632E78C0-0DAC-4B71-B411-2F1B333CC310}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning", "{762F6A0D-55EF-4173-8CDE-309D183F40C4}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Versioning\AssemblyVersion.cs = Versioning\AssemblyVersion.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -38,8 +49,26 @@ Global
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.Build.0 = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.Build.0 = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.ActiveCfg = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.Build.0 = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.Build.0 = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.ActiveCfg = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.Build.0 = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.ActiveCfg = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{762F6A0D-55EF-4173-8CDE-309D183F40C4} = {7AD02A71-1D4C-48F9-A8C1-789A5512424F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@@ -62,7 +62,7 @@ namespace Torch.Commands
|
||||
_parameters = commandMethod.GetParameters();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"/{string.Join(" ", Path)} ");
|
||||
sb.Append($"!{string.Join(" ", Path)} ");
|
||||
for (var i = 0; i < _parameters.Length; i++)
|
||||
{
|
||||
var param = _parameters[i];
|
||||
|
@@ -20,16 +20,18 @@ namespace Torch.Commands
|
||||
|
||||
public CommandTree Commands { get; set; } = new CommandTree();
|
||||
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
||||
[Dependency]
|
||||
private ChatManager _chatManager;
|
||||
|
||||
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
||||
{
|
||||
Prefix = prefix;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
RegisterCommandModule(typeof(TorchCommands));
|
||||
Torch.GetManager<ChatManager>().MessageRecieved += HandleCommand;
|
||||
_chatManager.MessageRecieved += HandleCommand;
|
||||
}
|
||||
|
||||
public bool HasPermission(ulong steamId, Command command)
|
||||
|
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Commands.Permissions;
|
||||
using Torch.Managers;
|
||||
using VRage.Game.ModAPI;
|
||||
@@ -50,7 +51,7 @@ namespace Torch.Commands
|
||||
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
||||
public void LongHelp()
|
||||
{
|
||||
var commandManager = Context.Torch.GetManager<CommandManager>();
|
||||
var commandManager = Context.Torch.Managers.GetManager<CommandManager>();
|
||||
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
||||
|
||||
if (node != null)
|
||||
|
@@ -27,16 +27,19 @@ namespace Torch.Managers
|
||||
internal void RaiseMessageRecieved(ChatMsg msg, ref bool sendToOthers) =>
|
||||
MessageRecieved?.Invoke(msg, ref sendToOthers);
|
||||
|
||||
[Dependency]
|
||||
private INetworkManager _networkManager;
|
||||
|
||||
public ChatManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
try
|
||||
{
|
||||
Torch.GetManager<INetworkManager>().RegisterNetworkHandler(new ChatIntercept(this));
|
||||
_networkManager.RegisterNetworkHandler(new ChatIntercept(this));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
309
Torch/Managers/DependencyManager.cs
Normal file
309
Torch/Managers/DependencyManager.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NLog;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
public sealed class DependencyManager : IDependencyManager
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private class DependencyInfo
|
||||
{
|
||||
private readonly Manager.DependencyAttribute _attribute;
|
||||
internal Type DependencyType => Field.FieldType;
|
||||
internal FieldInfo Field { get; private set; }
|
||||
internal bool Optional => _attribute.Optional;
|
||||
internal bool Ordered => _attribute.Ordered;
|
||||
|
||||
public DependencyInfo(FieldInfo field)
|
||||
{
|
||||
Field = field;
|
||||
_attribute = field.GetCustomAttribute<Manager.DependencyAttribute>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a registered instance of a manager.
|
||||
/// </summary>
|
||||
private class ManagerInstance
|
||||
{
|
||||
public IManager Instance { get; private set; }
|
||||
|
||||
internal readonly List<DependencyInfo> Dependencies;
|
||||
internal readonly HashSet<Type> SuppliedManagers;
|
||||
internal readonly HashSet<ManagerInstance> Dependents;
|
||||
|
||||
public ManagerInstance(IManager manager)
|
||||
{
|
||||
Instance = manager;
|
||||
|
||||
SuppliedManagers = new HashSet<Type>();
|
||||
Dependencies = new List<DependencyInfo>();
|
||||
Dependents = new HashSet<ManagerInstance>();
|
||||
var openBases = new Queue<Type>();
|
||||
openBases.Enqueue(manager.GetType());
|
||||
while (openBases.TryDequeue(out Type type))
|
||||
{
|
||||
if (!SuppliedManagers.Add(type))
|
||||
continue;
|
||||
|
||||
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
if (field.HasAttribute<Manager.DependencyAttribute>())
|
||||
Dependencies.Add(new DependencyInfo(field));
|
||||
|
||||
foreach (Type parent in type.GetInterfaces())
|
||||
openBases.Enqueue(parent);
|
||||
if (type.BaseType != null)
|
||||
openBases.Enqueue(type.BaseType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="DependencyManager"/> internally to topologically sort the dependency list.
|
||||
/// </summary>
|
||||
public int UnsolvedDependencies { get; set; }
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, ManagerInstance> _dependencySatisfaction;
|
||||
private readonly List<ManagerInstance> _registeredManagers;
|
||||
private readonly List<ManagerInstance> _orderedManagers;
|
||||
private readonly IDependencyProvider[] _parentProviders;
|
||||
|
||||
public DependencyManager(params IDependencyProvider[] parents)
|
||||
{
|
||||
_dependencySatisfaction = new Dictionary<Type, ManagerInstance>();
|
||||
_registeredManagers = new List<ManagerInstance>();
|
||||
_orderedManagers = new List<ManagerInstance>();
|
||||
_parentProviders = parents.Distinct().ToArray();
|
||||
}
|
||||
|
||||
private void AddDependencySatisfaction(ManagerInstance instance)
|
||||
{
|
||||
foreach (Type supplied in instance.SuppliedManagers)
|
||||
if (_dependencySatisfaction.ContainsKey(supplied))
|
||||
// When we already have a manager supplying this component we have to unregister it.
|
||||
_dependencySatisfaction[supplied] = null;
|
||||
else
|
||||
_dependencySatisfaction.Add(supplied, instance);
|
||||
}
|
||||
|
||||
private void RebuildDependencySatisfaction()
|
||||
{
|
||||
_dependencySatisfaction.Clear();
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
AddDependencySatisfaction(manager);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool AddManager(IManager manager)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't add new managers to an initialized dependency manager");
|
||||
// Protect against adding a manager derived from an existing manager
|
||||
if (_registeredManagers.Any(x => x.Instance.GetType().IsInstanceOfType(manager)))
|
||||
return false;
|
||||
// Protect against adding a manager when an existing manager derives from it.
|
||||
// ReSharper disable once ConvertIfStatementToReturnStatement
|
||||
if (_registeredManagers.Any(x => manager.GetType().IsInstanceOfType(x.Instance)))
|
||||
return false;
|
||||
|
||||
ManagerInstance instance = new ManagerInstance(manager);
|
||||
_registeredManagers.Add(instance);
|
||||
AddDependencySatisfaction(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearManagers()
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't remove managers from an initialized dependency manager");
|
||||
|
||||
_registeredManagers.Clear();
|
||||
_dependencySatisfaction.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RemoveManager(IManager manager)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't remove managers from an initialized dependency manager");
|
||||
if (manager == null)
|
||||
return false;
|
||||
for (int i = 0; i < _registeredManagers.Count; i++)
|
||||
if (_registeredManagers[i].Instance == manager)
|
||||
{
|
||||
_registeredManagers.RemoveAtFast(i);
|
||||
RebuildDependencySatisfaction();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
// Resets the previous sort results
|
||||
#region Reset
|
||||
_orderedManagers.Clear();
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
manager.Dependents.Clear();
|
||||
#endregion
|
||||
|
||||
// Creates the dependency graph
|
||||
#region Prepare
|
||||
var dagQueue = new List<ManagerInstance>();
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
{
|
||||
var inFactor = 0;
|
||||
foreach (DependencyInfo dependency in manager.Dependencies)
|
||||
{
|
||||
if (_dependencySatisfaction.TryGetValue(dependency.DependencyType, out var dependencyInstance))
|
||||
{
|
||||
if (dependency.Ordered)
|
||||
{
|
||||
inFactor++;
|
||||
dependencyInstance.Dependents.Add(manager);
|
||||
}
|
||||
}
|
||||
else if (!dependency.Optional && _parentProviders.All(x => x.GetManager(dependency.DependencyType) == null))
|
||||
_log.Warn("Unable to satisfy dependency {0} ({1}) of {2}.", dependency.DependencyType.Name,
|
||||
dependency.Field.Name, manager.Instance.GetType().Name);
|
||||
}
|
||||
manager.UnsolvedDependencies = inFactor;
|
||||
dagQueue.Add(manager);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// Sorts the dependency graph into _orderedManagers
|
||||
#region Sort
|
||||
var tmpQueue = new List<ManagerInstance>();
|
||||
while (dagQueue.Any())
|
||||
{
|
||||
tmpQueue.Clear();
|
||||
for (var i = 0; i < dagQueue.Count; i++)
|
||||
{
|
||||
if (dagQueue[i].UnsolvedDependencies == 0)
|
||||
tmpQueue.Add(dagQueue[i]);
|
||||
else
|
||||
dagQueue[i - tmpQueue.Count] = dagQueue[i];
|
||||
}
|
||||
dagQueue.RemoveRange(dagQueue.Count - tmpQueue.Count, tmpQueue.Count);
|
||||
if (tmpQueue.Count == 0)
|
||||
{
|
||||
_log.Fatal("Dependency loop detected in the following managers:");
|
||||
foreach (ManagerInstance manager in dagQueue)
|
||||
{
|
||||
_log.Fatal(" + {0} has {1} unsolved dependencies.", manager.Instance.GetType().FullName, manager.UnsolvedDependencies);
|
||||
_log.Fatal(" - Dependencies: {0}",
|
||||
string.Join(", ", manager.Dependencies.Select(x => x.DependencyType.Name + (x.Optional ? " (Optional)" : ""))));
|
||||
_log.Fatal(" - Dependents: {0}",
|
||||
string.Join(", ", manager.Dependents.Select(x => x.Instance.GetType().Name)));
|
||||
}
|
||||
throw new InvalidOperationException("Unable to satisfy all required manager dependencies");
|
||||
}
|
||||
// Update the number of unsolved dependencies
|
||||
foreach (ManagerInstance manager in tmpQueue)
|
||||
foreach (ManagerInstance dependent in manager.Dependents)
|
||||
dependent.UnsolvedDependencies--;
|
||||
// tmpQueue.Sort(); If we have priorities this is where to sort them.
|
||||
_orderedManagers.AddRange(tmpQueue);
|
||||
}
|
||||
_log.Debug("Dependency tree satisfied. Load order is:");
|
||||
foreach (ManagerInstance manager in _orderedManagers)
|
||||
{
|
||||
_log.Debug(" - {0}", manager.Instance.GetType().FullName);
|
||||
_log.Debug(" - Dependencies: {0}",
|
||||
string.Join(", ", manager.Dependencies.Select(x => x.DependencyType.Name + (x.Optional ? " (Optional)" : ""))));
|
||||
_log.Debug(" - Dependents: {0}",
|
||||
string.Join(", ", manager.Dependents.Select(x => x.Instance.GetType().Name)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Updates the dependency fields with the correct manager instances
|
||||
#region Satisfy
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
{
|
||||
manager.Dependents.Clear();
|
||||
foreach (DependencyInfo dependency in manager.Dependencies)
|
||||
dependency.Field.SetValue(manager.Instance, GetManager(dependency.DependencyType));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the dependency manager, and all its registered managers.
|
||||
/// </summary>
|
||||
public void Attach()
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't start the dependency manager more than once");
|
||||
_initialized = true;
|
||||
Sort();
|
||||
foreach (ManagerInstance manager in _orderedManagers)
|
||||
manager.Instance.Attach();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the dependency manager, and all its registered managers.
|
||||
/// </summary>
|
||||
public void Detach()
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("Can't dispose an uninitialized dependency manager");
|
||||
for (int i = _orderedManagers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_orderedManagers[i].Instance.Detach();
|
||||
foreach (DependencyInfo field in _orderedManagers[i].Dependencies)
|
||||
field.Field.SetValue(_orderedManagers[i].Instance, null);
|
||||
}
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IManager> AttachOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("Can't determine dependency load order when uninitialized");
|
||||
foreach (ManagerInstance k in _orderedManagers)
|
||||
yield return k.Instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IManager> DetachOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("Can't determine dependency load order when uninitialized");
|
||||
for (int i = _orderedManagers.Count - 1; i >= 0; i--)
|
||||
yield return _orderedManagers[i].Instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IManager GetManager(Type type)
|
||||
{
|
||||
// ReSharper disable once ConvertIfStatementToReturnStatement
|
||||
if (_dependencySatisfaction.TryGetValue(type, out ManagerInstance mgr))
|
||||
return mgr.Instance;
|
||||
foreach (IDependencyProvider provider in _parentProviders)
|
||||
{
|
||||
IManager entry = provider.GetManager(type);
|
||||
if (entry != null)
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,6 +16,46 @@ namespace Torch.Managers
|
||||
|
||||
public abstract class Manager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a field is a dependency of this parent manager.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class NetworkManager : Manager { }
|
||||
/// public class ChatManager : Manager {
|
||||
/// [Dependency(Optional = false)]
|
||||
/// private NetworkManager _network;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DependencyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// If this dependency isn't required.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tagged field can be null if, and only if, this is true.
|
||||
/// </remarks>
|
||||
public bool Optional { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Dependency must be loaded before and unloaded after the containing manager.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class NetworkManager : Manager { }
|
||||
/// public class ChatManager : Manager {
|
||||
/// [Dependency(Ordered = true)]
|
||||
/// private NetworkManager _network;
|
||||
/// }
|
||||
/// </code>
|
||||
/// Load order will be NetworkManager, then ChatManager.
|
||||
/// Unload order will be ChatManager, then NetworkManager
|
||||
/// </example>
|
||||
public bool Ordered { get; set; } = true;
|
||||
}
|
||||
|
||||
protected ITorchBase Torch { get; }
|
||||
|
||||
protected Manager(ITorchBase torchInstance)
|
||||
@@ -23,7 +63,12 @@ namespace Torch.Managers
|
||||
Torch = torchInstance;
|
||||
}
|
||||
|
||||
public virtual void Init()
|
||||
public virtual void Attach()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void Detach()
|
||||
{
|
||||
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ using NLog;
|
||||
using Torch;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
@@ -25,12 +26,14 @@ using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Collections;
|
||||
using Torch.Commands;
|
||||
using Torch.Utils;
|
||||
using Torch.ViewModels;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.GameServices;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
using VRage.Steam;
|
||||
using VRage.Utils;
|
||||
|
||||
namespace Torch.Managers
|
||||
@@ -50,7 +53,16 @@ namespace Torch.Managers
|
||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
private static readonly Logger ChatLog = LogManager.GetLogger("Chat");
|
||||
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
|
||||
|
||||
[ReflectedGetter(Name = "m_players")]
|
||||
private static Func<MyPlayerCollection, Dictionary<MyPlayer.PlayerId, MyPlayer>> _onlinePlayers;
|
||||
|
||||
[Dependency]
|
||||
private ChatManager _chatManager;
|
||||
[Dependency]
|
||||
private CommandManager _commandManager;
|
||||
[Dependency]
|
||||
private NetworkManager _networkManager;
|
||||
|
||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
||||
{
|
||||
@@ -58,10 +70,10 @@ namespace Torch.Managers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
Torch.SessionLoaded += OnSessionLoaded;
|
||||
Torch.GetManager<ChatManager>().MessageRecieved += Instance_MessageRecieved;
|
||||
_chatManager.MessageRecieved += Instance_MessageRecieved;
|
||||
}
|
||||
|
||||
private void Instance_MessageRecieved(ChatMsg msg, ref bool sendToOthers)
|
||||
@@ -89,21 +101,19 @@ namespace Torch.Managers
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerByName(string name)
|
||||
{
|
||||
ValidateOnlinePlayersList();
|
||||
return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||
{
|
||||
ValidateOnlinePlayersList();
|
||||
_onlinePlayers.TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||
_onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public ulong GetSteamId(long identityId)
|
||||
{
|
||||
foreach (var kv in _onlinePlayers)
|
||||
foreach (var kv in _onlinePlayers.Invoke(MySession.Static.Players))
|
||||
{
|
||||
if (kv.Value.Identity.IdentityId == identityId)
|
||||
return kv.Key.SteamId;
|
||||
@@ -125,10 +135,9 @@ namespace Torch.Managers
|
||||
return;
|
||||
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message));
|
||||
var commands = Torch.GetManager<CommandManager>();
|
||||
if (commands.IsCommand(message))
|
||||
if (_commandManager.IsCommand(message))
|
||||
{
|
||||
var response = commands.HandleCommandFromServer(message);
|
||||
var response = _commandManager.HandleCommandFromServer(message);
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response));
|
||||
}
|
||||
else
|
||||
@@ -141,30 +150,24 @@ namespace Torch.Managers
|
||||
return;
|
||||
|
||||
var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Torch.GetManager<NetworkManager>().RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
||||
_networkManager.RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateOnlinePlayersList()
|
||||
{
|
||||
if (_onlinePlayers == null)
|
||||
_onlinePlayers = MySession.Static.Players.GetPrivateField<Dictionary<MyPlayer.PlayerId, MyPlayer>>("m_players");
|
||||
}
|
||||
|
||||
private void OnSessionLoaded()
|
||||
{
|
||||
Log.Info("Initializing Steam auth");
|
||||
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||
|
||||
ValidateOnlinePlayersList();
|
||||
|
||||
//TODO: Move these with the methods?
|
||||
RemoveHandlers();
|
||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
||||
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
|
||||
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||
if (!RemoveHandlers())
|
||||
{
|
||||
Log.Error("Steam auth failed to initialize");
|
||||
return;
|
||||
}
|
||||
MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
||||
MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse;
|
||||
Log.Info("Steam auth initialized");
|
||||
}
|
||||
|
||||
@@ -186,8 +189,12 @@ namespace Torch.Managers
|
||||
//TODO: Split the following into a new file?
|
||||
//These methods override some Keen code to allow us full control over client authentication.
|
||||
//This lets us have a server set to private (admins only) or friends (friends of all listed admins)
|
||||
private List<ulong> _members;
|
||||
private HashSet<ulong> _waitingForGroup;
|
||||
[ReflectedGetter(Name = "m_members")]
|
||||
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||
[ReflectedGetter(Name = "m_kickedClients")]
|
||||
private static Func<MyMultiplayerBase, Dictionary<ulong, int>> _kickedClients;
|
||||
//private HashSet<ulong> _waitingForFriends;
|
||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||
//private IMultiplayer _multiplayerImplementation;
|
||||
@@ -195,152 +202,137 @@ namespace Torch.Managers
|
||||
/// <summary>
|
||||
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
|
||||
/// </summary>
|
||||
private static void RemoveHandlers()
|
||||
private static bool RemoveHandlers()
|
||||
{
|
||||
var eventField = typeof(GameServer).GetField("<backing_store>ValidateAuthTicketResponse", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (eventField?.GetValue(SteamServerAPI.Instance.GameServer) is MulticastDelegate eventDel)
|
||||
MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (methodValidateAuthTicket == null)
|
||||
{
|
||||
foreach (var handle in eventDel.GetInvocationList())
|
||||
{
|
||||
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
|
||||
{
|
||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= handle as ValidateAuthTicketResponse;
|
||||
Log.Debug("Removed GameServer_ValidateAuthTicketResponse");
|
||||
}
|
||||
}
|
||||
Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook");
|
||||
return false;
|
||||
}
|
||||
eventField = typeof(GameServer).GetField("<backing_store>UserGroupStatus", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
eventDel = eventField?.GetValue(SteamServerAPI.Instance.GameServer) as MulticastDelegate;
|
||||
if (eventDel != null)
|
||||
var eventValidateAuthTicket = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse))
|
||||
.FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action<ulong, JoinResult, ulong>;
|
||||
if (eventValidateAuthTicket == null)
|
||||
{
|
||||
foreach (var handle in eventDel.GetInvocationList())
|
||||
{
|
||||
if (handle.Method.Name == "GameServer_UserGroupStatus")
|
||||
{
|
||||
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
|
||||
Log.Debug("Removed GameServer_UserGroupStatus");
|
||||
}
|
||||
}
|
||||
Log.Error(
|
||||
"Unable to unhook the GameServer_ValidateAuthTicketResponse method from GameServer.ValidateAuthTicketResponse");
|
||||
Log.Debug(" Want to unhook {0}", methodValidateAuthTicket);
|
||||
Log.Debug(" Registered handlers: ");
|
||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer,
|
||||
nameof(MyGameService.GameServer.ValidateAuthTicketResponse)))
|
||||
Log.Debug(" - " + method.Method);
|
||||
return false;
|
||||
}
|
||||
|
||||
MethodInfo methodUserGroupStatus = typeof(MyDedicatedServerBase).GetMethod("GameServer_UserGroupStatus",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (methodUserGroupStatus == null)
|
||||
{
|
||||
Log.Error("Unable to find the GameServer_UserGroupStatus method to unhook");
|
||||
return false;
|
||||
}
|
||||
var eventUserGroupStatus = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse))
|
||||
.FirstOrDefault(x => x.Method == methodUserGroupStatus)
|
||||
as Action<ulong, ulong, bool, bool>;
|
||||
if (eventUserGroupStatus == null)
|
||||
{
|
||||
Log.Error("Unable to unhook the GameServer_UserGroupStatus method from GameServer.UserGroupStatus");
|
||||
Log.Debug(" Want to unhook {0}", methodUserGroupStatus);
|
||||
Log.Debug(" Registered handlers: ");
|
||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse)))
|
||||
Log.Debug(" - " + method.Method);
|
||||
return false;
|
||||
}
|
||||
|
||||
MyGameService.GameServer.ValidateAuthTicketResponse -=
|
||||
eventValidateAuthTicket;
|
||||
MyGameService.GameServer.UserGroupStatusResponse -=
|
||||
eventUserGroupStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Largely copied from SE
|
||||
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
|
||||
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
||||
{
|
||||
Log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
||||
|
||||
if (steamID != ownerSteamID)
|
||||
Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
||||
if (IsClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner))
|
||||
{
|
||||
Log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
||||
_gameOwnerIds[steamID] = ownerSteamID;
|
||||
|
||||
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
|
||||
{
|
||||
Log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
||||
UserRejected(steamID, JoinResult.BannedByAdmins);
|
||||
BanPlayer(steamID);
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
|
||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
|
||||
if (response == AuthSessionResponseEnum.OK)
|
||||
else if (IsClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
||||
{
|
||||
if (MySession.Static.MaxPlayers > 0 && _members.Count - 1 >= MySession.Static.MaxPlayers)
|
||||
{
|
||||
UserRejected(steamID, JoinResult.ServerFull);
|
||||
}
|
||||
else if (MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) /*|| MySandboxGame.ConfigDedicated.Administrators.Contains(MyDedicatedServerBase.ConvertSteamIDFrom64(steamID))*/)
|
||||
{
|
||||
UserAccepted(steamID);
|
||||
}
|
||||
else if (MySandboxGame.ConfigDedicated.GroupID == 0)
|
||||
{
|
||||
switch (MySession.Static.OnlineMode)
|
||||
{
|
||||
case MyOnlineModeEnum.PUBLIC:
|
||||
UserAccepted(steamID);
|
||||
break;
|
||||
case MyOnlineModeEnum.PRIVATE:
|
||||
UserRejected(steamID, JoinResult.NotInGroup);
|
||||
break;
|
||||
case MyOnlineModeEnum.FRIENDS:
|
||||
//TODO: actually verify friendship
|
||||
UserRejected(steamID, JoinResult.NotInGroup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (SteamServerAPI.Instance.GetAccountType(MySandboxGame.ConfigDedicated.GroupID) != AccountType.Clan)
|
||||
{
|
||||
UserRejected(steamID, JoinResult.GroupIdInvalid);
|
||||
}
|
||||
else if (SteamServerAPI.Instance.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
// Returns false when there's no connection to Steam
|
||||
_waitingForGroup.Add(steamID);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserRejected(steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
else
|
||||
if (response != JoinResult.OK)
|
||||
{
|
||||
JoinResult joinResult = JoinResult.TicketInvalid;
|
||||
switch (response)
|
||||
{
|
||||
case AuthSessionResponseEnum.AuthTicketCanceled:
|
||||
joinResult = JoinResult.TicketCanceled;
|
||||
break;
|
||||
case AuthSessionResponseEnum.AuthTicketInvalidAlreadyUsed:
|
||||
joinResult = JoinResult.TicketAlreadyUsed;
|
||||
break;
|
||||
case AuthSessionResponseEnum.LoggedInElseWhere:
|
||||
joinResult = JoinResult.LoggedInElseWhere;
|
||||
break;
|
||||
case AuthSessionResponseEnum.NoLicenseOrExpired:
|
||||
joinResult = JoinResult.NoLicenseOrExpired;
|
||||
break;
|
||||
case AuthSessionResponseEnum.UserNotConnectedToSteam:
|
||||
joinResult = JoinResult.UserNotConnected;
|
||||
break;
|
||||
case AuthSessionResponseEnum.VACBanned:
|
||||
joinResult = JoinResult.VACBanned;
|
||||
break;
|
||||
case AuthSessionResponseEnum.VACCheckTimedOut:
|
||||
joinResult = JoinResult.VACCheckTimedOut;
|
||||
break;
|
||||
}
|
||||
|
||||
UserRejected(steamID, joinResult);
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
|
||||
return;
|
||||
}
|
||||
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
|
||||
return;
|
||||
}
|
||||
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(ConvertSteamIDFrom64(steamID)))
|
||||
{
|
||||
this.UserAccepted(steamID);
|
||||
return;
|
||||
}
|
||||
if (GetServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
|
||||
return;
|
||||
}
|
||||
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
||||
return;
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
|
||||
private void UserGroupStatus(ulong userId, ulong groupId, bool member, bool officer)
|
||||
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||
{
|
||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Remove(userId))
|
||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
|
||||
{
|
||||
if (member || officer)
|
||||
{
|
||||
UserAccepted(userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserRejected(userId, JoinResult.NotInGroup);
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private void UserAccepted(ulong steamId)
|
||||
{
|
||||
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
|
||||
var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected};
|
||||
UserAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||
|
||||
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
||||
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||
Players.Add(steamId, vm);
|
||||
PlayerJoined?.Invoke(vm);
|
||||
}
|
||||
|
||||
private void UserRejected(ulong steamId, JoinResult reason)
|
||||
{
|
||||
typeof(MyDedicatedServerBase).GetMethod("UserRejected", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId, reason});
|
||||
}
|
||||
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))]
|
||||
private static Func<ulong, string> ConvertSteamIDFrom64;
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(MyGameService))]
|
||||
private static Func<ulong, MyGameServiceAccountType> GetServerAccountType;
|
||||
|
||||
[ReflectedMethod(Name = "UserAccepted")]
|
||||
private static Action<MyDedicatedServerBase, ulong> UserAcceptedImpl;
|
||||
|
||||
[ReflectedMethod]
|
||||
private static Action<MyDedicatedServerBase, ulong, JoinResult> UserRejected;
|
||||
[ReflectedMethod]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientBanned;
|
||||
[ReflectedMethod]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientKicked;
|
||||
[ReflectedMethod]
|
||||
private static Action<MyMultiplayerBase, ulong> RaiseClientKicked;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
@@ -20,12 +21,15 @@ namespace Torch.Managers
|
||||
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
|
||||
|
||||
private const string MyTransportLayerField = "TransportLayer";
|
||||
private const string TypeTableField = "m_typeTable";
|
||||
private const string TransportHandlersField = "m_handlers";
|
||||
private MyTypeTable m_typeTable = new MyTypeTable();
|
||||
private HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
||||
private bool _init;
|
||||
|
||||
[ReflectedGetter(Name = "m_typeTable")]
|
||||
private static Func<MyReplicationLayerBase, MyTypeTable> _typeTableGetter;
|
||||
[ReflectedGetter(Name = "m_methodInfoLookup")]
|
||||
private static Func<MyEventTable, Dictionary<MethodInfo, CallSite>> _methodInfoLookupGetter;
|
||||
|
||||
public NetworkManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
@@ -43,10 +47,6 @@ namespace Torch.Managers
|
||||
|
||||
var transportLayerType = transportLayerField.FieldType;
|
||||
|
||||
var replicationLayerType = typeof(MyReplicationLayerBase);
|
||||
if (!Reflection.HasField(replicationLayerType, TypeTableField))
|
||||
throw new TypeLoadException("Could not find TypeTable field");
|
||||
|
||||
if (!Reflection.HasField(transportLayerType, TransportHandlersField))
|
||||
throw new TypeLoadException("Could not find Handlers field");
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Torch.Managers
|
||||
/// <summary>
|
||||
/// Loads the network intercept system
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
Torch.SessionLoaded += OnSessionLoaded;
|
||||
}
|
||||
@@ -78,7 +78,6 @@ namespace Torch.Managers
|
||||
if (!ReflectionUnitTest())
|
||||
throw new InvalidOperationException("Reflection unit test failed.");
|
||||
|
||||
m_typeTable = typeof(MyReplicationLayerBase).GetField(TypeTableField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.ReplicationLayer) as MyTypeTable;
|
||||
//don't bother with nullchecks here, it was all handled in ReflectionUnitTest
|
||||
var transportType = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
|
||||
var transportInstance = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer);
|
||||
@@ -146,7 +145,7 @@ namespace Torch.Managers
|
||||
object obj;
|
||||
if (networkId.IsInvalid) // Static event
|
||||
{
|
||||
site = m_typeTable.StaticEventTable.Get(eventId);
|
||||
site = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).StaticEventTable.Get(eventId);
|
||||
obj = null;
|
||||
}
|
||||
else // Instance event
|
||||
@@ -156,7 +155,7 @@ namespace Torch.Managers
|
||||
{
|
||||
return;
|
||||
}
|
||||
var typeInfo = m_typeTable.Get(sendAs.GetType());
|
||||
var typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(sendAs.GetType());
|
||||
var eventCount = typeInfo.EventTable.Count;
|
||||
if (eventId < eventCount) // Directly
|
||||
{
|
||||
@@ -166,7 +165,7 @@ namespace Torch.Managers
|
||||
else // Through proxy
|
||||
{
|
||||
obj = ((IMyProxyTarget)sendAs).Target;
|
||||
typeInfo = m_typeTable.Get(obj.GetType());
|
||||
typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(obj.GetType());
|
||||
site = typeInfo.EventTable.Get(eventId - (uint)eventCount); // Subtract max id of Proxy
|
||||
}
|
||||
}
|
||||
@@ -243,11 +242,11 @@ namespace Torch.Managers
|
||||
/// <param name="method"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="args"></param>
|
||||
public void RaiseEvent(MethodInfo method, object obj, params object[] args)
|
||||
public void RaiseEvent(MethodInfo method, object obj, params object[] args)
|
||||
{
|
||||
//default(EndpointId) tells the network to broadcast the message
|
||||
RaiseEvent(method, obj, default(EndpointId), args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an event to one client by SteamId
|
||||
@@ -257,9 +256,9 @@ namespace Torch.Managers
|
||||
/// <param name="steamId"></param>
|
||||
/// <param name="args"></param>
|
||||
public void RaiseEvent(MethodInfo method, object obj, ulong steamId, params object[] args)
|
||||
{
|
||||
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
||||
}
|
||||
{
|
||||
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an event to one client
|
||||
@@ -280,7 +279,7 @@ namespace Torch.Managers
|
||||
if (obj != null && owner == null)
|
||||
throw new InvalidCastException("Provided event target is not of type IMyEventOwner!");
|
||||
|
||||
if(!method.HasAttribute<EventAttribute>())
|
||||
if (!method.HasAttribute<EventAttribute>())
|
||||
throw new CustomAttributeFormatException("Provided event target does not have the Event attribute! Replication will not succeed!");
|
||||
|
||||
//array to hold arguments to pass into DispatchEvent
|
||||
@@ -327,10 +326,10 @@ namespace Torch.Managers
|
||||
/// <param name="method"></param>
|
||||
/// <param name="args"></param>
|
||||
public void RaiseStaticEvent(MethodInfo method, params object[] args)
|
||||
{
|
||||
{
|
||||
//default(EndpointId) tells the network to broadcast the message
|
||||
RaiseStaticEvent(method, default(EndpointId), args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a static event to one client by SteamId
|
||||
@@ -339,9 +338,9 @@ namespace Torch.Managers
|
||||
/// <param name="steamId"></param>
|
||||
/// <param name="args"></param>
|
||||
public void RaiseStaticEvent(MethodInfo method, ulong steamId, params object[] args)
|
||||
{
|
||||
RaiseEvent(method, null, new EndpointId(steamId), args);
|
||||
}
|
||||
{
|
||||
RaiseEvent(method, null, new EndpointId(steamId), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a static event to one client
|
||||
@@ -355,18 +354,17 @@ namespace Torch.Managers
|
||||
}
|
||||
|
||||
private CallSite TryGetStaticCallSite(MethodInfo method)
|
||||
{
|
||||
var methodLookup = (Dictionary<MethodInfo, CallSite>)typeof(MyEventTable).GetField("m_methodInfoLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(m_typeTable.StaticEventTable);
|
||||
if (!methodLookup.TryGetValue(method, out CallSite result))
|
||||
{
|
||||
MyTypeTable typeTable = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer);
|
||||
if (!_methodInfoLookupGetter.Invoke(typeTable.StaticEventTable).TryGetValue(method, out CallSite result))
|
||||
throw new MissingMemberException("Provided event target not found!");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private CallSite TryGetCallSite(MethodInfo method, object arg)
|
||||
{
|
||||
var typeInfo = m_typeTable.Get(arg.GetType());
|
||||
var methodLookup = (Dictionary<MethodInfo, CallSite>)typeof(MyEventTable).GetField("m_methodInfoLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(typeInfo.EventTable);
|
||||
if (!methodLookup.TryGetValue(method, out CallSite result))
|
||||
private CallSite TryGetCallSite(MethodInfo method, object arg)
|
||||
{
|
||||
MySynchronizedTypeInfo typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(arg.GetType());
|
||||
if (!_methodInfoLookupGetter.Invoke(typeInfo.EventTable).TryGetValue(method, out CallSite result))
|
||||
throw new MissingMemberException("Provided event target not found!");
|
||||
return result;
|
||||
}
|
||||
|
@@ -20,7 +20,10 @@ namespace Torch.Managers
|
||||
{
|
||||
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||
[Dependency]
|
||||
private UpdateManager _updateManager;
|
||||
[Dependency]
|
||||
private CommandManager _commandManager;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
|
||||
@@ -45,7 +48,7 @@ namespace Torch.Managers
|
||||
/// <summary>
|
||||
/// Unloads all plugins.
|
||||
/// </summary>
|
||||
public void DisposePlugins()
|
||||
public override void Detach()
|
||||
{
|
||||
foreach (var plugin in Plugins)
|
||||
plugin.Dispose();
|
||||
@@ -87,9 +90,6 @@ namespace Torch.Managers
|
||||
/// <inheritdoc />
|
||||
public void LoadPlugins()
|
||||
{
|
||||
_updateManager = Torch.GetManager<UpdateManager>();
|
||||
var commands = Torch.GetManager<CommandManager>();
|
||||
|
||||
if (Torch.Config.ShouldUpdatePlugins)
|
||||
DownloadPlugins();
|
||||
else
|
||||
@@ -119,7 +119,7 @@ namespace Torch.Managers
|
||||
plugin.StoragePath = Torch.Config.InstancePath;
|
||||
Plugins.Add(plugin);
|
||||
|
||||
commands.RegisterPluginCommands(plugin);
|
||||
_commandManager.RegisterPluginCommands(plugin);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@@ -16,7 +16,7 @@ namespace Torch.Managers
|
||||
{
|
||||
private MyScriptWhitelist _whitelist;
|
||||
|
||||
public void Init()
|
||||
public void Attach()
|
||||
{
|
||||
_whitelist = MyScriptCompiler.Static.Whitelist;
|
||||
MyScriptCompiler.Static.AddConditionalCompilationSymbols("TORCH");
|
||||
@@ -41,6 +41,11 @@ namespace Torch.Managers
|
||||
Log.Info(whitelist);*/
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
// TODO unregister whitelist patches
|
||||
}
|
||||
|
||||
public void UnwhitelistType(Type t)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@@ -19,12 +19,13 @@ namespace Torch.Managers
|
||||
/// <summary>
|
||||
/// Handles updating of the DS and Torch plugins.
|
||||
/// </summary>
|
||||
public class UpdateManager : Manager, IDisposable
|
||||
public class UpdateManager : Manager
|
||||
{
|
||||
private Timer _updatePollTimer;
|
||||
private GitHubClient _gitClient = new GitHubClient(new ProductHeaderValue("Torch"));
|
||||
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName;
|
||||
private Logger _log = LogManager.GetLogger(nameof(UpdateManager));
|
||||
[Dependency]
|
||||
private FilesystemManager _fsManager;
|
||||
|
||||
public UpdateManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
@@ -33,9 +34,8 @@ namespace Torch.Managers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
_fsManager = Torch.GetManager<FilesystemManager>();
|
||||
CheckAndUpdateTorch();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,12 @@ namespace Torch.Managers
|
||||
|
||||
var zip = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip"));
|
||||
var versionName = Regex.Match(latest.TagName, "(\\d+\\.)+\\d+").ToString();
|
||||
return new Tuple<Version, string>(new Version(string.IsNullOrWhiteSpace(versionName) ? versionName : "0.0"), zip?.BrowserDownloadUrl);
|
||||
if (string.IsNullOrWhiteSpace(versionName))
|
||||
{
|
||||
_log.Warn("Unable to parse tag {0} for {1}/{2}", latest.TagName, owner, name);
|
||||
versionName = "0.0";
|
||||
}
|
||||
return new Tuple<Version, string>(new Version(versionName), zip?.BrowserDownloadUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -103,6 +108,9 @@ namespace Torch.Managers
|
||||
|
||||
private async void CheckAndUpdateTorch()
|
||||
{
|
||||
// Doesn't work properly or reliably, TODO update when Jenkins is fully configured
|
||||
return;
|
||||
|
||||
if (!Torch.Config.GetTorchUpdates)
|
||||
return;
|
||||
|
||||
@@ -146,7 +154,7 @@ namespace Torch.Managers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public override void Detach()
|
||||
{
|
||||
_updatePollTimer?.Dispose();
|
||||
}
|
||||
|
@@ -1,36 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Piston")]
|
||||
[assembly: AssemblyTitle("Torch")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Piston")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("7e01635c-3b67-472e-bcd6-c5539564f214")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
@@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public static class Reflection
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetLogger("Reflection");
|
||||
|
||||
public static bool HasMethod(Type type, string methodName, Type[] argTypes = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(methodName))
|
||||
return false;
|
||||
|
||||
if (argTypes == null)
|
||||
{
|
||||
var methodInfo = type.GetMethod(methodName);
|
||||
if (methodInfo == null)
|
||||
methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (methodInfo == null && type.BaseType != null)
|
||||
methodInfo = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (methodInfo == null)
|
||||
{
|
||||
Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "'" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo method = type.GetMethod(methodName, argTypes);
|
||||
if (method == null)
|
||||
method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
||||
if (method == null && type.BaseType != null)
|
||||
method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
||||
if (method == null)
|
||||
{
|
||||
Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "'" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "': " + ex.Message );
|
||||
Log.Error( ex );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasField(Type type, string fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(fieldName))
|
||||
return false;
|
||||
var field = type.GetField(fieldName);
|
||||
if (field == null)
|
||||
field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (field == null)
|
||||
field = type.BaseType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (field == null)
|
||||
{
|
||||
Log.Error("Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasProperty(Type type, string propertyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
return false;
|
||||
var prop = type.GetProperty(propertyName);
|
||||
if (prop == null)
|
||||
prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (prop == null)
|
||||
prop = type.BaseType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (prop == null)
|
||||
{
|
||||
Log.Error("Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static object InvokeStaticMethod(Type type, string methodName, params object[] args)
|
||||
{
|
||||
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method == null)
|
||||
{
|
||||
Log.Error($"Method {methodName} not found in static class {type.FullName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return method.Invoke(null, args);
|
||||
}
|
||||
|
||||
public static T GetPrivateField<T>(this object obj, string fieldName)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
return (T)type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
|
||||
}
|
||||
}
|
||||
}
|
49
Torch/Session/TorchSession.cs
Normal file
49
Torch/Session/TorchSession.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using Torch.Managers;
|
||||
|
||||
namespace Torch.Session
|
||||
{
|
||||
public class TorchSession : ITorchSession
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// The Torch instance this session is bound to
|
||||
/// </summary>
|
||||
public ITorchBase Torch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Space Engineers game session this session is bound to.
|
||||
/// </summary>
|
||||
public MySession KeenSession { get; }
|
||||
|
||||
/// <inheritdoc cref="IDependencyManager"/>
|
||||
public IDependencyManager Managers { get; }
|
||||
|
||||
public TorchSession(ITorchBase torch, MySession keenSession)
|
||||
{
|
||||
Torch = torch;
|
||||
KeenSession = keenSession;
|
||||
Managers = new DependencyManager(torch.Managers);
|
||||
}
|
||||
|
||||
internal void Attach()
|
||||
{
|
||||
Managers.Attach();
|
||||
}
|
||||
|
||||
internal void Detach()
|
||||
{
|
||||
Managers.Detach();
|
||||
}
|
||||
}
|
||||
}
|
93
Torch/Session/TorchSessionManager.cs
Normal file
93
Torch/Session/TorchSessionManager.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using Torch.Managers;
|
||||
using Torch.Session;
|
||||
|
||||
namespace Torch.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the creation and destruction of <see cref="TorchSession"/> instances for each <see cref="MySession"/> created by Space Engineers.
|
||||
/// </summary>
|
||||
public class TorchSessionManager : Manager, ITorchSessionManager
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
private TorchSession _currentSession;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ITorchSession CurrentSession => _currentSession;
|
||||
|
||||
private readonly HashSet<SessionManagerFactoryDel> _factories = new HashSet<SessionManagerFactoryDel>();
|
||||
|
||||
public TorchSessionManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool AddFactory(SessionManagerFactoryDel factory)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory), "Factory must be non-null");
|
||||
return _factories.Add(factory);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RemoveFactory(SessionManagerFactoryDel factory)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory), "Factory must be non-null");
|
||||
return _factories.Remove(factory);
|
||||
}
|
||||
|
||||
private void SessionLoaded()
|
||||
{
|
||||
if (_currentSession != null)
|
||||
{
|
||||
_log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
|
||||
_currentSession.Detach();
|
||||
}
|
||||
|
||||
_log.Info($"Starting new torch session for {MySession.Static.Name}");
|
||||
_currentSession = new TorchSession(Torch, MySession.Static);
|
||||
foreach (SessionManagerFactoryDel factory in _factories)
|
||||
{
|
||||
IManager manager = factory(CurrentSession);
|
||||
if (manager != null)
|
||||
CurrentSession.Managers.AddManager(manager);
|
||||
}
|
||||
(CurrentSession as TorchSession)?.Attach();
|
||||
}
|
||||
|
||||
private void SessionUnloaded()
|
||||
{
|
||||
if (_currentSession == null)
|
||||
return;
|
||||
_log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
|
||||
_currentSession.Detach();
|
||||
_currentSession = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Attach()
|
||||
{
|
||||
MySession.AfterLoading += SessionLoaded;
|
||||
MySession.OnUnloaded += SessionUnloaded;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Detach()
|
||||
{
|
||||
_currentSession?.Detach();
|
||||
_currentSession = null;
|
||||
MySession.AfterLoading -= SessionLoaded;
|
||||
MySession.OnUnloaded -= SessionUnloaded;
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,9 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using SteamSDK;
|
||||
using VRage.Steam;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Torch.Utils;
|
||||
using VRage.GameServices;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
@@ -17,48 +21,75 @@ namespace Torch
|
||||
/// </summary>
|
||||
public class SteamService : MySteamService
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
#pragma warning disable 649
|
||||
[ReflectedSetter(Name = nameof(SteamServerAPI))]
|
||||
private static Action<MySteamService, SteamServerAPI> _steamServerAPISetter;
|
||||
[ReflectedSetter(Name = "m_gameServer")]
|
||||
private static Action<MySteamService, MySteamGameServer> _steamGameServerSetter;
|
||||
[ReflectedSetter(Name = nameof(AppId))]
|
||||
private static Action<MySteamService, uint> _steamAppIdSetter;
|
||||
[ReflectedSetter(Name = nameof(API))]
|
||||
private static Action<MySteamService, SteamAPI> _steamApiSetter;
|
||||
[ReflectedSetter(Name = nameof(IsActive))]
|
||||
private static Action<MySteamService, bool> _steamIsActiveSetter;
|
||||
[ReflectedSetter(Name = nameof(UserId))]
|
||||
private static Action<MySteamService, ulong> _steamUserIdSetter;
|
||||
[ReflectedSetter(Name = nameof(UserName))]
|
||||
private static Action<MySteamService, string> _steamUserNameSetter;
|
||||
[ReflectedSetter(Name = nameof(OwnsGame))]
|
||||
private static Action<MySteamService, bool> _steamOwnsGameSetter;
|
||||
[ReflectedSetter(Name = nameof(UserUniverse))]
|
||||
private static Action<MySteamService, MyGameServiceUniverse> _steamUserUniverseSetter;
|
||||
[ReflectedSetter(Name = nameof(BranchName))]
|
||||
private static Action<MySteamService, string> _steamBranchNameSetter;
|
||||
[ReflectedSetter(Name = nameof(InventoryAPI))]
|
||||
private static Action<MySteamService, MySteamInventory> _steamInventoryAPISetter;
|
||||
[ReflectedMethod]
|
||||
private static Action<MySteamService> RegisterCallbacks;
|
||||
[ReflectedSetter(Name = nameof(Peer2Peer))]
|
||||
private static Action<MySteamService, IMyPeer2Peer> _steamPeer2PeerSetter;
|
||||
#pragma warning restore 649
|
||||
|
||||
public SteamService(bool isDedicated, uint appId)
|
||||
: base(true, appId)
|
||||
{
|
||||
// TODO: Add protection for this mess... somewhere
|
||||
SteamSDK.SteamServerAPI.Instance.Dispose();
|
||||
var steam = typeof(MySteamService);
|
||||
SteamServerAPI.Instance.Dispose();
|
||||
_steamServerAPISetter.Invoke(this, null);
|
||||
_steamGameServerSetter.Invoke(this, null);
|
||||
_steamAppIdSetter.Invoke(this, appId);
|
||||
|
||||
steam.GetProperty("SteamServerAPI").GetSetMethod(true).Invoke(this, new object[] { null });
|
||||
steam.GetField("m_gameServer", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(this, null);
|
||||
|
||||
steam.GetProperty("AppId").GetSetMethod(true).Invoke(this, new object[] { appId });
|
||||
if (isDedicated)
|
||||
{
|
||||
steam.GetProperty("SteamServerAPI").GetSetMethod(true).Invoke(this, new object[] { null });
|
||||
steam.GetField("m_gameServer").SetValue(this, new MySteamGameServer());
|
||||
_steamServerAPISetter.Invoke(this, null);
|
||||
_steamGameServerSetter.Invoke(this, new MySteamGameServer());
|
||||
}
|
||||
else
|
||||
{
|
||||
var SteamAPI = SteamSDK.SteamAPI.Instance;
|
||||
steam.GetProperty("API").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.Instance });
|
||||
steam.GetProperty("IsActive").GetSetMethod(true).Invoke(this, new object[] {
|
||||
SteamAPI.Instance.Init()
|
||||
});
|
||||
SteamAPI steamApi = SteamAPI.Instance;
|
||||
_steamApiSetter.Invoke(this, steamApi);
|
||||
bool initResult = steamApi.Init();
|
||||
if (!initResult)
|
||||
_log.Warn("Failed to initialize SteamService");
|
||||
_steamIsActiveSetter.Invoke(this, initResult);
|
||||
|
||||
if (IsActive)
|
||||
{
|
||||
steam.GetProperty("UserId").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.GetSteamUserId() });
|
||||
steam.GetProperty("UserName").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.GetSteamName() });
|
||||
steam.GetProperty("OwnsGame").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.HasGame() });
|
||||
steam.GetProperty("UserUniverse").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.GetSteamUserUniverse() });
|
||||
steam.GetProperty("BranchName").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.GetBranchName() });
|
||||
SteamAPI.LoadStats();
|
||||
_steamUserIdSetter.Invoke(this, steamApi.GetSteamUserId());
|
||||
_steamUserNameSetter.Invoke(this, steamApi.GetSteamName());
|
||||
_steamOwnsGameSetter.Invoke(this, steamApi.HasGame());
|
||||
_steamUserUniverseSetter.Invoke(this, (MyGameServiceUniverse)steamApi.GetSteamUserUniverse());
|
||||
_steamBranchNameSetter.Invoke(this, steamApi.GetBranchName());
|
||||
steamApi.LoadStats();
|
||||
|
||||
steam.GetProperty("InventoryAPI").GetSetMethod(true).Invoke(this, new object[] { new MySteamInventory() });
|
||||
|
||||
steam.GetMethod("RegisterCallbacks",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
.Invoke(this, null);
|
||||
}
|
||||
_steamInventoryAPISetter.Invoke(this, new MySteamInventory());
|
||||
RegisterCallbacks(this);
|
||||
} else
|
||||
_log.Warn("SteamService isn't initialized; Torch Client won't start");
|
||||
}
|
||||
|
||||
steam.GetProperty("Peer2Peer").GetSetMethod(true).Invoke(this, new object[] { new MySteamPeer2Peer() });
|
||||
_steamPeer2PeerSetter.Invoke(this, new MySteamPeer2Peer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,8 +2,6 @@
|
||||
<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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{7E01635C-3B67-472E-BCD6-C5539564F214}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -12,10 +10,12 @@
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -23,14 +23,14 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<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>bin\x64\Release\Torch.xml</DocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||
@@ -43,7 +43,7 @@
|
||||
<HintPath>..\GameBinaries\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Octokit, Version=0.24.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
@@ -143,14 +143,20 @@
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="VRage.Steam">
|
||||
<HintPath>..\..\..\..\..\..\..\steamcmd\steamapps\common\SpaceEngineersDedicatedServer\DedicatedServer64\VRage.Steam.dll</HintPath>
|
||||
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ChatMessage.cs" />
|
||||
<Compile Include="Collections\ObservableList.cs" />
|
||||
<Compile Include="DispatcherExtensions.cs" />
|
||||
<Compile Include="Managers\DependencyManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SaveGameStatus.cs" />
|
||||
<Compile Include="Collections\KeyTree.cs" />
|
||||
<Compile Include="Collections\ObservableDictionary.cs" />
|
||||
@@ -176,11 +182,15 @@
|
||||
<Compile Include="Managers\UpdateManager.cs" />
|
||||
<Compile Include="Persistent.cs" />
|
||||
<Compile Include="PluginManifest.cs" />
|
||||
<Compile Include="Reflection.cs" />
|
||||
<Compile Include="Utils\Reflection.cs" />
|
||||
<Compile Include="Managers\ScriptingManager.cs" />
|
||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||
<Compile Include="Utils\ReflectedManager.cs" />
|
||||
<Compile Include="Session\TorchSessionManager.cs" />
|
||||
<Compile Include="TorchBase.cs" />
|
||||
<Compile Include="SteamService.cs" />
|
||||
<Compile Include="TorchPluginBase.cs" />
|
||||
<Compile Include="Session\TorchSession.cs" />
|
||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||
@@ -189,7 +199,6 @@
|
||||
<Compile Include="ViewModels\PlayerViewModel.cs" />
|
||||
<Compile Include="ViewModels\ViewModel.cs" />
|
||||
<Compile Include="Managers\PluginManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ViewModels\PluginViewModel.cs" />
|
||||
<Compile Include="Views\CollectionEditor.xaml.cs">
|
||||
<DependentUpon>CollectionEditor.xaml</DependentUpon>
|
||||
@@ -199,7 +208,7 @@
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -211,12 +220,9 @@
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
@@ -19,8 +19,11 @@ using SpaceEngineers.Game;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.ModAPI;
|
||||
using Torch.API.Session;
|
||||
using Torch.Commands;
|
||||
using Torch.Managers;
|
||||
using Torch.Utils;
|
||||
using Torch.Session;
|
||||
using VRage.Collections;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game.ObjectBuilder;
|
||||
@@ -36,6 +39,12 @@ namespace Torch
|
||||
/// </summary>
|
||||
public abstract class TorchBase : ViewModel, ITorchBase, IPlugin
|
||||
{
|
||||
static TorchBase()
|
||||
{
|
||||
// We can safely never detach this since we don't reload assemblies.
|
||||
new ReflectedManager().Attach();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hack because *keen*.
|
||||
/// Use only if necessary, prefer dependency injection.
|
||||
@@ -44,21 +53,36 @@ namespace Torch
|
||||
/// <inheritdoc />
|
||||
public ITorchConfig Config { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
public Version TorchVersion { get; protected set; }
|
||||
public Version TorchVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The version of Torch used, with extra data.
|
||||
/// </summary>
|
||||
public string TorchVersionVerbose { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version GameVersion { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string[] RunArgs { get; set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public IPluginManager Plugins { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public IMultiplayerManager Multiplayer { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public EntityManager Entities { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public INetworkManager Network { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public CommandManager Commands { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITorchSession CurrentSession => Managers?.GetManager<ITorchSessionManager>()?.CurrentSession;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action SessionLoading;
|
||||
/// <inheritdoc />
|
||||
@@ -72,7 +96,10 @@ namespace Torch
|
||||
/// Common log for the Torch instance.
|
||||
/// </summary>
|
||||
protected static Logger Log { get; } = LogManager.GetLogger("Torch");
|
||||
private readonly List<IManager> _managers;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDependencyManager Managers { get; }
|
||||
|
||||
private bool _init;
|
||||
|
||||
/// <summary>
|
||||
@@ -87,40 +114,40 @@ namespace Torch
|
||||
Instance = this;
|
||||
|
||||
TorchVersion = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
TorchVersionVerbose = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? TorchVersion.ToString();
|
||||
RunArgs = new string[0];
|
||||
|
||||
Managers = new DependencyManager();
|
||||
|
||||
Plugins = new PluginManager(this);
|
||||
Multiplayer = new MultiplayerManager(this);
|
||||
Entities = new EntityManager(this);
|
||||
Network = new NetworkManager(this);
|
||||
Commands = new CommandManager(this);
|
||||
|
||||
_managers = new List<IManager> { new FilesystemManager(this), new UpdateManager(this), Network, Commands, Plugins, Multiplayer, Entities, new ChatManager(this), };
|
||||
|
||||
Managers.AddManager(new TorchSessionManager(this));
|
||||
Managers.AddManager(new FilesystemManager(this));
|
||||
Managers.AddManager(new UpdateManager(this));
|
||||
Managers.AddManager(Network);
|
||||
Managers.AddManager(Commands);
|
||||
Managers.AddManager(Plugins);
|
||||
Managers.AddManager(Multiplayer);
|
||||
Managers.AddManager(Entities);
|
||||
Managers.AddManager(new ChatManager(this));
|
||||
|
||||
TorchAPI.Instance = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ListReader<IManager> GetManagers()
|
||||
{
|
||||
return new ListReader<IManager>(_managers);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetManager<T>() where T : class, IManager
|
||||
{
|
||||
return _managers.FirstOrDefault(m => m is T) as T;
|
||||
return Managers.GetManager<T>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AddManager<T>(T manager) where T : class, IManager
|
||||
{
|
||||
if (_managers.Any(x => x is T))
|
||||
return false;
|
||||
|
||||
_managers.Add(manager);
|
||||
return true;
|
||||
return Managers.AddManager(manager);
|
||||
}
|
||||
|
||||
public bool IsOnGameThread()
|
||||
@@ -136,7 +163,7 @@ namespace Torch
|
||||
{
|
||||
callback?.Invoke(SaveGameStatus.GameNotReady);
|
||||
}
|
||||
else if(MyAsyncSaving.InProgress)
|
||||
else if (MyAsyncSaving.InProgress)
|
||||
{
|
||||
callback?.Invoke(SaveGameStatus.SaveInProgress);
|
||||
}
|
||||
@@ -222,20 +249,24 @@ namespace Torch
|
||||
public virtual void Init()
|
||||
{
|
||||
Debug.Assert(!_init, "Torch instance is already initialized.");
|
||||
|
||||
SpaceEngineersGame.SetupBasicGameInfo();
|
||||
SpaceEngineersGame.SetupPerGameSettings();
|
||||
|
||||
TorchVersion = Assembly.GetEntryAssembly().GetName().Version;
|
||||
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
|
||||
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
||||
var verInfo = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
|
||||
Console.Title = verInfo;
|
||||
try { Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; }
|
||||
catch
|
||||
{
|
||||
///Running as service
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Log.Info("DEBUG");
|
||||
#else
|
||||
Log.Info("RELEASE");
|
||||
#endif
|
||||
Log.Info(verInfo);
|
||||
Log.Info($"Torch Version: {TorchVersionVerbose}");
|
||||
Log.Info($"Game Version: {GameVersion}");
|
||||
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
|
||||
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
|
||||
|
||||
@@ -244,8 +275,7 @@ namespace Torch
|
||||
MySession.OnUnloading += OnSessionUnloading;
|
||||
MySession.OnUnloaded += OnSessionUnloaded;
|
||||
RegisterVRagePlugin();
|
||||
foreach (var manager in _managers)
|
||||
manager.Init();
|
||||
Managers.Attach();
|
||||
_init = true;
|
||||
}
|
||||
|
||||
@@ -313,7 +343,7 @@ namespace Torch
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Plugins.DisposePlugins();
|
||||
Managers.Detach();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -325,7 +355,7 @@ namespace Torch
|
||||
/// <inheritdoc />
|
||||
public virtual void Update()
|
||||
{
|
||||
Plugins.UpdatePlugins();
|
||||
GetManager<IPluginManager>().UpdatePlugins();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
394
Torch/Utils/ReflectedManager.cs
Normal file
394
Torch/Utils/ReflectedManager.cs
Normal file
@@ -0,0 +1,394 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Torch.API;
|
||||
|
||||
namespace Torch.Utils
|
||||
{
|
||||
public abstract class ReflectedMemberAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the member to access. If null, the tagged field's name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Declaring type of the member to access. If null, inferred from the instance argument type.
|
||||
/// </summary>
|
||||
public Type Type { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Assembly qualified name of <see cref="Type"/>
|
||||
/// </summary>
|
||||
public string TypeName
|
||||
{
|
||||
get => Type?.AssemblyQualifiedName;
|
||||
set => Type = value == null ? null : Type.GetType(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// [ReflectedGetterAttribute(Name="_instanceField")]
|
||||
/// private static Func<Example, int> _instanceGetter;
|
||||
///
|
||||
/// [ReflectedGetterAttribute(Name="_staticField", Type=typeof(Example))]
|
||||
/// private static Func<int> _staticGetter;
|
||||
///
|
||||
/// private class Example {
|
||||
/// private int _instanceField;
|
||||
/// private static int _staticField;
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedGetterAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain a delegate capable of setting the value of a field.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// [ReflectedSetterAttribute(Name="_instanceField")]
|
||||
/// private static Action<Example, int> _instanceSetter;
|
||||
///
|
||||
/// [ReflectedSetterAttribute(Name="_staticField", Type=typeof(Example))]
|
||||
/// private static Action<int> _staticSetter;
|
||||
///
|
||||
/// private class Example {
|
||||
/// private int _instanceField;
|
||||
/// private static int _staticField;
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedSetterAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain a delegate capable of invoking an instance method.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// [ReflectedMethodAttribute]
|
||||
/// private static Func<Example, int, float, string> ExampleInstance;
|
||||
///
|
||||
/// private class Example {
|
||||
/// private int ExampleInstance(int a, float b) {
|
||||
/// return a + ", " + b;
|
||||
/// }
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedMethodAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// When set the parameters types for the method are assumed to be this.
|
||||
/// </summary>
|
||||
public Type[] OverrideTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Assembly qualified names of <see cref="OverrideTypes"/>
|
||||
/// </summary>
|
||||
public string[] OverrideTypeNames
|
||||
{
|
||||
get => OverrideTypes.Select(x => x.AssemblyQualifiedName).ToArray();
|
||||
set => OverrideTypes = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain a delegate capable of invoking a static method.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// [ReflectedMethodAttribute(Type = typeof(Example)]
|
||||
/// private static Func<int, float, string> ExampleStatic;
|
||||
///
|
||||
/// private class Example {
|
||||
/// private static int ExampleStatic(int a, float b) {
|
||||
/// return a + ", " + b;
|
||||
/// }
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
|
||||
/// </summary>
|
||||
public class ReflectedManager
|
||||
{
|
||||
private static readonly string[] _namespaceBlacklist = new[] {
|
||||
"System", "VRage", "Sandbox", "SpaceEngineers"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Registers the assembly load event and loads every already existing assembly.
|
||||
/// </summary>
|
||||
public void Attach()
|
||||
{
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
Process(asm);
|
||||
AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deregisters the assembly load event
|
||||
/// </summary>
|
||||
public void Detach()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomain_AssemblyLoad;
|
||||
}
|
||||
|
||||
private void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
||||
{
|
||||
Process(args.LoadedAssembly);
|
||||
}
|
||||
|
||||
private static readonly HashSet<Type> _processedTypes = new HashSet<Type>();
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all reflected fields and methods contained in the given type are initialized
|
||||
/// </summary>
|
||||
/// <param name="t">Type to process</param>
|
||||
public static void Process(Type t)
|
||||
{
|
||||
if (_processedTypes.Add(t))
|
||||
{
|
||||
foreach (string ns in _namespaceBlacklist)
|
||||
if (t.FullName.StartsWith(ns))
|
||||
return;
|
||||
foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
Process(field);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all types in the given assembly are initialized using <see cref="Process(Type)"/>
|
||||
/// </summary>
|
||||
/// <param name="asm">Assembly to process</param>
|
||||
public static void Process(Assembly asm)
|
||||
{
|
||||
foreach (Type type in asm.GetTypes())
|
||||
Process(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the given field, determines if it's reflected, and initializes it if it is.
|
||||
/// </summary>
|
||||
/// <param name="field">Field to process</param>
|
||||
/// <returns>true if it was reflected, false if it wasn't reflectable</returns>
|
||||
/// <exception cref="ArgumentException">If the field failed to process</exception>
|
||||
public static bool Process(FieldInfo field)
|
||||
{
|
||||
var attr = field.GetCustomAttribute<ReflectedMethodAttribute>();
|
||||
if (attr != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
ProcessReflectedMethod(field, attr);
|
||||
return true;
|
||||
}
|
||||
var attr2 = field.GetCustomAttribute<ReflectedGetterAttribute>();
|
||||
if (attr2 != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
ProcessReflectedField(field, attr2);
|
||||
return true;
|
||||
}
|
||||
var attr3 = field.GetCustomAttribute<ReflectedSetterAttribute>();
|
||||
if (attr3 != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
{
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
}
|
||||
|
||||
ProcessReflectedField(field, attr3);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ProcessReflectedMethod(FieldInfo field, ReflectedMethodAttribute attr)
|
||||
{
|
||||
MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke");
|
||||
ParameterInfo[] parameters = delegateMethod.GetParameters();
|
||||
Type trueType = attr.Type;
|
||||
Type[] trueParameterTypes;
|
||||
if (attr is ReflectedStaticMethodAttribute)
|
||||
{
|
||||
trueParameterTypes = parameters.Select(x => x.ParameterType).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
trueType = trueType ?? parameters[0].ParameterType;
|
||||
trueParameterTypes = parameters.Skip(1).Select(x => x.ParameterType).ToArray();
|
||||
}
|
||||
|
||||
var invokeTypes = new Type[trueParameterTypes.Length];
|
||||
for (var i = 0; i < invokeTypes.Length; i++)
|
||||
invokeTypes[i] = attr.OverrideTypes?[i] ?? trueParameterTypes[i];
|
||||
|
||||
MethodInfo methodInstance = trueType.GetMethod(attr.Name ?? field.Name,
|
||||
(attr is ReflectedStaticMethodAttribute ? BindingFlags.Static : BindingFlags.Instance) |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic, null, CallingConventions.Any, invokeTypes, null);
|
||||
if (methodInstance == null)
|
||||
{
|
||||
string methodType = attr is ReflectedStaticMethodAttribute ? "static" : "instance";
|
||||
string methodParams = string.Join(", ",
|
||||
trueParameterTypes.Select(x => x.Name));
|
||||
throw new NoNullAllowedException(
|
||||
$"Unable to find {methodType} method {attr.Name ?? field.Name} in type {trueType.FullName} with parameters {methodParams}");
|
||||
}
|
||||
|
||||
|
||||
if (attr is ReflectedStaticMethodAttribute)
|
||||
{
|
||||
if (attr.OverrideTypes != null)
|
||||
{
|
||||
ParameterExpression[] paramExp =
|
||||
parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
|
||||
var argExp = new Expression[invokeTypes.Length];
|
||||
for (var i = 0; i < argExp.Length; i++)
|
||||
if (invokeTypes[i] != paramExp[i].Type)
|
||||
argExp[i] = Expression.Convert(paramExp[i], invokeTypes[i]);
|
||||
else
|
||||
argExp[i] = paramExp[i];
|
||||
field.SetValue(null,
|
||||
Expression.Lambda(Expression.Call(methodInstance, argExp), paramExp)
|
||||
.Compile());
|
||||
}
|
||||
else
|
||||
field.SetValue(null, Delegate.CreateDelegate(field.FieldType, methodInstance));
|
||||
}
|
||||
else
|
||||
{
|
||||
ParameterExpression[] paramExp =
|
||||
parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
|
||||
var argExp = new Expression[invokeTypes.Length];
|
||||
for (var i = 0; i < argExp.Length; i++)
|
||||
if (invokeTypes[i] != paramExp[i + 1].Type)
|
||||
argExp[i] = Expression.Convert(paramExp[i + 1], invokeTypes[i]);
|
||||
else
|
||||
argExp[i] = paramExp[i + 1];
|
||||
field.SetValue(null,
|
||||
Expression.Lambda(Expression.Call(paramExp[0], methodInstance, argExp), paramExp)
|
||||
.Compile());
|
||||
}
|
||||
}
|
||||
|
||||
private static T GetFieldPropRecursive<T>(Type baseType, string name, BindingFlags flags, Func<Type, string, BindingFlags, T> getter) where T : class
|
||||
{
|
||||
while (baseType != null)
|
||||
{
|
||||
T result = getter.Invoke(baseType, name, flags);
|
||||
if (result != null)
|
||||
return result;
|
||||
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ProcessReflectedField(FieldInfo field, ReflectedMemberAttribute attr)
|
||||
{
|
||||
MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke");
|
||||
ParameterInfo[] parameters = delegateMethod.GetParameters();
|
||||
string trueName = attr.Name ?? field.Name;
|
||||
Type trueType = attr.Type;
|
||||
bool isStatic;
|
||||
if (attr is ReflectedSetterAttribute)
|
||||
{
|
||||
if (delegateMethod.ReturnType != typeof(void) || (parameters.Length != 1 && parameters.Length != 2))
|
||||
throw new ArgumentOutOfRangeException(nameof(field),
|
||||
"Delegate for setter must be an action with one or two arguments");
|
||||
|
||||
isStatic = parameters.Length == 1;
|
||||
if (trueType == null && isStatic)
|
||||
throw new ArgumentException("Static field setters need their type defined", nameof(field));
|
||||
|
||||
if (!isStatic && trueType == null)
|
||||
trueType = parameters[0].ParameterType;
|
||||
}
|
||||
else if (attr is ReflectedGetterAttribute)
|
||||
{
|
||||
if (delegateMethod.ReturnType == typeof(void) || (parameters.Length != 0 && parameters.Length != 1))
|
||||
throw new ArgumentOutOfRangeException(nameof(field),
|
||||
"Delegate for getter must be an function with one or no arguments");
|
||||
|
||||
isStatic = parameters.Length == 0;
|
||||
if (trueType == null && isStatic)
|
||||
throw new ArgumentException("Static field getters need their type defined", nameof(field));
|
||||
|
||||
if (!isStatic && trueType == null)
|
||||
trueType = parameters[0].ParameterType;
|
||||
}
|
||||
else
|
||||
throw new ArgumentException($"Field attribute type {attr.GetType().FullName} is invalid", nameof(field));
|
||||
|
||||
BindingFlags bindingFlags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Public;
|
||||
FieldInfo sourceField = GetFieldPropRecursive(trueType, trueName, bindingFlags,
|
||||
(a, b, c) => a.GetField(b, c));
|
||||
PropertyInfo sourceProperty =
|
||||
GetFieldPropRecursive(trueType, trueName, bindingFlags, (a, b, c) => a.GetProperty(b, c));
|
||||
if (sourceField == null && sourceProperty == null)
|
||||
throw new ArgumentException(
|
||||
$"Unable to find field or property for {trueName} in {trueType.FullName} or its base types", nameof(field));
|
||||
|
||||
ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
|
||||
Expression instanceExpr = null;
|
||||
if (!isStatic)
|
||||
{
|
||||
instanceExpr = trueType == paramExp[0].Type ? (Expression) paramExp[0] : Expression.Convert(paramExp[0], trueType);
|
||||
}
|
||||
|
||||
MemberExpression fieldExp = sourceField != null
|
||||
? Expression.Field(instanceExpr, sourceField)
|
||||
: Expression.Property(instanceExpr, sourceProperty);
|
||||
Expression impl;
|
||||
if (attr is ReflectedSetterAttribute)
|
||||
{
|
||||
impl = Expression.Block(Expression.Assign(fieldExp, paramExp[isStatic ? 0 : 1]), Expression.Default(typeof(void)));
|
||||
}
|
||||
else
|
||||
{
|
||||
impl = fieldExp;
|
||||
}
|
||||
|
||||
field.SetValue(null, Expression.Lambda(impl, paramExp).Compile());
|
||||
}
|
||||
}
|
||||
}
|
227
Torch/Utils/Reflection.cs
Normal file
227
Torch/Utils/Reflection.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using SteamSDK;
|
||||
|
||||
namespace Torch.Utils
|
||||
{
|
||||
public static class Reflection
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetLogger("Reflection");
|
||||
|
||||
public static bool HasMethod(Type type, string methodName, Type[] argTypes = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(methodName))
|
||||
return false;
|
||||
|
||||
if (argTypes == null)
|
||||
{
|
||||
var methodInfo = type.GetMethod(methodName);
|
||||
if (methodInfo == null)
|
||||
methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (methodInfo == null && type.BaseType != null)
|
||||
methodInfo = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (methodInfo == null)
|
||||
{
|
||||
Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo method = type.GetMethod(methodName, argTypes);
|
||||
if (method == null)
|
||||
method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
||||
if (method == null && type.BaseType != null)
|
||||
method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
||||
if (method == null)
|
||||
{
|
||||
Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "': " + ex.Message);
|
||||
Log.Error(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasField(Type type, string fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(fieldName))
|
||||
return false;
|
||||
var field = type.GetField(fieldName);
|
||||
if (field == null)
|
||||
field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (field == null)
|
||||
field = type.BaseType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (field == null)
|
||||
{
|
||||
Log.Error("Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasProperty(Type type, string propertyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
return false;
|
||||
var prop = type.GetProperty(propertyName);
|
||||
if (prop == null)
|
||||
prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (prop == null)
|
||||
prop = type.BaseType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
if (prop == null)
|
||||
{
|
||||
Log.Error("Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the static method of the given type, with the given arguments.
|
||||
/// </summary>
|
||||
/// <param name="type">Type the method is contained in</param>
|
||||
/// <param name="methodName">Method name</param>
|
||||
/// <param name="args">Arguments to the method</param>
|
||||
/// <returns>return value of the invoked method, or null if it failed</returns>
|
||||
public static object InvokeStaticMethod(Type type, string methodName, params object[] args)
|
||||
{
|
||||
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method == null)
|
||||
{
|
||||
Log.Error($"Method {methodName} not found in static class {type.FullName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return method.Invoke(null, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the private method with the given arguments on the instance. Includes base types of instance.
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns>the return value of the method, or null if it failed</returns>
|
||||
public static object InvokePrivateMethod(object instance, string methodName, params object[] args)
|
||||
{
|
||||
Type type = instance.GetType();
|
||||
while (type != null)
|
||||
{
|
||||
MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method != null)
|
||||
return method.Invoke(instance, args);
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
Log.Error($"Method {methodName} not found in type {instance.GetType().FullName} or its parents");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a private field in an instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the private field</typeparam>
|
||||
/// <param name="obj">The instance</param>
|
||||
/// <param name="fieldName">Field name</param>
|
||||
/// <param name="recurse">Should the base types be examined</param>
|
||||
/// <returns></returns>
|
||||
public static T GetPrivateField<T>(this object obj, string fieldName, bool recurse = false)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
while (type != null)
|
||||
{
|
||||
FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field != null)
|
||||
return (T)field
|
||||
.GetValue(obj);
|
||||
|
||||
if (!recurse)
|
||||
break;
|
||||
type = type.BaseType;
|
||||
}
|
||||
Log.Error($"Field {fieldName} not found in type {obj.GetType().FullName}" + (recurse ? " or its parents" : ""));
|
||||
return default(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all delegates registered in the named static event
|
||||
/// </summary>
|
||||
/// <param name="type">The type (or child type) that contains the event</param>
|
||||
/// <param name="eventName">Name of the event</param>
|
||||
/// <returns>All delegates registered with the event</returns>
|
||||
public static IEnumerable<Delegate> GetStaticEvent(Type type, string eventName)
|
||||
{
|
||||
return GetEventsInternal(null, eventName, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all delegates registered in the named event
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance to retrieve the event list for</param>
|
||||
/// <param name="eventName">Name of the event</param>
|
||||
/// <returns>All delegates registered with the event</returns>
|
||||
public static IEnumerable<Delegate> GetInstanceEvent(object instance, string eventName)
|
||||
{
|
||||
return GetEventsInternal(instance, eventName);
|
||||
}
|
||||
|
||||
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
|
||||
private static IEnumerable<Delegate> GetEventsInternal(object instance, string eventName, Type baseType = null)
|
||||
{
|
||||
BindingFlags bindingFlags = BindingFlags.NonPublic |
|
||||
(instance == null ? BindingFlags.Static : BindingFlags.Instance);
|
||||
|
||||
FieldInfo eventField = null;
|
||||
baseType = baseType ?? instance?.GetType();
|
||||
Type type = baseType;
|
||||
while (type != null && eventField == null)
|
||||
{
|
||||
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
|
||||
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), bindingFlags);
|
||||
type = type.BaseType;
|
||||
}
|
||||
if (eventField?.GetValue(instance) is MulticastDelegate eventDel)
|
||||
{
|
||||
foreach (Delegate handle in eventDel.GetInvocationList())
|
||||
yield return handle;
|
||||
}
|
||||
else
|
||||
Log.Error($"{eventName} doesn't have a backing store in {baseType} or its parents.");
|
||||
}
|
||||
}
|
||||
}
|
98
Torch/Utils/TorchAssemblyResolver.cs
Normal file
98
Torch/Utils/TorchAssemblyResolver.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
|
||||
namespace Torch.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds and removes an additional library path
|
||||
/// </summary>
|
||||
public class TorchAssemblyResolver : IDisposable
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly Dictionary<string, Assembly> _assemblies = new Dictionary<string, Assembly>();
|
||||
private readonly string[] _paths;
|
||||
private readonly string _removablePathPrefix;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an assembly resolver that looks at the given paths for assemblies
|
||||
/// </summary>
|
||||
/// <param name="paths"></param>
|
||||
public TorchAssemblyResolver(params string[] paths)
|
||||
{
|
||||
string location = Assembly.GetEntryAssembly()?.Location ?? GetType().Assembly.Location;
|
||||
location = location != null ? Path.GetDirectoryName(location) + Path.DirectorySeparatorChar : null;
|
||||
_removablePathPrefix = location ?? "";
|
||||
_paths = paths;
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
|
||||
}
|
||||
|
||||
private string SimplifyPath(string path)
|
||||
{
|
||||
return path.StartsWith(_removablePathPrefix) ? path.Substring(_removablePathPrefix.Length) : path;
|
||||
}
|
||||
|
||||
private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
string assemblyName = new AssemblyName(args.Name).Name;
|
||||
lock (_assemblies)
|
||||
{
|
||||
if (_assemblies.TryGetValue(assemblyName, out Assembly asm))
|
||||
return asm;
|
||||
}
|
||||
lock (AppDomain.CurrentDomain)
|
||||
{
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
if (asm.GetName().Name.Equals(assemblyName))
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
_assemblies.Add(assemblyName, asm);
|
||||
return asm;
|
||||
}
|
||||
}
|
||||
}
|
||||
lock (this)
|
||||
{
|
||||
foreach (string path in _paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
string assemblyPath = Path.Combine(path, assemblyName + ".dll");
|
||||
if (!File.Exists(assemblyPath))
|
||||
continue;
|
||||
_log.Debug("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath));
|
||||
LogManager.Flush();
|
||||
Assembly asm = Assembly.LoadFrom(assemblyPath);
|
||||
_assemblies.Add(assemblyName, asm);
|
||||
// Recursively load SE dependencies since they don't trigger AssemblyResolve.
|
||||
// This trades some performance on load for actually working code.
|
||||
foreach (AssemblyName dependency in asm.GetReferencedAssemblies())
|
||||
CurrentDomainOnAssemblyResolve(sender, new ResolveEventArgs(dependency.Name, asm));
|
||||
return asm;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the assembly resolver
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainOnAssemblyResolve;
|
||||
_assemblies.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
<package id="Octokit" version="0.24.0" targetFramework="net461" />
|
||||
</packages>
|
9
TransformOnBuild.targets
Normal file
9
TransformOnBuild.targets
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Target Name="TransformOnBuild">
|
||||
<ItemGroup>
|
||||
<TemplatePaths Include="@(None)" Condition="'%(None.Generator)' == 'TextTemplatingFileGenerator'" />
|
||||
</ItemGroup>
|
||||
<Exec Command=""$(SolutionDir)\packages\Mono.TextTransform.1.0.0\tools\TextTransform.exe" "%(TemplatePaths.FullPath)"" Condition="'%(TemplatePaths.Identity)' != ''"/>
|
||||
</Target>
|
||||
</Project>
|
4
Versioning/AssemblyVersion.cs
Normal file
4
Versioning/AssemblyVersion.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("0.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("v0.0.0-dev")]
|
20
Versioning/version.ps1
Normal file
20
Versioning/version.ps1
Normal file
@@ -0,0 +1,20 @@
|
||||
$gitVersion = git describe --tags
|
||||
$gitSimpleVersion = git describe --tags --abbrev=0
|
||||
if ($gitSimpleVersion.Equals($gitVersion)) {
|
||||
$buildSalt = 0
|
||||
} else {
|
||||
$gitLatestCommit = git rev-parse --short HEAD
|
||||
$buildSalt = [System.Numerics.BigInteger]::Abs([System.Numerics.BigInteger]::Parse($gitLatestCommit, [System.Globalization.NumberStyles]::HexNumber) % 16383) + 1
|
||||
}
|
||||
$dotNetVersion = echo $gitSimpleVersion | Select-String -Pattern "([0-9]+)\.([0-9]+)\.([0-9]+)" | % {$_.Matches} | %{$_.Groups[1].Value+"."+$_.Groups[2].Value+"."+$_.Groups[3].Value+".$buildSalt"}
|
||||
|
||||
$fileContent = @"
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("$dotNetVersion")]
|
||||
[assembly: AssemblyInformationalVersion("$gitVersion")]
|
||||
"@
|
||||
|
||||
echo $fileContent | Set-Content "$PSScriptRoot/AssemblyVersion.cs"
|
||||
|
||||
echo "$gitVersion / $dotNetVersion"
|
Reference in New Issue
Block a user