using System.Diagnostics; using System.Reflection; using System.Text; using HarmonyLib; using Sandbox; using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Game; using Sandbox.Game.Gui; using Sandbox.Game.Screens; using Sandbox.Game.World; using Sandbox.Graphics.GUI; using VRage; using VRage.Game; using VRage.GameServices; using VRage.Plugins; using VRage.Utils; namespace TimeoutFix; public class TimeoutFix : IPlugin { private const string Version = "v2.2"; private static readonly MethodInfo Target = typeof(MyJoinGameHelper).GetMethod(nameof(DownloadWorld), BindingFlags.Static | BindingFlags.NonPublic); private static readonly MethodInfo Patch = typeof(TimeoutFix).GetMethod(nameof(DownloadWorld), BindingFlags.Static | BindingFlags.NonPublic); private static readonly MethodInfo ReceiveTarget = typeof(MyJoinGameHelper).GetMethod(nameof(WorldReceived), BindingFlags.Static | BindingFlags.Public); private static readonly MethodInfo ReceivePatch = typeof(TimeoutFix).GetMethod(nameof(WorldReceived), BindingFlags.Static | BindingFlags.NonPublic); private static readonly FieldInfo ProgressField = typeof(MyJoinGameHelper).GetField("m_progress", BindingFlags.Static | BindingFlags.NonPublic); private static bool _worldReceived; private static MyGuiScreenProgressBase Progress { get => (MyGuiScreenProgressBase)ProgressField.GetValue(null); set => ProgressField.SetValue(null, value); } public void Dispose() { } public void Init(object gameInstance) { var harmony = new Harmony(nameof(TimeoutFix)); harmony.Patch(Target, new HarmonyMethod(Patch)); harmony.Patch(ReceiveTarget, new HarmonyMethod(ReceivePatch)); } public void Update() { } private static bool DownloadWorld(MyGuiScreenProgress progress, MyMultiplayerBase multiplayer) { _worldReceived = false; if (progress.Text != null) { progress.Text.Clear(); progress.Text.Append(MyTexts.Get(MyCommonTexts.MultiplayerStateConnectingToServer)); } MyLog.Default.WriteLine($"World requested: Timeout fix {Version}"); var worldRequestTime = Stopwatch.StartNew(); var serverId = multiplayer.GetOwner(); var connected = false; progress.Tick += delegate { var myP2PSessionState = default(MyP2PSessionState); MyGameService.Peer2Peer.GetSessionState(multiplayer.ServerId, ref myP2PSessionState); if (!connected && myP2PSessionState.ConnectionActive) { MyLog.Default.WriteLine("World requested - connection alive"); connected = true; if (progress.Text != null) { progress.Text.Clear(); progress.Text.AppendLine("Using Rexxar's fixed join code v2.2! :D"); progress.Text.Append(MyTexts.Get(MyCommonTexts.MultiplayerStateWaitingForServer)); } } if (connected && !myP2PSessionState.ConnectionActive) { MyLog.Default.WriteLine("World request - connection dropped"); progress.Cancel(); MyGuiSandbox.Show(MyCommonTexts.MultiplaterJoin_ServerIsNotResponding, default, MyMessageBoxStyleEnum.Error); MySessionLoader.UnloadAndExitToMenu(); } var progressScreenOnTop = MyScreenManager.IsScreenOnTop(progress); if (serverId != multiplayer.GetOwner()) { MyLog.Default.WriteLine(string.Format("World requested - failed, server changed: Expected {0} got {1}", serverId, multiplayer.GetOwner())); progress.Cancel(); MyGuiSandbox.Show(MyCommonTexts.MultiplayerErrorServerHasLeft, default, MyMessageBoxStyleEnum.Error); multiplayer.Dispose(); } var downloadScreenOpen = MyScreenManager.IsScreenOfTypeOpen(typeof(MyGuiScreenDownloadMods)); if (!downloadScreenOpen && progressScreenOnTop && !worldRequestTime.IsRunning) { worldRequestTime.Start(); } else { if (downloadScreenOpen || !progressScreenOnTop && worldRequestTime.IsRunning) worldRequestTime.Stop(); } if (downloadScreenOpen && progress.Visible) { progress.HideScreen(); } else { if (!downloadScreenOpen && !progress.Visible) progress.UnhideScreen(); } if (!_worldReceived && worldRequestTime.IsRunning && worldRequestTime.ElapsedTicks / Stopwatch.Frequency > 120f) { MyLog.Default.WriteLine("World requested - failed, timeout reached"); MyLog.Default.WriteLine(string.Format("Elapsed : {0:N2}", worldRequestTime.ElapsedTicks / Stopwatch.Frequency)); progress.Cancel(); MyGuiSandbox.Show(MyCommonTexts.MultiplaterJoin_ServerIsNotResponding, default, MyMessageBoxStyleEnum.Error); MySessionLoader.UnloadAndExitToMenu(); } }; multiplayer.DownloadWorld(MyFinalBuildConstants.APP_VERSION.Version); return false; } private static void CheckDx11AndJoin(MyObjectBuilder_World world, MyMultiplayerBase multiplayer) { if (multiplayer.Scenario) MySessionLoader.LoadMultiplayerScenarioWorld(world, multiplayer); else MySessionLoader.LoadMultiplayerSession(world, multiplayer); } private static bool WorldReceived(MyObjectBuilder_World world, MyMultiplayerBase multiplayer) { if (world == null) { MyLog.Default.WriteLine("World requested - failed, version mismatch"); Progress.Cancel(); Progress = null; MyGuiSandbox.Show(MyCommonTexts.MultiplayerErrorAppVersionMismatch, default, MyMessageBoxStyleEnum.Error); multiplayer.Dispose(); } else { _worldReceived = true; MyLog.Default.WriteLine("World requested - world data received"); if (world.Checkpoint?.Settings != null && !MySandboxGame.Config.ExperimentalMode) { MySessionLoader.UnloadAndExitToMenu(); var stringBuilder = new StringBuilder(); stringBuilder.AppendFormat(MyCommonTexts.DialogTextJoinWorldFailed, MyTexts.GetString(MyCommonTexts.MultiplayerErrorExperimental)); MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, stringBuilder, MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), null, null, null, null, null, 0, MyGuiScreenMessageBox.ResultEnum.YES, true, null, true, null, true, false, null)); } else { Progress = null; CheckDx11AndJoin(world, multiplayer); } } return false; } }