diff --git a/Jenkinsfile b/Jenkinsfile
index 3b62660..d1f7870 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -71,16 +71,4 @@ node {
]]
])
}
-
- if (env.BRANCH_NAME == "master") {
- gitVersion = bat(returnStdout: true, script: "@git describe --tags").trim()
- gitSimpleVersion = bat(returnStdout: true, script: "@git describe --tags --abbrev=0").trim()
- if (gitVersion == gitSimpleVersion) {
- stage('${buildMode}') {
- withCredentials([usernamePassword(credentialsId: 'torch-github', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
- powershell "& ./Jenkins/${buildMode}.ps1 \"https://api.github.com/repos/TorchAPI/Torch/\" \"$gitSimpleVersion\" \"$USERNAME:$PASSWORD\" @(\"bin/torch-server.zip\", \"bin/torch-client.zip\")"
- }
- }
- }
- }
}
\ No newline at end of file
diff --git a/Torch.API/Event/IEventManager.cs b/Torch.API/Event/IEventManager.cs
index 038db33..29ad642 100644
--- a/Torch.API/Event/IEventManager.cs
+++ b/Torch.API/Event/IEventManager.cs
@@ -1,11 +1,12 @@
using System.Runtime.CompilerServices;
+using Torch.API.Managers;
namespace Torch.API.Event
{
///
/// Manager class responsible for registration of event handlers.
///
- public interface IEventManager
+ public interface IEventManager : IManager
{
///
/// Registers all event handler methods contained in the given instance
diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs
index ba766c5..a89534c 100644
--- a/Torch.API/ITorchBase.cs
+++ b/Torch.API/ITorchBase.cs
@@ -80,12 +80,12 @@ namespace Torch.API
Task InvokeAsync(Action action, [CallerMemberName] string caller = "");
///
- /// Start the Torch instance.
+ /// Signals the torch instance to start, then blocks until it's started.
///
void Start();
///
- /// Stop the Torch instance.
+ /// Signals the torch instance to stop, then blocks until it's stopped.
///
void Stop();
@@ -101,10 +101,15 @@ namespace Torch.API
Task Save(long callerId);
///
- /// Initialize the Torch instance.
+ /// Initialize the Torch instance. Before this is invalid.
///
void Init();
+ ///
+ /// Disposes the Torch instance. After this is invalid.
+ ///
+ void Dispose();
+
///
/// The current state of the game this instance of torch is controlling.
///
diff --git a/Torch.Client/Torch.Client.csproj b/Torch.Client/Torch.Client.csproj
index 9ed8a34..40f2506 100644
--- a/Torch.Client/Torch.Client.csproj
+++ b/Torch.Client/Torch.Client.csproj
@@ -127,7 +127,6 @@
-
@@ -140,6 +139,9 @@
Settings.settings
True
+
+
+
ResXFileCodeGenerator
Resources.Designer.cs
diff --git a/Torch.Client/TorchClient.cs b/Torch.Client/TorchClient.cs
index 839c40a..22d2d15 100644
--- a/Torch.Client/TorchClient.cs
+++ b/Torch.Client/TorchClient.cs
@@ -14,6 +14,7 @@ using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Client.Manager;
+using Torch.Client.UI;
using Torch.Session;
using VRage;
using VRage.FileSystem;
@@ -27,7 +28,9 @@ namespace Torch.Client
{
private MyCommonProgramStartup _startup;
private IMyRender _renderer;
- private const uint APP_ID = 244850;
+
+ protected override uint SteamAppId => 244850;
+ protected override string SteamAppName => "Space Engineers";
public TorchClient()
{
@@ -46,10 +49,9 @@ namespace Torch.Client
Directory.SetCurrentDirectory(Program.SpaceEngineersInstallAlias);
MyFileSystem.ExePath = Path.Combine(Program.SpaceEngineersInstallAlias, Program.SpaceEngineersBinaries);
Log.Info("Initializing Torch Client");
- base.Init();
-
- SpaceEngineersGame.SetupBasicGameInfo();
_startup = new MyCommonProgramStartup(RunArgs);
+ SpaceEngineersGame.SetupBasicGameInfo();
+ SpaceEngineersGame.SetupPerGameSettings();
if (_startup.PerformReporting())
throw new InvalidOperationException("Torch client won't launch when started in error reporting mode");
@@ -58,27 +60,16 @@ namespace Torch.Client
throw new InvalidOperationException("Only one instance of Space Engineers can be running at a time.");
var appDataPath = _startup.GetAppDataPath();
- MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath);
- MyInitializer.InitCheckSum();
- _startup.InitSplashScreen();
- if (!_startup.Check64Bit())
- throw new InvalidOperationException("Torch requires a 64bit operating system");
+ Config.InstancePath = appDataPath;
+ base.Init();
+ OverrideMenus();
+ SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
+ }
- _startup.DetectSharpDxLeaksBeforeRun();
- var steamService = new SteamService(Game.IsDedicated, APP_ID);
- MyServiceManager.Instance.AddService(steamService);
- _renderer = null;
- SpaceEngineersGame.SetupPerGameSettings();
- // I'm sorry, but it's what Keen does in SpaceEngineers.MyProgram
-#pragma warning disable 612
- SpaceEngineersGame.SetupRender();
-#pragma warning restore 612
- InitializeRender();
- if (!_startup.CheckSteamRunning())
- throw new InvalidOperationException("Space Engineers requires steam to be running");
-
- if (!Game.IsDedicated)
- MyFileSystem.InitUserSpecific(MyGameService.UserId.ToString());
+ public override void Dispose()
+ {
+ base.Dispose();
+ _startup.DetectSharpDxLeaksAfterRun();
}
private void OverrideMenus()
@@ -95,18 +86,6 @@ namespace Torch.Client
MyPerGameSettings.GUI.MainMenu = typeof(TorchMainMenuScreen);
}
-
- public override void Start()
- {
- using (var spaceEngineersGame = new SpaceEngineersGame(RunArgs))
- {
- Log.Info("Starting client");
- OverrideMenus();
- spaceEngineersGame.OnGameLoaded += SpaceEngineersGame_OnGameLoaded;
- spaceEngineersGame.OnGameExit += Dispose;
- spaceEngineersGame.Run(false, _startup.DisposeSplashScreen);
- }
- }
private void SetRenderWindowTitle(string title)
{
@@ -125,58 +104,9 @@ namespace Torch.Client
});
}
- private void SpaceEngineersGame_OnGameLoaded(object sender, EventArgs e)
+ public override void Restart()
{
- SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
- }
-
- public override void Dispose()
- {
- MyGameService.ShutDown();
- _startup.DetectSharpDxLeaksAfterRun();
- MyInitializer.InvokeAfterRun();
- }
-
- public override void Stop()
- {
- MySandboxGame.ExitThreadSafe();
- }
-
- private void InitializeRender()
- {
- try
- {
- if (Game.IsDedicated)
- {
- _renderer = new MyNullRender();
- }
- else
- {
- var graphicsRenderer = MySandboxGame.Config.GraphicsRenderer;
- if (graphicsRenderer == MySandboxGame.DirectX11RendererKey)
- {
- _renderer = new MyDX11Render();
- if (!_renderer.IsSupported)
- {
- MySandboxGame.Log.WriteLine("DirectX 11 renderer not supported. No renderer to revert back to.");
- _renderer = null;
- }
- }
- if (_renderer == null)
- throw new MyRenderException("The current version of the game requires a Dx11 card. \\n For more information please see : http://blog.marekrosa.org/2016/02/space-engineers-news-full-source-code_26.html", MyRenderExceptionEnum.GpuNotSupported);
-
- MySandboxGame.Config.GraphicsRenderer = graphicsRenderer;
- }
-
- MyRenderProxy.Initialize(_renderer);
- MyRenderProxy.GetRenderProfiler().SetAutocommit(false);
- MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message, "Render Initialization Failed");
- Environment.Exit(-1);
- }
+ throw new NotImplementedException();
}
}
}
diff --git a/Torch.Client/TorchMainMenuScreen.cs b/Torch.Client/TorchMainMenuScreen.cs
deleted file mode 100644
index 0bd9e5c..0000000
--- a/Torch.Client/TorchMainMenuScreen.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma warning disable 618
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Sandbox.Game.Gui;
-using Sandbox.Graphics;
-using Sandbox.Graphics.GUI;
-using Sandbox.Gui;
-using SpaceEngineers.Game.GUI;
-using VRage.Game;
-using VRage.Utils;
-using VRageMath;
-
-namespace Torch.Client
-{
- public class TorchMainMenuScreen : MyGuiScreenMainMenu
- {
- public TorchMainMenuScreen()
- : this(false)
- {
- }
-
- public TorchMainMenuScreen(bool pauseGame)
- : base(pauseGame)
- {
- }
- ///
- public override void RecreateControls(bool constructor)
- {
- base.RecreateControls(constructor);
-
- var buttonSize = MyGuiControlButton.GetVisualStyle(MyGuiControlButtonStyleEnum.Default).NormalTexture.MinSizeGui;
- Vector2 leftButtonPositionOrigin = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_BOTTOM) + new Vector2(buttonSize.X / 2f, 0f);
- var btn = MakeButton(leftButtonPositionOrigin - 9 * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA, MyStringId.GetOrCompute("Torch"), TorchButtonClicked);
- Controls.Add(btn);
- }
-
- private void TorchButtonClicked(MyGuiControlButton obj)
- {
- MyGuiSandbox.AddScreen(new TorchSettingsScreen());
- }
- }
-}
diff --git a/Torch.Client/UI/TorchMainMenuScreen.cs b/Torch.Client/UI/TorchMainMenuScreen.cs
new file mode 100644
index 0000000..2ccbfe6
--- /dev/null
+++ b/Torch.Client/UI/TorchMainMenuScreen.cs
@@ -0,0 +1,56 @@
+#pragma warning disable 618
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Sandbox.Engine.Networking;
+using Sandbox.Engine.Utils;
+using Sandbox.Game;
+using Sandbox.Game.Gui;
+using Sandbox.Graphics;
+using Sandbox.Graphics.GUI;
+using Sandbox.Gui;
+using SpaceEngineers.Game.GUI;
+using Torch.Utils;
+using VRage.Game;
+using VRage.Utils;
+using VRageMath;
+
+namespace Torch.Client.UI
+{
+ public class TorchMainMenuScreen : MyGuiScreenMainMenu
+ {
+#pragma warning disable 169
+ [ReflectedGetter(Name = "m_elementGroup")]
+ private static Func _elementsGroup;
+#pragma warning restore 169
+
+ public TorchMainMenuScreen() : this(false)
+ {
+ }
+
+ public TorchMainMenuScreen(bool pauseGame)
+ : base(pauseGame)
+ {
+ }
+ ///
+ public override void RecreateControls(bool constructor)
+ {
+ base.RecreateControls(constructor);
+
+ Vector2 minSizeGui = MyGuiControlButton.GetVisualStyle(MyGuiControlButtonStyleEnum.Default).NormalTexture.MinSizeGui;
+ Vector2 value = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_BOTTOM, 54, 54) + new Vector2(minSizeGui.X / 2f, 0f) + new Vector2(15f, 0f) / MyGuiConstants.GUI_OPTIMAL_SIZE;
+
+ MyGuiControlButton myGuiControlButton = MakeButton(value - 9 * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA,
+ MyStringId.GetOrCompute("Torch"), TorchButtonClicked, MyCommonTexts.ToolTipExitToWindows);
+ Controls.Add(myGuiControlButton);
+ _elementsGroup.Invoke(this).Add(myGuiControlButton);
+ }
+
+ private void TorchButtonClicked(MyGuiControlButton obj)
+ {
+ MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen());
+ }
+ }
+}
diff --git a/Torch.Client/UI/TorchNavScreen.cs b/Torch.Client/UI/TorchNavScreen.cs
new file mode 100644
index 0000000..9b657a2
--- /dev/null
+++ b/Torch.Client/UI/TorchNavScreen.cs
@@ -0,0 +1,49 @@
+using Sandbox;
+using Sandbox.Game.Gui;
+using Sandbox.Graphics.GUI;
+using VRage;
+using VRage.Game;
+using VRage.Utils;
+using VRageMath;
+
+namespace Torch.Client.UI
+{
+ public class TorchNavScreen : MyGuiScreenBase
+ {
+ private MyGuiControlElementGroup _elementGroup;
+
+ public TorchNavScreen() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR, new Vector2(0.35875f, 0.558333337f))
+ {
+ EnabledBackgroundFade = true;
+ RecreateControls(true);
+ }
+
+ public override void RecreateControls(bool constructor)
+ {
+ base.RecreateControls(constructor);
+ _elementGroup = new MyGuiControlElementGroup();
+ _elementGroup.HighlightChanged += ElementGroupHighlightChanged;
+ AddCaption(MyCommonTexts.ScreenCaptionOptions, null, null);
+ var value = new Vector2(0f, -m_size.Value.Y / 2f + 0.146f);
+ var num = 0;
+ var myGuiControlButton = new MyGuiControlButton(value + num++ * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA, MyGuiControlButtonStyleEnum.Default, null, null, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, null, MyTexts.Get(MyCommonTexts.ScreenOptionsButtonGame), 0.8f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, MyGuiControlHighlightType.WHEN_ACTIVE, delegate(MyGuiControlButton sender)
+ {
+ MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen());
+ }, GuiSounds.MouseClick, 1f, null);
+ Controls.Add(myGuiControlButton);
+ _elementGroup.Add(myGuiControlButton);
+ CloseButtonEnabled = true;
+ }
+
+ private void ElementGroupHighlightChanged(MyGuiControlElementGroup obj)
+ {
+ foreach (MyGuiControlBase current in _elementGroup)
+ if (current.HasFocus && obj.SelectedElement != current)
+ FocusedControl = obj.SelectedElement;
+ }
+
+ public override string GetFriendlyName() => "Torch";
+
+ public void OnBackClick(MyGuiControlButton sender) => CloseScreen();
+ }
+}
diff --git a/Torch.Client/UI/TorchSettingsScreen.cs b/Torch.Client/UI/TorchSettingsScreen.cs
new file mode 100644
index 0000000..07f4726
--- /dev/null
+++ b/Torch.Client/UI/TorchSettingsScreen.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Sandbox.Graphics.GUI;
+using VRageMath;
+
+namespace Torch.Client.UI
+{
+ public class TorchSettingsScreen : MyGuiScreenBase
+ {
+ public TorchSettingsScreen() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR,
+ new Vector2(0.35875f, 0.558333337f))
+ {
+ EnabledBackgroundFade = true;
+ RecreateControls(true);
+ }
+
+ ///
+ public override string GetFriendlyName() => "Torch Settings";
+
+ public void OnBackClick(MyGuiControlButton sender) => CloseScreen();
+ }
+}
diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs
index b025727..020171f 100644
--- a/Torch.Server/Initializer.cs
+++ b/Torch.Server/Initializer.cs
@@ -90,7 +90,7 @@ quit";
{
var ui = new TorchUI(_server);
if (_config.Autostart)
- new Thread(_server.Start).Start();
+ _server.Start();
ui.ShowDialog();
}
else
diff --git a/Torch.Server/Managers/InstanceManager.cs b/Torch.Server/Managers/InstanceManager.cs
index f11a446..ab26246 100644
--- a/Torch.Server/Managers/InstanceManager.cs
+++ b/Torch.Server/Managers/InstanceManager.cs
@@ -32,23 +32,13 @@ namespace Torch.Server.Managers
{
}
-
- ///
- public override void Attach()
- {
- MyFileSystem.ExePath = Path.Combine(_filesystemManager.TorchDirectory, "DedicatedServer64");
- MyFileSystem.Init("Content", Torch.Config.InstancePath);
- //Initializes saves path. Why this isn't in Init() we may never know.
- MyFileSystem.InitUserSpecific(null);
- }
-
+
public void LoadInstance(string path, bool validate = true)
{
if (validate)
ValidateInstance(path);
MyFileSystem.Reset();
- MyFileSystem.ExePath = Path.Combine(_filesystemManager.TorchDirectory, "DedicatedServer64");
MyFileSystem.Init("Content", path);
//Initializes saves path. Why this isn't in Init() we may never know.
MyFileSystem.InitUserSpecific(null);
diff --git a/Torch.Server/Managers/MultiplayerManagerDedicated.cs b/Torch.Server/Managers/MultiplayerManagerDedicated.cs
index d8427fc..e08521e 100644
--- a/Torch.Server/Managers/MultiplayerManagerDedicated.cs
+++ b/Torch.Server/Managers/MultiplayerManagerDedicated.cs
@@ -25,8 +25,7 @@ namespace Torch.Server.Managers
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
#pragma warning disable 649
- [ReflectedGetter(Name = "m_members")]
- private static Func> _members;
+ [ReflectedGetter(Name = "m_members")] private static Func> _members;
[ReflectedGetter(Name = "m_waitingForGroup")]
private static Func> _waitingForGroup;
#pragma warning restore 649
@@ -37,7 +36,9 @@ namespace Torch.Server.Managers
private Dictionary _gameOwnerIds = new Dictionary();
///
- public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { }
+ public MultiplayerManagerDedicated(ITorchBase torch) : base(torch)
+ {
+ }
///
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
@@ -54,7 +55,8 @@ namespace Torch.Server.Managers
}
///
- public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) || MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
+ public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) ||
+ MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
///
public override void Attach()
@@ -62,8 +64,10 @@ namespace Torch.Server.Managers
base.Attach();
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
- _gameServerValidateAuthTicketReplacer.Replace(new Action(ValidateAuthTicketResponse), MyGameService.GameServer);
- _gameServerUserGroupStatusReplacer.Replace(new Action(UserGroupStatusResponse), MyGameService.GameServer);
+ _gameServerValidateAuthTicketReplacer.Replace(
+ new Action(ValidateAuthTicketResponse), MyGameService.GameServer);
+ _gameServerUserGroupStatusReplacer.Replace(new Action(UserGroupStatusResponse),
+ MyGameService.GameServer);
_log.Info("Inserted steam authentication intercept");
}
@@ -80,15 +84,20 @@ namespace Torch.Server.Managers
#pragma warning disable 649
- [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
+ [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse),
+ typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
private static Func _gameServerValidateAuthTicketFactory;
- [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
+
+ [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse),
+ typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
private static Func _gameServerUserGroupStatusFactory;
+
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
#pragma warning restore 649
#region CustomAuth
+
#pragma warning disable 649
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
private static Func _convertSteamIDFrom64;
@@ -96,78 +105,130 @@ namespace Torch.Server.Managers
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
private static Func _getServerAccountType;
- [ReflectedMethod(Name = "UserAccepted")]
- private static Action _userAcceptedImpl;
+ [ReflectedMethod(Name = "UserAccepted")] private static Action _userAcceptedImpl;
[ReflectedMethod(Name = "UserRejected")]
private static Action _userRejected;
- [ReflectedMethod(Name = "IsClientBanned")]
- private static Func _isClientBanned;
- [ReflectedMethod(Name = "IsClientKicked")]
- private static Func _isClientKicked;
+
+ [ReflectedMethod(Name = "IsClientBanned")] private static Func _isClientBanned;
+ [ReflectedMethod(Name = "IsClientKicked")] private static Func _isClientKicked;
+
[ReflectedMethod(Name = "RaiseClientKicked")]
private static Action _raiseClientKicked;
#pragma warning restore 649
+ private const int _waitListSize = 32;
+ private readonly List _waitingForGroupLocal = new List(_waitListSize);
+
+ private struct WaitingForGroup
+ {
+ public readonly ulong SteamId;
+ public readonly JoinResult Response;
+ public readonly ulong SteamOwner;
+
+ public WaitingForGroup(ulong id, JoinResult response, ulong owner)
+ {
+ SteamId = id;
+ Response = response;
+ SteamOwner = owner;
+ }
+ }
+
//Largely copied from SE
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
{
_log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
- if (IsBanned(steamOwner))
+ if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
+ RunEvent(new ValidateAuthTicketEvent(steamID, steamOwner, response, 0, true, false));
+ else if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
+ UserRejected(steamID, JoinResult.GroupIdInvalid);
+ else if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
+ lock (_waitingForGroupLocal)
+ {
+ if (_waitingForGroupLocal.Count >= _waitListSize)
+ _waitingForGroupLocal.RemoveAt(0);
+ _waitingForGroupLocal.Add(new WaitingForGroup(steamID, response, steamOwner));
+ }
+ else
+ UserRejected(steamID, JoinResult.SteamServersOffline);
+ }
+
+ private void RunEvent(ValidateAuthTicketEvent info)
+ {
+ MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
+
+ if (info.FutureVerdict == null)
{
- _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
- _raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
- }
- else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
- {
- _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
- _raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
- }
- if (response != JoinResult.OK)
- {
- _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
+ if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
+ CommitVerdict(info.SteamID, JoinResult.BannedByAdmins);
+ else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
+ _isClientKicked(MyMultiplayer.Static, info.SteamOwner))
+ CommitVerdict(info.SteamID, JoinResult.KickedRecently);
+ else if (info.SteamResponse != JoinResult.OK)
+ CommitVerdict(info.SteamID, info.SteamResponse);
+ else if (MyMultiplayer.Static.MemberLimit > 0 &&
+ MyMultiplayer.Static.MemberCount + 1 > MyMultiplayer.Static.MemberLimit)
+ CommitVerdict(info.SteamID, JoinResult.ServerFull);
+ else if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
+ MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
+ MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
+ CommitVerdict(info.SteamID, JoinResult.OK);
+ else if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
+ CommitVerdict(info.SteamID, JoinResult.OK);
+ else
+ CommitVerdict(info.SteamID, JoinResult.NotInGroup);
return;
}
- if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
+
+ info.FutureVerdict.ContinueWith((task) =>
{
- _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);
+ JoinResult verdict;
+ if (task.IsFaulted)
+ {
+ _log.Error(task.Exception, $"Future validation verdict faulted");
+ verdict = JoinResult.TicketCanceled;
+ }
+ else
+ verdict = task.Result;
+ Torch.Invoke(() => { CommitVerdict(info.SteamID, verdict); });
+ });
+ }
+
+ private void CommitVerdict(ulong steamId, JoinResult verdict)
+ {
+ if (verdict == JoinResult.OK)
+ UserAccepted(steamId);
+ else
+ UserRejected(steamId, verdict);
}
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
{
- if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
- {
- if (member || officer)
- UserAccepted(userId);
- else
- _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
- }
+ lock (_waitingForGroupLocal)
+ for (var j = 0; j < _waitingForGroupLocal.Count; j++)
+ {
+ var wait = _waitingForGroupLocal[j];
+ if (wait.SteamId == userId)
+ {
+ RunEvent(new ValidateAuthTicketEvent(wait.SteamId, wait.SteamOwner, wait.Response, groupId,
+ member, officer));
+ _waitingForGroupLocal.RemoveAt(j);
+ break;
+ }
+ }
}
+
+ private void UserRejected(ulong steamId, JoinResult reason)
+ {
+ _userRejected.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId, reason);
+ }
+
private void UserAccepted(ulong steamId)
{
- _userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
+ _userAcceptedImpl.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId);
base.RaiseClientJoined(steamId);
}
+
#endregion
}
-}
+}
\ No newline at end of file
diff --git a/Torch.Server/Managers/MultiplayerManagerDedicatedEventShim.cs b/Torch.Server/Managers/MultiplayerManagerDedicatedEventShim.cs
new file mode 100644
index 0000000..c9a302d
--- /dev/null
+++ b/Torch.Server/Managers/MultiplayerManagerDedicatedEventShim.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NLog;
+using Sandbox;
+using Torch.API.Event;
+using Torch.Event;
+using VRage.Network;
+
+namespace Torch.Server.Managers
+{
+ [EventShim]
+ internal static class MultiplayerManagerDedicatedEventShim
+ {
+ private static readonly EventList _eventValidateAuthTicket =
+ new EventList();
+
+
+ internal static void RaiseValidateAuthTicket(ref ValidateAuthTicketEvent info)
+ {
+ _eventValidateAuthTicket?.RaiseEvent(ref info);
+ }
+ }
+
+ ///
+ /// Event that occurs when a player tries to connect to a dedicated server.
+ /// Use these values to choose a ,
+ /// or leave it unset to allow the default logic to handle the request.
+ ///
+ public struct ValidateAuthTicketEvent : IEvent
+ {
+ ///
+ /// SteamID of the player
+ ///
+ public readonly ulong SteamID;
+
+ ///
+ /// SteamID of the game owner
+ ///
+ public readonly ulong SteamOwner;
+
+ ///
+ /// The response from steam
+ ///
+ public readonly JoinResult SteamResponse;
+
+ ///
+ /// ID of the queried group, or 0 if no group.
+ ///
+ public readonly ulong Group;
+
+ ///
+ /// Is this person a member of . If no group this is true.
+ ///
+ public readonly bool Member;
+
+ ///
+ /// Is this person an officer of . If no group this is false.
+ ///
+ public readonly bool Officer;
+
+ ///
+ /// A future verdict on this authorization request. If null, let the default logic choose. If not async use
+ ///
+ public Task FutureVerdict;
+
+ internal ValidateAuthTicketEvent(ulong steamId, ulong steamOwner, JoinResult steamResponse,
+ ulong serverGroup, bool member, bool officer)
+ {
+ SteamID = steamId;
+ SteamOwner = steamOwner;
+ SteamResponse = steamResponse;
+ Group = serverGroup;
+ Member = member;
+ Officer = officer;
+ FutureVerdict = null;
+ }
+
+ ///
+ public bool Cancelled => FutureVerdict != null;
+ }
+}
\ No newline at end of file
diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj
index ea5c705..2a75e82 100644
--- a/Torch.Server/Torch.Server.csproj
+++ b/Torch.Server/Torch.Server.csproj
@@ -198,6 +198,7 @@
+
diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs
index 81bf533..aad2fc5 100644
--- a/Torch.Server/TorchServer.cs
+++ b/Torch.Server/TorchServer.cs
@@ -44,19 +44,58 @@ namespace Torch.Server
{
//public MyConfigDedicated DedicatedConfig { get; set; }
///
- public float SimulationRatio { get => _simRatio; set { _simRatio = value; OnPropertyChanged(); } }
+ public float SimulationRatio
+ {
+ get => _simRatio;
+ set
+ {
+ _simRatio = value;
+ OnPropertyChanged();
+ }
+ }
+
///
- public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set { _elapsedPlayTime = value; OnPropertyChanged(); } }
+ public TimeSpan ElapsedPlayTime
+ {
+ get => _elapsedPlayTime;
+ set
+ {
+ _elapsedPlayTime = value;
+ OnPropertyChanged();
+ }
+ }
+
///
public Thread GameThread { get; private set; }
+
///
- public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
+ public ServerState State
+ {
+ get => _state;
+ private set
+ {
+ _state = value;
+ OnPropertyChanged();
+ }
+ }
+
///
- public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } }
+ public bool IsRunning
+ {
+ get => _isRunning;
+ set
+ {
+ _isRunning = value;
+ OnPropertyChanged();
+ }
+ }
+
///
public InstanceManager DedicatedInstance { get; }
+
///
public string InstanceName => Config?.InstanceName;
+
///
public string InstancePath => Config?.InstancePath;
@@ -64,7 +103,6 @@ namespace Torch.Server
private ServerState _state;
private TimeSpan _elapsedPlayTime;
private float _simRatio;
- private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
private Timer _watchdog;
private Stopwatch _uptime;
@@ -80,30 +118,20 @@ namespace Torch.Server
sessionManager.AddFactory((x) => new MultiplayerManagerDedicated(this));
}
+ ///
+ protected override uint SteamAppId => 244850;
+
+ ///
+ protected override string SteamAppName => "SpaceEngineersDedicated";
+
///
public override void Init()
{
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
+ Sandbox.Engine.Platform.Game.IsDedicated = true;
+
base.Init();
- MyPerGameSettings.SendLogToKeen = false;
- MyPerServerSettings.GameName = MyPerGameSettings.GameName;
- MyPerServerSettings.GameNameSafe = MyPerGameSettings.GameNameSafe;
- MyPerServerSettings.GameDSName = MyPerServerSettings.GameNameSafe + "Dedicated";
- MyPerServerSettings.GameDSDescription = "Your place for space engineering, destruction and exploring.";
- MySessionComponentExtDebug.ForceDisable = true;
- MyPerServerSettings.AppId = 244850;
- MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
- InvokeBeforeRun();
-
- //MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
- MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
- MyPlugins.RegisterGameObjectBuildersAssemblyFile(MyPerGameSettings.GameModObjBuildersAssembly);
- MyPlugins.RegisterSandboxAssemblyFile(MyPerGameSettings.SandboxAssembly);
- MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly);
- MyPlugins.Load();
- MyGlobalTypeMetadata.Static.Init();
-
Managers.GetManager().SessionStateChanged += OnSessionStateChanged;
GetManager().LoadInstance(Config.InstancePath);
}
@@ -117,81 +145,65 @@ namespace Torch.Server
}
}
- private void InvokeBeforeRun()
- {
- MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
- MySandboxGame.Log.WriteLine("Steam build: Always true");
- MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount);
- //MySandboxGame.Log.WriteLine("Environment.OSVersion: " + GetOsName());
- MySandboxGame.Log.WriteLine("Environment.CommandLine: " + Environment.CommandLine);
- MySandboxGame.Log.WriteLine("Environment.Is64BitProcess: " + MyEnvironment.Is64BitProcess);
- MySandboxGame.Log.WriteLine("Environment.Is64BitOperatingSystem: " + Environment.Is64BitOperatingSystem);
- //MySandboxGame.Log.WriteLine("Environment.Version: " + GetNETFromRegistry());
- MySandboxGame.Log.WriteLine("Environment.CurrentDirectory: " + Environment.CurrentDirectory);
- MySandboxGame.Log.WriteLine("MainAssembly.ProcessorArchitecture: " + Assembly.GetExecutingAssembly().GetArchitecture());
- MySandboxGame.Log.WriteLine("ExecutingAssembly.ProcessorArchitecture: " + MyFileSystem.MainAssembly.GetArchitecture());
- MySandboxGame.Log.WriteLine("IntPtr.Size: " + IntPtr.Size);
- MySandboxGame.Log.WriteLine("Default Culture: " + CultureInfo.CurrentCulture.Name);
- MySandboxGame.Log.WriteLine("Default UI Culture: " + CultureInfo.CurrentUICulture.Name);
- MySandboxGame.Log.WriteLine("IsAdmin: " + new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator));
-
- MyLog.Default = MySandboxGame.Log;
-
- Thread.CurrentThread.Name = "Main thread";
-
- //Because we want exceptions from users to be in english
- Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
- Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
-
- MySandboxGame.Config = new MyConfig("SpaceEngineers-Dedicated.cfg");
- MySandboxGame.Config.Load();
- }
-
- [ReflectedStaticMethod(Type = typeof(DedicatedServer), Name = "RunInternal")]
- private static Action _dsRunInternal;
-
///
public override void Start()
{
if (State != ServerState.Stopped)
return;
+ State = ServerState.Starting;
+ IsRunning = true;
+ Log.Info("Starting server.");
+ MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model;
DedicatedInstance.SaveConfig();
_uptime = Stopwatch.StartNew();
- IsRunning = true;
- GameThread = Thread.CurrentThread;
- State = ServerState.Starting;
- Log.Info("Starting server.");
-
- MySandboxGame.IsDedicated = true;
- MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model;
- 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.
- MySandboxGame.IsReloading = true;
- try
- {
- _dsRunInternal.Invoke();
- }
- catch (TargetInvocationException e)
- {
- // Makes log formatting a little nicer.
- throw e.InnerException ?? e;
- }
+ }
+
+ ///
+ public override void Stop()
+ {
+ if (State == ServerState.Stopped)
+ Log.Error("Server is already stopped");
+ Log.Info("Stopping server.");
+ base.Stop();
+ Log.Info("Server stopped.");
- MySandboxGame.Log.Close();
State = ServerState.Stopped;
+ IsRunning = false;
+ }
+
+ ///
+ /// Restart the program.
+ ///
+ public override void Restart()
+ {
+ Save(0).Wait();
+ Stop();
+ LogManager.Flush();
+
+ var exe = Assembly.GetExecutingAssembly().Location;
+ ((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
+ Config.Autostart = true;
+ Process.Start(exe, Config.ToString());
+
+ Process.GetCurrentProcess().Kill();
}
///
public override void Init(object gameInstance)
{
base.Init(gameInstance);
- State = ServerState.Running;
- SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch");
+ var game = gameInstance as MySandboxGame;
+ if (game != null && MySession.Static != null)
+ {
+ State = ServerState.Running;
+// SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch");
+ }
+ else
+ {
+ State = ServerState.Stopped;
+ }
}
///
@@ -205,18 +217,22 @@ namespace Torch.Server
if (_watchdog == null && Config.TickTimeout > 0)
{
Log.Info("Starting server watchdog.");
- _watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Config.TickTimeout));
+ _watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero,
+ TimeSpan.FromSeconds(Config.TickTimeout));
}
}
+ #region Freeze Detection
+
private static void CheckServerResponding(object state)
{
var mre = new ManualResetEvent(false);
- ((TorchServer)state).Invoke(() => mre.Set());
+ ((TorchServer) state).Invoke(() => mre.Set());
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
{
#if DEBUG
- Log.Error($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
+ Log.Error(
+ $"Server watchdog detected that the server was frozen for at least {((TorchServer) state).Config.TickTimeout} seconds.");
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
#else
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
@@ -278,47 +294,7 @@ namespace Torch.Server
return stack;
}
- ///
- public override void Stop()
- {
- if (State == ServerState.Stopped)
- Log.Error("Server is already stopped");
-
- if (Thread.CurrentThread != MySandboxGame.Static.UpdateThread)
- {
- Log.Debug("Invoking server stop on game thread.");
- Invoke(Stop);
- return;
- }
-
- Log.Info("Stopping server.");
-
- //Unload all the static junk.
- //TODO: Finish unloading all server data so it's in a completely clean state.
- MySandboxGame.Static.Exit();
-
- Log.Info("Server stopped.");
- LogManager.Flush();
- _stopHandle.Set();
- State = ServerState.Stopped;
- IsRunning = false;
- Process.GetCurrentProcess().Kill();
- }
-
- ///
- /// Restart the program. DOES NOT SAVE!
- ///
- public override void Restart()
- {
- var exe = Assembly.GetExecutingAssembly().Location;
- ((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
- Config.Autostart = true;
- Process.Start(exe, Config.ToString());
- Save(0).Wait();
- Stop();
- LogManager.Flush();
- Process.GetCurrentProcess().Kill();
- }
+ #endregion
///
public override Task Save(long callerId)
@@ -357,8 +333,9 @@ namespace Torch.Server
}
if (MySession.Static.Players.TryGetPlayerId(callerId, out MyPlayer.PlayerId result))
{
- Managers.GetManager()?.SendMessageAsOther("Server", response, statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
+ Managers.GetManager()?.SendMessageAsOther("Server", response,
+ statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs
index 92b1083..1bee4c3 100644
--- a/Torch.Server/Views/TorchUI.xaml.cs
+++ b/Torch.Server/Views/TorchUI.xaml.cs
@@ -66,7 +66,7 @@ namespace Torch.Server
private void BtnStart_Click(object sender, RoutedEventArgs e)
{
_server.GetManager().SaveConfig();
- new Thread(_server.Start).Start();
+ _server.Start();
}
private void BtnStop_Click(object sender, RoutedEventArgs e)
diff --git a/Torch.Tests/ReflectionTestManager.cs b/Torch.Tests/ReflectionTestManager.cs
index 87c3757..61494a9 100644
--- a/Torch.Tests/ReflectionTestManager.cs
+++ b/Torch.Tests/ReflectionTestManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Reflection;
using Torch.Utils;
@@ -42,8 +43,15 @@ namespace Torch.Tests
public ReflectionTestManager Init(Assembly asm)
{
- foreach (Type type in asm.GetTypes())
- Init(type);
+ try
+ {
+ foreach (Type type in asm.GetTypes())
+ Init(type);
+ }
+ catch (ReflectionTypeLoadException e)
+ {
+ throw e.LoaderExceptions[0];
+ }
return this;
}
diff --git a/Torch.Tests/TestUtils.cs b/Torch.Tests/TestUtils.cs
index 323186b..286fc66 100644
--- a/Torch.Tests/TestUtils.cs
+++ b/Torch.Tests/TestUtils.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Torch.Utils;
@@ -32,4 +34,4 @@ namespace Torch.Tests
private static TorchAssemblyResolver _torchResolver;
}
-}
+}
\ No newline at end of file
diff --git a/Torch/Collections/MTObservableCollection.cs b/Torch/Collections/MTObservableCollection.cs
index 3096bea..f6fa204 100644
--- a/Torch/Collections/MTObservableCollection.cs
+++ b/Torch/Collections/MTObservableCollection.cs
@@ -38,7 +38,9 @@ namespace Torch.Collections
~MtObservableCollection()
{
- _flushEventQueue.Dispose();
+ Timer queue = _flushEventQueue;
+ _flushEventQueue = null;
+ queue?.Dispose();
}
///
@@ -208,10 +210,10 @@ namespace Torch.Collections
return;
_collectionEventQueue.Enqueue(e);
// In half a second, flush the events
- _flushEventQueue.Change(500, -1);
+ _flushEventQueue?.Change(500, -1);
}
- private readonly Timer _flushEventQueue;
+ private Timer _flushEventQueue;
private readonly Queue _collectionEventQueue =
new Queue();
diff --git a/Torch/Event/EventManager.cs b/Torch/Event/EventManager.cs
index e752322..f3875aa 100644
--- a/Torch/Event/EventManager.cs
+++ b/Torch/Event/EventManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
@@ -36,7 +37,7 @@ namespace Torch.Event
_log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it isn't declared singleton");
var listsFound = 0;
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
- foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
+ foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(EventList<>))
{
Type eventType = field.FieldType.GenericTypeArguments[0];
@@ -70,7 +71,7 @@ namespace Torch.Event
ParameterInfo[] ps = x.GetParameters();
if (ps.Length != 1)
return false;
- return ps[0].ParameterType.IsByRef && typeof(IEvent).IsAssignableFrom(ps[0].ParameterType);
+ return ps[0].ParameterType.IsByRef && typeof(IEvent).IsAssignableFrom(ps[0].ParameterType.GetElementType());
});
return exploreType.BaseType != null ? enumerable.Concat(EventHandlers(exploreType.BaseType)) : enumerable;
}
@@ -78,9 +79,12 @@ namespace Torch.Event
///
private static void RegisterHandlerInternal(IEventHandler instance)
{
+ var foundHandler = false;
foreach (MethodInfo handler in EventHandlers(instance.GetType()))
{
- Type eventType = handler.GetParameters()[0].ParameterType;
+ Type eventType = handler.GetParameters()[0].ParameterType.GetElementType();
+ Debug.Assert(eventType != null);
+ foundHandler = true;
if (eventType.IsInterface)
{
var foundList = false;
@@ -100,6 +104,9 @@ namespace Torch.Event
}
_log.Error($"Unable to find event handler list for event type {eventType.FullName}");
}
+ if (!foundHandler)
+ _log.Warn($"Found no handlers in {instance.GetType().FullName} or base types");
+
}
///
diff --git a/Torch/Event/EventShimAttribute.cs b/Torch/Event/EventShimAttribute.cs
index 87e5c66..8b2d25e 100644
--- a/Torch/Event/EventShimAttribute.cs
+++ b/Torch/Event/EventShimAttribute.cs
@@ -14,7 +14,7 @@ namespace Torch.Event
/// Event shims should be singleton, and have one (or more) fields that are of type .
///
[AttributeUsage(AttributeTargets.Class)]
- internal class EventShimAttribute : Attribute
+ public class EventShimAttribute : Attribute
{
}
}
diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
index 879343d..a85e265 100644
--- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
+++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
@@ -186,10 +186,10 @@ namespace Torch.Managers.PatchManager.MSIL
-#pragma warning disable 169
+#pragma warning disable 649
[ReflectedMethod(Name = "StackChange")]
private static Func _stackChange;
-#pragma warning restore 169
+#pragma warning restore 649
///
/// Estimates the stack delta for this instruction.
diff --git a/Torch/Session/TorchSessionManager.cs b/Torch/Session/TorchSessionManager.cs
index 426b320..7d8ea14 100644
--- a/Torch/Session/TorchSessionManager.cs
+++ b/Torch/Session/TorchSessionManager.cs
@@ -97,6 +97,7 @@ namespace Torch.Session
CurrentSession.Managers.AddManager(manager);
}
(CurrentSession as TorchSession)?.Attach();
+ _log.Info($"Loaded torch session for {MySession.Static.Name}");
SetState(TorchSessionState.Loaded);
}
catch (Exception e)
@@ -115,7 +116,9 @@ namespace Torch.Session
_log.Warn("Session unloading event occurred when we don't have a session.");
return;
}
+ _log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
SetState(TorchSessionState.Unloading);
+ _currentSession.Detach();
}
catch (Exception e)
{
@@ -133,9 +136,8 @@ namespace Torch.Session
_log.Warn("Session unloading event occurred when we don't have a session.");
return;
}
- _log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
+ _log.Info($"Unloaded torch session for {_currentSession.KeenSession.Name}");
SetState(TorchSessionState.Unloaded);
- _currentSession.Detach();
_currentSession = null;
}
catch (Exception e)
diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj
index a77ba4a..d999355 100644
--- a/Torch/Torch.csproj
+++ b/Torch/Torch.csproj
@@ -255,6 +255,7 @@
CollectionEditor.xaml
+
diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs
index 0d93ac7..83b6dc1 100644
--- a/Torch/TorchBase.cs
+++ b/Torch/TorchBase.cs
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime;
using System.Runtime.CompilerServices;
+using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -12,6 +15,9 @@ using NLog;
using ProtoBuf.Meta;
using Sandbox;
using Sandbox.Engine.Multiplayer;
+using Sandbox.Engine.Networking;
+using Sandbox.Engine.Platform.VideoMode;
+using Sandbox.Engine.Utils;
using Sandbox.Game;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.Screens.Helpers;
@@ -19,6 +25,7 @@ using Sandbox.Game.World;
using Sandbox.Graphics.GUI;
using Sandbox.ModAPI;
using SpaceEngineers.Game;
+using SpaceEngineers.Game.GUI;
using Torch.API;
using Torch.API.Managers;
using Torch.API.ModAPI;
@@ -31,16 +38,22 @@ using Torch.Managers.PatchManager;
using Torch.Patches;
using Torch.Utils;
using Torch.Session;
+using VRage;
using VRage.Collections;
using VRage.FileSystem;
using VRage.Game;
using VRage.Game.Common;
using VRage.Game.Components;
using VRage.Game.ObjectBuilder;
+using VRage.Game.SessionComponents;
+using VRage.GameServices;
+using VRage.Library;
using VRage.ObjectBuilders;
using VRage.Plugins;
using VRage.Scripting;
+using VRage.Steam;
using VRage.Utils;
+using VRageRender;
namespace Torch
{
@@ -67,8 +80,10 @@ namespace Torch
/// Use only if necessary, prefer dependency injection.
///
public static ITorchBase Instance { get; private set; }
+
///
public ITorchConfig Config { get; protected set; }
+
///
public Version TorchVersion { get; }
@@ -79,8 +94,10 @@ namespace Torch
///
public Version GameVersion { get; private set; }
+
///
public string[] RunArgs { get; set; }
+
///
[Obsolete("Use GetManager() or the [Dependency] attribute.")]
public IPluginManager Plugins { get; protected set; }
@@ -90,10 +107,13 @@ namespace Torch
///
public event Action SessionLoading;
+
///
public event Action SessionLoaded;
+
///
public event Action SessionUnloading;
+
///
public event Action SessionUnloaded;
@@ -120,7 +140,9 @@ namespace Torch
Instance = this;
TorchVersion = Assembly.GetExecutingAssembly().GetName().Version;
- TorchVersionVerbose = Assembly.GetEntryAssembly().GetCustomAttribute()?.InformationalVersion ?? TorchVersion.ToString();
+ TorchVersionVerbose = Assembly.GetEntryAssembly()
+ .GetCustomAttribute()
+ ?.InformationalVersion ?? TorchVersion.ToString();
RunArgs = new string[0];
Managers = new DependencyManager();
@@ -140,6 +162,39 @@ namespace Torch
Managers.AddManager(new EventManager(this));
Managers.AddManager(Plugins);
TorchAPI.Instance = this;
+
+ GameStateChanged += (game, state) =>
+ {
+ if (state == TorchGameState.Created)
+ {
+ // If the attached assemblies change (MySandboxGame.ctor => MySandboxGame.ParseArgs => MyPlugins.RegisterFromArgs)
+ // attach assemblies to object factories again.
+ ObjectFactoryInitPatch.ForceRegisterAssemblies();
+ // safe to commit here; all important static ctors have run
+ PatchManager.CommitInternal();
+ }
+ };
+
+ sessionManager.SessionStateChanged += (session, state) =>
+ {
+ switch (state)
+ {
+ case TorchSessionState.Loading:
+ SessionLoading?.Invoke();
+ break;
+ case TorchSessionState.Loaded:
+ SessionLoaded?.Invoke();
+ break;
+ case TorchSessionState.Unloading:
+ SessionUnloading?.Invoke();
+ break;
+ case TorchSessionState.Unloaded:
+ SessionUnloaded?.Invoke();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(state), state, null);
+ }
+ };
}
[Obsolete("Prefer using Managers.GetManager for global managers")]
@@ -252,34 +307,30 @@ namespace Torch
#endregion
+ #region Torch Init/Destroy
+
+ protected abstract uint SteamAppId { get; }
+ protected abstract string SteamAppName { get; }
+
///
public virtual void Init()
{
Debug.Assert(!_init, "Torch instance is already initialized.");
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
- // If the attached assemblies change (MySandboxGame.ctor => MySandboxGame.ParseArgs => MyPlugins.RegisterFromArgs)
- // attach assemblies to object factories again.
ObjectFactoryInitPatch.ForceRegisterAssemblies();
- GameStateChanged += (game, state) =>
- {
- if (state == TorchGameState.Created)
- {
- ObjectFactoryInitPatch.ForceRegisterAssemblies();
- // safe to commit here; all important static ctors have run
- PatchManager.CommitInternal();
- }
- };
- Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
- GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
+ Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null,
+ "MyPerGameSettings.BasicGameInfo.GameVersion != null");
+ GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText
+ .ToString().Replace("_", "."));
try
{
Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
}
catch
{
- // Running as service
+ // Running without a console
}
#if DEBUG
@@ -292,11 +343,9 @@ namespace Torch
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
- MySession.OnLoading += OnSessionLoading;
- MySession.AfterLoading += OnSessionLoaded;
- MySession.OnUnloading += OnSessionUnloading;
- MySession.OnUnloaded += OnSessionUnloaded;
- RegisterVRagePlugin();
+ _game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs);
+ if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromMinutes(5)))
+ Log.Warn("Failed to wait for game to be initialized");
Managers.GetManager().LoadPlugins();
Managers.Attach();
_init = true;
@@ -306,109 +355,57 @@ namespace Torch
PatchManager.CommitInternal();
}
- private void OnSessionLoading()
+ ///
+ public virtual void Dispose()
{
- Log.Debug("Session loading");
- try
- {
- SessionLoading?.Invoke();
- }
- catch (Exception e)
- {
- Log.Error(e);
- throw;
- }
+ Managers.Detach();
+ _game.SignalDestroy();
+ if (!_game.WaitFor(VRageGame.GameState.Destroyed, TimeSpan.FromSeconds(15)))
+ Log.Warn("Failed to wait for the game to be destroyed");
+ _game = null;
}
- private void OnSessionLoaded()
- {
- Log.Debug("Session loaded");
- try
- {
- SessionLoaded?.Invoke();
- }
- catch (Exception e)
- {
- Log.Error(e);
- throw;
- }
- }
+ #endregion
- private void OnSessionUnloading()
- {
- Log.Debug("Session unloading");
- try
- {
- SessionUnloading?.Invoke();
- }
- catch (Exception e)
- {
- Log.Error(e);
- throw;
- }
- }
-
- private void OnSessionUnloaded()
- {
- Log.Debug("Session unloaded");
- try
- {
- SessionUnloaded?.Invoke();
- }
- catch (Exception e)
- {
- Log.Error(e);
- throw;
- }
- }
+ private VRageGame _game;
///
- /// Hook into the VRage plugin system for updates.
+ /// Called after the basic game information is filled, but before the game is created.
///
- private void RegisterVRagePlugin()
+ protected virtual void TweakGameSettings()
{
- var fieldName = "m_plugins";
- var pluginList = typeof(MyPlugins).GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as List;
- if (pluginList == null)
- throw new TypeLoadException($"{fieldName} field not found in {nameof(MyPlugins)}");
-
- pluginList.Add(this);
}
+
///
public virtual Task Save(long callerId)
{
- return Task.CompletedTask;
+ return SaveGameAsync(null);
}
///
public virtual void Start()
{
-
+ _game.SignalStart();
+ if (!_game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15)))
+ Log.Warn("Failed to wait for the game to be started");
}
///
public virtual void Stop()
{
-
+ LogManager.Flush();
+ _game.SignalStop();
+ if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15)))
+ Log.Warn("Failed to wait for the game to be stopped");
}
///
- public virtual void Restart()
- {
-
- }
-
- ///
- public virtual void Dispose()
- {
- Managers.Detach();
- }
+ public abstract void Restart();
///
public virtual void Init(object gameInstance)
{
-
}
///
@@ -435,6 +432,7 @@ namespace Torch
public event TorchGameStateChangedDel GameStateChanged;
private static readonly HashSet _registeredCoreAssemblies = new HashSet();
+
///
/// Registers a core (Torch) assembly with the system, including its
/// shims, shims, and components.
@@ -467,4 +465,4 @@ namespace Torch
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Torch/Utils/Reflected/ReflectedManager.cs b/Torch/Utils/Reflected/ReflectedManager.cs
index 08eb4a1..f6d1180 100644
--- a/Torch/Utils/Reflected/ReflectedManager.cs
+++ b/Torch/Utils/Reflected/ReflectedManager.cs
@@ -237,8 +237,12 @@ namespace Torch.Utils
argExp[i] = Expression.Convert(paramExp[i + 1], invokeTypes[i]);
else
argExp[i] = paramExp[i + 1];
+ Debug.Assert(methodInstance.DeclaringType != null);
+ Expression instanceExp = paramExp[0].Type != methodInstance.DeclaringType
+ ? Expression.Convert(paramExp[0], methodInstance.DeclaringType)
+ : (Expression) paramExp[0];
field.SetValue(null,
- Expression.Lambda(Expression.Call(paramExp[0], methodInstance, argExp), paramExp)
+ Expression.Lambda(Expression.Call(instanceExp, methodInstance, argExp), paramExp)
.Compile());
_log.Trace($"Reflecting field {field.DeclaringType?.FullName}#{field.Name} with {methodInstance.DeclaringType?.FullName}#{methodInstance.Name}");
}
@@ -318,7 +322,12 @@ namespace Torch.Utils
Expression impl;
if (attr is ReflectedSetterAttribute)
{
- impl = Expression.Block(Expression.Assign(fieldExp, paramExp[isStatic ? 0 : 1]), Expression.Default(typeof(void)));
+ var valParam = paramExp[isStatic ? 0 : 1];
+ var valExpr = (Expression)valParam;
+ var setType = sourceField?.FieldType ?? sourceProperty.PropertyType;
+ if (valParam.Type != setType)
+ valExpr = Expression.Convert(valExpr, setType);
+ impl = Expression.Block(Expression.Assign(fieldExp, valExpr), Expression.Default(typeof(void)));
}
else
{
diff --git a/Torch/VRageGame.cs b/Torch/VRageGame.cs
new file mode 100644
index 0000000..65fd80b
--- /dev/null
+++ b/Torch/VRageGame.cs
@@ -0,0 +1,310 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Havok;
+using NLog;
+using NLog.Fluent;
+using Sandbox;
+using Sandbox.Engine.Multiplayer;
+using Sandbox.Engine.Networking;
+using Sandbox.Engine.Platform.VideoMode;
+using Sandbox.Game;
+using Sandbox.Game.World;
+using SpaceEngineers.Game;
+using SpaceEngineers.Game.GUI;
+using Torch.Utils;
+using VRage;
+using VRage.Audio;
+using VRage.FileSystem;
+using VRage.Game;
+using VRage.Game.SessionComponents;
+using VRage.GameServices;
+using VRage.Plugins;
+using VRage.Steam;
+using VRage.Utils;
+using VRageRender;
+
+namespace Torch
+{
+ public class VRageGame
+ {
+ private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
+
+#pragma warning disable 649
+ [ReflectedGetter(Name = "m_plugins", Type = typeof(MyPlugins))]
+ private static readonly Func> _getVRagePluginList;
+
+ [ReflectedGetter(Name = "Static", TypeName = "Sandbox.Game.Audio.MyMusicController, Sandbox.Game")]
+ private static readonly Func