diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs
new file mode 100644
index 000000000..64649068e
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs
@@ -0,0 +1,16 @@
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+public class DisplayableData : IDisplayableData
+{
+ public string Name { get; private set; }
+ public string ModName { get; private set; }
+ public string DisplayName { get; private set; }
+ public string DisplayModName { get; private set; }
+ public string DisplayCategory { get; private set; }
+ public string Tooltip { get; private set; }
+ public string ImageIcon { get; private set; }
+ public Point IconResolution { get; private set; }
+ public bool ShowWhenNotLoaded { get; private set; }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs
new file mode 100644
index 000000000..3c5fbc5f7
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs
@@ -0,0 +1,6 @@
+using Barotrauma.LuaCs.Configuration;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+public partial interface IConfigBase : IDisplayableData, IDisplayableInitialize { }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs
new file mode 100644
index 000000000..69449ed6b
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs
@@ -0,0 +1,66 @@
+using System.Numerics;
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+///
+/// Contains the Display Data for use with Menus.
+///
+public interface IDisplayableData
+{
+ ///
+ /// Internal name of the instance.
+ ///
+ string Name { get; }
+ ///
+ /// Internal mod name of the instance. ContentPackage name will be used by default.
+ ///
+ string ModName { get; }
+ ///
+ /// The name to display in GUIs and Menus.
+ ///
+ string DisplayName { get; }
+ ///
+ /// The mod name to display in GUIs and Menus.
+ ///
+ string DisplayModName { get; }
+ ///
+ /// Category this instance falls under. Used by menus when filtering by category.
+ ///
+ string DisplayCategory { get; }
+ ///
+ /// The tooltip shown on hover.
+ ///
+ string Tooltip { get; }
+ ///
+ /// The fully qualified filepath to the image icon for this config.
+ ///
+ string ImageIcon { get; }
+ ///
+ /// Required if ImageIcon is set. X,Y resolution of the image.
+ ///
+ Point IconResolution { get; }
+ ///
+ /// Whether to show the entry in the menu when not loaded.
+ ///
+ bool ShowWhenNotLoaded { get; }
+}
+
+public interface IDisplayableInitialize
+{
+ void Initialize(IDisplayableData values);
+
+ // copy this as needed
+ /*public void Initialize(IDisplayableData values)
+ {
+ this.Name = values.Name;
+ this.ModName = values.ModName;
+ this.DisplayName = values.DisplayName;
+ this.DisplayModName = values.DisplayModName;
+ this.DisplayCategory = values.DisplayCategory;
+ this.Tooltip = values.Tooltip;
+ this.ImageIcon = values.ImageIcon;
+ this.IconResolution = values.IconResolution;
+ this.ShowWhenNotLoaded = values.ShowWhenNotLoaded;
+ }*/
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs
new file mode 100644
index 000000000..d46048703
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs
@@ -0,0 +1,21 @@
+using System.Collections.Immutable;
+using System.Globalization;
+
+namespace Barotrauma.LuaCs.Data;
+
+public partial record ModConfigInfo : IModConfigInfo
+{
+ public ImmutableArray StylesResourceInfos { get; init; }
+}
+
+public record StylesResourceInfo : IStylesResourceInfo
+{
+ public Platform SupportedPlatforms { get; init; }
+ public Target SupportedTargets { get; init; }
+ public int LoadPriority { get; init; }
+ public ImmutableArray FilePaths { get; init; }
+ public bool Optional { get; init; }
+ public ImmutableArray SupportedCultures { get; init; }
+ public string InternalName { get; init; }
+ public ImmutableArray Dependencies { get; init; }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs
new file mode 100644
index 000000000..45d563fa7
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs
@@ -0,0 +1,5 @@
+using Barotrauma.LuaCs.Configuration;
+
+namespace Barotrauma.LuaCs.Data;
+
+public partial interface IConfigInfo : IDisplayableData { }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs
new file mode 100644
index 000000000..14974ac8c
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs
@@ -0,0 +1,15 @@
+using System.Collections.Immutable;
+
+namespace Barotrauma.LuaCs.Data;
+
+public partial interface IModConfigInfo : IStylesResourcesInfo { }
+
+public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, ILoadableResourceInfo, IPackageDependenciesInfo { }
+
+public interface IStylesResourcesInfo
+{
+ ///
+ /// Collection of loadable styles data.
+ ///
+ ImmutableArray StylesResourceInfos { get; }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs
new file mode 100644
index 000000000..b3e2a456b
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Data;
+
+public interface IStylesInfo
+{
+
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs
new file mode 100644
index 000000000..21cc88352
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs
@@ -0,0 +1,7 @@
+namespace Barotrauma.LuaCs.Services;
+
+public interface IClientLoggerService : IService
+{
+ void AddToGUIUpdateList();
+ void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f);
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs
new file mode 100644
index 000000000..cd0b38829
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs
@@ -0,0 +1,51 @@
+namespace Barotrauma.LuaCs.Services;
+
+// TODO: Rework interface to support resource infos.
+///
+/// Loads XML Style assets from the given content package.
+///
+public interface IStylesService : IService
+{
+ ///
+ /// Tries to load the styles file for the given contentpackage and path into a new UIStylesProcessor instance.
+ ///
+ ///
+ ///
+ ///
+ bool TryLoadStylesFile(ContentPackage package, ContentPath path);
+ ///
+ /// Unloads all styles assets and UIStyleProcessor instances.
+ ///
+ void UnloadAllStyles();
+
+ ///
+ /// Tries to the get the font asset by xml asset name, returns null on failure.
+ ///
+ /// XML Name of the asset.
+ /// The asset or null if none are found.
+ GUIFont GetFont(string fontName);
+ ///
+ /// Tries to the get the sprite asset by xml asset name, returns null on failure.
+ ///
+ /// XML Name of the asset.
+ /// The asset or null if none are found.
+ GUISprite GetSprite(string spriteName);
+ ///
+ /// Tries to the get the sprite sheet asset by xml asset name, returns null on failure.
+ ///
+ /// XML Name of the asset.
+ /// The asset or null if none are found.
+ GUISpriteSheet GetSpriteSheet(string spriteSheetName);
+ ///
+ /// Tries to the get the cursor asset by xml asset name, returns null on failure.
+ ///
+ /// XML Name of the asset.
+ /// The asset or null if none are found.
+ GUICursor GetCursor(string cursorName);
+ ///
+ /// Tries to the get the color asset by xml asset name, returns null on failure.
+ ///
+ /// XML Name of the asset.
+ /// The asset or null if none are found.
+ GUIColor GetColor(string colorName);
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs
new file mode 100644
index 000000000..aee2efabb
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs
@@ -0,0 +1,50 @@
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma.LuaCs.Services;
+
+public partial class LoggerService : ILoggerService, IClientLoggerService
+{
+ private GUIFrame _overlayFrame;
+ private GUITextBlock _textBlock;
+ private double _showTimer = 0;
+
+
+ private void CreateOverlay(string message)
+ {
+ _overlayFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.03f), null), null, new Color(50, 50, 50, 100))
+ {
+ CanBeFocused = false
+ };
+
+ GUILayoutGroup layout =
+ new GUILayoutGroup(
+ new RectTransform(new Vector2(0.8f, 0.8f), _overlayFrame.RectTransform, Anchor.CenterLeft), false,
+ Anchor.Center);
+
+ _textBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), layout.RectTransform), message);
+ _overlayFrame.RectTransform.MinSize = new Point((int)(_textBlock.TextSize.X * 1.2), 0);
+
+ layout.Recalculate();
+ }
+
+ public void AddToGUIUpdateList()
+ {
+ if (_overlayFrame != null && Timing.TotalTime <= _showTimer)
+ {
+ _overlayFrame.AddToGUIUpdateList();
+ }
+ }
+
+ public void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f)
+ {
+ if (Timing.TotalTime <= _showTimer)
+ {
+ return;
+ }
+
+ CreateOverlay(message);
+
+ _overlayFrame.Flash(Color.Red, duration, true);
+ _showTimer = Timing.TotalTime + time;
+ }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs
new file mode 100644
index 000000000..a079ef1ec
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Barotrauma.LuaCs.Data;
+using Barotrauma.LuaCs.Services.Processing;
+
+namespace Barotrauma.LuaCs.Services;
+
+public partial class PackageService : IStylesResourcesInfo
+{
+ private readonly Lazy _stylesService;
+
+ public PackageService(
+ Lazy converterService,
+ Lazy legacyConfigService,
+ Lazy luaScriptService,
+ Lazy localizationService,
+ Lazy pluginService,
+ Lazy stylesService,
+ Lazy configService,
+ IPackageManagementService packageManagementService,
+ IStorageService storageService,
+ ILoggerService loggerService)
+ {
+ _modConfigConverterService = converterService;
+ _legacyConfigService = legacyConfigService;
+ _luaScriptService = luaScriptService;
+ _localizationService = localizationService;
+ _pluginService = pluginService;
+ _stylesService = stylesService;
+ _configService = configService;
+ _packageManagementService = packageManagementService;
+ _storageService = storageService;
+ _loggerService = loggerService;
+ }
+
+ public ImmutableArray StylesResourceInfos => ModConfigInfo?.StylesResourceInfos ?? ImmutableArray.Empty;
+
+ public void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs
new file mode 100644
index 000000000..8c81c7fb5
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs
@@ -0,0 +1,9 @@
+using System.Xml.Linq;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services.Processing;
+
+#region XmlToResourceParsers
+public interface IXmlStylesToResConverterService : IXmlResourceConverterService { }
+
+#endregion
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs
new file mode 100644
index 000000000..f859dcd09
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace Barotrauma.LuaCs.Services;
+
+public class StylesService : IStylesService
+{
+ private readonly Dictionary _loadedProcessors = new();
+ private readonly IStorageService _storageService;
+ private readonly ILoggerService _loggerService;
+
+ public StylesService(IStorageService storageService, ILoggerService loggerService)
+ {
+ _storageService = storageService;
+ _loggerService = loggerService;
+ }
+
+ public bool TryLoadStylesFile(ContentPackage package, ContentPath path)
+ {
+ //check if file already in dict
+ if (_loadedProcessors.ContainsKey(path.FullPath))
+ {
+ return true;
+ }
+ //check if file exists
+ if (_storageService.FileExists(path.FullPath))
+ {
+ try
+ {
+ var styleProcessor = new UIStyleProcessor(package, path);
+ styleProcessor.LoadFile();
+ _loadedProcessors.Add(path.FullPath, styleProcessor);
+ }
+ catch (InvalidDataException exception)
+ {
+ _loggerService.LogError($"XmlAssetService.TryLoadStylesFile failed for ContentPackage {package.Name}: Exception: {exception.Message}");
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void UnloadAllStyles()
+ {
+ if (NoProcessorsLoaded)
+ return;
+
+ foreach (var processor in _loadedProcessors)
+ {
+ processor.Value.UnloadFile();
+ }
+ _loadedProcessors.Clear();
+ }
+
+ public GUIFont GetFont(string fontName)
+ {
+ if (NoProcessorsLoaded)
+ return null;
+ foreach (var processor in _loadedProcessors.Values)
+ {
+ if (processor.Fonts.TryGetValue(fontName, out var asset))
+ return asset;
+ }
+
+ return null;
+ }
+
+ public GUISprite GetSprite(string spriteName)
+ {
+ if (NoProcessorsLoaded)
+ return null;
+ foreach (var processor in _loadedProcessors.Values)
+ {
+ if (processor.Sprites.TryGetValue(spriteName, out var asset))
+ return asset;
+ }
+
+ return null;
+ }
+
+ public GUISpriteSheet GetSpriteSheet(string spriteSheetName)
+ {
+ if (NoProcessorsLoaded)
+ return null;
+ foreach (var processor in _loadedProcessors.Values)
+ {
+ if (processor.SpriteSheets.TryGetValue(spriteSheetName, out var asset))
+ return asset;
+ }
+
+ return null;
+ }
+
+ public GUICursor GetCursor(string cursorName)
+ {
+ if (NoProcessorsLoaded)
+ return null;
+ foreach (var processor in _loadedProcessors.Values)
+ {
+ if (processor.Cursors.TryGetValue(cursorName, out var asset))
+ return asset;
+ }
+
+ return null;
+ }
+
+ public GUIColor GetColor(string colorName)
+ {
+ if (NoProcessorsLoaded)
+ return null;
+ foreach (var processor in _loadedProcessors.Values)
+ {
+ if (processor.Colors.TryGetValue(colorName, out var asset))
+ return asset;
+ }
+
+ return null;
+ }
+
+ private bool NoProcessorsLoaded => _loadedProcessors.Count < 1;
+
+ public void Dispose()
+ {
+ UnloadAllStyles();
+ GC.SuppressFinalize(this);
+ }
+
+ public void Reset()
+ {
+ UnloadAllStyles();
+ }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs
new file mode 100644
index 000000000..b90276e7a
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml.Linq;
+using Barotrauma.Extensions;
+
+namespace Barotrauma.LuaCs.Services;
+
+public class UIStyleProcessor : HashlessFile
+{
+ private readonly UIStyleFile _fake;
+ public readonly Dictionary Fonts = new();
+ public readonly Dictionary Sprites = new();
+ public readonly Dictionary SpriteSheets = new();
+ public readonly Dictionary Cursors = new();
+ public readonly Dictionary Colors = new();
+
+ public UIStyleProcessor(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path)
+ {
+ _fake = new UIStyleFile(contentPackage, path);
+ }
+
+ public override void LoadFile()
+ {
+ var element = XMLExtensions.TryLoadXml(path: Path)?.Root?.FromPackage(ContentPackage);
+ if (element is null)
+ throw new InvalidDataException($"UIStyleProcessor: Failed to load UI style file: {Path}");
+
+ var styleElement = element.Name.LocalName.ToLowerInvariant() == "style" ? element : element.GetChildElement("style");
+ if (styleElement is null)
+ throw new InvalidDataException($"UIStyleProcessor: no 'style' XmlElement found in file: {Path}");
+
+ var childElements = styleElement.GetChildElements("Font");
+ if (childElements is not null)
+ AddToList(Fonts, childElements, _fake);
+
+ childElements = styleElement.GetChildElements("Sprite");
+ if (childElements is not null)
+ AddToList(Sprites, childElements, _fake);
+
+ childElements = styleElement.GetChildElements("Spritesheet");
+ if (childElements is not null)
+ AddToList(SpriteSheets, childElements, _fake);
+
+ childElements = styleElement.GetChildElements("Cursor");
+ if (childElements is not null)
+ AddToList(Cursors, childElements, _fake);
+
+ childElements = styleElement.GetChildElements("Color");
+ if (childElements is not null)
+ AddToList(Colors, childElements, _fake);
+
+
+ void AddToList(Dictionary dict, IEnumerable ele, UIStyleFile file) where T1 : GUISelector where T2 : GUIPrefab
+ {
+ foreach (ContentXElement prefabElement in ele)
+ {
+ string name = prefabElement.GetAttributeString("name", string.Empty);
+ if (name != string.Empty)
+ {
+ var prefab = (T2)Activator.CreateInstance(typeof(T2), new object[]{ prefabElement, file })!;
+ if (!dict.ContainsKey(name))
+ dict[name] = (T1)Activator.CreateInstance(typeof(T1), new object[] { name })!;
+ dict[name].Prefabs.Add(prefab, false);
+ }
+ }
+ }
+ }
+
+ public override void UnloadFile()
+ {
+ Fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake));
+ Sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake));
+ SpriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake));
+ Cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake));
+ Colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake));
+
+ Fonts.Clear();
+ Sprites.Clear();
+ SpriteSheets.Clear();
+ Cursors.Clear();
+ Colors.Clear();
+ }
+
+ public override void Sort()
+ {
+ Fonts.Values.ForEach(p => p.Prefabs.Sort());
+ Sprites.Values.ForEach(p => p.Prefabs.Sort());
+ SpriteSheets.Values.ForEach(p => p.Prefabs.Sort());
+ Cursors.Values.ForEach(p => p.Prefabs.Sort());
+ Colors.Values.ForEach(p => p.Prefabs.Sort());
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs
new file mode 100644
index 000000000..4b97459b1
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Barotrauma.LuaCs.Services.Processing;
+
+// ReSharper disable once CheckNamespace
+namespace Barotrauma.LuaCs.Services;
+
+public partial class PackageService
+{
+ public PackageService(
+ Lazy converterService,
+ Lazy legacyConfigService,
+ Lazy luaScriptService,
+ Lazy localizationService,
+ Lazy pluginService,
+ Lazy configService,
+ IPackageManagementService packageManagementService,
+ IStorageService storageService,
+ ILoggerService loggerService)
+ {
+ _modConfigConverterService = converterService;
+ _legacyConfigService = legacyConfigService;
+ _luaScriptService = luaScriptService;
+ _localizationService = localizationService;
+ _pluginService = pluginService;
+ _configService = configService;
+ _packageManagementService = packageManagementService;
+ _storageService = storageService;
+ _loggerService = loggerService;
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props
index cf9a48490..62c907748 100644
--- a/Barotrauma/BarotraumaShared/Luatrauma.props
+++ b/Barotrauma/BarotraumaShared/Luatrauma.props
@@ -5,6 +5,8 @@
+
+
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs
new file mode 100644
index 000000000..37e076506
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs
@@ -0,0 +1,21 @@
+using System;
+using Barotrauma.Networking;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+public partial interface IConfigBase : IVarId
+{
+ bool IsInitialized { get; }
+ string GetValue();
+ bool TrySetValue(string value);
+ bool IsAssignable(string value);
+ Type GetValueType();
+ void Initialize(IVarId id, string defaultValue);
+}
+
+public interface IVarId
+{
+ Guid InstanceId { get; }
+ string InternalName { get; }
+ ContentPackage OwnerPackage { get; }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs
new file mode 100644
index 000000000..30ba129c6
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs
@@ -0,0 +1,12 @@
+using System;
+using Barotrauma.LuaCs.Networking;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+public interface IConfigEntry : IConfigBase, INetVar where T : IConvertible, IEquatable
+{
+ T Value { get; }
+ bool TrySetValue(T value);
+ bool IsAssignable(T value);
+ void Initialize(IVarId id, T defaultValue);
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs
new file mode 100644
index 000000000..226ddbe08
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs
@@ -0,0 +1,8 @@
+using Barotrauma.LuaCs.Networking;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+public interface IConfigList : IConfigBase, INetVar
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs
new file mode 100644
index 000000000..571bb1d3f
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Barotrauma.LuaCs.Configuration;
+
+public interface IConfigRangeEntry : IConfigEntry where T : IConvertible, IEquatable
+{
+ T MinValue { get; }
+ T MaxValue { get; }
+
+ int GetStepCount();
+ float GetRangeMin();
+ float GetRangeMax();
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs
new file mode 100644
index 000000000..7362f990f
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace Barotrauma.LuaCs.Data;
+
+#region ModConfigurationInfo
+
+public partial record ModConfigInfo : IModConfigInfo
+{
+ public ContentPackage Package { get; init; }
+ public string PackageName { get; init; }
+ public TargetRunMode RunModes { get; init; }
+
+ public ImmutableArray SupportedCultures { get; init; }
+ public ImmutableArray Assemblies { get; init; }
+ public ImmutableArray Localizations { get; init; }
+ public ImmutableArray LuaScripts { get; init; }
+ public ImmutableArray Configs { get; init; }
+ public ImmutableArray ConfigProfiles { get; init; }
+}
+
+#endregion
+
+#region DataContracts
+
+public record AssemblyResourceInfo : IAssemblyResourceInfo
+{
+ public ContentPackage OwnerPackage { get; init; }
+ public string FriendlyName { get; init; }
+ public bool IsScript { get; init; }
+ public string InternalName { get; init; }
+ public bool LazyLoad { get; init; }
+ public Platform SupportedPlatforms { get; init; }
+ public Target SupportedTargets { get; init; }
+ public int LoadPriority { get; init; }
+ public ImmutableArray FilePaths { get; init; }
+ public ImmutableArray SupportedCultures { get; init; }
+ public ImmutableArray Dependencies { get; init; }
+ public bool Optional { get; init; }
+}
+
+public record DependencyInfo : IPackageDependencyInfo
+{
+ public ContentPackage OwnerPackage { get; init; }
+ public string FolderPath { get; init; }
+ public string PackageName { get; init; }
+ public ulong SteamWorkshopId { get; init; }
+ public ContentPackage DependencyPackage { get; init; }
+}
+
+public record LocalizationResourceInfo : ILocalizationResourceInfo
+{
+ public ContentPackage OwnerPackage { get; init; }
+ public CultureInfo TargetCulture { get; init; }
+ public Platform SupportedPlatforms { get; init; }
+ public Target SupportedTargets { get; init; }
+ public int LoadPriority { get; init; }
+ public ImmutableArray FilePaths { get; init; }
+ public ImmutableArray SupportedCultures { get; init; }
+ public ImmutableArray Dependencies { get; init; }
+ public bool Optional { get; init; }
+}
+
+public readonly struct LuaScriptResourceInfo : ILuaResourceInfo
+{
+ public ContentPackage OwnerPackage { get; init; }
+ public Platform SupportedPlatforms { get; init; }
+ public Target SupportedTargets { get; init; }
+ public int LoadPriority { get; init; }
+ public ImmutableArray FilePaths { get; init; }
+ public ImmutableArray SupportedCultures { get; init; }
+ public ImmutableArray Dependencies { get; init; }
+ public bool Optional { get; init; }
+ public string InternalName { get; init; }
+ public bool LazyLoad { get; init; }
+}
+
+#endregion
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs
new file mode 100644
index 000000000..42d702b7f
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Barotrauma.LuaCs.Data;
+
+[Flags]
+public enum Platform
+{
+ Linux=0x1,
+ OSX=0x2,
+ Windows=0x4
+}
+
+[Flags]
+public enum Target
+{
+ Client=0x1,
+ Server=0x2
+}
+
+[Flags]
+public enum TargetRunMode
+{
+ ClientEnabled = 0x1,
+ ClientAlways = 0x2,
+ ServerEnabled = 0x4,
+ ServerAlways = 0x8
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs
new file mode 100644
index 000000000..e970aa9ce
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+
+namespace Barotrauma.LuaCs.Data;
+
+public interface IPlatformInfo
+{
+ ///
+ /// Platforms that these localization files should be loaded for.
+ ///
+ [Required]
+ Platform SupportedPlatforms { get; }
+
+ ///
+ /// Targets that these localization files should be loaded for.
+ ///
+ [Required]
+ Target SupportedTargets { get; }
+}
+
+
+///
+/// Which package does the following data belong to?
+///
+public interface IPackageInfo
+{
+ ContentPackage OwnerPackage { get; }
+}
+
+
+///
+/// ResourceInfos contain metadata about a resource.
+///
+public interface IResourceInfo : IPlatformInfo
+{
+ ///
+ /// [Optional]
+ /// Allows you to specify the loading order for all assets of the same type (ie. styles, assemblies, etc.).
+ ///
+ int LoadPriority { get; }
+
+ ///
+ /// Resource absolute file paths.
+ ///
+ [Required]
+ ImmutableArray FilePaths { get; }
+
+ ///
+ /// Marks this resource as optional (ie. Cross-CP content). Setting this to true will allow the dependency system to
+ /// try and order the loading but not fail if it runs into circular dependency issues.
+ ///
+ bool Optional { get; }
+}
+
+///
+/// Information about supported cultures. It is intended to be ignored if the array is ImmutableArray.Empty .
+///
+public interface IResourceCultureInfo
+{
+ ///
+ /// List of supported cultures by this resource.
+ ///
+ ImmutableArray SupportedCultures { get; }
+}
+
+
+public interface ILoadableResourceInfo
+{
+ ///
+ /// [UNIQUE] The name that will be used when trying to reference this resource for execution or loading.
+ ///
+ [Required]
+ public string InternalName { get; }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs
new file mode 100644
index 000000000..c56cb5aa1
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs
@@ -0,0 +1,26 @@
+using System;
+using Barotrauma.Networking;
+
+namespace Barotrauma.LuaCs.Data;
+
+// TODO: Finish
+public partial interface IConfigInfo
+{
+ string Name { get; }
+ string PackageName { get; }
+ ConfigDataType Type { get; }
+ string DefaultValue { get; }
+ ClientPermissions RequiredPermissions { get; }
+}
+
+public enum ConfigDataType
+{
+ Boolean, Int32, Int64, Single, Double, String,
+ Color, Vector2, Vector3, List,
+ RangeInt32, RangeSingle, ControlInput
+}
+
+public enum NetSync
+{
+ None, TwoWay, ServerAuthority, ClientOneWay
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs
new file mode 100644
index 000000000..7be1db56c
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Data;
+
+public interface IConfigProfileInfo
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs
new file mode 100644
index 000000000..c05887e5a
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Data;
+
+public interface ILocalizationInfo
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs
new file mode 100644
index 000000000..8a324f870
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs
@@ -0,0 +1,14 @@
+using System.Collections.Immutable;
+
+namespace Barotrauma.LuaCs.Data;
+
+public partial interface IModConfigInfo : IResourceCultureInfo, IAssembliesResourcesInfo,
+ ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo,
+ IConfigProfilesResourcesInfo
+{
+ // package info
+ ContentPackage Package { get; }
+ string PackageName { get; }
+ // configuration
+ TargetRunMode RunModes { get; }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs
new file mode 100644
index 000000000..ef96428f1
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs
@@ -0,0 +1,31 @@
+using System.Collections.Immutable;
+
+namespace Barotrauma.LuaCs.Data;
+
+public interface IPackageDependencyInfo : IPackageInfo
+{
+ ///
+ /// Root folder of the content package.
+ ///
+ public string FolderPath { get; }
+ ///
+ /// Name of the package.
+ ///
+ public string PackageName { get; }
+ ///
+ /// Steam ID of the package.
+ ///
+ public ulong SteamWorkshopId { get; }
+ ///
+ /// The dependency package, if found in the ALL Packages List.
+ ///
+ public ContentPackage DependencyPackage { get; }
+}
+
+public interface IPackageDependenciesInfo
+{
+ ///
+ /// List of required packages.
+ ///
+ ImmutableArray Dependencies { get; }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs
new file mode 100644
index 000000000..8b6f28e9b
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Globalization;
+
+namespace Barotrauma.LuaCs.Data;
+
+public interface IConfigResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { }
+public interface IConfigProfileResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { }
+public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { }
+///
+/// Represents loadable Lua files.
+///
+public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo { }
+public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo
+{
+ ///
+ /// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name.
+ /// Legacy scripts will all be given the sanitized name of the Content Package they belong to.
+ ///
+ public string FriendlyName { get; }
+ ///
+ /// Is this entry referring to a script file collection.
+ ///
+ public bool IsScript { get; }
+}
+
+
+#region Collections
+
+public interface IAssembliesResourcesInfo
+{
+ ImmutableArray Assemblies { get; }
+}
+
+public interface ILocalizationsResourcesInfo
+{
+ ImmutableArray Localizations { get; }
+}
+
+public interface ILuaScriptsResourcesInfo
+{
+ ImmutableArray LuaScripts { get; }
+}
+
+public interface IConfigsResourcesInfo
+{
+ ImmutableArray Configs { get; }
+}
+
+public interface IConfigProfilesResourcesInfo
+{
+ ImmutableArray ConfigProfiles { get; }
+}
+
+#endregion
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs
new file mode 100644
index 000000000..a0da6263b
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs
@@ -0,0 +1,18 @@
+namespace Barotrauma.LuaCs.Data;
+
+///
+/// Legacy data contract for the old run configuration system. Should be deprecated
+/// once no longer needed.
+///
+public interface IRunConfig
+{
+ bool UseNonPublicizedAssemblies { get; }
+ bool AutoGenerated { get; }
+ bool UseInternalAssemblyName { get; }
+ string Client { get; }
+ string Server { get; }
+
+ bool IsForced();
+ bool IsStandard();
+ bool IsForcedOrStandard();
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs
index a8816c595..e854a8e04 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs
@@ -19,7 +19,7 @@ namespace Barotrauma
#if SERVER
private const string LogPrefix = "SV";
- private const int NetMaxLength = 1024;
+ private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system.
private const int NetMaxMessages = 60;
// This is used so its possible to call logging functions inside the serverLog
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs
index fb2ef00f1..4a479c11f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs
@@ -11,6 +11,7 @@ using MoonSharp.VsCodeDebugger;
using System.Reflection;
using System.Runtime.Loader;
using System.Xml.Linq;
+using Barotrauma.LuaCs.Services;
using Barotrauma.Networking;
namespace Barotrauma
@@ -99,7 +100,7 @@ namespace Barotrauma
public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this);
public LuaCsModStore ModStore { get; private set; }
- private LuaRequire require { get; set; }
+ private LuaRequire Require { get; set; }
public LuaCsSetupConfig Config { get; private set; }
public MoonSharpVsCodeDebugServer DebugServer { get; private set; }
public bool IsInitialized { get; private set; }
@@ -320,6 +321,7 @@ namespace Barotrauma
#endif
}
+
public void Stop()
{
PluginPackageManager.UnloadPlugins();
@@ -395,7 +397,7 @@ namespace Barotrauma
Lua.Options.CheckThreadAccess = false;
Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; };
- require = new LuaRequire(Lua);
+ Require = new LuaRequire(Lua);
Game = new LuaGame();
Networking = new LuaCsNetworking();
@@ -428,7 +430,7 @@ namespace Barotrauma
Lua.Globals["dofile"] = (Func)DoFile;
Lua.Globals["loadfile"] = (Func)LoadFile;
- Lua.Globals["require"] = (Func)require.Require;
+ Lua.Globals["require"] = (Func)Require.Require;
Lua.Globals["dostring"] = (Func)Lua.DoString;
Lua.Globals["load"] = (Func)Lua.LoadString;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs
new file mode 100644
index 000000000..f498da372
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Barotrauma.LuaCs.Networking;
+
+public partial interface INetCallback
+{
+ public ushort CallbackId { get; }
+}
+
+#if SERVER
+public partial interface INetCallback
+{
+
+}
+#endif
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs
new file mode 100644
index 000000000..b4a7d5885
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs
@@ -0,0 +1,25 @@
+using Barotrauma.LuaCs.Configuration;
+using Barotrauma.LuaCs.Data;
+using Barotrauma.LuaCs.Networking;
+using Barotrauma.Networking;
+
+namespace Barotrauma.LuaCs.Networking;
+
+public interface INetVar : IVarId
+{
+ ///
+ /// Synchronized network id, uninitialized if value is zero/0. Used by Networking service.
+ ///
+ ushort NetId { get; }
+ ///
+ /// Synchronization type
+ ///
+ NetSync SyncType { get; }
+ ///
+ /// Permissions needed by clients to send net-events or receive net messages.
+ ///
+ ClientPermissions WritePermissions { get; }
+ void ReadNetMessage(INetReadMessage message);
+ void WriteNetMessage(INetWriteMessage message);
+ void Initialize(ushort netId, NetSync syncMode, ClientPermissions writePermissions);
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs
new file mode 100644
index 000000000..c68b318ae
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs
@@ -0,0 +1,151 @@
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma.LuaCs.Networking;
+
+#region Wrapper-IWriteMessage
+
+///
+/// Literally just exists because Barotrauma.IWriteMessage is internal only.
+///
+public interface INetWriteMessage
+{
+ internal IWriteMessage Message { get; }
+ internal void SetMessage(IWriteMessage msg);
+
+ void WriteBoolean(bool val) => Message.WriteBoolean(val);
+
+ void WritePadBits() => Message.WritePadBits();
+
+ void WriteByte(byte val) => Message.WriteByte(val);
+
+ void WriteInt16(short val) => Message.WriteInt16(val);
+
+ void WriteUInt16(ushort val) => Message.WriteUInt16(val);
+
+ void WriteInt32(int val) => Message.WriteInt32(val);
+
+ void WriteUInt32(uint val) => Message.WriteUInt32(val);
+
+ void WriteInt64(long val) => Message.WriteInt64(val);
+
+ void WriteUInt64(ulong val) => Message.WriteUInt64(val);
+
+ void WriteSingle(float val) => Message.WriteSingle(val);
+
+ void WriteDouble(double val) => Message.WriteDouble(val);
+
+ void WriteColorR8G8B8(Color val) => Message.WriteColorR8G8B8(val);
+
+ void WriteColorR8G8B8A8(Color val) => Message.WriteColorR8G8B8A8(val);
+
+ void WriteVariableUInt32(uint val) => Message.WriteVariableUInt32(val);
+
+ void WriteString(string val) => Message.WriteString(val);
+
+ void WriteIdentifier(Identifier val) => Message.WriteIdentifier(val);
+
+ void WriteRangedInteger(int val, int min, int max) => Message.WriteRangedInteger(val, min, max);
+
+ void WriteRangedSingle(float val, float min, float max, int bitCount) =>
+ Message.WriteRangedSingle(val, min, max, bitCount);
+
+ void WriteBytes(byte[] val, int startIndex, int length) => Message.WriteBytes(val, startIndex, length);
+
+ byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength) =>
+ Message.PrepareForSending(compressPastThreshold, out isCompressed, out outLength);
+
+ int BitPosition
+ {
+ get => Message.BitPosition;
+ set => Message.BitPosition = value;
+ }
+
+ int BytePosition => Message.BytePosition;
+
+ byte[] Buffer => Message.Buffer;
+
+ int LengthBits
+ {
+ get => Message.LengthBits;
+ set => Message.LengthBits = value;
+ }
+
+ int LengthBytes => Message.LengthBytes;
+}
+
+#endregion
+
+#region Wrapper-IReadMessage
+
+///
+/// Literally just exists because Barotrauma.IReadMessage is internal only.
+///
+public interface INetReadMessage
+{
+ internal IReadMessage Message { get; }
+ internal void SetMessage(IReadMessage msg);
+
+ bool ReadBoolean() => Message.ReadBoolean();
+ void ReadPadBits() => Message.ReadPadBits();
+ byte ReadByte() => Message.ReadByte();
+ byte PeekByte() => Message.PeekByte();
+ ushort ReadUInt16() => Message.ReadUInt16();
+ short ReadInt16() => Message.ReadInt16();
+ uint ReadUInt32() => Message.ReadUInt32();
+ int ReadInt32() => Message.ReadInt32();
+ ulong ReadUInt64() => Message.ReadUInt64();
+ long ReadInt64() => Message.ReadInt64();
+ float ReadSingle() => Message.ReadSingle();
+ double ReadDouble() => Message.ReadDouble();
+ uint ReadVariableUInt32() => Message.ReadVariableUInt32();
+ string ReadString() => Message.ReadString();
+ Identifier ReadIdentifier() => Message.ReadIdentifier();
+ Color ReadColorR8G8B8() => Message.ReadColorR8G8B8();
+ Color ReadColorR8G8B8A8() => Message.ReadColorR8G8B8A8();
+ int ReadRangedInteger(int min, int max) => Message.ReadRangedInteger(min, max);
+ float ReadRangedSingle(float min, float max, int bitCount) => Message.ReadRangedSingle(min, max, bitCount);
+ byte[] ReadBytes(int numberOfBytes) => Message.ReadBytes(numberOfBytes);
+ int BitPosition
+ {
+ get => Message.BitPosition;
+ set => Message.BitPosition = value;
+ }
+ int BytePosition => Message.BytePosition;
+ byte[] Buffer => Message.Buffer;
+ int LengthBits
+ {
+ get => Message.LengthBits;
+ set => Message.LengthBits = value;
+ }
+ int LengthBytes => Message.LengthBytes;
+}
+
+#endregion
+
+#region HelperImplementations
+
+public class NetWriteMessage : INetWriteMessage
+{
+ private IWriteMessage Message { get; set; }
+
+ IWriteMessage INetWriteMessage.Message => Message;
+
+ void INetWriteMessage.SetMessage(IWriteMessage msg)
+ {
+ Message = msg;
+ }
+}
+
+public class NetReadMessage : INetReadMessage
+{
+ private IReadMessage Message { get; set; }
+ IReadMessage INetReadMessage.Message => Message;
+
+ void INetReadMessage.SetMessage(IReadMessage msg)
+ {
+ Message = msg;
+ }
+}
+
+#endregion
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs
index 8ba1f8921..a23d70f7b 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs
@@ -8,6 +8,7 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
+using Barotrauma.LuaCs.Services;
using Barotrauma.Steam;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -149,23 +150,23 @@ public sealed class CsPackageManager : IDisposable
#endregion
///
- /// Whether or not assemblies have been loaded.
+ /// Whether assemblies have been loaded.
///
public bool AssembliesLoaded { get; private set; }
///
- /// Whether or not loaded plugins had their preloader run.
+ /// Whether loaded plugins had their preloader run.
///
public bool PluginsPreInit { get; private set; }
///
- /// Whether or not plugins' types have been instantiated.
+ /// Whether plugins' types have been instantiated.
///
public bool PluginsInitialized { get; private set; }
///
- /// Whether or not plugins are fully loaded.
+ /// Whether plugins are fully loaded.
///
public bool PluginsLoaded { get; private set; }
@@ -966,7 +967,7 @@ public sealed class CsPackageManager : IDisposable
/// Packages with errors or cyclic dependencies. Element is error message. Null if empty.
/// Optional: Allows for a custom checks to be performed on each package.
/// Returns a bool indicating if the package is ready to load.
- /// Whether or not the process produces a usable list.
+ /// Whether the process produces a usable list.
private static bool OrderAndFilterPackagesByDependencies(
Dictionary> packages,
out IEnumerable readyToLoad,
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs
index dd61c0108..e01db5f4c 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs
@@ -7,13 +7,14 @@ using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
+using Barotrauma.LuaCs.Services;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
// ReSharper disable ConditionIsAlwaysTrueOrFalse
[assembly: InternalsVisibleTo("CompiledAssembly")]
-namespace Barotrauma;
+namespace Barotrauma.LuaCs;
///
/// AssemblyLoadContext to compile from syntax trees in memory and to load from disk/file. Provides dependency resolution.
@@ -31,11 +32,11 @@ public class MemoryFileAssemblyContextLoader : AssemblyLoadContext
// internal
private readonly Dictionary _dependencyResolvers = new(); // path-folder, resolver
protected bool IsResolving; //this is to avoid circular dependency lookup.
- private AssemblyManager _assemblyManager;
+ private IAssemblyManagementService _assemblyManager;
public bool IsTemplateMode { get; set; }
public bool IsDisposed { get; private set; }
- public MemoryFileAssemblyContextLoader(AssemblyManager assemblyManager) : base(isCollectible: true)
+ public MemoryFileAssemblyContextLoader(IAssemblyManagementService assemblyManager) : base(isCollectible: true)
{
this._assemblyManager = assemblyManager;
this.IsDisposed = false;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs
index 64bc66006..0e46032c1 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs
@@ -1,25 +1,26 @@
using System;
using System.ComponentModel;
using System.Xml.Serialization;
+using Barotrauma.LuaCs.Data;
namespace Barotrauma;
[Serializable]
-public sealed class RunConfig
+public sealed class RunConfig : IRunConfig
{
///
/// How should scripts be run on the server.
///
[XmlElement(ElementName = "Server")]
[DefaultValue("Standard")]
- public string Server;
+ public string Server { get; set; }
///
/// How should scripts be run on the client.
///
[XmlElement(ElementName = "Client")]
[DefaultValue("Standard")]
- public string Client;
+ public string Client { get; set; }
///
/// List of dependencies by either Steam Workshop ID or by Partial Inclusive Name (ie. "ModDep" will match a mod named "A ModDependency").
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs
similarity index 80%
rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs
rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs
index c7f582395..9d1b7c38f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs
@@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.CSharp;
// ReSharper disable EventNeverSubscribedTo.Global
// ReSharper disable InconsistentNaming
-namespace Barotrauma;
+namespace Barotrauma.LuaCs.Services;
/***
* Note: This class was written to be thread-safe in order to allow parallelization in loading in the future if the need
@@ -25,36 +25,14 @@ namespace Barotrauma;
/// Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlugin.
/// All plugins are loaded into their own AssemblyLoadContext along with their dependencies.
///
-public class AssemblyManager
+public class AssemblyManager : IAssemblyManagementService
{
#region ExternalAPI
-
- ///
- /// Called when an assembly is loaded.
- ///
+
public event Action OnAssemblyLoaded;
-
- ///
- /// Called when an assembly is marked for unloading, before unloading begins. You should use this to cleanup
- /// any references that you have to this assembly.
- ///
public event Action OnAssemblyUnloading;
-
- ///
- /// Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception.
- ///
public event Action OnException;
-
- ///
- /// For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unloaded.
- ///
public event Action OnACLUnload;
-
-
- ///
- /// [DEBUG ONLY]
- /// Returns a list of the current unloading ACLs.
- ///
public ImmutableList> StillUnloadingACLs
{
get
@@ -70,12 +48,6 @@ public class AssemblyManager
}
}
}
-
-
- // ReSharper disable once MemberCanBePrivate.Global
- ///
- /// Checks if there are any AssemblyLoadContexts still in the process of unloading.
- ///
public bool IsCurrentlyUnloading
{
get
@@ -95,21 +67,6 @@ public class AssemblyManager
}
}
}
-
- // Old API compatibility
- public IEnumerable GetSubTypesInLoadedAssemblies()
- {
- return GetSubTypesInLoadedAssemblies(false);
- }
-
-
- ///
- /// Allows iteration over all non-interface types in all loaded assemblies in the AsmMgr that are assignable to the given type (IsAssignableFrom).
- /// Warning: care should be used when using this method in hot paths as performance may be affected.
- ///
- /// The type to compare against
- /// Forces caches to clear and for the lists of types to be rebuilt.
- /// An Enumerator for matching types.
public IEnumerable GetSubTypesInLoadedAssemblies(bool rebuildList)
{
Type targetType = typeof(T);
@@ -165,14 +122,6 @@ public class AssemblyManager
OpsLockLoaded.ExitReadLock();
}
}
-
- ///
- /// Tries to get types assignable to type from the ACL given the Guid.
- ///
- ///
- ///
- ///
- ///
public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types)
{
Type targetType = typeof(T);
@@ -188,13 +137,6 @@ public class AssemblyManager
types = null;
return false;
}
-
- ///
- /// Tries to get types from the ACL given the Guid.
- ///
- ///
- ///
- ///
public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types)
{
if (TryGetACL(id, out var acl))
@@ -206,14 +148,6 @@ public class AssemblyManager
types = null;
return false;
}
-
-
- ///
- /// Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's names match the string.
- /// Note: Will return the by-reference equivalent type if the type name is prefixed with "out " or "ref ".
- ///
- /// The string name of the type to search for.
- /// An Enumerator for matching types. List will be empty if bad params are supplied.
public IEnumerable GetTypesByName(string typeName)
{
List types = new();
@@ -299,12 +233,6 @@ public class AssemblyManager
}
}
}
-
- ///
- /// Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr.
- /// Warning: High usage may result in performance issues.
- ///
- /// An Enumerator for iteration.
public IEnumerable GetAllTypesInLoadedAssemblies()
{
OpsLockLoaded.EnterReadLock();
@@ -325,13 +253,6 @@ public class AssemblyManager
OpsLockLoaded.ExitReadLock();
}
}
-
- ///
- /// Returns a list of all loaded ACLs.
- /// WARNING: References to these ACLs outside of the AssemblyManager should be kept in a WeakReference in order
- /// to avoid causing issues with unloading/disposal.
- ///
- ///
public IEnumerable GetAllLoadedACLs()
{
OpsLockLoaded.EnterReadLock();
@@ -358,36 +279,14 @@ public class AssemblyManager
#region InternalAPI
- ///
- /// [Unsafe] Warning: only for use in nested threading functions. Requires care to manage access.
- /// Does not make any guarantees about the state of the ACL after the list has been returned.
- ///
- ///
[MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.NoInlining)]
- internal ImmutableList UnsafeGetAllLoadedACLs()
+ ImmutableList IAssemblyManagementService.UnsafeGetAllLoadedACLs()
{
if (LoadedACLs.IsEmpty)
return ImmutableList.Empty;
return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList();
}
-
- ///
- /// Used by content package and plugin management to stop unloading of a given ACL until all plugins have gracefully closed.
- ///
public event System.Func IsReadyToUnloadACL;
-
- ///
- /// Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoader.
- /// A new ACL will be created if the Guid supplied is Guid.Empty.
- ///
- ///
- ///
- ///
- ///
- /// A non-unique name for later reference. Optional, set to null if unused.
- /// The guid of the assembly
- ///
- ///
public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName,
[NotNull] IEnumerable syntaxTree,
IEnumerable externalMetadataReferences,
@@ -440,14 +339,6 @@ public class AssemblyManager
return state;
}
-
- ///
- /// Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for any assemblies loaded in it.
- /// These ACLs are intended to be used to host Assemblies for information only and not for code execution.
- /// WARNING: This process is irreversible.
- ///
- /// Guid of the ACL.
- /// Whether or not an ACL was found with the given ID.
public bool SetACLToTemplateMode(Guid guid)
{
if (!TryGetACL(guid, out var acl))
@@ -455,16 +346,6 @@ public class AssemblyManager
acl.Acl.IsTemplateMode = true;
return true;
}
-
- ///
- /// Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid.
- /// If the supplied Guid is Empty, then a new ACl will be created and the Guid will be assigned to it.
- ///
- /// List of assemblies to try and load.
- /// A non-unique name for later reference. Optional.
- /// Guid of the ACL or Empty if none specified. Guid of ACL will be assigned to this var.
- /// Operation success messages.
- ///
public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable filePaths,
string friendlyName, ref Guid id)
{
@@ -507,9 +388,7 @@ public class AssemblyManager
return AssemblyLoadingSuccessState.ACLLoadFailure;
}
-
-
- [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Synchronized)]
+ [MethodImpl(MethodImplOptions.NoInlining)]
public bool TryBeginDispose()
{
OpsLockLoaded.EnterWriteLock();
@@ -563,8 +442,6 @@ public class AssemblyManager
OpsLockLoaded.ExitWriteLock();
}
}
-
-
[MethodImpl(MethodImplOptions.NoInlining)]
public bool FinalizeDispose()
{
@@ -606,15 +483,7 @@ public class AssemblyManager
return isUnloaded;
}
-
- ///
- /// Tries to retrieve the LoadedACL with the given ID or null if none is found.
- /// WARNING: External references to this ACL with long lifespans should be kept in a WeakReference
- /// to avoid causing unloading/disposal issues.
- ///
- /// GUID of the ACL.
- /// The found ACL or null if none was found.
- /// Whether or not an ACL was found.
+
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryGetACL(Guid id, out LoadedACL acl)
{
@@ -865,6 +734,16 @@ public class AssemblyManager
}
#endregion
+
+ public void Dispose()
+ {
+ TryBeginDispose();
+ }
+
+ public void Reset()
+ {
+ TryBeginDispose();
+ }
}
public static class AssemblyExtensions
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs
new file mode 100644
index 000000000..1ba46fa9a
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+// ReSharper disable InconsistentNaming
+
+namespace Barotrauma.LuaCs.Services;
+
+
+public interface IAssemblyManagementService : IService
+{
+ #region Public API
+
+ ///
+ /// Called when an assembly is loaded.
+ ///
+ public event Action OnAssemblyLoaded;
+
+ ///
+ /// Called when an assembly is marked for unloading, before unloading begins. You should use this to cleanup
+ /// any references that you have to this assembly.
+ ///
+ public event Action OnAssemblyUnloading;
+
+ ///
+ /// Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception.
+ ///
+ public event Action OnException;
+
+ ///
+ /// For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unloaded.
+ ///
+ // ReSharper disable once InconsistentNaming
+ public event Action OnACLUnload;
+
+
+ ///
+ /// [DEBUG ONLY]
+ /// Returns a list of the current unloading ACLs.
+ ///
+ // ReSharper disable once InconsistentNaming
+ public ImmutableList> StillUnloadingACLs { get; }
+
+ // ReSharper disable once MemberCanBePrivate.Global
+ ///
+ /// Checks if there are any AssemblyLoadContexts still in the process of unloading.
+ ///
+ public bool IsCurrentlyUnloading { get; }
+
+ ///
+ /// Allows iteration over all non-interface types in all loaded assemblies in the AsmMgr that are assignable to the given type (IsAssignableFrom).
+ /// Warning: care should be used when using this method in hot paths as performance may be affected.
+ ///
+ /// The type to compare against
+ /// Forces caches to clear and for the lists of types to be rebuilt.
+ /// An Enumerator for matching types.
+ public IEnumerable GetSubTypesInLoadedAssemblies(bool rebuildList);
+
+ ///
+ /// Tries to get types assignable to type from the ACL given the Guid.
+ ///
+ ///
+ ///
+ ///
+ /// Operation success.
+ public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types);
+
+ ///
+ /// Tries to get types from the ACL given the Guid.
+ ///
+ ///
+ ///
+ ///
+ public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types);
+
+ ///
+ /// Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's names match the string.
+ /// Note: Will return the by-reference equivalent type if the type name is prefixed with "out " or "ref ".
+ ///
+ /// The string name of the type to search for.
+ /// An Enumerator for matching types. List will be empty if bad params are supplied.
+ public IEnumerable GetTypesByName(string typeName);
+
+ ///
+ /// Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr.
+ /// Warning: High usage may result in performance issues.
+ ///
+ /// An Enumerator for iteration.
+ public IEnumerable GetAllTypesInLoadedAssemblies();
+
+ ///
+ /// Returns a list of all loaded ACLs.
+ /// WARNING: References to these ACLs outside the AssemblyManager should be kept in a WeakReference in order
+ /// to avoid causing issues with unloading/disposal.
+ ///
+ ///
+ public IEnumerable GetAllLoadedACLs();
+
+ #endregion
+
+ #region InternalAPI
+ /*** Notes: Internal API uses the 'public' modifier because of the common and recommended use of publicized APIs
+ * by third-party add-ins.
+ */
+
+ ///
+ /// [Unsafe] Warning: only for use in nested threading functions. Requires care to manage access.
+ /// Does not make any guarantees about the state of the ACL after the list has been returned.
+ ///
+ ///
+ public ImmutableList UnsafeGetAllLoadedACLs();
+
+ ///
+ /// Used by content package and plugin management to stop unloading of a given ACL until all plugins have gracefully closed.
+ ///
+ public event System.Func IsReadyToUnloadACL;
+
+ ///
+ /// Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoader.
+ /// A new ACL will be created if the Guid supplied is Guid.Empty.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// A non-unique name for later reference. Optional, set to null if unused.
+ /// The guid of the assembly
+ ///
+ ///
+ public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName,
+ [NotNull] IEnumerable syntaxTree,
+ IEnumerable externalMetadataReferences,
+ [NotNull] CSharpCompilationOptions compilationOptions,
+ string friendlyName,
+ ref Guid id,
+ IEnumerable externFileAssemblyRefs = null);
+
+ ///
+ /// Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for any assemblies loaded in it.
+ /// These ACLs are intended to be used to host Assemblies for information only and not for code execution.
+ /// WARNING: This process is irreversible.
+ ///
+ /// Guid of the ACL.
+ /// Whether an ACL was found with the given ID.
+ public bool SetACLToTemplateMode(Guid guid);
+
+
+ ///
+ /// Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid.
+ /// If the supplied Guid is Empty, then a new ACl will be created and the Guid will be assigned to it.
+ ///
+ /// List of assemblies to try and load.
+ /// A non-unique name for later reference. Optional.
+ /// Guid of the ACL or Empty if none specified. Guid of ACL will be assigned to this var.
+ /// Operation success messages.
+ ///
+ public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable filePaths,
+ string friendlyName, ref Guid id);
+
+
+ ///
+ /// Tries to begin the disposal process of ACLs.
+ ///
+ /// Returns whether the unloading process could be initiated.
+ public bool TryBeginDispose();
+
+
+ ///
+ /// Returns whether unloading is completed and updates the styate of the unloading cache.
+ ///
+ ///
+ public bool FinalizeDispose();
+
+ ///
+ /// Tries to retrieve the LoadedACL with the given ID or null if none is found.
+ /// WARNING: External references to this ACL with long lifespans should be kept in a WeakReference
+ /// to avoid causing unloading/disposal issues.
+ ///
+ /// GUID of the ACL.
+ /// The found ACL or null if none was found.
+ /// Whether an ACL was found.
+ public bool TryGetACL(Guid id, out AssemblyManager.LoadedACL acl);
+
+ #endregion
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs
new file mode 100644
index 000000000..ac6deedd5
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Barotrauma.LuaCs.Configuration;
+using Barotrauma.LuaCs.Data;
+using Barotrauma.Networking;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface IConfigService : IService
+{
+ /*
+ * Resource Files.
+ */
+ bool TryAddConfigs(ImmutableArray configResources);
+ bool TryAddConfigsProfiles(ImmutableArray configProfileResources);
+ void RemoveConfigs(ImmutableArray configResources);
+ void RemoveConfigsProfiles(ImmutableArray configProfilesResources);
+
+
+ /*
+ * Already processed
+ */
+ bool TryAddConfigs(ImmutableArray configs);
+ bool TryAddConfigsProfiles(ImmutableArray configProfiles);
+ void RemoveConfigs(ImmutableArray configs);
+ void RemoveConfigsProfiles(ImmutableArray configProfiles);
+
+ /*
+ * Immediate mode, does not have displayable functionality
+ */
+ IConfigEntry AddConfigEntry(ContentPackage package, string name,
+ T defaultValue,
+ NetSync syncMode = NetSync.None,
+ ClientPermissions permissions = ClientPermissions.None,
+ Func valueChangePredicate = null,
+ Action> onValueChanged = null) where T : IConvertible, IEquatable;
+
+ IConfigList AddConfigList(ContentPackage package, string name,
+ int defaultIndex, IReadOnlyList values,
+ NetSync syncMode = NetSync.None,
+ ClientPermissions permissions = ClientPermissions.None,
+ Func valueChangePredicate = null,
+ Action onValueChanged = null);
+
+ IReadOnlyDictionary GetConfigsForPackage(ContentPackage package);
+ IReadOnlyDictionary GetConfigsForPackage(string packageName);
+ IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs();
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs
new file mode 100644
index 000000000..3c0caef4e
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services;
+
+public interface IEventService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs
new file mode 100644
index 000000000..5428ed704
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services;
+
+public interface IHookManagementService : IService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs
new file mode 100644
index 000000000..a93559084
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs
@@ -0,0 +1,8 @@
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface ILegacyConfigService : IService
+{
+ bool TryBuildModConfigFromLegacy(ContentPackage package, out IModConfigInfo configInfo);
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs
new file mode 100644
index 000000000..045d3e986
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Globalization;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface ILocalizationService : IService
+{
+ IReadOnlyCollection GetLoadedLocales();
+ void Remove(ImmutableArray localizations);
+ bool TrySetCurrentCulture(CultureInfo culture);
+ bool TrySetCurrentCulture(string cultureName);
+ bool TryLoadLocalizations(ImmutableArray localizationResources);
+ string GetLocalizedString(string key, string fallback);
+ string GetLocalizedString(string key, CultureInfo targetCulture);
+ bool TryRegisterLocalizationResolver(CultureInfo targetCulture, Func factoryResolver);
+ bool ReplaceSymbols(string text, string symbolExpr);
+ bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo);
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs
new file mode 100644
index 000000000..eb457b7d3
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs
@@ -0,0 +1,25 @@
+using System;
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma.LuaCs.Services;
+
+///
+/// Provides console and debug logging services
+///
+public interface ILoggerService : IService
+{
+ void HandleException(Exception exception, string prefix = null);
+ void LogError(string message);
+ void LogWarning(string message);
+ void LogMessage(string message, Color? serverColor = null, Color? clientColor = null);
+ void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage);
+
+ #region DebugBuilds
+
+ void LogDebug(string message, Color? color = null);
+ void LogDebugWarning(string message);
+ void LogDebugError(string message);
+
+ #endregion
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs
new file mode 100644
index 000000000..6d4a8e673
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Reflection;
+using Barotrauma.LuaCs.Data;
+using MoonSharp.Interpreter;
+using MoonSharp.Interpreter.Interop;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface ILuaScriptService : IService
+{
+ #region Script_File_Collector
+
+ ///
+ /// Adds the script files to the runner but does not execute them.
+ ///
+ ///
+ ///
+ bool TryAddScriptFiles(ImmutableArray luaResource);
+ ///
+ /// Removes the specific resources from the script runner. Important: Does not stop the
+ /// execution of any code related to the files nor guarantee cleanup of resources!
+ ///
+ ///
+ void RemoveScriptFiles(ImmutableArray luaResource);
+
+ ///
+ /// Executes loaded script files on the management service.
+ ///
+ ///
+ ///
+ ///
+ bool TryExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false);
+ ImmutableArray GetScriptResources();
+
+ #endregion
+}
+
+public interface ILuaScriptManagementService : IService
+{
+ #region Script_File_Execution
+
+ bool TryExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false);
+ bool TryExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false);
+ bool TryExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false);
+
+ #endregion
+
+ #region Type_Registration
+
+ IUserDataDescriptor RegisterType(Type type);
+ IUserDataDescriptor RegisterType(string typeName);
+ IUserDataDescriptor RegisterGenericType(Type type);
+ IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs);
+ void UnregisterType(Type type);
+ void UnregisterType(string typeName);
+ void UnregisterAllTypes();
+
+ #endregion
+
+ #region Type_Checks_&Utilities
+
+ bool IsRegistered(Type type);
+ bool IsTargetType(object obj, string typeName);
+ string TypeOf(object obj);
+ object CreateStatic(string typeName);
+ object CreateEnumTable(string typeName);
+ FieldInfo FindFieldRecursively(Type type, string fieldName);
+ void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName);
+ MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null);
+ void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null);
+ PropertyInfo FindPropertyRecursively(Type type, string propertyName);
+ void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName);
+ void AddMethod(IUserDataDescriptor descriptor, string methodName, object function);
+ void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value);
+ void RemoveMember(IUserDataDescriptor descriptor, string memberName);
+ bool HasMember(object obj, string memberName);
+ DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor);
+ DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType);
+
+ #endregion
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs
new file mode 100644
index 000000000..3b2c7269b
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs
@@ -0,0 +1,23 @@
+using System;
+using Barotrauma.LuaCs.Data;
+using Barotrauma.LuaCs.Networking;
+using Barotrauma.Networking;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface INetworkingService : IService
+{
+ bool IsActive { get; }
+ bool IsSynchronized { get; }
+ bool TryRegisterVar(INetVar var, NetSync mode, ClientPermissions permissions);
+ void UnregisterVar(Guid varId);
+ bool SendEvent(Guid varId);
+ void SendMessageGlobal(string id, string message);
+ void Synchronize();
+
+ #region LegacyAPI
+
+ bool RestrictMessageSize { get; set; }
+
+ #endregion
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs
new file mode 100644
index 000000000..3ef92394a
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Globalization;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface IPackageManagementService : IService
+{
+ void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages,
+ bool executeImmediately = false,
+ bool errorOnFailures = false,
+ bool errorOnExistingPackageFound = false);
+ void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false);
+ void UnloadPackages(bool errorOnFailures = true);
+ bool IsPackageLoaded(ContentPackage package);
+ bool CheckDependencyLoaded(IPackageDependencyInfo info);
+ bool CheckDependenciesLoaded(IEnumerable infos, out IReadOnlyList missingPackages);
+ bool CheckEnvironmentSupported(IPlatformInfo platform);
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs
new file mode 100644
index 000000000..1411cb313
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface IPackageService : IService,
+ // These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member
+ IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo
+{
+ ContentPackage Package { get; }
+ IModConfigInfo ModConfigInfo { get; }
+ ///
+ /// Try to load the XML config and resources information from the given package.
+ ///
+ ///
+ /// Whether the package was parsed without errors and any information was found. Will return false for purely vanilla packages.
+ bool TryLoadResourcesInfo([NotNull]ContentPackage package);
+ ///
+ /// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load.
+ /// Will sort by load priority unless overriden/bypassed.
+ ///
+ ///
+ ///
+ /// Whether loading is successful. Returns true on an empty list.
+ void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false);
+ void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo);
+ void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo);
+#if CLIENT
+ void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo);
+#endif
+ void LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo);
+}
+
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs
new file mode 100644
index 000000000..b5c505c83
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs
@@ -0,0 +1,7 @@
+namespace Barotrauma.LuaCs.Services;
+
+public interface IPluginManagementService : IService
+{
+ bool IsAssemblyLoadedGlobal(string friendlyName);
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs
new file mode 100644
index 000000000..cbdd9ba74
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Reflection;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface IPluginService : IService
+{
+ bool IsAssemblyLoaded(string friendlyName);
+ ///
+ /// Loads the assemblies for the given information
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool TryLoadAndInstanceTypes(IEnumerable assemblyResourcesInfo, bool injectServices, out ImmutableArray typeInstances) where T : class, IAssemblyPlugin;
+ ImmutableArray GetLoadedPluginTypesInPackage() where T : class, IAssemblyPlugin;
+ ///
+ /// Advances the loading/execution state of the plugin. IMPORTANT: You cannot set the execution state of plugins
+ /// to 'Disposed'. You must instead call the 'DisposePlugins' method.
+ ///
+ ///
+ ///
+ bool AdvancePluginStates(PluginRunState newState);
+
+ ///
+ /// Disposes of all running plugins hosted by the service and releases their references to allow unloading.
+ ///
+ /// Success of the operation. Returns false if any plugin threw errors during disposal.
+ bool DisposePlugins();
+
+ ///
+ /// Gets the current plugin execution state.
+ ///
+ ///
+ PluginRunState GetPluginRunState();
+}
+
+public enum PluginRunState
+{
+ Instanced=0,
+ PreInitialization=1,
+ Initialized=2,
+ LoadingCompleted=3,
+ Disposed=4
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs
new file mode 100644
index 000000000..bd7595143
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Barotrauma.LuaCs.Services;
+
+///
+/// Base interface inherited by all services
+///
+public interface IService : IDisposable
+{
+ ///
+ /// Returns the service to its original state (post-instantiation).
+ /// Allows a service instance to be reused without disposing of the instance.
+ ///
+ void Reset();
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs
new file mode 100644
index 000000000..0304b7da4
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using LightInject;
+
+namespace Barotrauma.LuaCs.Services;
+
+///
+/// Provides instancing and management of IServices.
+///
+public interface IServicesProvider
+{
+ #region Type_Registration
+
+ ///
+ /// Registers a type as a service for a given interface.
+ ///
+ ///
+ ///
+ ///
+ ///
+ void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new();
+
+ ///
+ /// Registers a type as a service for a given interface that can be requested by name.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new();
+
+ ///
+ /// Called whenever a new service type for a given interface is implemented.
+ /// Args[0]: Interface type
+ /// Args[1]: Implementing type
+ ///
+ event System.Action OnServiceRegistered;
+
+ ///
+ /// Runs compilation of registered services.
+ ///
+ public void Compile();
+
+ #endregion
+
+ #region Services_Instancing_Injection
+
+ ///
+ /// Injects services into the properties of already instanced objects.
+ ///
+ ///
+ ///
+ void InjectServices(T inst) where T : class;
+
+ ///
+ /// Tries to get a service for the given interface, returns success/failure.
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool TryGetService(out IService service) where TSvcInterface : class, IService;
+
+ ///
+ /// Tries to get a service for the given name and interface, returns success/failure.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool TryGetService(string name, out IService service) where TSvcInterface : class, IService;
+
+ ///
+ /// Called whenever a new service is created/instanced.
+ /// Args[0]: The interface type of the service.
+ /// Args[1]: The instance of the service.
+ ///
+ event System.Action OnServiceInstanced;
+
+ #endregion
+
+ #region ActiveServices
+
+ ///
+ /// Returns all services for the given interface.
+ ///
+ ///
+ ///
+ ImmutableArray GetAllServices() where TSvc : class, IService;
+
+ #endregion
+
+ // Notes: Left public due to the common use of Publicizers
+ #region Internal_Use
+
+ ///
+ /// Notes: Internal use only if hosted by LuaCsForBarotrauma. Disposes of all services and resets DI container. Warning: unable to dispose of services held by other objects.
+ ///
+ void DisposeAndReset();
+
+ #endregion
+}
+
+public enum ServiceLifetime
+{
+ Transient, Singleton, PerThread, Invalid, Custom
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs
new file mode 100644
index 000000000..608a5417c
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs
@@ -0,0 +1,42 @@
+using System.Collections.Immutable;
+using System.Xml.Linq;
+
+namespace Barotrauma.LuaCs.Services;
+
+public interface IStorageService : IService
+{
+ #region LocalGameData
+
+ bool TryLoadLocalXml(ContentPackage package, string localFilePath, out XDocument document);
+ bool TryLoadLocalBinary(ContentPackage package, string localFilePath, out byte[] bytes);
+ bool TryLoadLocalText(ContentPackage package, string localFilePath, out string text);
+ bool FileExistsInLocalData(ContentPackage package, string localFilePath);
+
+ #endregion
+
+ #region ContentPackageData
+ bool TryLoadPackageXml(ContentPackage package, string localFilePath, out XDocument document);
+ bool TryLoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes);
+ bool TryLoadPackageText(ContentPackage package, string localFilePath, out string text);
+
+ ImmutableArray TryLoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray document);
+ ImmutableArray TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray bytes);
+ ImmutableArray TryLoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray text);
+
+ bool FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively, out ImmutableArray localFilePaths);
+ bool FileExistsInPackage(ContentPackage package, string localFilePath);
+
+ #endregion
+
+ #region AbsolutePaths
+
+ bool TryLoadXml(string filePath, out XDocument document);
+ bool TrySaveXml(string filePath, in XDocument document);
+ bool TryLoadBinary(string filePath, out byte[] bytes);
+ bool TrySaveBinary(string filePath, in byte[] bytes);
+ bool TryLoadText(string filePath, out string text);
+ bool TrySaveText(string filePath, string text);
+ bool FileExists(string filePath);
+
+ #endregion
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs
new file mode 100644
index 000000000..25fb27542
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs
@@ -0,0 +1,149 @@
+using System;
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+using MoonSharp.Interpreter;
+
+namespace Barotrauma.LuaCs.Services;
+
+public partial class LoggerService : ILoggerService
+{
+ public bool HideUserNames = true;
+
+#if SERVER
+ private const string LogPrefix = "SV";
+ private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system.
+ private const int NetMaxMessages = 60;
+
+ // This is used so it's possible to call logging functions inside the serverLog
+ // hook without creating an infinite loop
+ private bool _lockLog = false;
+#else
+ private const string LogPrefix = "CL";
+#endif
+
+ public void HandleException(Exception exception, string prefix = null)
+ {
+ string errorString = "";
+ switch (exception)
+ {
+ case NetRuntimeException netRuntimeException:
+ if (netRuntimeException.DecoratedMessage == null)
+ {
+ errorString = $"{prefix ?? ""}{netRuntimeException.ToString()}";
+ }
+ else
+ {
+ // FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace...
+ errorString = $"{prefix ?? ""}{netRuntimeException.DecoratedMessage}: {netRuntimeException}";
+ }
+ break;
+ case InterpreterException interpreterException:
+ if (interpreterException.DecoratedMessage == null)
+ {
+ errorString = $"{prefix ?? ""}{interpreterException.ToString()}";
+ }
+ else
+ {
+ errorString = $"{prefix ?? ""}{interpreterException.DecoratedMessage}";
+ }
+ break;
+ default:
+ string s = exception.StackTrace != null ? exception.ToString() : $"{exception}\n{Environment.StackTrace}";
+ errorString = $"{prefix ?? ""}{s}";
+ break;
+ }
+
+ LogError(prefix + Environment.UserName + " " + errorString);
+ }
+
+ public void LogError(string message)
+ {
+ if (HideUserNames && !Environment.UserName.IsNullOrEmpty())
+ {
+ message = message.Replace(Environment.UserName, "USERNAME");
+ }
+
+ Log($"{message}", Color.Red, ServerLog.MessageType.Error);
+ }
+
+ public void LogWarning(string message)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void LogMessage(string message, Color? serverColor = null, Color? clientColor = null)
+ {
+ serverColor ??= Color.MediumPurple;
+ clientColor ??= Color.Purple;
+
+#if SERVER
+ Log(message, serverColor);
+#else
+ Log(message, clientColor);
+#endif
+ }
+
+ public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage)
+ {
+ DebugConsole.NewMessage(message, color);
+
+#if SERVER
+ void BroadcastMessage(string m)
+ {
+ foreach (var client in GameMain.Server.ConnectedClients)
+ {
+ ChatMessage consoleMessage = ChatMessage.Create("", m, ChatMessageType.Console, null, textColor: color);
+ GameMain.Server.SendDirectChatMessage(consoleMessage, client);
+
+ if (!GameMain.Server.ServerSettings.SaveServerLogs || !client.HasPermission(ClientPermissions.ServerLog))
+ {
+ continue;
+ }
+
+ ChatMessage logMessage = ChatMessage.Create(messageType.ToString(), "[LuaCs] " + m, ChatMessageType.ServerLog, null);
+ GameMain.Server.SendDirectChatMessage(logMessage, client);
+ }
+ }
+
+ if (GameMain.Server != null)
+ {
+ if (GameMain.Server.ServerSettings.SaveServerLogs)
+ {
+ string logMessage = "[LuaCs] " + message;
+ GameMain.Server.ServerSettings.ServerLog.WriteLine(logMessage, messageType, false);
+
+ if (!_lockLog)
+ {
+ _lockLog = true;
+ GameMain.LuaCs?.Hook?.Call("serverLog", logMessage, messageType);
+ _lockLog = false;
+ }
+ }
+
+ for (int i = 0; i < message.Length; i += NetMaxLength)
+ {
+ string subStr = message.Substring(i, Math.Min(1024, message.Length - i));
+ BroadcastMessage(subStr);
+ }
+ }
+#endif
+ }
+
+ public void LogDebug(string message, Color? color = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void LogDebugWarning(string message)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void LogDebugError(string message)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose() { }
+ public void Reset() { }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs
new file mode 100644
index 000000000..b1c30f23e
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services;
+
+public class LuaScriptService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs
new file mode 100644
index 000000000..2f5d5eb6f
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services;
+
+public class PackageManagementService : IPackageManagementService, IPluginManagementService
+{
+ private readonly Func _contentPackageServiceFactory;
+ private readonly Lazy _assemblyManagementService;
+
+ public PackageManagementService(
+ Func getPackageService,
+ Lazy assemblyManagementService)
+ {
+ this._contentPackageServiceFactory = getPackageService;
+ this._assemblyManagementService = assemblyManagementService;
+ }
+
+
+ public void Dispose()
+ {
+ // TODO release managed resources here
+ }
+
+ public void Reset()
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsAssemblyLoadedGlobal(string friendlyName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages, bool executeImmediately = false, bool errorOnFailures = false,
+ bool errorOnExistingPackageFound = false)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void UnloadPackages(bool errorOnFailures = true)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsPackageLoaded(ContentPackage package)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool CheckDependencyLoaded(IPackageDependencyInfo info)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool CheckDependenciesLoaded(IEnumerable infos, out IReadOnlyList missingPackages)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool CheckEnvironmentSupported(IPlatformInfo platform)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs
new file mode 100644
index 000000000..97e1f0cfe
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs
@@ -0,0 +1,627 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using Barotrauma.Extensions;
+using Barotrauma.LuaCs.Data;
+using Barotrauma.LuaCs.Services.Processing;
+
+namespace Barotrauma.LuaCs.Services;
+
+public partial class PackageService : IPackageService
+{
+ private readonly ReaderWriterLockSlim _operationsUsageLock = new();
+ // only stops race conditions for pointer access
+
+
+ // mod config / package scanners/parsers
+ private readonly Lazy _modConfigConverterService;
+ private readonly Lazy _legacyConfigService;
+ private readonly Lazy _luaScriptService;
+ private readonly Lazy _localizationService;
+ private readonly Lazy _pluginService;
+ private readonly Lazy _configService;
+ private readonly IPackageManagementService _packageManagementService;
+ private readonly IStorageService _storageService;
+ private readonly ILoggerService _loggerService;
+
+ // .ctor in server source and client source
+
+ // state monitors
+ private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed;
+ private int _loadingOperationsRunning;
+
+ public bool ConfigsLoaded
+ {
+ get => GetThreadSafeBool(ref _configsLoaded);
+ private set => SetThreadSafeBool(ref _configsLoaded, value);
+ }
+ public bool LocalizationsLoaded
+ {
+ get => GetThreadSafeBool(ref _localizationsLoaded);
+ private set => SetThreadSafeBool(ref _localizationsLoaded, value);
+ }
+ public bool LuaScriptsLoaded
+ {
+ get => GetThreadSafeBool(ref _luaScriptsLoaded);
+ private set => SetThreadSafeBool(ref _luaScriptsLoaded, value);
+ }
+ public bool PluginsLoaded
+ {
+ get => GetThreadSafeBool(ref _pluginsLoaded);
+ private set => SetThreadSafeBool(ref _pluginsLoaded, value);
+ }
+ public bool IsDisposed
+ {
+ get => GetThreadSafeBool(ref _isDisposed);
+ private set => SetThreadSafeBool(ref _isDisposed, value);
+ }
+
+ private bool LoadingOperationsRunning
+ {
+ get => Interlocked.CompareExchange(ref _loadingOperationsRunning, 0, 0) > 0;
+ set // we use the set as our inc/decr
+ {
+ if (value)
+ {
+ Interlocked.Add(ref _loadingOperationsRunning, 1);
+ }
+ else
+ {
+ Interlocked.Add(ref _loadingOperationsRunning, -1);
+ }
+ }
+ }
+
+ #region Member: ContentPackage
+
+ private readonly ReaderWriterLockSlim _packageAccessLock = new();
+ private ContentPackage _package;
+ public ContentPackage Package
+ {
+ get
+ {
+ _packageAccessLock.EnterReadLock();
+ try
+ {
+ return _package;
+ }
+ finally
+ {
+ _packageAccessLock.ExitReadLock();
+ }
+ }
+ private set
+ {
+ _packageAccessLock.EnterWriteLock();
+ try
+ {
+ _package = value;
+ }
+ finally
+ {
+ _packageAccessLock.ExitWriteLock();
+ }
+ }
+ }
+
+ #endregion
+
+ #region DataContracts
+
+ #region Member: ModConfigInfo
+
+ private readonly ReaderWriterLockSlim _modConfigUsageLock = new();
+ private IModConfigInfo _modConfigInfo;
+ public IModConfigInfo ModConfigInfo
+ {
+ get
+ {
+ _modConfigUsageLock.EnterReadLock();
+ try
+ {
+ return _modConfigInfo;
+ }
+ finally
+ {
+ _modConfigUsageLock.ExitReadLock();
+ }
+ }
+ private set
+ {
+ _modConfigUsageLock.EnterWriteLock();
+ try
+ {
+ _modConfigInfo = value;
+ }
+ finally
+ {
+ _modConfigUsageLock.ExitWriteLock();
+ }
+ }
+ }
+
+ #endregion
+
+ public ImmutableArray SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray.Empty;
+ public ImmutableArray Assemblies => ModConfigInfo?.Assemblies ?? ImmutableArray.Empty;
+ public ImmutableArray Localizations => ModConfigInfo?.Localizations ?? ImmutableArray.Empty;
+ public ImmutableArray LuaScripts => ModConfigInfo?.LuaScripts ?? ImmutableArray.Empty;
+ public ImmutableArray Configs => ModConfigInfo?.Configs ?? ImmutableArray.Empty;
+ public ImmutableArray ConfigProfiles => ModConfigInfo?.ConfigProfiles ?? ImmutableArray.Empty;
+
+ #endregion
+
+ #region PublicAPI
+
+ public bool TryLoadResourcesInfo(ContentPackage package)
+ {
+ _operationsUsageLock.EnterWriteLock();
+ LoadingOperationsRunning = true;
+ try
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException($"This package service instance is disposed!");
+ }
+
+ // try loading the ModConfig.xml. If it fails, use the Legacy loader to try and construct one from the package structure.
+ if (_storageService.TryLoadPackageXml(package, "ModConfig.xml", out var configXml)
+ && configXml.Root is not null)
+ {
+ if (_modConfigConverterService.Value.TryParseResource(configXml.Root, out IModConfigInfo configInfo))
+ {
+ ModConfigInfo = configInfo;
+ }
+ else
+ {
+ _loggerService.LogError(
+ $"Failed to parse ModConfig.xml for package {package.Name}, package mod content not loaded.");
+ return false;
+ }
+ }
+ else if (_legacyConfigService.Value.TryBuildModConfigFromLegacy(package, out var legacyConfig))
+ {
+ ModConfigInfo = legacyConfig;
+ }
+ else
+ {
+ // vanilla mod or broken
+ return false;
+ }
+
+ return true;
+ }
+ finally
+ {
+ LoadingOperationsRunning = false;
+ _operationsUsageLock.ExitWriteLock();
+ }
+ }
+
+ public void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false)
+ {
+ _operationsUsageLock.EnterReadLock();
+ LoadingOperationsRunning = true;
+ try
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException($"This package service instance is disposed!");
+ }
+
+ SanitationChecksCore(assembliesInfo, "assemblies", nameof(LoadPlugins));
+ SanitationChecksEnumerable(assembliesInfo.Assemblies, "assemblies", nameof(LoadPlugins));
+
+#if DEBUG
+ assembliesInfo.Assemblies.ForEach(ari =>
+ {
+ if (!this.Assemblies.Contains(ari))
+ {
+ throw new ArgumentException(
+ $"Package Service: tried to load the assembly resource {ari.InternalName} for package {this.Package.Name} but it is not in the list for this package.");
+ }
+ });
+#endif
+
+ // Order these assemblies by internal dependencies
+ ImmutableArray resources;
+ if (ignoreDependencySorting)
+ {
+ resources = assembliesInfo.Assemblies;
+ }
+ else // sort by load order
+ {
+ resources = assembliesInfo.Assemblies
+ .OrderByDescending(a => a.LoadPriority)
+ .ToImmutableArray();
+ }
+
+ // Try loading them, throw on failure.
+ if (!_pluginService.Value.TryLoadAndInstanceTypes(resources, true, out var instancedTypes))
+ {
+ throw new TypeLoadException($"PackageService: unable to load assemblies for package {this.Package.Name}! Aborting loading!");
+ }
+
+ PluginsLoaded = true;
+ }
+ finally
+ {
+ LoadingOperationsRunning = false;
+ _operationsUsageLock.ExitReadLock();
+ }
+ }
+
+ public void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo)
+ {
+ _operationsUsageLock.EnterReadLock();
+ LoadingOperationsRunning = true;
+ try
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException($"This package service instance is disposed!");
+ }
+
+ SanitationChecksCore(localizationsInfo, "localizations", nameof(LoadLocalizations));
+ SanitationChecksEnumerable(localizationsInfo.Localizations, "localizations", nameof(LoadLocalizations));
+
+#if DEBUG
+ localizationsInfo.Localizations.ForEach(ri =>
+ {
+ if (!this.Localizations.Contains(ri))
+ {
+ throw new ArgumentException(
+ $"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package.");
+ }
+ });
+#endif
+
+ if (!_localizationService.Value.TryLoadLocalizations(localizationsInfo.Localizations))
+ {
+ throw new FileLoadException($"Package Service: unable to load localizations for package {this.Package.Name}! Aborting!");
+ }
+
+ LocalizationsLoaded = true;
+ }
+ finally
+ {
+ LoadingOperationsRunning = false;
+ _operationsUsageLock.ExitReadLock();
+ }
+ }
+
+ public void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo)
+ {
+ _operationsUsageLock.EnterReadLock();
+ LoadingOperationsRunning = true;
+ try
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException($"This package service instance is disposed!");
+ }
+
+ SanitationChecksCore(luaScriptsInfo, "luaScripts", nameof(AddLuaScripts));
+ SanitationChecksEnumerable(luaScriptsInfo.LuaScripts, "luaScripts", nameof(AddLuaScripts));
+
+#if DEBUG
+ luaScriptsInfo.LuaScripts.ForEach(ri =>
+ {
+ if (!this.LuaScripts.Contains(ri))
+ {
+ throw new ArgumentException(
+ $"Package Service: tried to load the lua script resource for package {this.Package.Name} but it is not in the list for this package.");
+ }
+ });
+#endif
+
+ if (!_luaScriptService.Value.TryAddScriptFiles(luaScriptsInfo.LuaScripts))
+ {
+ throw new ArgumentException(
+ $"Package Service: unable to add lua files for package {this.Package.Name}! Aborting!");
+ }
+
+ LuaScriptsLoaded = true;
+ }
+ finally
+ {
+ LoadingOperationsRunning = false;
+ _operationsUsageLock.ExitReadLock();
+ }
+ }
+
+ public void LoadConfig(
+ [NotNull]IConfigsResourcesInfo configsResourcesInfo,
+ [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo)
+ {
+ _operationsUsageLock.EnterReadLock();
+ LoadingOperationsRunning = true;
+ try
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException($"This package service instance is disposed!");
+ }
+
+ SanitationChecksCore(configsResourcesInfo, "config", nameof(LoadConfig));
+ SanitationChecksCore(configProfilesResourcesInfo, "config profiles", nameof(LoadConfig));
+ SanitationChecksEnumerable(configsResourcesInfo.Configs, "config", nameof(LoadConfig));
+ SanitationChecksEnumerable(configProfilesResourcesInfo.ConfigProfiles, "config profiles", nameof(LoadConfig));
+
+#if DEBUG
+ configsResourcesInfo.Configs.ForEach(ri =>
+ {
+ if (!this.Configs.Contains(ri))
+ {
+ throw new ArgumentException(
+ $"Package Service: tried to load the configs resource for package {this.Package.Name} but it is not in the list for this package.");
+ }
+ });
+
+ configProfilesResourcesInfo.ConfigProfiles.ForEach(ri =>
+ {
+ if (!this.ConfigProfiles.Contains(ri))
+ {
+ throw new ArgumentException(
+ $"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package.");
+ }
+ });
+#endif
+
+ if (!_configService.Value.TryAddConfigs(configsResourcesInfo.Configs))
+ {
+ throw new ArgumentException(
+ $"Package Service: unable to add configs for package {this.Package.Name}! Aborting!");
+ }
+
+ if (!_configService.Value.TryAddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles))
+ {
+ throw new ArgumentException(
+ $"Package Service: unable to add configs profiles for package {this.Package.Name}! Aborting!");
+ }
+ ConfigsLoaded = true;
+ }
+ finally
+ {
+ LoadingOperationsRunning = false;
+ _operationsUsageLock.ExitReadLock();
+ }
+ }
+
+ public void Dispose()
+ {
+ /*
+ * Notes: we need to unload this package from services in the order that the services are dependent on each other.
+ * Unloading Order: Lua Scripts > Assemblies > Config Profiles > Configs > Styles > Localizations
+ */
+ _operationsUsageLock.EnterWriteLock();
+ try
+ {
+ if (this.Package is null)
+ {
+ _loggerService.LogError(
+ $"Package Service: cannot Dispose of service as ContentPackage and info is not set!");
+ return;
+ }
+
+ if (this.ModConfigInfo is null)
+ {
+ _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!");
+ return;
+ }
+
+ /*
+ * To be graceful, we want to ensure that any async calls and other threads are allowed to be processed before we begin
+ * disposal to reduce friction with other thread operations, so we release the lock and periodically check it
+ * to see of other threads have finished operations before cleaning everything up.
+ */
+
+ IsDisposed = true; // set stop flag, callers should handle exception cases
+ Interlocked.MemoryBarrier(); //ensure cache states
+
+ DateTime timeoutLimit = DateTime.Now.AddSeconds(10);
+ while (LoadingOperationsRunning)
+ {
+ _operationsUsageLock.ExitWriteLock();
+ Thread.Sleep(1);
+ _operationsUsageLock.EnterWriteLock();
+ if (timeoutLimit < DateTime.Now)
+ {
+ _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing.");
+ break;
+ }
+ }
+
+ GC.SuppressFinalize(this);
+
+ _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts);
+ _pluginService.Value.DisposePlugins();
+ _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles);
+ _configService.Value.RemoveConfigs(this.Configs);
+#if CLIENT
+ _stylesService.Value.UnloadAllStyles();
+#endif
+ _localizationService.Value.Remove(this.Localizations);
+
+ ModConfigInfo = null;
+ Package = null;
+ }
+ catch
+ {
+ _loggerService.LogError($"Package Service: exception while running Dispose().");
+ throw;
+ }
+ finally
+ {
+ _operationsUsageLock.ExitWriteLock();
+ }
+ }
+
+ public void Reset()
+ {
+ _operationsUsageLock.EnterWriteLock();
+ try
+ {
+ if (this.Package is null)
+ {
+ _loggerService.LogError(
+ $"Package Service: cannot Dispose of service as ContentPackage and info is not set!");
+ return;
+ }
+
+ if (this.ModConfigInfo is null)
+ {
+ _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!");
+ return;
+ }
+
+ Interlocked.MemoryBarrier(); //ensure cache states
+
+ DateTime timeoutLimit = DateTime.Now.AddSeconds(10);
+ while (LoadingOperationsRunning)
+ {
+ _operationsUsageLock.ExitWriteLock();
+ Thread.Sleep(1);
+ _operationsUsageLock.EnterWriteLock();
+ if (timeoutLimit < DateTime.Now)
+ {
+ _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing.");
+ break;
+ }
+ }
+
+ if (LuaScriptsLoaded)
+ {
+ _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts);
+ LuaScriptsLoaded = false;
+ }
+
+ if (PluginsLoaded)
+ {
+ _pluginService.Value.DisposePlugins();
+ PluginsLoaded = false;
+ }
+
+ if (ConfigsLoaded)
+ {
+ _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles);
+ _configService.Value.RemoveConfigs(this.Configs);
+ ConfigsLoaded = false;
+ }
+
+ if (LocalizationsLoaded)
+ {
+ _localizationService.Value.Remove(this.Localizations);
+ LocalizationsLoaded = false;
+ }
+ }
+ finally
+ {
+ _operationsUsageLock.ExitWriteLock();
+ }
+ }
+
+ #endregion
+
+ #region INTERNAL
+
+ private void SanitationChecksCore(object o, string resTypeInfoName, string callerName)
+ {
+ if (o is null)
+ {
+ _loggerService.LogError($"Package Service: {resTypeInfoName} resources list is null!");
+ throw new NullReferenceException($"Package Service: {resTypeInfoName} resources list is null!");
+ }
+
+ if (this.Package is null)
+ {
+ _loggerService.LogError($"Package Service: package not set at {callerName}()!");
+ throw new NullReferenceException($"Package Service: package not set at {callerName}()!");
+ }
+ }
+
+ private void SanitationChecksEnumerable(ImmutableArray resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo
+ {
+ // Check if list is empty. Nothing more to do.
+ if (resourceInfos.IsDefaultOrEmpty)
+ return;
+
+ // Check if all resources in the list are registered to this package, throw if not.
+ foreach (var resourceInfo in resourceInfos)
+ {
+ // ownership checks
+ if (resourceInfo.OwnerPackage is null)
+ {
+ throw new ArgumentException($"Package Service: {resTypeInfoName} info for resource does not have a package name set! Run by {this.Package.Name}.");
+ }
+
+ if (resourceInfo.OwnerPackage != this.Package)
+ {
+ throw new ArgumentException(
+ $"Package Service: {resTypeInfoName} info does not belong to this package! Owned by {resourceInfo.OwnerPackage.Name} but is run by {this.Package.Name}.");
+ }
+
+ // Check if external dependencies are loaded and if current environment is supported, throw if not
+ if (resourceInfo.Dependencies.IsDefaultOrEmpty)
+ continue;
+
+ bool resourceMissing = false;
+
+ resourceInfo.Dependencies.ForEach(pdi =>
+ {
+ // for clarification: assemblies passed to the function should always be loaded.
+ // optional assemblies should be filtered out before the list is sent.
+ // left this as a reminder :)
+ /*if (pdi.Optional)
+ return;*/
+ if (!_packageManagementService.CheckDependencyLoaded(pdi))
+ {
+ resourceMissing = true;
+ _loggerService.LogError(
+ $"Package Service: the following dependency for package {resourceInfo.OwnerPackage.Name} is not loaded: {pdi.DependencyPackage?.Name ?? (pdi.PackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.PackageName)}");
+ }
+ });
+
+ if (!resourceMissing)
+ {
+ throw new FileLoadException($"Package Service: dependencies for package {resourceInfo.OwnerPackage.Name} are not loaded.");
+ }
+
+ // check runtime platform
+ if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo))
+ {
+ throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported on this platform.");
+ }
+
+ // check local culture
+ if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo))
+ {
+ throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported in this culture.");
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool GetThreadSafeBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) == 1;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void SetThreadSafeBool(ref int var, bool value)
+ {
+ if (value)
+ {
+ Interlocked.CompareExchange(ref var, 1, 0);
+ }
+ else
+ {
+ Interlocked.CompareExchange(ref var, 0, 1);
+ }
+ }
+
+ #endregion
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs
new file mode 100644
index 000000000..dbb150ff0
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Xml.Linq;
+using Barotrauma.LuaCs.Data;
+
+namespace Barotrauma.LuaCs.Services.Processing;
+
+#region TypeDef
+
+// ReSharper disable once TypeParameterCanBeVariant
+public interface IConverterService : IService
+{
+ bool TryParseResource(TSrc src, out TOut resources);
+ bool TryParseResources(IEnumerable sources, out List resources);
+}
+
+public interface IXmlResourceConverterService : IConverterService { }
+public interface IResourceToXmlConverterService : IConverterService { }
+
+#endregion
+
+///
+/// Parses Xml to produce loading metadata info for linked loadable files.
+///
+#region XmlToResourceInfoParsers
+
+public interface IXmlAssemblyResConverter : IXmlResourceConverterService { }
+public interface IXmlConfigResConverterService : IXmlResourceConverterService { }
+public interface IXmlLocalizationResConverterService : IXmlResourceConverterService { }
+
+#endregion
+
+///
+/// Parses Xml to produce ready-to-use info/data without any additional file/data loading.
+///
+#region XmlToInfoParsers
+public interface IXmlDependencyConverterService : IXmlResourceConverterService { }
+public interface IXmlModConfigConverterService : IXmlResourceConverterService { }
+///
+/// Parses legacy packages that make use of the RunConfig.xml structure to produce a ModConfig.
+///
+public interface IXmlLegacyModConfigConverterService : IXmlResourceConverterService { }
+
+#endregion
+
+
+#region ResToInfoParsers
+public interface ILocalizationResToInfoParser : IConverterService { }
+public interface IConfigResConverterService : IConverterService { }
+public interface IConfigProfileResConverterService : IConverterService { }
+
+#endregion
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs
new file mode 100644
index 000000000..2f4004e3b
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services.Safe;
+
+public interface ILuaConfigService : ILuaService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs
new file mode 100644
index 000000000..04f4893bc
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs
@@ -0,0 +1,39 @@
+using MoonSharp.Interpreter;
+
+namespace Barotrauma.LuaCs.Services.Safe;
+
+///
+/// Service for providing stateful functions and in-memory storage for lua functions
+///
+public interface ILuaDataService : ILuaService
+{
+ ///
+ /// Returns stored table for the given object if it exists.
+ ///
+ ///
+ ///
+ /// The table data or null if none exists.
+ Table GetObjectTable(object obj, string tableName);
+
+ ///
+ /// Returns stored table data under the given name if it exists.
+ ///
+ ///
+ /// The table data or null if none exists.
+ Table GetTable(string tableName);
+
+ ///
+ /// Returns stored table data for the given object or creates a new table if one doesn't exist.
+ ///
+ ///
+ ///
+ ///
+ Table GetOrCreateObjectTable(object obj, string tableName);
+
+ ///
+ /// Returns stored table data or creates a new table if one doesn't exist.
+ ///
+ ///
+ ///
+ Table GetOrCreateTable(string tableName);
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs
new file mode 100644
index 000000000..07f38202c
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services.Safe;
+
+public interface ILuaEventService : ILuaService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs
new file mode 100644
index 000000000..5df591472
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services.Safe;
+
+public interface ILuaNetworkingService : ILuaService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs
new file mode 100644
index 000000000..391d680a5
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services.Safe;
+
+public interface ILuaPackageManagementService : ILuaService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs
new file mode 100644
index 000000000..891224015
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services.Safe;
+
+public interface ILuaPackageService : ILuaService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs
new file mode 100644
index 000000000..0c6093319
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs
@@ -0,0 +1,6 @@
+namespace Barotrauma.LuaCs.Services.Safe;
+
+public interface ILuaService
+{
+
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs
new file mode 100644
index 000000000..b06b74940
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Immutable;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using LightInject;
+
+namespace Barotrauma.LuaCs.Services;
+
+
+public class ServicesProvider : IServicesProvider
+{
+ private ServiceContainer _serviceContainerInst;
+ private ServiceContainer ServiceContainer
+ {
+ get
+ {
+ // ReSharper disable once ConvertIfStatementToNullCoalescingExpression
+ if (_serviceContainerInst is null)
+ _serviceContainerInst = new ServiceContainer();
+ return _serviceContainerInst;
+ }
+ }
+
+ private readonly ReaderWriterLockSlim _serviceLock = new();
+
+ public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new()
+ {
+ if (lifetimeInstance is null)
+ {
+ switch (lifetime)
+ {
+ case ServiceLifetime.Singleton:
+ lifetimeInstance = new PerContainerLifetime();
+ break;
+ case ServiceLifetime.PerThread:
+ lifetimeInstance = new PerThreadLifetime();
+ break;
+ // treat these as transient
+ case ServiceLifetime.Transient:
+ case ServiceLifetime.Invalid:
+ case ServiceLifetime.Custom: // lifetime should not be null here
+ default:
+ lifetimeInstance = new PerRequestLifeTime();
+ break;
+ }
+ }
+
+ try
+ {
+ _serviceLock.EnterReadLock();
+ ServiceContainer.Register(lifetimeInstance);
+ ServiceContainer.Compile();
+ OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService));
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ public void RegisterServiceType(string name, ServiceLifetime lifetime,
+ ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new()
+ {
+ if (name.IsNullOrWhiteSpace())
+ {
+ throw new ArgumentNullException($"Tried to register a service of type {typeof(TService).Name} but the name provided is null or empty." );
+ }
+
+ if (lifetimeInstance is null)
+ {
+ switch (lifetime)
+ {
+ case ServiceLifetime.Singleton:
+ lifetimeInstance = new PerContainerLifetime();
+ break;
+ case ServiceLifetime.PerThread:
+ lifetimeInstance = new PerThreadLifetime();
+ break;
+ // treat these as transient
+ case ServiceLifetime.Transient:
+ case ServiceLifetime.Invalid:
+ case ServiceLifetime.Custom: // lifetime should not be null here
+ default:
+ lifetimeInstance = new PerRequestLifeTime();
+ break;
+ }
+ }
+
+ try
+ {
+ _serviceLock.EnterReadLock();
+ ServiceContainer.Register(name, lifetimeInstance);
+ ServiceContainer.Compile();
+ OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService));
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ public void Compile()
+ {
+ try
+ {
+ _serviceLock.EnterReadLock();
+ ServiceContainer?.Compile();
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ public event Action OnServiceRegistered;
+
+ public void InjectServices(T inst) where T : class
+ {
+ try
+ {
+ _serviceLock.EnterReadLock();
+ ServiceContainer.InjectProperties(inst);
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ public bool TryGetService(out IService service) where TSvcInterface : class, IService
+ {
+ try
+ {
+ _serviceLock.EnterReadLock();
+ service = ServiceContainer.TryGetInstance();
+ return service is not null;
+ }
+ catch
+ {
+ service = null;
+ return false;
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ public bool TryGetService(string name, out IService service) where TSvcInterface : class, IService
+ {
+ try
+ {
+ _serviceLock.EnterReadLock();
+ service = ServiceContainer.TryGetInstance(name);
+ return service is not null;
+ }
+ catch
+ {
+ service = null;
+ return false;
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ public event Action OnServiceInstanced;
+
+ public ImmutableArray GetAllServices() where TSvc : class, IService
+ {
+ try
+ {
+ _serviceLock.EnterReadLock();
+ return ServiceContainer.GetAllInstances().ToImmutableArray();
+ }
+ finally
+ {
+ _serviceLock.ExitReadLock();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.NoInlining)]
+ public void DisposeAndReset()
+ {
+ // Plugins should never be allowed to execute this.
+ if (Assembly.GetCallingAssembly() != Assembly.GetExecutingAssembly())
+ {
+ throw new MethodAccessException(
+ $"Assembly {Assembly.GetCallingAssembly().FullName} attempted to call DisposeAllServices().");
+ }
+
+ try
+ {
+ _serviceLock.EnterWriteLock();
+ _serviceContainerInst?.Dispose();
+ _serviceContainerInst = new ServiceContainer();
+ }
+ finally
+ {
+ _serviceLock.ExitWriteLock();
+ }
+ }
+}
+
+public class PerThreadLifetime : ILifetime
+{
+ private readonly ThreadLocal