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
This commit is contained in:
MapleWheels
2026-04-07 14:20:30 -04:00
committed by Evil Factory
parent d440ccbce2
commit ebe8ec455f
6 changed files with 186 additions and 131 deletions

View File

@@ -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;
/// <summary>
/// Returns whether execution should continue
/// </summary>
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<ContentPackage> PromptPackages) ReadyToRunNoPrompt()
{
if (this.IsCsEnabled)
{
return (true, ImmutableArray<ContentPackage>.Empty);
}
if (string.IsNullOrEmpty(sb.ToString()))
if (!ShouldPromptForCs)
{
return true;
return (true, ImmutableArray<ContentPackage>.Empty);
}
if (!_isClientPromptActive)
ImmutableArray<ContentPackage> 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<ContentPackage> contentPackages, Action<bool> 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(

View File

@@ -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<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)

View File

@@ -6,6 +6,11 @@ namespace Barotrauma;
partial class LuaCsSetup
{
partial void CheckReadyToRun(Action onReadyToRun)
{
onReadyToRun?.Invoke();
}
/// <summary>
/// Handles changes in game states tracked by screen changes.
/// </summary>

View File

@@ -2,7 +2,14 @@
<Configuration>
<Settings>
<Setting Name="HideUserNamesInLogs" Type="bool" Value="true"/>
<Setting Name="IsCsEnabled" Type="bool" Value="false" AllowChangesWhileExecuting="false"/>
<Setting Name="CsRunPolicy" Type="listString" Value="Prompt" AllowChangesWhileExecuting="false">
<Values>
<Value Value="Disabled"/>
<Value Value="Prompt"/>
<Value Value="Enabled"/>
</Values>
</Setting>
<Setting Name="UseCaching" Type="bool" Value="true" AllowChangesWhileExecuting="false"/>
<Setting Name="CsRunPolicySession" Type="bool" AllowChangesWhileExecuting="false" ShowInMenus="false"/>
</Settings>
</Configuration>

View File

@@ -4,9 +4,9 @@
<LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>
<!-- Settings -->
<!-- Is Cs Enabled-->
<LuaCsForBarotrauma.IsCsEnabled.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.IsCsEnabled.DisplayName>
<LuaCsForBarotrauma.IsCsEnabled.Tooltip>Should unsandboxed scripts and dlls be allowed to run.</LuaCsForBarotrauma.IsCsEnabled.Tooltip>
<LuaCsForBarotrauma.IsCsEnabled.DisplayCategory>General</LuaCsForBarotrauma.IsCsEnabled.DisplayCategory>
<LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName>
<LuaCsForBarotrauma.CsRunPolicy.Tooltip>Should unsandboxed scripts and dlls be allowed to run.</LuaCsForBarotrauma.CsRunPolicy.Tooltip>
<LuaCsForBarotrauma.CsRunPolicy.DisplayCategory>General</LuaCsForBarotrauma.CsRunPolicy.DisplayCategory>
<!-- Use Caching -->
<LuaCsForBarotrauma.UseCaching.DisplayName>Use Pre-Caching</LuaCsForBarotrauma.UseCaching.DisplayName>
<LuaCsForBarotrauma.UseCaching.Tooltip>Should mod files be preloaded to speed up loading. Should only be turned off if you have mods that have issues with this.</LuaCsForBarotrauma.UseCaching.Tooltip>

View File

@@ -84,18 +84,22 @@ namespace Barotrauma
private LuaGame _game;
public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>();
/// <summary>
/// Whether C# plugin code is enabled.
/// </summary>
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<bool> _isCsEnabled;
private ISettingList<string> _csRunPolicy;
private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt";
/// <summary>
/// Whether usernames are anonymized or show in logs.
/// </summary>
@@ -123,8 +127,8 @@ namespace Barotrauma
{
var luaCsPackage = GetLuaCsPackage();
_isCsEnabled =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "IsCsEnabled", out var val1)
_csRunPolicy =
ConfigService.TryGetConfig<ISettingList<string>>(luaCsPackage, "CsRunPolicy", out var val1)
? val1
: null;
_hideUserNamesInLogs =
@@ -380,16 +384,27 @@ namespace Barotrauma
void RunStateRunning_OnExit(State<RunState> currentState)
{
EventService.Call("stop");
#if CLIENT
_isCsEnabledForSession = false;
#endif
Logger.LogResults(PackageManagementService.StopRunningPackages());
Logger.LogMessage("LuaCs running state exited");
}
// ReSharper restore InconsistentNaming
}
#endregion
/// <summary>
/// Checks for Cs Execution Policy (ie. prompting the user) and then calls the delegate once completed.
/// </summary>
/// <param name="onReadyToRun"></param>
partial void CheckReadyToRun(Action onReadyToRun);
#region LegacyRedirects
// --- Compatibility
@@ -457,7 +472,7 @@ namespace Barotrauma
void DisposeLuaCsConfig()
{
_isCsEnabled = null;
_csRunPolicy = null;
_hideUserNamesInLogs = null;
}
}