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.CharacterEditor;
using Barotrauma.LuaCs; using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
// ReSharper disable ObjectCreationAsStatement // ReSharper disable ObjectCreationAsStatement
@@ -15,120 +17,147 @@ namespace Barotrauma
partial class LuaCsSetup partial class LuaCsSetup
{ {
private bool _isClientPromptActive; private bool _isClientPromptActive;
private bool _isCsEnabledForSession = false;
/// <summary>
/// Returns whether execution should continue public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun)
/// </summary>
public bool CheckReadyToRun()
{ {
// Fast exit if enabled var res = ReadyToRunNoPrompt();
if (this.IsCsEnabled) if (res.ShouldRun)
{ {
return true; onReadyToRun?.Invoke();
return;
} }
StringBuilder sb = new StringBuilder(); DisplayCsModsPromptClient(res.Item2, (selectedYes) =>
foreach (ContentPackage cp in PackageManagementService.GetLoadedAssemblyPackages())
{ {
if (cp.NameMatches(PackageId)) if (selectedYes)
{ {
continue; onReadyToRun?.Invoke();
} }
});
}
if (cp.UgcId.TryUnwrap(out ContentPackageId id)) private (bool ShouldRun, ImmutableArray<ContentPackage> PromptPackages) ReadyToRunNoPrompt()
{ {
sb.AppendLine($"- {cp.Name} ({id})"); if (this.IsCsEnabled)
} {
else return (true, ImmutableArray<ContentPackage>.Empty);
{
sb.AppendLine($"- {cp.Name} (Not On Workshop)");
}
} }
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; onReadyToRun?.Invoke();
if (GameMain.Client == null || GameMain.Client.IsServerOwner) 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; return true;
} }
else };
new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Cancel")
{
OnClicked = (btn, userdata) =>
{ {
DisplayCsModsPromptClient(sb); _isClientPromptActive = false;
return 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) private void SetupServicesProviderClient(IServicesProvider serviceProvider)
@@ -172,16 +201,10 @@ namespace Barotrauma
case SpriteEditorScreen: case SpriteEditorScreen:
case SubEditorScreen: case SubEditorScreen:
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated.
if (!CheckReadyToRun()) CheckReadyToRun(() =>
{ {
if (CurrentRunState >= RunState.Running) SetRunState(RunState.Running);
{ });
SetRunState(RunState.LoadedNoExec);
}
return;
}
SetRunState(RunState.Running);
break; break;
default: default:
Logger.LogError( Logger.LogError(

View File

@@ -1012,20 +1012,25 @@ namespace Barotrauma
private void TryStartServer() 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") }); if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
waitBox.Buttons[0].OnClicked += (btn, userdata) =>
{ {
CoroutineManager.StopCoroutines(waitCoroutine); var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
return true; var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
}; waitBox.Buttons[0].OnClicked += (btn, userdata) =>
} {
else CoroutineManager.StopCoroutines(waitCoroutine);
{ return true;
StartServer(); };
} }
else
{
StartServer();
}
});
} }
private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox) private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)

View File

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

View File

@@ -2,7 +2,14 @@
<Configuration> <Configuration>
<Settings> <Settings>
<Setting Name="HideUserNamesInLogs" Type="bool" Value="true"/> <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="UseCaching" Type="bool" Value="true" AllowChangesWhileExecuting="false"/>
<Setting Name="CsRunPolicySession" Type="bool" AllowChangesWhileExecuting="false" ShowInMenus="false"/>
</Settings> </Settings>
</Configuration> </Configuration>

View File

@@ -4,9 +4,9 @@
<LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton> <LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>
<!-- Settings --> <!-- Settings -->
<!-- Is Cs Enabled--> <!-- Is Cs Enabled-->
<LuaCsForBarotrauma.IsCsEnabled.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.IsCsEnabled.DisplayName> <LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName>
<LuaCsForBarotrauma.IsCsEnabled.Tooltip>Should unsandboxed scripts and dlls be allowed to run.</LuaCsForBarotrauma.IsCsEnabled.Tooltip> <LuaCsForBarotrauma.CsRunPolicy.Tooltip>Should unsandboxed scripts and dlls be allowed to run.</LuaCsForBarotrauma.CsRunPolicy.Tooltip>
<LuaCsForBarotrauma.IsCsEnabled.DisplayCategory>General</LuaCsForBarotrauma.IsCsEnabled.DisplayCategory> <LuaCsForBarotrauma.CsRunPolicy.DisplayCategory>General</LuaCsForBarotrauma.CsRunPolicy.DisplayCategory>
<!-- Use Caching --> <!-- Use Caching -->
<LuaCsForBarotrauma.UseCaching.DisplayName>Use Pre-Caching</LuaCsForBarotrauma.UseCaching.DisplayName> <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> <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; private LuaGame _game;
public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>(); public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>();
/// <summary> /// <summary>
/// Whether C# plugin code is enabled. /// Whether C# plugin code is enabled.
/// </summary> /// </summary>
public bool IsCsEnabled public bool IsCsEnabled
{ {
get => _isCsEnabled?.Value ?? false; #if CLIENT
internal set => _isCsEnabled?.TrySetValue(value); 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 ISettingList<string> _csRunPolicy;
private ISettingBase<bool> _isCsEnabled; private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt";
/// <summary> /// <summary>
/// Whether usernames are anonymized or show in logs. /// Whether usernames are anonymized or show in logs.
/// </summary> /// </summary>
@@ -123,8 +127,8 @@ namespace Barotrauma
{ {
var luaCsPackage = GetLuaCsPackage(); var luaCsPackage = GetLuaCsPackage();
_isCsEnabled = _csRunPolicy =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "IsCsEnabled", out var val1) ConfigService.TryGetConfig<ISettingList<string>>(luaCsPackage, "CsRunPolicy", out var val1)
? val1 ? val1
: null; : null;
_hideUserNamesInLogs = _hideUserNamesInLogs =
@@ -380,16 +384,27 @@ namespace Barotrauma
void RunStateRunning_OnExit(State<RunState> currentState) void RunStateRunning_OnExit(State<RunState> currentState)
{ {
EventService.Call("stop"); EventService.Call("stop");
#if CLIENT
_isCsEnabledForSession = false;
#endif
Logger.LogResults(PackageManagementService.StopRunningPackages()); Logger.LogResults(PackageManagementService.StopRunningPackages());
Logger.LogMessage("LuaCs running state exited"); Logger.LogMessage("LuaCs running state exited");
} }
// ReSharper restore InconsistentNaming // ReSharper restore InconsistentNaming
} }
#endregion #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 #region LegacyRedirects
// --- Compatibility // --- Compatibility
@@ -457,7 +472,7 @@ namespace Barotrauma
void DisposeLuaCsConfig() void DisposeLuaCsConfig()
{ {
_isCsEnabled = null; _csRunPolicy = null;
_hideUserNamesInLogs = null; _hideUserNamesInLogs = null;
} }
} }