From 796a78a87f2e106546391f4383f358242c6ba9ac Mon Sep 17 00:00:00 2001 From: Bob Da Ross <52760019+BobDaRoss@users.noreply.github.com> Date: Mon, 15 Mar 2021 23:00:40 -0500 Subject: [PATCH] Add project files. --- Messages/ClientMessages.cs | 68 ++++ Messages/WorldRequest.cs | 65 ++++ Properties/AssemblyInfo.cs | 36 +++ SeamlessClient.cs | 226 ++++++++++++++ SeamlessClientPlugin.csproj | 158 ++++++++++ SeamlessClientPlugin.sln | 25 ++ SeamlessTransfer/LoadServer.cs | 551 +++++++++++++++++++++++++++++++++ SeamlessTransfer/Transfer.cs | 145 +++++++++ Utilities/Utility.cs | 38 +++ packages.config | 4 + 10 files changed, 1316 insertions(+) create mode 100644 Messages/ClientMessages.cs create mode 100644 Messages/WorldRequest.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 SeamlessClient.cs create mode 100644 SeamlessClientPlugin.csproj create mode 100644 SeamlessClientPlugin.sln create mode 100644 SeamlessTransfer/LoadServer.cs create mode 100644 SeamlessTransfer/Transfer.cs create mode 100644 Utilities/Utility.cs create mode 100644 packages.config diff --git a/Messages/ClientMessages.cs b/Messages/ClientMessages.cs new file mode 100644 index 0000000..6eaa343 --- /dev/null +++ b/Messages/ClientMessages.cs @@ -0,0 +1,68 @@ +using ProtoBuf; +using Sandbox.Game.World; +using Sandbox.ModAPI; +using SeamlessClientPlugin.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SeamlessClientPlugin.ClientMessages +{ + public enum ClientMessageType + { + FirstJoin, + TransferServer + } + + + [ProtoContract] + public class ClientMessage + { + [ProtoMember(1)] + public ClientMessageType MessageType; + [ProtoMember(2)] + public byte[] MessageData; + [ProtoMember(3)] + public long IdentityID; + [ProtoMember(4)] + public ulong SteamID; + [ProtoMember(5)] + public string PluginVersion = "0"; + + + + public ClientMessage(ClientMessageType Type) + { + MessageType = Type; + + if (!MyAPIGateway.Multiplayer.IsServer) + { + if(MyAPIGateway.Session.LocalHumanPlayer == null) + { + return; + } + + IdentityID = MySession.Static?.LocalHumanPlayer?.Identity?.IdentityId ?? 0; + SteamID = MySession.Static?.LocalHumanPlayer?.Id.SteamId ?? 0; + PluginVersion = SeamlessClient.Version; + } + } + + public ClientMessage() { } + + public void SerializeData(T Data) + { + MessageData = Utility.Serialize(Data); + } + + public T DeserializeData() + { + if (MessageData == null) + return default(T); + + return Utility.Deserialize(MessageData); + } + } +} diff --git a/Messages/WorldRequest.cs b/Messages/WorldRequest.cs new file mode 100644 index 0000000..b1b7020 --- /dev/null +++ b/Messages/WorldRequest.cs @@ -0,0 +1,65 @@ +using NLog; +using ProtoBuf; +using Sandbox.Engine.Multiplayer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using VRage.Game; +using VRage.ObjectBuilders; + +namespace SeamlessClientPlugin.ClientMessages +{ + [ProtoContract] + public class WorldRequest + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + [ProtoMember(1)] + public ulong PlayerID; + [ProtoMember(2)] + public long IdentityID; + [ProtoMember(3)] + public string PlayerName; + [ProtoMember(4)] + public byte[] WorldData; + + public WorldRequest(ulong PlayerID,long PlayerIdentity, string Name) + { + this.PlayerID = PlayerID; + this.PlayerName = Name; + this.IdentityID = PlayerIdentity; + } + + public WorldRequest() { } + + public void SerializeWorldData(MyObjectBuilder_World WorldData) + { + MethodInfo CleanupData = typeof(MyMultiplayerServerBase).GetMethod("CleanUpData", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[3] +{ + typeof(MyObjectBuilder_World), + typeof(ulong), + typeof(long), +}, null); + object[] Data = new object[] { WorldData, PlayerID, IdentityID }; + CleanupData.Invoke(null, Data); + WorldData = (MyObjectBuilder_World)Data[0]; + using (MemoryStream memoryStream = new MemoryStream()) + { + MyObjectBuilderSerializer.SerializeXML(memoryStream, WorldData, MyObjectBuilderSerializer.XmlCompression.Gzip); + this.WorldData = memoryStream.ToArray(); + Log.Warn("Successfully Converted World"); + } + } + + public MyObjectBuilder_World DeserializeWorldData() + { + MyObjectBuilderSerializer.DeserializeGZippedXML(new MemoryStream(WorldData), out var objectBuilder); + return objectBuilder; + } + + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f78dd9c --- /dev/null +++ b/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("SeamlessClientPlugin")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SeamlessClientPlugin")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[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("102a3d80-b588-43ba-b686-000fa8ff1a0c")] + +// 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/SeamlessClient.cs b/SeamlessClient.cs new file mode 100644 index 0000000..bafc6eb --- /dev/null +++ b/SeamlessClient.cs @@ -0,0 +1,226 @@ +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game; +using Sandbox.Game.Entities; +using Sandbox.Game.Gui; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Sandbox.Graphics.GUI; +using Sandbox.ModAPI; +using SeamlessClientPlugin.ClientMessages; +using SeamlessClientPlugin.SeamlessTransfer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Timers; +using VRage.Input; +using VRage.Plugins; +using VRage.Utils; +using VRageRender; + +namespace SeamlessClientPlugin +{ + + //SendAllMembersDataToClient + + public class SeamlessClient : IPlugin + { + /* First Step. How does a player join a game? + * First JoinGameInternal is called with the ServersLobbyID. + * In this method, MySessionLoader.UnloadAndExitToMenu() is called. Ultimatley we want to prevent this as we dont want to unload the entire game. Just the basic neccessities. + * Then JoinLobby is called. This looks to be simply a check to see if the client gets a result from the server. If it does, it initilizes a new STATIC/Multiplayer base by: [Static = new MyMultiplayerLobbyClient(lobby, new MySyncLayer(new MyTransportLayer(2)))));] + * Once this above method is done and join is done, we begin the OnJoin() + * + * On join begins by checking join result. Success downloads world and failed sends you back to menu. (May need to use this to send players to menu) + * Download world Requires the multiplayerbase and MyGUIScreenProgress. Which is essentially checking that the user hasnt cliecked left or closed. + * + * StringBuilder text = MyTexts.Get(MyCommonTexts.DialogTextJoiningWorld); + * MyGuiScreenProgress progress = new MyGuiScreenProgress(text, MyCommonTexts.Cancel); + * + * This just looks to be like what happens still with the little GUI popupscreen before load starts + * + * DownloadWorld also contains a method to get the serversessionstate too. We will need to check this before load. Ultimatley once everything has passed join checks Downloadworld is called. (MyMultiplayerClientBase.DownloadWorld) + * + * + * MyMultiplayerClientBase.DownloadWorld simply rasies a static event to the server for world request. [return MyMultiplayerServerBase.WorldRequest;] + * + * + * + * + * MyMultiplayerServerBase.WorldRequest (WorldRequest) This is near the start of where the magic happens. THIS IS ALL RAN SERVERSIDE + * Checks to see if the client has been kicked or banned to prevent world requests. Not sure we really need to worry about this. + * + * Server gets world clears non important data such as player gps. Our player gps gets added on join so we can yeet this. + * Also theres a sendfluch via transport layer. Might need to keep this in mind and use this for our testings + * Theres a CleanUpData that gets called with world and playerid/identity ID. This is probably to limit things and only send whats neccessary + * CLEANUPDATA shows me how to send allplayers data synced on the server. + * + * + * Once we have everythings we use a MS to serialize everything to byte[] and via replication layer we send world to client. + * + * + * BACK TO CLIENT: + * RecieveWorld is called and packet is deserialized and then MyJoinGameHelper.WorldReceived(...,...) is called + * + * WorldReceived does some checks for extra version mismatches and to make sure the CheckPointObjectBuilder isnt null etc. + * Once it passes all these checks, CheckDx11AndJoin(world, MyMutliplayerBase) is called + * + * CheckDx11AndJoin just checks if its a scenario world or not. Forget about this. We can figure that out all later. Then it runs: MySessionLoader.LoadMultiplayerSession(world, multiplayer); + * + * + * MySessionLoader.LoadMultiplayerSession looks to be the start of the join code. It also checks for mod mismatches. (And Downloads them). However once it passes this, MySession.LoadMultiplayer is called. + * + * + * + * + * + * + * + * MySession.LoadMultiplayer (The most important step in world loading) + * Creates new MySession. + * Does settings stuff. + * LoadMembersFromWorld (Loads Clients) + * FixSessionComponentObjectBuilders + * + * + * + * PrepareBaseSession is something we need. Looks like it does some weird stuff to init fonts, Sector Enviroment settings, Loading datacomponents from world, and re-initilizes modAPI stuff + * DeserializeClusters + * Loads planets. + * RegistersChat + * + * LOADWORLD ----------- + * Static.BeforeStartComponents + * + * + * + * + * -plugin "../Plugins/SeamlessClientPlugin.dll" + */ + + + + public static string Version = "1.0.0"; + private bool Initilized = false; + private bool SentPingResponse = false; + public const ushort SeamlessClientNetID = 2936; + private Timer PingTimer = new Timer(2000); + + public static LoadServer Server = new LoadServer(); + + + public static bool IsSwitching = false; + public static bool RanJoin = false; + + public static Action JoinAction = () => { }; + + + public void Dispose() + { +#pragma warning disable CS0618 // Type or member is obsolete + MyAPIGateway.Multiplayer?.UnregisterMessageHandler(SeamlessClientNetID, MessageHandler); +#pragma warning restore CS0618 // Type or member is obsolete + Initilized = false; + SentPingResponse = false; + PingTimer.Stop(); + //throw new NotImplementedException(); + } + + public void Init(object gameInstance) + { + TryShow("Running Seamless Client Plugin v[" + Version + "]"); + // Reload = new ReloadPatch(); + //Patching goes here + + + PingTimer.Elapsed += PingTimer_Elapsed; + PingTimer.Start(); + //throw new NotImplementedException(); + } + + private void PingTimer_Elapsed(object sender, ElapsedEventArgs e) + { + //TryShow("Sending PluginVersion to Server!"); + try + { + + ClientMessage PingServer = new ClientMessage(ClientMessageType.FirstJoin); + MyAPIGateway.Multiplayer.SendMessageToServer(SeamlessClientNetID, Utilities.Utility.Serialize(PingServer)); + } + catch (Exception ex) + { + TryShow(ex.ToString()); + } + } + + + public void Update() + { + if (MyAPIGateway.Multiplayer == null) + return; + + if (!Initilized) + { + TryShow("Initilizing Communications!"); + RunInitilizations(); + Initilized = true; + } + //OnNewPlayerRequest + //throw new NotImplementedException(); + } + + + + public static void RunInitilizations() + { +#pragma warning disable CS0618 // Type or member is obsolete + MyAPIGateway.Multiplayer.RegisterMessageHandler(SeamlessClientNetID, MessageHandler); +#pragma warning restore CS0618 // Type or member is obsolete + //We need to initiate ping request + } + + public static void DisposeInitilizations() + { + MyAPIGateway.Multiplayer.UnregisterMessageHandler(SeamlessClientNetID, MessageHandler); + } + + + private static void MessageHandler(byte[] bytes) + { + try + { + ClientMessage Recieved = Utilities.Utility.Deserialize(bytes); + if (Recieved.MessageType == ClientMessageType.TransferServer) + { + Transfer TransferMessage = Recieved.DeserializeData(); + IsSwitching = true; + TransferMessage.PingServerAndBeginRedirect(); + RanJoin = false; + //DisposeInitilizations(); + } + else if (Recieved.MessageType == ClientMessageType.FirstJoin) + { + + } + } + catch (Exception ex) + { + TryShow(ex.ToString()); + } + } + + + + + public static void TryShow(string message) + { + if (MySession.Static?.LocalHumanPlayer != null) + MyAPIGateway.Utilities?.ShowMessage("NetworkClient", message); + + MyLog.Default?.WriteLineAndConsole($"SeamlessClient: {message}"); + } + } +} diff --git a/SeamlessClientPlugin.csproj b/SeamlessClientPlugin.csproj new file mode 100644 index 0000000..b7ae61d --- /dev/null +++ b/SeamlessClientPlugin.csproj @@ -0,0 +1,158 @@ + + + + + Debug + AnyCPU + {102A3D80-B588-43BA-B686-000FA8FF1A0C} + Library + Properties + SeamlessClientPlugin + SeamlessClientPlugin + v4.7.2 + 512 + true + + + + + + true + full + false + ..\..\..\..\..\Program Files %28x86%29\Steam\steamapps\common\SpaceEngineers\Plugins\ + DEBUG;TRACE + prompt + 4 + x64 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + false + + + + ..\Nexus\packages\Lib.Harmony.2.0.4\lib\net472\0Harmony.dll + + + ..\..\..\Desktop\TorchServers\torch-server\NLog.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\ProtoBuf.Net.Core.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Common.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Game.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Game.XmlSerializers.dll + False + + + False + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Graphics.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.Game.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.ObjectBuilders.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.ObjectBuilders.XmlSerializers.dll + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Audio.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Game.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Input.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Library.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Math.dll + False + + + ..\..\..\Desktop\TorchServers\torch-server\DedicatedServer64\VRage.Render.dll + False + + + ..\..\..\Desktop\TorchServers\torch-server\DedicatedServer64\VRage.Steam.dll + False + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.UserInterface.dll + False + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SeamlessClientPlugin.sln b/SeamlessClientPlugin.sln new file mode 100644 index 0000000..072235d --- /dev/null +++ b/SeamlessClientPlugin.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31005.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeamlessClientPlugin", "SeamlessClientPlugin.csproj", "{102A3D80-B588-43BA-B686-000FA8FF1A0C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {102A3D80-B588-43BA-B686-000FA8FF1A0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {102A3D80-B588-43BA-B686-000FA8FF1A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {102A3D80-B588-43BA-B686-000FA8FF1A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {102A3D80-B588-43BA-B686-000FA8FF1A0C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8B085CF6-FC14-48F2-B9F2-4D88971D74DD} + EndGlobalSection +EndGlobal diff --git a/SeamlessTransfer/LoadServer.cs b/SeamlessTransfer/LoadServer.cs new file mode 100644 index 0000000..8fed05d --- /dev/null +++ b/SeamlessTransfer/LoadServer.cs @@ -0,0 +1,551 @@ +using HarmonyLib; +using Sandbox; +using Sandbox.Definitions; +using Sandbox.Engine; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Engine.Physics; +using Sandbox.Engine.Utils; +using Sandbox.Engine.Voxels; +using Sandbox.Game; +using Sandbox.Game.Entities; +using Sandbox.Game.Gui; +using Sandbox.Game.GUI; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.Screens.Helpers; +using Sandbox.Game.SessionComponents; +using Sandbox.Game.World; +using Sandbox.Graphics.GUI; +using Sandbox.ModAPI; +using SeamlessClientPlugin.Utilities; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using VRage; +using VRage.Game; +using VRage.Game.ModAPI; +using VRage.Game.SessionComponents; +using VRage.Game.Voxels; +using VRage.GameServices; +using VRage.Network; +using VRage.Serialization; +using VRage.Utils; +using VRageMath; +using VRageRender; +using VRageRender.Messages; + +namespace SeamlessClientPlugin.SeamlessTransfer +{ + public class LoadServer + { + //Protected or internal class types + private static readonly Type ClientType = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClient, Sandbox.Game"); + private static readonly Type SyncLayerType = Type.GetType("Sandbox.Game.Multiplayer.MySyncLayer, Sandbox.Game"); + private static readonly Type MyTransportLayerType = Type.GetType("Sandbox.Engine.Multiplayer.MyTransportLayer, Sandbox.Game"); + private static readonly Type MySessionType = Type.GetType("Sandbox.Game.World.MySession, Sandbox.Game"); + private static readonly Type VirtualClientsType = Type.GetType("Sandbox.Engine.Multiplayer.MyVirtualClients, Sandbox.Game"); + private static readonly Type GUIScreenChat = Type.GetType("Sandbox.Game.Gui.MyGuiScreenChat, Sandbox.Game"); + + private static Harmony Patcher = new Harmony("SeamlessClientReUnload"); + private static MyGameServerItem Server; + private static MyObjectBuilder_World World; + + public static object MyMulitplayerClient; + + + + public static ConstructorInfo ClientConstructor; + public static ConstructorInfo SyncLayerConstructor; + public static ConstructorInfo TransportLayerConstructor; + public static ConstructorInfo MySessionConstructor; + + //Reflected Methods + public static FieldInfo VirtualClients; + public static FieldInfo AdminSettings; + public static FieldInfo RemoteAdminSettings; + public static FieldInfo MPlayerGPSCollection; + public static MethodInfo RemovePlayerFromDictionary; + public static MethodInfo InitVirtualClients; + public static MethodInfo LoadPlayerInternal; + public static MethodInfo LoadMembersFromWorld; + + + public LoadServer() + { + InitiatePatches(); + + + + + //TargetWorld = World; + //Server = e; + + + } + + + + public void InitiatePatches() + { + //Patch the on connection event + MethodInfo OnJoin = ClientType.GetMethod("OnUserJoined", BindingFlags.NonPublic | BindingFlags.Instance); + Patcher.Patch(OnJoin, postfix: new HarmonyMethod(GetPatchMethod(nameof(OnUserJoined)))); + + + ClientConstructor = ClientType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(MyGameServerItem), SyncLayerType }, null); + SyncLayerConstructor = SyncLayerType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { MyTransportLayerType }, null); + TransportLayerConstructor = MyTransportLayerType?.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(int) }, null); + MySessionConstructor = MySessionType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(MySyncLayer), typeof(bool) }, null); + + if (ClientConstructor == null) + { + throw new InvalidOperationException("Couldn't find ClientConstructor"); + } + + if (SyncLayerConstructor == null) + { + throw new InvalidOperationException("Couldn't find SyncLayerConstructor"); + } + + if (TransportLayerConstructor == null) + { + throw new InvalidOperationException("Couldn't find TransportLayerConstructor"); + } + + if (MySessionConstructor == null) + { + throw new InvalidOperationException("Couldn't find MySessionConstructor"); + } + + RemovePlayerFromDictionary = typeof(MyPlayerCollection).GetMethod("RemovePlayerFromDictionary", BindingFlags.Instance | BindingFlags.NonPublic); + VirtualClients = typeof(MySession).GetField("VirtualClients", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + InitVirtualClients = VirtualClientsType.GetMethod("Init", BindingFlags.Instance | BindingFlags.Public); + LoadPlayerInternal = typeof(MyPlayerCollection).GetMethod("LoadPlayerInternal", BindingFlags.Instance | BindingFlags.NonPublic); + LoadMembersFromWorld = typeof(MySession).GetMethod("LoadMembersFromWorld", BindingFlags.NonPublic | BindingFlags.Instance); + AdminSettings = typeof(MySession).GetField("m_adminSettings", BindingFlags.Instance | BindingFlags.NonPublic); + RemoteAdminSettings = typeof(MySession).GetField("m_remoteAdminSettings", BindingFlags.Instance | BindingFlags.NonPublic); + MPlayerGPSCollection = typeof(MyPlayerCollection).GetField("m_players", BindingFlags.Instance | BindingFlags.NonPublic); + } + + + private static MethodInfo GetPatchMethod(string v) + { + return typeof(LoadServer).GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + + + private static void OnUserJoined(ref JoinResultMsg msg) + { + if (SeamlessClient.IsSwitching && msg.JoinResult == JoinResult.OK) + { + SeamlessClient.TryShow("User Joined! Result: "+msg.JoinResult.ToString()); + ForceClientConnection(); + }else if (SeamlessClient.IsSwitching && msg.JoinResult != JoinResult.OK) + { + SeamlessClient.TryShow("Failed to join server! Reason: " + msg.JoinResult.ToString()); + MySession.Static.Unload(); + } + } + + + + public static void LoadWorldData(MyGameServerItem TargetServer, MyObjectBuilder_World TargetWorld) + { + Server = TargetServer; + World = TargetWorld; + } + + + public static void ResetMPClient() + { + try + { + + + MySandboxGame.Static.SessionCompatHelper.FixSessionComponentObjectBuilders(World.Checkpoint, World.Sector); + + + var LayerInstance = TransportLayerConstructor.Invoke(new object[] { 2 }); + var SyncInstance = SyncLayerConstructor.Invoke(new object[] { LayerInstance }); + var instance = ClientConstructor.Invoke(new object[] { Server, SyncInstance }); + MyMulitplayerClient = instance; + + + + + MyMultiplayer.Static = (MyMultiplayerBase)instance; + MyMultiplayer.Static.ExperimentalMode = MySandboxGame.Config.ExperimentalMode; + SeamlessClient.TryShow("Successfully set MyMultiplayer.Static"); + //var m = ClientType.GetMethod("SendPlayerData", BindingFlags.Public | BindingFlags.Instance); + //m.Invoke(MyMultiplayer.Static, new object[] { MyGameService.UserName }); + Server.GetGameTagByPrefix("gamemode"); + //typeof(MySession).GetMethod("LoadMembersFromWorld", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MySession.Static, new object[] { LoadServer.TargetWorld, MyMultiplayer.Static }); + + + //MyScreenManager.CloseScreen(GUIScreenChat); + MyHud.Chat.RegisterChat(MyMultiplayer.Static); + + } + catch (Exception ex) + { + SeamlessClient.TryShow("Error! " + ex.ToString()); + } + } + + public static void LoadMP(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession) + { + SeamlessClient.TryShow("Starting LoadMP!"); + + + //var MySessionConstructor = MySessionType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(MySyncLayer), typeof(bool) }, null); + //MySession.Static = (MySession)MySessionConstructor.Invoke(new object[] { MyMultiplayer.Static.SyncLayer, true }); + MySession.Static.Mods = World.Checkpoint.Mods; + MySession.Static.Settings = World.Checkpoint.Settings; + MySession.Static.CurrentPath = MyLocalCache.GetSessionSavesPath(MyUtils.StripInvalidChars(world.Checkpoint.SessionName), contentFolder: false, createIfNotExists: false); + MySession.Static.WorldBoundaries = world.Checkpoint.WorldBoundaries; + MySession.Static.InGameTime = MyObjectBuilder_Checkpoint.DEFAULT_DATE; + + + + // MySession.Static.Players.LoadConnectedPlayers(world.Checkpoint); + + //typeof(MySession).GetMethod("PrepareBaseSession", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(MyObjectBuilder_Checkpoint), typeof(MyObjectBuilder_Sector) }, null).Invoke(MySession.Static, new object[] { world.Checkpoint, world.Sector }); + + if (MyFakes.MP_SYNC_CLUSTERTREE) + { + SeamlessClient.TryShow("Deserializing Clusters!"); + //MyPhysics.DeserializeClusters(world.Clusters); + } + + + + //_ = world.Checkpoint.ControlledObject; + //world.Checkpoint.ControlledObject = -1L; + LoadOnlinePlayers(world.Checkpoint); + LoadWorld(world.Checkpoint, world.Sector); + SeamlessClient.TryShow("Loading World Complete!"); + } + + + + private static void LoadWorld(MyObjectBuilder_Checkpoint checkpoint, MyObjectBuilder_Sector sector) + { + + Dictionary AdminSettingsList = (Dictionary)RemoteAdminSettings.GetValue(MySession.Static); + AdminSettingsList.Clear(); + + MySession.Static.PromotedUsers.Clear(); + MySession.Static.CreativeTools.Clear(); + + MyEntities.MemoryLimitAddFailureReset(); + MySession.Static.ElapsedGameTime = new TimeSpan(checkpoint.ElapsedGameTime); + MySession.Static.InGameTime = checkpoint.InGameTime; + MySession.Static.Name = MyStatControlText.SubstituteTexts(checkpoint.SessionName); + MySession.Static.Description = checkpoint.Description; + + + if (checkpoint.PromotedUsers != null) + { + MySession.Static.PromotedUsers = checkpoint.PromotedUsers.Dictionary; + } + else + { + MySession.Static.PromotedUsers = new Dictionary(); + } + + + + + foreach (KeyValuePair item in checkpoint.AllPlayersData.Dictionary) + { + ulong clientId = item.Key.GetClientId(); + AdminSettingsEnum adminSettingsEnum = (AdminSettingsEnum)item.Value.RemoteAdminSettings; + if (checkpoint.RemoteAdminSettings != null && checkpoint.RemoteAdminSettings.Dictionary.TryGetValue(clientId, out var value)) + { + adminSettingsEnum = (AdminSettingsEnum)value; + } + if (!MyPlatformGameSettings.IsIgnorePcuAllowed) + { + adminSettingsEnum &= ~AdminSettingsEnum.IgnorePcu; + adminSettingsEnum &= ~AdminSettingsEnum.KeepOriginalOwnershipOnPaste; + } + + + AdminSettingsList[clientId] = adminSettingsEnum; + if (!Sync.IsDedicated && clientId == Sync.MyId) + { + AdminSettings.SetValue(MySession.Static, adminSettingsEnum); + + //m_adminSettings = adminSettingsEnum; + } + + + + if (!MySession.Static.PromotedUsers.TryGetValue(clientId, out var value2)) + { + value2 = MyPromoteLevel.None; + } + if (item.Value.PromoteLevel > value2) + { + MySession.Static.PromotedUsers[clientId] = item.Value.PromoteLevel; + } + if (!MySession.Static.CreativeTools.Contains(clientId) && item.Value.CreativeToolsEnabled) + { + MySession.Static.CreativeTools.Add(clientId); + } + } + + + //MySession.Static.WorkshopId = checkpoint.WorkshopId; + MySession.Static.Password = checkpoint.Password; + MySession.Static.PreviousEnvironmentHostility = checkpoint.PreviousEnvironmentHostility; + MySession.Static.RequiresDX = checkpoint.RequiresDX; + MySession.Static.CustomLoadingScreenImage = checkpoint.CustomLoadingScreenImage; + MySession.Static.CustomLoadingScreenText = checkpoint.CustomLoadingScreenText; + MySession.Static.CustomSkybox = checkpoint.CustomSkybox; + //FixIncorrectSettings(Settings); + // MySession.Static.AppVersionFromSave = checkpoint.AppVersion; + //MyToolbarComponent.InitCharacterToolbar(checkpoint.CharacterToolbar); + //LoadCameraControllerSettings(checkpoint); + + + + + foreach(var GPS in checkpoint.Gps.Dictionary) + { + if (GPS.Key != MySession.Static.LocalPlayerId) + continue; + + SeamlessClient.TryShow(GPS.Key + ":" + GPS.Value.Entries.Count); + + + } + + + SeamlessClient.TryShow("LocalPlayerID: " + MySession.Static.LocalPlayerId); + checkpoint.Gps.Dictionary.TryGetValue(MySession.Static.LocalPlayerId, out MyObjectBuilder_Gps GPSCollection); + SeamlessClient.TryShow("You have " + GPSCollection.Entries.Count + " gps points!"); + + + Dictionary> m_playerGpss = (Dictionary>)typeof(MyGpsCollection).GetField("m_playerGpss", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(MySession.Static.Gpss); + m_playerGpss.Clear(); + + + foreach (var GPS in GPSCollection.Entries) + { + MyGps myGps = new MyGps(GPS); + + if(MySession.Static.Gpss.AddPlayerGps(MySession.Static.LocalPlayerId, ref myGps)) + { + MyHud.GpsMarkers.RegisterMarker(myGps); + } + else + { + SeamlessClient.TryShow("Failed to registered Marker! It already exsists!"); + } + } + + + + MySession.Static.Gpss.LoadGpss(checkpoint); + MySession.Static.Toolbars.LoadToolbars(checkpoint); + + Sync.Players.RespawnComponent.InitFromCheckpoint(checkpoint); + + + } + + + + + + + + private static void ForceClientConnection() + { + SeamlessClient.IsSwitching = false; + + try + { + + try + { + if (MyMultiplayer.Static == null) + SeamlessClient.TryShow("MyMultiplayer.Static is null"); + + if (World == null) + SeamlessClient.TryShow("TargetWorld is null"); + + LoadClients(); + + LoadMP(World, MyMultiplayer.Static); + + + }catch(Exception ex) + { + SeamlessClient.TryShow(ex.ToString()); + + } + + + SeamlessClient.TryShow("Requesting Player From Server"); + Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, realPlayer: true, initialPlayer: true); + if (MySession.Static.ControlledEntity == null && Sync.IsServer && !Sandbox.Engine.Platform.Game.IsDedicated) + { + SeamlessClient.TryShow("C"); + MyLog.Default.WriteLine("ControlledObject was null, respawning character"); + //m_cameraAwaitingEntity = true; + MyPlayerCollection.RequestLocalRespawn(); + } + + + + + + //typeof(MyGuiScreenTerminal).GetMethod("CreateTabs") + MyMultiplayer.Static.OnSessionReady(); + MySession.Static.LoadDataComponents(); + //MyGuiSandbox.LoadData(false); + //MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.HUDScreen)); + MyRenderProxy.RebuildCullingStructure(); + MyRenderProxy.CollectGarbage(); + + SeamlessClient.TryShow("OnlinePlayers: " + MySession.Static.Players.GetOnlinePlayers().Count); + SeamlessClient.TryShow("Loading Complete!"); + + } + catch (Exception Ex) + { + SeamlessClient.TryShow(Ex.ToString()); + } + + + + + + + + } + + + + private static void LoadClients() + { + + + try + { + //Remove all old players + foreach (var Client in MySession.Static.Players.GetOnlinePlayers()) + { + if (Client.Id.SteamId == Sync.MyId) + continue; + + SeamlessClient.TryShow("Disconnecting: " + Client.DisplayName); + RemovePlayerFromDictionary.Invoke(MySession.Static.Players, new object[] { Client.Id }); + } + + //Clear all exsisting clients + foreach (var Client in Sync.Clients.GetClients().ToList()) + { + if (Client.SteamUserId == Sync.MyId) + continue; + + Sync.Clients.RemoveClient(Client.SteamUserId); + } + + + object VirtualClientsValue = VirtualClients.GetValue(MySession.Static); + + //Re-Initilize Virtual clients + SeamlessClient.TryShow("Initilizing Virtual Clients!"); + InitVirtualClients.Invoke(VirtualClientsValue, null); + + + //Load Members from world + SeamlessClient.TryShow("Loading Members From World!"); + LoadMembersFromWorld.Invoke(MySession.Static, new object[] { World, MyMulitplayerClient }); + foreach (var Client in World.Checkpoint.Clients) + { + SeamlessClient.TryShow("Adding New Client: " + Client.Name); + Sync.Clients.AddClient(Client.SteamId, Client.Name); + } + + + + + } + catch (Exception ex) + { + SeamlessClient.TryShow(ex.ToString()); + } + + + } + + private static void LoadOnlinePlayers(MyObjectBuilder_Checkpoint checkpoint) + { + //Get This players ID + MyPlayer.PlayerId? savingPlayerId = new MyPlayer.PlayerId(Sync.MyId); + if (!savingPlayerId.HasValue) + { + SeamlessClient.TryShow("SavingPlayerID is null! Creating Default!"); + savingPlayerId = new MyPlayer.PlayerId(Sync.MyId); + } + + SeamlessClient.TryShow("Saving PlayerID: "+savingPlayerId.ToString()); + + + SeamlessClient.TryShow("Checkpoint.AllPlayers: " + checkpoint.AllPlayers.Count); + //These both are null/empty. Server doesnt need to send them to the client + //SeamlessClient.TryShow("Checkpoint.ConnectedPlayers: " + checkpoint.ConnectedPlayers.Dictionary.Count); + //SeamlessClient.TryShow("Checkpoint.DisconnectedPlayers: " + checkpoint.DisconnectedPlayers.Dictionary.Count); + SeamlessClient.TryShow("Checkpoint.AllPlayersData: " + checkpoint.AllPlayersData.Dictionary.Count); + + + foreach (KeyValuePair item3 in checkpoint.AllPlayersData.Dictionary) + { + MyPlayer.PlayerId playerId5 = new MyPlayer.PlayerId(item3.Key.GetClientId(), item3.Key.SerialId); + if (savingPlayerId.HasValue && playerId5.SteamId == savingPlayerId.Value.SteamId) + { + playerId5 = new MyPlayer.PlayerId(Sync.MyId, playerId5.SerialId); + + } + + LoadPlayerInternal.Invoke(MySession.Static.Players, new object[] { playerId5, item3.Value, false }); + ConcurrentDictionary Players = (ConcurrentDictionary)MPlayerGPSCollection.GetValue(MySession.Static.Players); + //LoadPlayerInternal(ref playerId5, item3.Value); + if (Players.TryGetValue(playerId5, out MyPlayer myPlayer)) + { + List value2 = null; + if (checkpoint.AllPlayersColors != null && checkpoint.AllPlayersColors.Dictionary.TryGetValue(item3.Key, out value2)) + { + myPlayer.SetBuildColorSlots(value2); + } + else if (checkpoint.CharacterToolbar != null && checkpoint.CharacterToolbar.ColorMaskHSVList != null && checkpoint.CharacterToolbar.ColorMaskHSVList.Count > 0) + { + myPlayer.SetBuildColorSlots(checkpoint.CharacterToolbar.ColorMaskHSVList); + } + } + } + } + + + private static void UpdatePlayerData() + { + + } + + } + + + + + +} diff --git a/SeamlessTransfer/Transfer.cs b/SeamlessTransfer/Transfer.cs new file mode 100644 index 0000000..7038cd0 --- /dev/null +++ b/SeamlessTransfer/Transfer.cs @@ -0,0 +1,145 @@ +using ProtoBuf; +using Sandbox; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game.Entities; +using Sandbox.Game.Gui; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Sandbox.Graphics.GUI; +using SeamlessClientPlugin.ClientMessages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using VRage; +using VRage.Game; +using VRage.GameServices; +using VRage.Network; +using VRage.Steam; +using VRage.Utils; +using VRageMath; + +namespace SeamlessClientPlugin.SeamlessTransfer +{ + + [ProtoContract] + public class Transfer + { + [ProtoMember(1)] + public ulong TargetServerID; + [ProtoMember(2)] + public string IPAdress; + [ProtoMember(6)] + public WorldRequest WorldRequest; + [ProtoMember(7)] + public string PlayerName; + + [ProtoMember(8)] + public List PlayerGPSCoords; + + [ProtoMember(9)] + public MyObjectBuilder_Toolbar PlayerToolbar; + + public List PlayerBuildSlots; + + public Transfer(ulong ServerID, string IPAdress) + { + /* This is only called serverside + */ + + this.IPAdress = IPAdress; + TargetServerID = ServerID; + } + + public Transfer() { } + + + + public void PingServerAndBeginRedirect() + { + if (TargetServerID == 0) + { + SeamlessClient.TryShow("This is not a valid server!"); + return; + } + + SeamlessClient.TryShow("SyncMyID: " + Sync.MyId.ToString()); + SeamlessClient.TryShow("Beginning Redirect to server: " + TargetServerID); + MyGameService.OnPingServerResponded += MyGameService_OnPingServerResponded; + MyGameService.OnPingServerFailedToRespond += MyGameService_OnPingServerFailedToRespond; + + MyGameService.PingServer(IPAdress); + } + + private void MyGameService_OnPingServerFailedToRespond(object sender, EventArgs e) + { + MyGameService.OnPingServerResponded -= MyGameService_OnPingServerResponded; + MyGameService.OnPingServerFailedToRespond -= MyGameService_OnPingServerFailedToRespond; + SeamlessClient.TryShow("ServerPing failed!"); + + } + + private void MyGameService_OnPingServerResponded(object sender, MyGameServerItem e) + { + MyGameService.OnPingServerResponded -= MyGameService_OnPingServerResponded; + MyGameService.OnPingServerFailedToRespond -= MyGameService_OnPingServerFailedToRespond; + SeamlessClient.TryShow("ServerPing Successful! Attempting to connect to lobby: " + e.GameID); + + + LoadServer.LoadWorldData(e, WorldRequest.DeserializeWorldData()); + MySandboxGame.Static.Invoke(delegate + { + //MySessionLoader.UnloadAndExitToMenu(); + UnloadCurrentServer(); + //MyJoinGameHelper.JoinGame(e, true); + LoadServer.ResetMPClient(); + ClearEntities(); + //ReloadPatch.SeamlessSwitch = false; + }, "SeamlessClient"); + return; + + } + + + + + + private void UnloadCurrentServer() + { + if (MyMultiplayer.Static != null) + { + MyHud.Chat.UnregisterChat(MyMultiplayer.Static); + + MyMultiplayer.Static.ReplicationLayer.Disconnect(); + MyMultiplayer.Static.ReplicationLayer.Dispose(); + + MyMultiplayer.Static.Dispose(); + MyMultiplayer.Static = null; + + //Sync.Clients.Clear(); + + + + // MyGuiSandbox.UnloadContent(); + } + } + + + private void ClearEntities() + { + foreach (var ent in MyEntities.GetEntities()) + { + if (ent is MyPlanet) + continue; + + ent.Close(); + } + } + + } +} diff --git a/Utilities/Utility.cs b/Utilities/Utility.cs new file mode 100644 index 0000000..ab7bd46 --- /dev/null +++ b/Utilities/Utility.cs @@ -0,0 +1,38 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SeamlessClientPlugin.Utilities +{ + public static class Utility + { + public static byte[] Serialize(T instance) + { + if (instance == null) + return null; + + using (var m = new MemoryStream()) + { + // m.Seek(0, SeekOrigin.Begin); + Serializer.Serialize(m, instance); + + return m.ToArray(); + } + } + + public static T Deserialize(byte[] data) + { + if (data == null) + return default(T); + + using (var m = new MemoryStream(data)) + { + return Serializer.Deserialize(m); + } + } + } +} diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..f0f60a9 --- /dev/null +++ b/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file