using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using Barotrauma.LuaCs; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Processing; using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; using Barotrauma.Steam; using FluentResults; using ImpromptuInterface; using Microsoft.Toolkit.Diagnostics; namespace Barotrauma { internal delegate void LuaCsMessageLogger(string message); internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, IEventReloadAllPackages { public LuaCsSetup() { // == startup _servicesProvider = SetupServicesProvider(); if (!ValidateLuaCsContent()) { Logger.LogError($"{nameof(LuaCsSetup)}: ModConfigXml missing. Unable to continue."); throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua ModConfig.xml is missing. Unable to continue."); } _runStateMachine = SetupStateMachine(); SubscribeToLuaCsEvents(); SetRunState(RunState.LoadedNoExec); LoadLuaCsConfig(); } bool ValidateLuaCsContent() { #if DEBUG // TODO: we just wanna boot for now return true; #endif // check if /Content/ModConfig.xml exists // if not, try to copy missing files from the Local Mods folder // if not, try to copy missing files from the Workshop Mods folder // if that fails, throw an error and exit. throw new NotImplementedException(); } void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in EventService.Subscribe(this); EventService.Subscribe(this); } #region CONST_DEF #if SERVER public const bool IsServer = true; #else public const bool IsServer = false; #endif public const bool IsClient = !IsServer; #endregion #region Services_CVars /* * === Singleton Services */ private readonly IServicesProvider _servicesProvider; public PerformanceCounterService PerformanceCounter => _servicesProvider.GetService(); public ILoggerService Logger => _servicesProvider.GetService(); public IConfigService ConfigService => _servicesProvider.GetService(); public IPackageManagementService PackageManagementService => _servicesProvider.GetService(); public IPluginManagementService PluginManagementService => _servicesProvider.GetService(); public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.GetService(); public INetworkingService NetworkingService => _servicesProvider.GetService(); public IEventService EventService => _servicesProvider.GetService(); public LuaGame Game => _servicesProvider.GetService(); internal IStorageService StorageService => _servicesProvider.GetService(); /// /// Whether C# plugin code is enabled. /// internal IConfigEntry IsCsEnabled { get; private set; } /// /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. /// internal IConfigEntry TreatForcedModsAsNormal { get; private set; } /// /// Whether the lua script runner from Workshop package should be used over the in-built version. /// internal IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } /// /// Whether the popup error GUI should be hidden/suppressed. /// internal IConfigEntry DisableErrorGUIOverlay { get; private set; } /// /// Whether usernames are anonymized or show in logs. /// internal IConfigEntry HideUserNamesInLogs { get; private set; } /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// internal IConfigEntry LuaForBarotraumaSteamId { get; private set; } /// /// TODO: @evilfactory@users.noreply.github.com /// internal IConfigEntry RestrictMessageSize { get; private set; } /// /// The local save path for all local data storage for mods. /// internal IConfigEntry LocalDataSavePath { get; private set; } void LoadLuaCsConfig() { IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); } private IServicesProvider SetupServicesProvider() { var servicesProvider = new ServicesProvider(); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // TODO: INetworkingService servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); // service config data servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // gen IL servicesProvider.Compile(); return servicesProvider; } #endregion #region StateMachine private RunState _runState; /// /// The current run state of all services managed by LuaCs. /// public RunState CurrentRunState { get => _runState; private set => _runState = value; } private readonly StateMachine _runStateMachine; public void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages) { ProcessEnabledPackageChanges(new []{ package }.Concat(regularPackages).ToImmutableArray()); } public void OnReloadAllPackages() { if (CurrentRunState <= RunState.Unloaded) { return; } var state = CurrentRunState; SetRunState(RunState.Unloaded); SetRunState(state); } private void ProcessEnabledPackageChanges(ImmutableArray packages) { if (CurrentRunState < RunState.LoadedNoExec) { return; } var state = CurrentRunState; if (CurrentRunState > RunState.LoadedNoExec) { SetRunState(RunState.LoadedNoExec); } PackageManagementService.SyncLoadedPackagesList(packages); SetRunState(state); // restore } private void SetRunState(RunState targetRunState) { if (CurrentRunState == targetRunState) { return; } _runStateMachine.GotoState(targetRunState); } private StateMachine SetupStateMachine() { return new StateMachine(false, RunState.Unloaded, RunStateUnloaded_OnEnter, null) .AddState(RunState.LoadedNoExec, RunStateLoadedNoExec_OnEnter, null) .AddState(RunState.Running, RunStateRunning_OnEnter, RunStateRunning_OnExit); // ReSharper disable InconsistentNaming void RunStateUnloaded_OnEnter(State currentState) { if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } if (PackageManagementService.IsAnyPackageRunning()) { DisposeLuaCsConfig(); Logger.LogResults(PackageManagementService.UnloadAllPackages()); } CurrentRunState = RunState.Unloaded; } void RunStateLoadedNoExec_OnEnter(State currentState) { if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } if (!PackageManagementService.IsAnyPackageLoaded()) { Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); LoadLuaCsConfig(); } CurrentRunState = RunState.LoadedNoExec; } void RunStateRunning_OnEnter(State currentState) { if (!PackageManagementService.IsAnyPackageLoaded()) { Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); LoadLuaCsConfig(); } if (!PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); } CurrentRunState = RunState.Running; } void RunStateRunning_OnExit(State currentState) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } // ReSharper restore InconsistentNaming } #endregion #region LegacyRedirects public ILuaCsHook Hook => this.EventService; #endregion public void Dispose() { try { SetRunState(RunState.Unloaded); } catch (Exception e) { Logger.LogError(e.Message); } try { DisposeLuaCsConfig(); PluginManagementService.Dispose(); LuaScriptManagementService.Dispose(); ConfigService.Dispose(); PackageManagementService.Dispose(); // TODO: Add all missing services. //NetworkingService.Dispose(); EventService.Dispose(); _servicesProvider.DisposeAndReset(); } catch (Exception e) { Console.WriteLine(e); throw; } GC.SuppressFinalize(this); } /// /// Handles changes in game states tracked by screen changes. /// /// The new game screen. public partial void OnScreenSelected(Screen screen); void DisposeLuaCsConfig() { IsCsEnabled = null; TreatForcedModsAsNormal = null; DisableErrorGUIOverlay = null; HideUserNamesInLogs = null; LuaForBarotraumaSteamId = null; RestrictMessageSize = null; } } /// /// Specifies the current run state of the LuaCs Modding System. /// [Important]Enum State values ordering must be in the form of (lower state) === (higher state) /// public enum RunState : byte { /// /// No assets are loaded, code execution suspended. /// Unloaded = 0, /// /// Loaded mod configs, settings and assets. No code execution. /// LoadedNoExec = 1, /// /// All assets loaded, code execution is active. /// Running = 2 } }