diff --git a/Jenkinsfile b/Jenkinsfile index 89a8bb7..8b12c0b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -19,7 +19,7 @@ node { stage('Test') { bat 'IF NOT EXIST GameBinaries MKDIR GameBinaries' - bat "\"packages/xunit.runner.console.2.2.0/tools/xunit.console.exe\" \"bin-test/x64/Release/Torch.Tests.dll\" -parallel none -xml \"reports/Torch.Tests.xml\"" + 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\" -parallel none -xml \"reports/Torch.Tests.xml\"" step([ $class: 'XUnitBuilder', thresholdMode: 1, diff --git a/Torch.Client/Program.cs b/Torch.Client/Program.cs index 1ff5f54..99ee7fe 100644 --- a/Torch.Client/Program.cs +++ b/Torch.Client/Program.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Windows; using System.Windows.Forms; using NLog; +using Torch.Utils; using MessageBox = System.Windows.MessageBox; namespace Torch.Client diff --git a/Torch.Server.Tests/Properties/AssemblyInfo.cs b/Torch.Server.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..11ceb07 --- /dev/null +++ b/Torch.Server.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("Torch.Server.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Torch.Server.Tests")] +[assembly: AssemblyCopyright("Copyright © 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("9efd1d91-2fa2-47ed-b537-d8bc3b0e543e")] + +// 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")] diff --git a/Torch.Server.Tests/Torch.Server.Tests.csproj b/Torch.Server.Tests/Torch.Server.Tests.csproj new file mode 100644 index 0000000..96dac2a --- /dev/null +++ b/Torch.Server.Tests/Torch.Server.Tests.csproj @@ -0,0 +1,85 @@ + + + + + {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E} + Library + Properties + Torch.Server.Tests + Torch.Server.Tests + v4.6.1 + 512 + + + + 1591,0649 + + + true + $(SolutionDir)\bin-test\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + $(SolutionDir)\bin-test\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + $(SolutionDir)\bin-test\x64\Release\Torch.Server.Tests.xml + + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll + + + + + + + + + {fba5d932-6254-4a1e-baf4-e229fa94e3c2} + Torch.API + + + {ca50886b-7b22-4cd8-93a0-c06f38d4f77d} + Torch.Server + + + {c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5} + Torch.Tests + + + {7e01635c-3b67-472e-bcd6-c5539564f214} + Torch + True + + + + + + + \ No newline at end of file diff --git a/Torch.Server.Tests/TorchServerReflectionTest.cs b/Torch.Server.Tests/TorchServerReflectionTest.cs new file mode 100644 index 0000000..e4ec2de --- /dev/null +++ b/Torch.Server.Tests/TorchServerReflectionTest.cs @@ -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 Getters => Manager().Getters; + + public static IEnumerable Setters => Manager().Setters; + + public static IEnumerable 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 + } +} diff --git a/Torch.Server.Tests/packages.config b/Torch.Server.Tests/packages.config new file mode 100644 index 0000000..1158086 --- /dev/null +++ b/Torch.Server.Tests/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 41df9e5..ca1ee43 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -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; @@ -121,6 +122,9 @@ namespace Torch.Server MySandboxGame.Config.Load(); } + [ReflectedStaticMethod(Type = typeof(DedicatedServer), Name = "RunInternal")] + private static Action _dsRunInternal; + /// public override void Start() { @@ -134,8 +138,6 @@ 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()); @@ -144,7 +146,7 @@ namespace Torch.Server base.Start(); //Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager. MySandboxGame.IsReloading = true; - runInternal.Invoke(null, null); + _dsRunInternal.Invoke(); MySandboxGame.Log.Close(); State = ServerState.Stopped; diff --git a/Torch.Tests/ReflectionTests.cs b/Torch.Tests/ReflectionSystemTest.cs similarity index 59% rename from Torch.Tests/ReflectionTests.cs rename to Torch.Tests/ReflectionSystemTest.cs index eec6ab7..0e6fc92 100644 --- a/Torch.Tests/ReflectionTests.cs +++ b/Torch.Tests/ReflectionSystemTest.cs @@ -1,61 +1,54 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Torch.API; -using Torch.Client; -using Torch.Managers; +using Torch.Utils; using Xunit; -using Xunit.Abstractions; namespace Torch.Tests { - public class ReflectionTests + public class ReflectionSystemTest { - private static string GetGameBinaries() + static ReflectionSystemTest() { - 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"); + TestUtils.Init(); } + + private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding)); + public static IEnumerable Getters => _manager.Getters; - private static readonly TorchAssemblyResolver _torchResolver = - new TorchAssemblyResolver(GetGameBinaries()); + public static IEnumerable Setters => _manager.Setters; + + public static IEnumerable Invokers => _manager.Invokers; #region Binding [Theory] [MemberData(nameof(Getters))] - public void TestBindingGetter(FieldRef field) + public void TestBindingGetter(ReflectionTestManager.FieldRef field) { - Assert.True(ReflectionManager.Process(field.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(FieldRef field) + public void TestBindingSetter(ReflectionTestManager.FieldRef field) { - Assert.True(ReflectionManager.Process(field.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(FieldRef field) + public void TestBindingInvoker(ReflectionTestManager.FieldRef field) { - Assert.True(ReflectionManager.Process(field.Field)); + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); if (field.Field.IsStatic) Assert.NotNull(field.Field.GetValue(null)); } @@ -129,7 +122,7 @@ namespace Torch.Tests [Fact] public void TestInstanceFieldGet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); var target = new ReflectionTestTarget { @@ -140,7 +133,7 @@ namespace Torch.Tests [Fact] public void TestInstanceFieldSet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); var target = new ReflectionTestTarget(); ReflectionTestBinding.TestFieldSetter.Invoke(target, testNumber); @@ -150,7 +143,7 @@ namespace Torch.Tests [Fact] public void TestInstancePropertyGet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); var target = new ReflectionTestTarget { @@ -162,7 +155,7 @@ namespace Torch.Tests [Fact] public void TestInstancePropertySet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); var target = new ReflectionTestTarget(); ReflectionTestBinding.TestPropertySetter.Invoke(target, testNumber); @@ -172,7 +165,7 @@ namespace Torch.Tests [Fact] public void TestInstanceInvoke() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); var target = new ReflectionTestTarget(); Assert.True(ReflectionTestBinding.TestCall.Invoke(target, 1)); Assert.False(ReflectionTestBinding.TestCall.Invoke(target, -1)); @@ -183,7 +176,7 @@ namespace Torch.Tests [Fact] public void TestStaticFieldGet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); ReflectionTestTarget.TestFieldStatic = testNumber; Assert.Equal(testNumber, ReflectionTestBinding.TestStaticFieldGetter.Invoke()); @@ -191,7 +184,7 @@ namespace Torch.Tests [Fact] public void TestStaticFieldSet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); ReflectionTestBinding.TestStaticFieldSetter.Invoke(testNumber); Assert.Equal(testNumber, ReflectionTestTarget.TestFieldStatic); @@ -200,7 +193,7 @@ namespace Torch.Tests [Fact] public void TestStaticPropertyGet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); ReflectionTestTarget.TestPropertyStatic = testNumber; Assert.Equal(testNumber, ReflectionTestBinding.TestStaticPropertyGetter.Invoke()); @@ -209,7 +202,7 @@ namespace Torch.Tests [Fact] public void TestStaticPropertySet() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); int testNumber = AcquireRandomNum(); ReflectionTestBinding.TestStaticPropertySetter.Invoke(testNumber); Assert.Equal(testNumber, ReflectionTestTarget.TestPropertyStatic); @@ -218,89 +211,11 @@ namespace Torch.Tests [Fact] public void TestStaticInvoke() { - ReflectionManager.Process(typeof(ReflectionTestBinding)); + ReflectedManager.Process(typeof(ReflectionTestBinding)); Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1)); Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1)); } #endregion #endregion - - #region FieldProvider - public struct FieldRef - { - public FieldInfo Field; - - public FieldRef(FieldInfo f) - { - Field = f; - } - - public override string ToString() - { - return Field.DeclaringType?.FullName + "." + Field.Name; - } - } - - private static bool _init = false; - private static HashSet _getters, _setters, _invokers; - - private static void Init() - { - if (_init) - return; - _getters = new HashSet(); - _setters = new HashSet(); - _invokers = new HashSet(); - - foreach (Type type in typeof(TorchBase).Assembly.GetTypes()) - InternalInit(type); - InternalInit(typeof(ReflectionTestBinding)); - - _init = true; - } - - private static void InternalInit(Type type) - { - foreach (FieldInfo field in type.GetFields(BindingFlags.Static | - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic)) - { - if (field.GetCustomAttribute() != null) - _invokers.Add(new object[] { new FieldRef(field) }); - if (field.GetCustomAttribute() != null) - _getters.Add(new object[] { new FieldRef(field) }); - if (field.GetCustomAttribute() != null) - _setters.Add(new object[] { new FieldRef(field) }); - } - } - - public static IEnumerable Getters - { - get - { - Init(); - return _getters; - } - } - - public static IEnumerable Setters - { - get - { - Init(); - return _setters; - } - } - - public static IEnumerable Invokers - { - get - { - Init(); - return _invokers; - } - } - #endregion - } + } } diff --git a/Torch.Tests/ReflectionTestManager.cs b/Torch.Tests/ReflectionTestManager.cs new file mode 100644 index 0000000..425024e --- /dev/null +++ b/Torch.Tests/ReflectionTestManager.cs @@ -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 _getters = new HashSet(); + private readonly HashSet _setters = new HashSet(); + private readonly HashSet _invokers = new HashSet(); + + 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() != null) + _invokers.Add(new object[] { new FieldRef(field) }); + if (field.GetCustomAttribute() != null) + _getters.Add(new object[] { new FieldRef(field) }); + if (field.GetCustomAttribute() != null) + _setters.Add(new object[] { new FieldRef(field) }); + } + return this; + } + + public IEnumerable Getters => _getters; + + public IEnumerable Setters => _setters; + + public IEnumerable Invokers => _invokers; + + #endregion + + } +} diff --git a/Torch.Tests/TestUtils.cs b/Torch.Tests/TestUtils.cs new file mode 100644 index 0000000..323186b --- /dev/null +++ b/Torch.Tests/TestUtils.cs @@ -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; + } +} diff --git a/Torch.Tests/Torch.Tests.csproj b/Torch.Tests/Torch.Tests.csproj index 21cbdcb..20ab0ad 100644 --- a/Torch.Tests/Torch.Tests.csproj +++ b/Torch.Tests/Torch.Tests.csproj @@ -56,7 +56,11 @@ - + + + + + diff --git a/Torch.Tests/TorchReflectionTest.cs b/Torch.Tests/TorchReflectionTest.cs new file mode 100644 index 0000000..bb495d8 --- /dev/null +++ b/Torch.Tests/TorchReflectionTest.cs @@ -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 Getters => Manager().Getters; + + public static IEnumerable Setters => Manager().Setters; + + public static IEnumerable 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 + } +} diff --git a/Torch.sln b/Torch.sln index f01c4d6..fa96edb 100644 --- a/Torch.sln +++ b/Torch.sln @@ -18,6 +18,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,6 +58,12 @@ Global {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|Any CPU.ActiveCfg = Release|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|Any CPU.ActiveCfg = Debug|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|Any CPU.ActiveCfg = Release|x64 + {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.ActiveCfg = Release|x64 + {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index a267256..ac1f09f 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -26,6 +26,7 @@ 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; @@ -167,15 +168,6 @@ namespace Torch.Managers } MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse; MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse; -// _members = MyMultiplayer.Static.GetPrivateField>("m_members", true); -// if (_members == null) -// throw new InvalidOperationException("Unable to get m_members from MyMultiplayer.Static. Is this a dedicated server?"); -// _waitingForGroup = MyMultiplayer.Static.GetPrivateField>("m_waitingForGroup", true); -// if (_waitingForGroup == null) -// throw new InvalidOperationException("Unable to get m_waitingForGroup from MyMultiplayer.Static. Is this a dedicated server?"); -// _kickedClients = MyMultiplayer.Static.GetPrivateField>("m_kickedClients", true); -// if (_kickedClients == null) -// throw new InvalidOperationException("Unable to get m_kickedClients from MyMultiplayer.Static. Is this a dedicated server?"); Log.Info("Steam auth initialized"); } @@ -279,19 +271,19 @@ namespace Torch.Managers UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response); return; } - if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase) MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit) + 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((string)Reflection.InvokeStaticMethod(typeof(MyDedicatedServerBase), "ConvertSteamIDFrom64", steamID))) + MySandboxGame.ConfigDedicated.Administrators.Contains(ConvertSteamIDFrom64(steamID))) { this.UserAccepted(steamID); return; } - if ((MyGameServiceAccountType)Reflection.InvokeStaticMethod(typeof(MyGameService), "GetServerAccountType", MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) + if (GetServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) { UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid); return; @@ -317,7 +309,7 @@ namespace Torch.Managers private void UserAccepted(ulong steamId) { - Reflection.InvokePrivateMethod(MyMultiplayer.Static, "UserAccepted", steamId); + UserAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId); var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected }; Log.Info($"Player {vm.Name} joined ({vm.SteamId})"); @@ -325,6 +317,15 @@ namespace Torch.Managers PlayerJoined?.Invoke(vm); } + [ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))] + private static Func ConvertSteamIDFrom64; + + [ReflectedStaticMethod(Type = typeof(MyGameService))] + private static Func GetServerAccountType; + + [ReflectedMethod(Name = "UserAccepted")] + private static Action UserAcceptedImpl; + [ReflectedMethod] private static Action UserRejected; [ReflectedMethod] diff --git a/Torch/Managers/NetworkManager/NetworkManager.cs b/Torch/Managers/NetworkManager/NetworkManager.cs index a46fbd3..91dff87 100644 --- a/Torch/Managers/NetworkManager/NetworkManager.cs +++ b/Torch/Managers/NetworkManager/NetworkManager.cs @@ -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 _networkHandlers = new HashSet(); private bool _init; + [ReflectedGetter(Name = "m_typeTable")] + private static Func _typeTableGetter; + [ReflectedGetter(Name = "m_methodInfoLookup")] + private static Func> _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"); @@ -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); @@ -107,7 +106,7 @@ namespace Torch.Managers } #region Network Intercept - + /// /// This is the main body of the network intercept system. When messages come in from clients, they are processed here /// before being passed on to the game server. @@ -132,7 +131,7 @@ namespace Torch.Managers } return; } - + var stream = new BitStream(); stream.ResetRead(packet); @@ -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 } } @@ -236,18 +235,18 @@ namespace Torch.Managers #region Network Injection - + /// /// Broadcasts an event to all connected clients /// /// /// /// - 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); - } + } /// /// Sends an event to one client by SteamId @@ -257,9 +256,9 @@ namespace Torch.Managers /// /// 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); + } /// /// 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()) + if (!method.HasAttribute()) 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 /// /// public void RaiseStaticEvent(MethodInfo method, params object[] args) - { + { //default(EndpointId) tells the network to broadcast the message RaiseStaticEvent(method, default(EndpointId), args); - } + } /// /// Sends a static event to one client by SteamId @@ -339,9 +338,9 @@ namespace Torch.Managers /// /// public void RaiseStaticEvent(MethodInfo method, ulong steamId, params object[] args) - { - RaiseEvent(method, null, new EndpointId(steamId), args); - } + { + RaiseEvent(method, null, new EndpointId(steamId), args); + } /// /// Sends a static event to one client @@ -355,18 +354,17 @@ namespace Torch.Managers } private CallSite TryGetStaticCallSite(MethodInfo method) - { - var methodLookup = (Dictionary)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)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; } diff --git a/Torch/SteamService.cs b/Torch/SteamService.cs index 4044bc5..a39b443 100644 --- a/Torch/SteamService.cs +++ b/Torch/SteamService.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using SteamSDK; using VRage.Steam; using Sandbox; +using Torch.Utils; +using VRage.GameServices; namespace Torch { @@ -17,48 +19,67 @@ namespace Torch /// public class SteamService : MySteamService { + [ReflectedSetter(Name = nameof(SteamServerAPI))] + private static Action _steamServerAPISetter; + [ReflectedSetter(Name = "m_gameServer")] + private static Action _steamGameServerSetter; + [ReflectedSetter(Name = nameof(AppId))] + private static Action _steamAppIdSetter; + [ReflectedSetter(Name = nameof(API))] + private static Action _steamApiSetter; + [ReflectedSetter(Name = nameof(IsActive))] + private static Action _steamIsActiveSetter; + [ReflectedSetter(Name = nameof(UserId))] + private static Action _steamUserIdSetter; + [ReflectedSetter(Name = nameof(UserName))] + private static Action _steamUserNameSetter; + [ReflectedSetter(Name = nameof(OwnsGame))] + private static Action _steamOwnsGameSetter; + [ReflectedSetter(Name = nameof(UserUniverse))] + private static Action _steamUserUniverseSetter; + [ReflectedSetter(Name = nameof(BranchName))] + private static Action _steamBranchNameSetter; + [ReflectedSetter(Name = nameof(InventoryAPI))] + private static Action _steamInventoryAPISetter; + [ReflectedMethod] + private static Action RegisterCallbacks; + [ReflectedSetter(Name = nameof(Peer2Peer))] + private static Action _steamPeer2PeerSetter; + public SteamService(bool isDedicated, uint appId) : base(true, appId) { - // TODO: Add protection for this mess... somewhere - SteamSDK.SteamServerAPI.Instance.Dispose(); - var steam = typeof(MySteamService); - - 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 }); + SteamServerAPI.Instance.Dispose(); + _steamServerAPISetter.Invoke(this, null); + _steamGameServerSetter.Invoke(this, null); + _steamAppIdSetter.Invoke(this, 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); + _steamIsActiveSetter.Invoke(this, steamApi.Init()); 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); } } - steam.GetProperty("Peer2Peer").GetSetMethod(true).Invoke(this, new object[] { new MySteamPeer2Peer() }); + _steamPeer2PeerSetter.Invoke(this, new MySteamPeer2Peer()); } } } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index 5ab2905..92974e5 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -151,7 +151,7 @@ - + @@ -177,9 +177,9 @@ - + - + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 519d7d1..7bdc650 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -21,6 +21,7 @@ using Torch.API.Managers; using Torch.API.ModAPI; using Torch.Commands; using Torch.Managers; +using Torch.Utils; using VRage.Collections; using VRage.FileSystem; using VRage.Game.ObjectBuilder; @@ -36,6 +37,12 @@ namespace Torch /// public abstract class TorchBase : ViewModel, ITorchBase, IPlugin { + static TorchBase() + { + // We can safely never detach this since we don't reload assemblies. + new ReflectedManager().Attach(); + } + /// /// Hack because *keen*. /// Use only if necessary, prefer dependency injection. @@ -224,7 +231,6 @@ namespace Torch public virtual void Init() { Debug.Assert(!_init, "Torch instance is already initialized."); - SpaceEngineersGame.SetupBasicGameInfo(); SpaceEngineersGame.SetupPerGameSettings(); diff --git a/Torch/Managers/ReflectionManager.cs b/Torch/Utils/ReflectedManager.cs similarity index 96% rename from Torch/Managers/ReflectionManager.cs rename to Torch/Utils/ReflectedManager.cs index 624cebb..ce27f93 100644 --- a/Torch/Managers/ReflectionManager.cs +++ b/Torch/Utils/ReflectedManager.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Sandbox.Engine.Multiplayer; using Torch.API; -namespace Torch.Managers +namespace Torch.Utils { public abstract class ReflectedMemberAttribute : Attribute { @@ -118,27 +118,28 @@ namespace Torch.Managers } /// - /// Automatically calls for every assembly already loaded, and every assembly that is loaded in the future. + /// Automatically calls for every assembly already loaded, and every assembly that is loaded in the future. /// - public class ReflectionManager : Manager + public class ReflectedManager { private static readonly string[] _namespaceBlacklist = new[] { "System", "VRage", "Sandbox", "SpaceEngineers" }; - - /// - public ReflectionManager(ITorchBase torchInstance) : base(torchInstance) { } - - /// - public override void Attach() + + /// + /// Registers the assembly load event and loads every already existing assembly. + /// + public void Attach() { foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) Process(asm); AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad; } - /// - public override void Detach() + /// + /// Deregisters the assembly load event + /// + public void Detach() { AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomain_AssemblyLoad; } diff --git a/Torch/Reflection.cs b/Torch/Utils/Reflection.cs similarity index 99% rename from Torch/Reflection.cs rename to Torch/Utils/Reflection.cs index 9c523b8..84e383a 100644 --- a/Torch/Reflection.cs +++ b/Torch/Utils/Reflection.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using NLog; using SteamSDK; -namespace Torch +namespace Torch.Utils { public static class Reflection { diff --git a/Torch/TorchAssemblyResolver.cs b/Torch/Utils/TorchAssemblyResolver.cs similarity index 89% rename from Torch/TorchAssemblyResolver.cs rename to Torch/Utils/TorchAssemblyResolver.cs index 4091508..ca1f6bb 100644 --- a/Torch/TorchAssemblyResolver.cs +++ b/Torch/Utils/TorchAssemblyResolver.cs @@ -7,8 +7,11 @@ using System.Text; using System.Threading.Tasks; using NLog; -namespace Torch.Client +namespace Torch.Utils { + /// + /// Adds and removes an additional library path + /// public class TorchAssemblyResolver : IDisposable { private static readonly Logger _log = LogManager.GetCurrentClassLogger(); @@ -17,6 +20,10 @@ namespace Torch.Client private readonly string[] _paths; private readonly string _removablePathPrefix; + /// + /// Initializes an assembly resolver that looks at the given paths for assemblies + /// + /// public TorchAssemblyResolver(params string[] paths) { string location = Assembly.GetEntryAssembly()?.Location ?? GetType().Assembly.Location; @@ -79,6 +86,9 @@ namespace Torch.Client return null; } + /// + /// Unregisters the assembly resolver + /// public void Dispose() { AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainOnAssemblyResolve;