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.Reflection; 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)}: ModConfig.xml missing. Unable to continue."); throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua's ModConfig.xml is missing. Unable to continue."); } _runStateMachine = SetupStateMachine(); SubscribeToLuaCsEvents(); } private 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(); } private 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. /// public bool IsCsEnabled { get => _isCsEnabled?.Value ?? false; internal set => _isCsEnabled?.TrySetValue(value); } private ISettingEntry _isCsEnabled; /// /// Whether the popup error GUI should be hidden/suppressed. /// public bool DisableErrorGUIOverlay { get => _disableErrorGUIOverlay?.Value ?? false; internal set => _disableErrorGUIOverlay?.TrySetValue(value); } private ISettingEntry _disableErrorGUIOverlay; /// /// Whether usernames are anonymized or show in logs. /// public bool HideUserNamesInLogs { get => _hideUserNamesInLogs?.Value ?? false; internal set => _hideUserNamesInLogs?.TrySetValue(value); } private ISettingEntry _hideUserNamesInLogs; /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// public ulong LuaForBarotraumaSteamId { get => _luaForBarotraumaSteamId?.Value ?? 0; internal set => _luaForBarotraumaSteamId?.TrySetValue(value); } private ISettingEntry _luaForBarotraumaSteamId; /// /// TODO: @evilfactory@users.noreply.github.com /// public bool RestrictMessageSize { get => _restrictMessageSize?.Value ?? false; internal set => _restrictMessageSize?.TrySetValue(value); } private ISettingEntry _restrictMessageSize; /// /// The local save path for all local data storage for mods. /// public string LocalDataSavePath { get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods"); internal set => _localDataSavePath?.TrySetValue(value); } private ISettingEntry _localDataSavePath; void LoadLuaCsConfig() { _isCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 : throw new NullReferenceException($"{nameof(IsCsEnabled)} 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."); _localDataSavePath = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LocalDataSavePath", out var val8) ? val8 : throw new NullReferenceException($"{nameof(LocalDataSavePath)} 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, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, SettingsFileParserService>(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, onEnter: RunStateUnloaded_OnEnter, null) .AddState(RunState.LoadedNoExec, onEnter: RunStateLoadedNoExec_OnEnter, null) .AddState(RunState.Running, onEnter: RunStateRunning_OnEnter, RunStateRunning_OnExit); // ReSharper disable InconsistentNaming void RunStateUnloaded_OnEnter(State currentState) { if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } if (PackageManagementService.IsAnyPackageLoaded()) { DisposeLuaCsConfig(); Logger.LogResults(PackageManagementService.UnloadAllPackages()); } LuaScriptManagementService.Reset(); PackageManagementService.Reset(); EventService.Reset(); SubscribeToLuaCsEvents(); 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())); // TODO: Implement full xml content necessary for this to work. //LoadLuaCsConfig(); } CurrentRunState = RunState.LoadedNoExec; } void RunStateRunning_OnEnter(State currentState) { if (!PackageManagementService.IsAnyPackageLoaded()) { Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); // TODO: Enable later //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; _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 } }