diff --git a/Messages/ClientMessages.cs b/Messages/ClientMessages.cs index 6eaa343..c375cc4 100644 --- a/Messages/ClientMessages.cs +++ b/Messages/ClientMessages.cs @@ -37,7 +37,7 @@ namespace SeamlessClientPlugin.ClientMessages { MessageType = Type; - if (!MyAPIGateway.Multiplayer.IsServer) + if (MyAPIGateway.Multiplayer != null && !MyAPIGateway.Multiplayer.IsServer) { if(MyAPIGateway.Session.LocalHumanPlayer == null) { diff --git a/SeamlessClient.cs b/SeamlessClient.cs index bafc6eb..58fa6ae 100644 --- a/SeamlessClient.cs +++ b/SeamlessClient.cs @@ -9,13 +9,16 @@ using Sandbox.Graphics.GUI; using Sandbox.ModAPI; using SeamlessClientPlugin.ClientMessages; using SeamlessClientPlugin.SeamlessTransfer; +using SeamlessClientPlugin.Updater; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Timers; +using System.Windows.Forms; using VRage.Input; using VRage.Plugins; using VRage.Utils; @@ -103,11 +106,11 @@ namespace SeamlessClientPlugin - public static string Version = "1.0.0"; + public static string Version = "1.1.0"; private bool Initilized = false; private bool SentPingResponse = false; public const ushort SeamlessClientNetID = 2936; - private Timer PingTimer = new Timer(2000); + private System.Timers.Timer PingTimer = new System.Timers.Timer(3000); public static LoadServer Server = new LoadServer(); @@ -132,7 +135,20 @@ namespace SeamlessClientPlugin public void Init(object gameInstance) { TryShow("Running Seamless Client Plugin v[" + Version + "]"); - // Reload = new ReloadPatch(); + + UpdateChecker Checker = new UpdateChecker(Version, false); + + Task UpdateChecker = new Task(() => Checker.PingUpdateServer()); + UpdateChecker.Start(); + + + + + + + + + // Reload = new ReloadPatch(); //Patching goes here @@ -141,6 +157,8 @@ namespace SeamlessClientPlugin //throw new NotImplementedException(); } + + private void PingTimer_Elapsed(object sender, ElapsedEventArgs e) { //TryShow("Sending PluginVersion to Server!"); @@ -148,7 +166,7 @@ namespace SeamlessClientPlugin { ClientMessage PingServer = new ClientMessage(ClientMessageType.FirstJoin); - MyAPIGateway.Multiplayer.SendMessageToServer(SeamlessClientNetID, Utilities.Utility.Serialize(PingServer)); + MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetID, Utilities.Utility.Serialize(PingServer)); } catch (Exception ex) { @@ -222,5 +240,38 @@ namespace SeamlessClientPlugin MyLog.Default?.WriteLineAndConsole($"SeamlessClient: {message}"); } + + + public static void RestartClientAfterUpdate() + { + try + { + TryShow("Restarting Client!"); + + string exe = Assembly.GetEntryAssembly().Location; + + + Process currentProcess = Process.GetCurrentProcess(); + + string[] CommandArgs = Environment.GetCommandLineArgs(); + + string NewCommandLine = ""; + for(int i = 1; i < CommandArgs.Length; i++) + { + NewCommandLine += " "+ CommandArgs[i]; + } + + TryShow(NewCommandLine); + + Process.Start(exe, NewCommandLine); + + + currentProcess.Kill(); + } + catch (Exception ex) + { + TryShow("Restarting Client error!"); + } + } } } diff --git a/SeamlessClientPlugin.csproj b/SeamlessClientPlugin.csproj index b7ae61d..981e56a 100644 --- a/SeamlessClientPlugin.csproj +++ b/SeamlessClientPlugin.csproj @@ -84,9 +84,21 @@ False + + ..\Nexus\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + + ..\Nexus\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + True + True + False + False @@ -149,6 +161,7 @@ + diff --git a/SeamlessTransfer/LoadServer.cs b/SeamlessTransfer/LoadServer.cs index 8fed05d..d5a74a4 100644 --- a/SeamlessTransfer/LoadServer.cs +++ b/SeamlessTransfer/LoadServer.cs @@ -2,6 +2,7 @@ using Sandbox; using Sandbox.Definitions; using Sandbox.Engine; +using Sandbox.Engine.Analytics; using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Engine.Physics; @@ -21,6 +22,7 @@ using SeamlessClientPlugin.Utilities; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -28,6 +30,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using VRage; +using VRage.FileSystem; using VRage.Game; using VRage.Game.ModAPI; using VRage.Game.SessionComponents; @@ -68,6 +71,7 @@ namespace SeamlessClientPlugin.SeamlessTransfer //Reflected Methods public static FieldInfo VirtualClients; public static FieldInfo AdminSettings; + public static FieldInfo RemoteAdminSettings; public static FieldInfo MPlayerGPSCollection; public static MethodInfo RemovePlayerFromDictionary; @@ -75,6 +79,8 @@ namespace SeamlessClientPlugin.SeamlessTransfer public static MethodInfo LoadPlayerInternal; public static MethodInfo LoadMembersFromWorld; + public static MethodInfo LoadMultiplayer; + public LoadServer() { @@ -131,6 +137,12 @@ namespace SeamlessClientPlugin.SeamlessTransfer 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); + LoadMultiplayer = typeof(MySession).GetMethod("LoadMultiplayer", BindingFlags.Static | BindingFlags.NonPublic); + + + MethodInfo LoadingAction = typeof(MySessionLoader).GetMethod("LoadMultiplayerSession", BindingFlags.Public | BindingFlags.Static); + Patcher.Patch(LoadingAction, prefix: new HarmonyMethod(GetPatchMethod(nameof(LoadMultiplayerSession)))); + } @@ -140,6 +152,101 @@ namespace SeamlessClientPlugin.SeamlessTransfer } + + private static bool GetCustomLoadingScreenPath(List Mods, out string File) + { + + + string WorkshopDir = MyFileSystem.ModsPath; + + SeamlessClient.TryShow(WorkshopDir); + + + File = null; + SeamlessClient.TryShow("Installed Mods: " + Mods); + foreach(var Mod in Mods) + { + string SearchDir = Mod.GetPath(); + var files = Directory.EnumerateFiles(SearchDir, "*.dds", SearchOption.TopDirectoryOnly); + foreach (var file in files) + { + if (Path.GetFileNameWithoutExtension(file) == "CustomLoadingBackground") + { + SeamlessClient.TryShow(Mod.FriendlyName + " contains a custom loading background!"); + File = file; + return true; + } + + + } + + + } + + SeamlessClient.TryShow("No installed custom loading screen!"); + return false; + } + + + + private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession) + { + MyLog.Default.WriteLine("LoadSession() - Start"); + if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, allowLocalMods: false)) + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer))); + MyLog.Default.WriteLine("LoadSession() - End"); + return false; + } + MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, delegate (bool success) + { + if (success) + { + MyScreenManager.CloseAllScreensNowExcept(null); + MyGuiSandbox.Update(16); + if (MySession.Static != null) + { + MySession.Static.Unload(); + MySession.Static = null; + } + + string CustomBackgroundImage = null; + GetCustomLoadingScreenPath(world.Checkpoint.Mods, out CustomBackgroundImage); + + MySessionLoader.StartLoading(delegate + { + + LoadMultiplayer.Invoke(null, new object[] { world, multiplayerSession }); + //MySession.LoadMultiplayer(world, multiplayerSession); + }, null, CustomBackgroundImage, null); + } + else + { + multiplayerSession.Dispose(); + MySessionLoader.UnloadAndExitToMenu(); + if (MyGameService.IsOnline) + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed))); + } + else + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: new StringBuilder(string.Format(MyTexts.GetString(MyCommonTexts.DialogTextDownloadModsFailedSteamOffline), MySession.GameServiceName)))); + } + } + MyLog.Default.WriteLine("LoadSession() - End"); + }, delegate + { + multiplayerSession.Dispose(); + MySessionLoader.UnloadAndExitToMenu(); + }); + + return false; + } + + + + + private static void OnUserJoined(ref JoinResultMsg msg) { if (SeamlessClient.IsSwitching && msg.JoinResult == JoinResult.OK) @@ -317,48 +424,19 @@ namespace SeamlessClientPlugin.SeamlessTransfer - 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!"); - } - } + //checkpoint.Gps.Dictionary.TryGetValue(MySession.Static.LocalPlayerId, out MyObjectBuilder_Gps GPSCollection); + //SeamlessClient.TryShow("You have " + GPSCollection.Entries.Count + " gps points!"); + MySession.Static.Gpss = new MyGpsCollection(); MySession.Static.Gpss.LoadGpss(checkpoint); - MySession.Static.Toolbars.LoadToolbars(checkpoint); + MyRenderProxy.RebuildCullingStructure(); + //MySession.Static.Toolbars.LoadToolbars(checkpoint); Sync.Players.RespawnComponent.InitFromCheckpoint(checkpoint); - - } diff --git a/SeamlessTransfer/Transfer.cs b/SeamlessTransfer/Transfer.cs index 7038cd0..388bde7 100644 --- a/SeamlessTransfer/Transfer.cs +++ b/SeamlessTransfer/Transfer.cs @@ -124,7 +124,12 @@ namespace SeamlessClientPlugin.SeamlessTransfer //Sync.Clients.Clear(); + var PCollection = MySession.Static.Gpss[MySession.Static.LocalPlayerId]; + PCollection.Clear(); + MyHud.GpsMarkers.Clear(); + + SeamlessClient.TryShow(PCollection.Count + "registered GPS points"); // MyGuiSandbox.UnloadContent(); } } diff --git a/Updater/UpdateChecker.cs b/Updater/UpdateChecker.cs new file mode 100644 index 0000000..38ac0b6 --- /dev/null +++ b/Updater/UpdateChecker.cs @@ -0,0 +1,300 @@ +using ProtoBuf; +using Sandbox.Graphics.GUI; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Sockets; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace SeamlessClientPlugin.Updater +{ + public class UpdateChecker + { + public string PluginFolder; + public string CurrentVersion; + public bool DownloadUpdate; + + const int PORT_NO = 27010; + //AWS Website server + const string SERVER_IP = "3.80.137.183"; + + private byte[] RecievedZipMememory; + + public UpdateChecker(string Version, bool AutoUpdate) + { + this.DownloadUpdate = AutoUpdate; + PluginFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + CurrentVersion = Version; + + SeamlessClient.TryShow("You are running @" + PluginFolder); + DeleteOLDFiles(); + } + + + public void PingUpdateServer() + { + try + { + + TcpClient client = new TcpClient(SERVER_IP, PORT_NO); + client.ReceiveBufferSize = 1000000; + + NetworkStream nwStream = client.GetStream(); + + UpdateMessage Message = new UpdateMessage(); + Message.ClientVersion = CurrentVersion; + Message.DownloadNewUpdate = DownloadUpdate; + + //Send current version to server + //string XMLData = Utility.Serialize(Message); + byte[] bytesToSend = Utility.Serialize(Message); + nwStream.Write(bytesToSend, 0, bytesToSend.Length); + + //Get server reply + byte[] bytesToRead = new byte[client.ReceiveBufferSize]; + int bytesRead = nwStream.Read(bytesToRead, 0, client.ReceiveBufferSize); + + + byte[] ReMessage = bytesToRead.Take(bytesRead).ToArray(); + //string StringServerReply = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead); + // SeamlessClient.TryShow(StringServerReply); + + + Message = Utility.Deserialize(ReMessage); + UpdateServerReply(Message); + + SeamlessClient.TryShow("Received! Latest Version: " + Message.ServerVersion); + }catch(SocketException) + { + //Cant connect to server. + SeamlessClient.TryShow("Cant Connect to UpdateServer!"); + return; + }catch(Exception ex) + { + SeamlessClient.TryShow("Update Error! \n"+ex.ToString()); + return; + } + + } + + private void UpdateServerReply(UpdateMessage Message) + { + if (!NeedsUpdate(Message.ClientVersion, Message.ServerVersion)) + return; + + SeamlessClient.TryShow("An Update is required! ClientVersion: [" + Message.ClientVersion + "] Server Version: [" + Message.ServerVersion + "]"); + RecievedZipMememory = Message.XmlCharactersAsBytes; + + + if (!DownloadUpdate) + { + //Create Update question and UI + StringBuilder Title = new StringBuilder(); + Title.Append($"Would you like to download Seamless Client Plugin {Message.ServerVersion} Update?"); + StringBuilder Caption = new StringBuilder(); + Caption.AppendLine(" - Patch Notes - "); + Caption.AppendLine(); + Caption.AppendLine(Message.UpdateNotes); + Caption.AppendLine(); + Caption.AppendLine("Your client will restart after install"); + + + MyScreenManager.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Info, MyMessageBoxButtonsType.YES_NO_TIMEOUT, Caption, Title, null, null, null, null, UpdateMessageBoxCallback, 60000, MyGuiScreenMessageBox.ResultEnum.YES, canHideOthers: true, new VRageMath.Vector2(.5f,.4f), useOpacity: false)); + + + return; + } + else + { + ExtractAndReplace(); + } + + + + + } + + + private void UpdateMessageBoxCallback(MyGuiScreenMessageBox.ResultEnum Result) + { + //Update chcker + if (Result == MyGuiScreenMessageBox.ResultEnum.YES) + { + SeamlessClient.TryShow("Clicked Yes"); + //If they clicked yes, ping the update server again and request new file + DownloadUpdate = true; + PingUpdateServer(); + return; + } + + if (Result == MyGuiScreenMessageBox.ResultEnum.NO || Result == MyGuiScreenMessageBox.ResultEnum.CANCEL) + { + //Return. (Do not update) + SeamlessClient.TryShow("Clicked No or Cancel"); + return; + } + + } + + + private void DeleteOLDFiles() + { + foreach(var OLDFile in Directory.GetFiles(PluginFolder, "*.old")) + { + File.Delete(OLDFile); + } + + SeamlessClient.TryShow("Deleted all OLD update files"); + + } + + + private void ExtractAndReplace() + { + //save latest zip in dir + string ZipPath = Path.Combine(PluginFolder, "Latest.zip"); + File.WriteAllBytes(ZipPath, RecievedZipMememory); + + + //Start extractor + using (ZipArchive archive = ZipFile.OpenRead(ZipPath)) + { + foreach (ZipArchiveEntry entry in archive.Entries) + { + string ExsistingFilePath = Path.Combine(PluginFolder, entry.Name); + string OldFilePath = Path.Combine(PluginFolder, entry.Name+".old"); + SeamlessClient.TryShow(ExsistingFilePath + "=>" + OldFilePath); + + + if (File.Exists(OldFilePath)) + File.Delete(OldFilePath); + + File.Move(ExsistingFilePath, OldFilePath); + entry.ExtractToFile(ExsistingFilePath, true); + //File.Delete(OldFilePath); + } + } + + //Delete latest zip + File.Delete(ZipPath); + + //Restart client + SeamlessClient.TryShow("UpdateComplete!"); + SeamlessClient.RestartClientAfterUpdate(); + } + + private bool NeedsUpdate(string ClientVersion, string ServerVersion) + { + + + Version Client = new Version(ClientVersion); + Version Latest = new Version(ServerVersion); + + var result = Client.CompareTo(Latest); + if (result > 0) + { + //Console.WriteLine("Client is greater"); + return false; + } + else if (result < 0) + { + //Console.WriteLine("Latest is greater"); + return true; + } + else + { + //Console.WriteLine("versions are equal"); + return false; + } + } + + } + + + [ProtoContract] + public class UpdateMessage + { + [ProtoMember(10)] + public string ClientVersion = ""; + + [ProtoMember(20)] + public bool UpToDate = false; + + [ProtoMember(30)] + public bool DownloadNewUpdate = false; + + [ProtoMember(40)] + public string ServerVersion = ""; + + [ProtoMember(50)] + public string UpdateNotes = ""; + + + + /* Misc Stuff incase I need it */ + [ProtoMember(60)] + public byte[] XmlCharactersAsBytes; + + + public UpdateMessage() { } + + + } + + public static class Utility + { + public static string SerializeObject(this T toSerialize) + { + XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType()); + + using (StringWriter textWriter = new StringWriter()) + { + xmlSerializer.Serialize(textWriter, toSerialize); + return textWriter.ToString(); + } + } + + + public static T XmlDeserialize(this string toDeserialize) + { + XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); + using (StringReader textReader = new StringReader(toDeserialize)) + { + return (T)xmlSerializer.Deserialize(textReader); + } + } + + + 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; + + using (var m = new MemoryStream(data)) + { + return Serializer.Deserialize(m); + } + } + } + +} diff --git a/packages.config b/packages.config index f0f60a9..8b50108 100644 --- a/packages.config +++ b/packages.config @@ -1,4 +1,6 @@  + + \ No newline at end of file