using System.Reflection; using System.Text; using HarmonyLib; using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Game.Gui; using Sandbox.Game.World; using Sandbox.Game.World.Generator; using Sandbox.Graphics; using Sandbox.Graphics.GUI; using VRage; using VRage.FileSystem; using VRage.Game; using VRage.GameServices; using VRage.Network; using VRage.Utils; using VRageMath; namespace SeamlessClientPlugin.Utilities; public static class Patches { /* Harmony Patcher */ private static readonly Harmony Patcher = new("SeamlessClientPatcher"); /* WorldGenerator */ public static MethodInfo UnloadProceduralWorldGenerator { get; private set; } public static event EventHandler OnJoinEvent; public static void GetPatches() { //Get reflected values and store them /* Get Methods */ var onJoin = GetMethod(typeof(MyMultiplayerClient), "OnUserJoined", BindingFlags.NonPublic | BindingFlags.Instance); var loadingAction = GetMethod(typeof(MySessionLoader), "LoadMultiplayerSession", BindingFlags.Public | BindingFlags.Static); UnloadProceduralWorldGenerator = GetMethod(typeof(MyProceduralWorldGenerator), "UnloadData", BindingFlags.Instance | BindingFlags.NonPublic); //MethodInfo ConnectToServer = GetMethod(typeof(MyGameService), "ConnectToServer", BindingFlags.Static | BindingFlags.Public); var loadingScreenDraw = GetMethod(typeof(MyGuiScreenLoading), "DrawInternal", BindingFlags.Instance | BindingFlags.NonPublic); //Test patches //MethodInfo SetPlayerDed = GetMethod(typeof(MyPlayerCollection), "SetPlayerDeadInternal", BindingFlags.Instance | BindingFlags.NonPublic); Patcher.Patch(loadingScreenDraw, new HarmonyMethod(GetPatchMethod(nameof(DrawInternal)))); Patcher.Patch(onJoin, postfix: new HarmonyMethod(GetPatchMethod(nameof(OnUserJoined)))); Patcher.Patch(loadingAction, new HarmonyMethod(GetPatchMethod(nameof(LoadMultiplayerSession)))); //Patcher.Patch(SetPlayerDed, prefix: new HarmonyMethod(GetPatchMethod(nameof(SetPlayerDeadInternal)))); } private static MethodInfo GetPatchMethod(string v) { return typeof(Patches).GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } private static void OnUserJoined(ref JoinResultMsg msg) { if (msg.JoinResult == JoinResult.OK) //SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString()); //Invoke the switch event OnJoinEvent?.Invoke(null, msg); } private static bool OnConnectToServer(MyGameServerItem server, Action onDone) { if (SeamlessClient.IsSwitching) return false; return true; } /* Patch Utils */ private static MethodInfo GetMethod(Type type, string methodName, BindingFlags flags) { var foundMethod = type.GetMethod(methodName, flags); if (foundMethod == null) throw new NullReferenceException($"Method for {methodName} is null!"); return foundMethod; } private static FieldInfo GetField(Type type, string fieldName, BindingFlags flags) { var foundField = type.GetField(fieldName, flags); if (foundField == null) throw new NullReferenceException($"Field for {fieldName} is null!"); return foundField; } private static PropertyInfo GetProperty(Type type, string propertyName, BindingFlags flags) { var foundProperty = type.GetProperty(propertyName, flags); if (foundProperty == null) throw new NullReferenceException($"Property for {propertyName} is null!"); return foundProperty; } private static ConstructorInfo GetConstructor(Type type, BindingFlags flags, Type[] types) { var foundConstructor = type.GetConstructor(flags, null, types, null); if (foundConstructor == null) throw new NullReferenceException($"Contructor for {type.Name} is null!"); return foundConstructor; } #region LoadingScreen /* Loading Screen Stuff */ private static string _loadingScreenTexture; private static string _serverName; private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession) { // MyLog.Default.WriteLine("LoadSession() - Start"); if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, false)) { MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox( messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer))); MyLog.Default.WriteLine("LoadSession() - End"); return false; } MyLog.Default.WriteLine("Seamless Downloading mods!"); MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, result => { if (result == MyGameServiceCallResult.OK) { MyScreenManager.CloseAllScreensNowExcept(null); MyGuiSandbox.Update(16); if (MySession.Static != null) { MySession.Static.Unload(); MySession.Static = null; } _serverName = multiplayerSession.HostName; GetCustomLoadingScreenPath(world.Checkpoint.Mods, out _loadingScreenTexture); MySessionLoader.StartLoading(delegate { MySession.LoadMultiplayer(world, multiplayerSession); }); } else { multiplayerSession.Dispose(); MySessionLoader.UnloadAndExitToMenu(); if (MyGameService.IsOnline) MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox( messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed))); else MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox( messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: new StringBuilder(string.Format( MyTexts.GetString(MyCommonTexts.DialogTextDownloadModsFailedSteamOffline), MySession.GameServiceName)))); } MyLog.Default.WriteLine("LoadSession() - End"); }, () => { multiplayerSession.Dispose(); MySessionLoader.UnloadAndExitToMenu(); }); return false; } private static bool DrawInternal(MyGuiScreenLoading __instance) { //If we dont have a custom loading screen texture, do not do the special crap below if (string.IsNullOrEmpty(_loadingScreenTexture)) return true; //MyGuiControlMultilineText m_multiTextControl = (MyGuiControlMultilineText)typeof(MyGuiScreenLoading).GetField("m_multiTextControl", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance); var color = new Color(255, 255, 255, 250); color.A = (byte)(color.A * __instance.m_transitionAlpha); var fullscreenRectangle = MyGuiManager.GetFullscreenRectangle(); MyGuiManager.DrawSpriteBatch(@"Textures\GUI\Blank.dds", fullscreenRectangle, Color.Black, false, true); MyGuiManager.GetSafeHeightFullScreenPictureSize(MyGuiConstants.LOADING_BACKGROUND_TEXTURE_REAL_SIZE, out var outRect); MyGuiManager.DrawSpriteBatch(_loadingScreenTexture, outRect, new Color(new Vector4(1f, 1f, 1f, __instance.m_transitionAlpha)), true, true); MyGuiManager.DrawSpriteBatch(@"Textures\Gui\Screens\screen_background_fade.dds", outRect, new Color(new Vector4(1f, 1f, 1f, __instance.m_transitionAlpha)), true, true); //MyGuiSandbox.DrawGameLogoHandler(m_transitionAlpha, MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, 44, 68)); var loadScreen = $"Loading into {_serverName}! Please wait!"; MyGuiManager.DrawString(MyFontEnum.LoadingScreen, loadScreen, new Vector2(0.5f, 0.95f), MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f, new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * __instance.m_transitionAlpha), MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM); MyGuiManager.DrawString(MyFontEnum.LoadingScreen, "Nexus & SeamlessClient Made by: Casimir", new Vector2(0.95f, 0.95f), MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f, new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * __instance.m_transitionAlpha), MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM); /* if (string.IsNullOrEmpty(m_customTextFromConstructor)) { string font = m_font; Vector2 positionAbsoluteBottomLeft = m_multiTextControl.GetPositionAbsoluteBottomLeft(); Vector2 textSize = m_multiTextControl.TextSize; Vector2 normalizedCoord = positionAbsoluteBottomLeft + new Vector2((m_multiTextControl.Size.X - textSize.X) * 0.5f + 0.025f, 0.025f); MyGuiManager.DrawString(font, m_authorWithDash.ToString(), normalizedCoord, MyGuiSandbox.GetDefaultTextScaleWithLanguage()); } */ //m_multiTextControl.Draw(1f, 1f); return false; } private static bool GetCustomLoadingScreenPath(List mods, out string file) { file = null; var workshopDir = MyFileSystem.ModsPath; var backgrounds = new List(); SeamlessClient.TryShow(workshopDir); try { SeamlessClient.TryShow($"Installed Mods: ({mods.Count}) [{string.Join(", ", mods.Select(x => x.FriendlyName))}]"); foreach (var mod in mods) { var searchDir = mod.GetPath(); if (!Directory.Exists(searchDir)) continue; var files = Directory.GetFiles(searchDir, "CustomLoadingBackground*.dds", SearchOption.TopDirectoryOnly); foreach (var filePath in files) { // Adds all files containing CustomLoadingBackground to a list for later randomisation SeamlessClient.TryShow($"{mod.FriendlyName} contains a custom loading background!"); backgrounds.Add(filePath); } } // Randomly pick a loading screen from the available backgrounds file = backgrounds[Random.Shared.Next(0, backgrounds.Count - 1)]; return true; } catch (Exception ex) { SeamlessClient.TryShow(ex.ToString()); } SeamlessClient.TryShow("No installed custom loading screen!"); return false; } #endregion }