From ebe8ec455fa9336e1bc31a6c657d6c6bda594525 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 7 Apr 2026 14:20:30 -0400 Subject: [PATCH] LuaCs CSharp Enabled Rework - New UI for the prompt - Third time's the charm. - Fixed TOCTOU for cs prompt on main menu. - Fixed SubEditor not running lua when don't run is selected for cs. - LuaCs CSharp Enabled Rework --- .../ClientSource/LuaCs/LuaCsSetup.cs | 231 ++++++++++-------- .../Screens/MainMenuScreen/MainMenuScreen.cs | 29 ++- .../ServerSource/LuaCs/LuaCsSetup.cs | 5 + .../Config/SettingsShared.xml | 9 +- .../LuaCsForBarotrauma/Texts/English.xml | 6 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 37 ++- 6 files changed, 186 insertions(+), 131 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 8a20415ab..f1fdab03a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -7,6 +7,8 @@ using System.Text; using Barotrauma.CharacterEditor; using Barotrauma.LuaCs; using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; // ReSharper disable ObjectCreationAsStatement @@ -15,120 +17,147 @@ namespace Barotrauma partial class LuaCsSetup { private bool _isClientPromptActive; - - /// - /// Returns whether execution should continue - /// - public bool CheckReadyToRun() + private bool _isCsEnabledForSession = false; + + public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun) { - // Fast exit if enabled - if (this.IsCsEnabled) + var res = ReadyToRunNoPrompt(); + if (res.ShouldRun) { - return true; + onReadyToRun?.Invoke(); + return; } - StringBuilder sb = new StringBuilder(); - - foreach (ContentPackage cp in PackageManagementService.GetLoadedAssemblyPackages()) + DisplayCsModsPromptClient(res.Item2, (selectedYes) => { - if (cp.NameMatches(PackageId)) + if (selectedYes) { - continue; + onReadyToRun?.Invoke(); } + }); + } - if (cp.UgcId.TryUnwrap(out ContentPackageId id)) - { - sb.AppendLine($"- {cp.Name} ({id})"); - } - else - { - sb.AppendLine($"- {cp.Name} (Not On Workshop)"); - } + private (bool ShouldRun, ImmutableArray PromptPackages) ReadyToRunNoPrompt() + { + if (this.IsCsEnabled) + { + return (true, ImmutableArray.Empty); } - if (string.IsNullOrEmpty(sb.ToString())) + if (!ShouldPromptForCs) { - return true; + return (true, ImmutableArray.Empty); } - if (!_isClientPromptActive) + ImmutableArray contentPackages = PackageManagementService.GetLoadedAssemblyPackages() + .Where(p => p.Name != PackageId) + .ToImmutableArray(); + + return (contentPackages.IsEmpty, contentPackages); + } + + partial void CheckReadyToRun(Action onReadyToRun) + { + var res = ReadyToRunNoPrompt(); + if (res.ShouldRun) { - _isClientPromptActive = true; - if (GameMain.Client == null || GameMain.Client.IsServerOwner) + onReadyToRun?.Invoke(); + return; + } + + if (GameMain.Client?.ClientPeer is P2POwnerPeer) + { + SetCsPolicyAndContinue(true); + return; + } + + DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) => + { + SetCsPolicyAndContinue(selectedYes); + return; + }); + + void SetCsPolicyAndContinue(bool csSessionExecutionPolicy) + { + var prevRunState = this.CurrentRunState; + if (CurrentRunState >= RunState.Running) { - DisplayCsModsPromptServer(sb); + SetRunState(RunState.LoadedNoExec); + } + this._isCsEnabledForSession = csSessionExecutionPolicy; + CoroutineManager.Invoke(() => + { + if (CurrentRunState != prevRunState) + { + SetRunState(prevRunState); + } + onReadyToRun?.Invoke(); + }, 0f); + } + } + + void DisplayCsModsPromptClient(ImmutableArray contentPackages, Action onSelection) + { + if (_isClientPromptActive) { return; } + + _isClientPromptActive = true; + + GUIMessageBox messageBox = new GUIMessageBox( + TextManager.Get("warning"), + relativeSize: new Vector2(0.3f, 0.55f), + minSize: new Point(400, 500), + text: string.Empty, + buttons: []); + + GUILayoutGroup msgBoxLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.75f), messageBox.Content.RectTransform), isHorizontal: false, childAnchor: Anchor.TopCenter) + { + RelativeSpacing = 0.01f, + Stretch = true + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code", + font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center); + + GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform)) + { + CurrentSelectMode = GUIListBox.SelectMode.None + }; + + foreach (ContentPackage package in contentPackages) + { + GUIFrame packageFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), packageListBox.Content.RectTransform), style: "ListBoxElement"); + new GUITextBlock(new RectTransform(new Vector2(1f, 1f), packageFrame.RectTransform), package.Name); + } + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), "CSharp mods are not sandboxed, meaning that they have unrestrictive access to your computer, please make sure you trust these mods before you continue. If you are not hosting a server, selecting cancel will only run Lua-mods.", wrap: true) + { + Wrap = true + }; + + GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), messageBox.Content.RectTransform, Anchor.BottomCenter), isHorizontal: false, childAnchor: Anchor.TopCenter); + + new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Continue") + { + TextBlock = { AutoScaleHorizontal = true }, + OnClicked = (btn, userdata) => + { + _isClientPromptActive = false; + onSelection(true); + messageBox.Close(); return true; } - else + }; + + new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Cancel") + { + OnClicked = (btn, userdata) => { - DisplayCsModsPromptClient(sb); - return false; + _isClientPromptActive = false; + onSelection(false); + messageBox.Close(); + return true; } - } - else - { - return false; - } - - void DisplayCsModsPromptServer(StringBuilder sb) - { - var msg = new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, " + - $"those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); - foreach (var button in msg.Buttons) - { - var old = button.OnClicked; - button.OnClicked = (btn, obj) => - { - var ret = old?.Invoke(btn, obj); - _isClientPromptActive = false; - return ret ?? true; - }; - } - } - - void DisplayCsModsPromptClient(StringBuilder sb) - { - GUIMessageBox msg = new GUIMessageBox( - "Confirm", - $"This server has the following CSharp mods installed: \n{sb}\nDo you wish to run them? Cs mods are not sandboxed so make sure you trust these mods.", - new LocalizedString[2] { "Run", "Don't Run" }); - - msg.Buttons[0].OnClicked = (GUIButton button, object obj) => - { - try - { - this._isClientPromptActive = false; - CoroutineManager.Invoke(() => - { - SetRunState(RunState.LoadedNoExec); - this.IsCsEnabled = true; - SetRunState(RunState.Running); - }, 0f); - return true; - } - finally - { - msg.Close(); - } - }; - - msg.Buttons[1].OnClicked = (GUIButton button, object obj) => - { - try - { - // avoid a TOCTOU scenario. - this.IsCsEnabled = false; - this._isClientPromptActive = false; - SetRunState(RunState.LoadedNoExec); - SetRunState(RunState.Running); - return true; - } - finally - { - msg.Close(); - } - }; - } + }; } private void SetupServicesProviderClient(IServicesProvider serviceProvider) @@ -172,16 +201,10 @@ namespace Barotrauma case SpriteEditorScreen: case SubEditorScreen: case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - if (!CheckReadyToRun()) + CheckReadyToRun(() => { - if (CurrentRunState >= RunState.Running) - { - SetRunState(RunState.LoadedNoExec); - } - return; - } - - SetRunState(RunState.Running); + SetRunState(RunState.Running); + }); break; default: Logger.LogError( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index e287d991b..6704de400 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -1012,20 +1012,25 @@ namespace Barotrauma private void TryStartServer() { - if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash)) + LuaCsSetup.Instance.CheckRunConditionalHostingCsEnabled(() => { - var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") }); - var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations"); - waitBox.Buttons[0].OnClicked += (btn, userdata) => + if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash)) { - CoroutineManager.StopCoroutines(waitCoroutine); - return true; - }; - } - else - { - StartServer(); - } + var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") }); + var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations"); + waitBox.Buttons[0].OnClicked += (btn, userdata) => + { + CoroutineManager.StopCoroutines(waitCoroutine); + return true; + }; + } + else + { + StartServer(); + } + }); + + } private IEnumerable WaitForSubmarineHashCalculations(GUIMessageBox messageBox) diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs index 3e39a4b72..7c33a0f64 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -6,6 +6,11 @@ namespace Barotrauma; partial class LuaCsSetup { + partial void CheckReadyToRun(Action onReadyToRun) + { + onReadyToRun?.Invoke(); + } + /// /// Handles changes in game states tracked by screen changes. /// diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index 35fa892f7..103091314 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -2,7 +2,14 @@ - + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index 2462c80ea..a0f8ccd72 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -4,9 +4,9 @@ Mod Gameplay Settings - Are C# Mods Allowed - Should unsandboxed scripts and dlls be allowed to run. - General + Are C# Mods Allowed + Should unsandboxed scripts and dlls be allowed to run. + General Use Pre-Caching Should mod files be preloaded to speed up loading. Should only be turned off if you have mods that have issues with this. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 7e900d231..8c6502776 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -84,18 +84,22 @@ namespace Barotrauma private LuaGame _game; public LuaGame Game => _game ??= _servicesProvider.GetService(); - + /// /// Whether C# plugin code is enabled. /// public bool IsCsEnabled { - get => _isCsEnabled?.Value ?? false; - internal set => _isCsEnabled?.TrySetValue(value); +#if CLIENT + get => _csRunPolicy?.Value == "Enabled" || _isCsEnabledForSession; +#elif SERVER + // cs settings cannot be changed on the server after launch + get => _csRunPolicy?.Value is "Enabled" or "Prompt"; +#endif } - - private ISettingBase _isCsEnabled; - + private ISettingList _csRunPolicy; + private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt"; + /// /// Whether usernames are anonymized or show in logs. /// @@ -123,8 +127,8 @@ namespace Barotrauma { var luaCsPackage = GetLuaCsPackage(); - _isCsEnabled = - ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) + _csRunPolicy = + ConfigService.TryGetConfig>(luaCsPackage, "CsRunPolicy", out var val1) ? val1 : null; _hideUserNamesInLogs = @@ -380,16 +384,27 @@ namespace Barotrauma void RunStateRunning_OnExit(State currentState) { EventService.Call("stop"); - +#if CLIENT + _isCsEnabledForSession = false; +#endif Logger.LogResults(PackageManagementService.StopRunningPackages()); - Logger.LogMessage("LuaCs running state exited"); } // ReSharper restore InconsistentNaming } + + + + #endregion + /// + /// Checks for Cs Execution Policy (ie. prompting the user) and then calls the delegate once completed. + /// + /// + partial void CheckReadyToRun(Action onReadyToRun); + #region LegacyRedirects // --- Compatibility @@ -457,7 +472,7 @@ namespace Barotrauma void DisposeLuaCsConfig() { - _isCsEnabled = null; + _csRunPolicy = null; _hideUserNamesInLogs = null; } }