using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.Toolkit.Diagnostics; using MoonSharp.VsCodeDebugger.SDK; namespace Barotrauma.LuaCs.Services.Processing; public sealed class ModConfigService : IModConfigService { private IStorageService _storageService; private ILoggerService _logger; private IParserServiceAsync _assemblyParserService; private IParserServiceAsync _luaScriptParserService; private IParserServiceAsync _configParserService; private readonly AsyncReaderWriterLock _operationsLock = new(); public ModConfigService(IStorageService storageService, IParserServiceAsync assemblyParserService, IParserServiceAsync luaScriptParserService, IParserServiceAsync configParserService, ILoggerService logger) { _storageService = storageService; _assemblyParserService = assemblyParserService; _luaScriptParserService = luaScriptParserService; _configParserService = configParserService; _logger = logger; } #region Dispose public void Dispose() { using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) return; try { _storageService.Dispose(); _logger.Dispose(); _assemblyParserService.Dispose(); _luaScriptParserService.Dispose(); _configParserService.Dispose(); _storageService = null; _logger = null; _assemblyParserService = null; _luaScriptParserService = null; _configParserService = null; } catch { // ignored } } private int _isDisposed = 0; public bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } #endregion public async Task> CreateConfigAsync(ContentPackage src) { Guard.IsNotNull(src, nameof(src)); using var lck = await _operationsLock.AcquireReaderLock(); IService.CheckDisposed(this); if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config }) { return await CreateFromConfigXmlAsync(src, config); } return await CreateFromLegacyAsync(src); } public async Task Config)>> CreateConfigsAsync(ImmutableArray src) { if (src.IsDefaultOrEmpty) ThrowHelper.ThrowArgumentNullException($"{nameof(CreateConfigsAsync)}: The supplied array is default or empty!"); using var lck = await _operationsLock.AcquireReaderLock(); IService.CheckDisposed(this); var builder = new ConcurrentQueue<(ContentPackage Source, Result Config)>(); await src.ParallelForEachAsync(async package => { var res = await CreateConfigAsync(package); builder.Enqueue((package, res)); }); return builder.OrderBy(pkg => src.IndexOf(pkg.Source)).ToImmutableArray(); } //--- Helpers private async Task> TryGetModConfigXmlAsync(ContentPackage src) { return await _storageService.LoadPackageXmlAsync(ContentPath.FromRaw(src, "%ModDir%/ModConfig.xml")) is { IsSuccess: true, Value: { Root: {} config} } ? FluentResults.Result.Ok(config) : FluentResults.Result.Fail("ModConfig.xml not found"); } private async Task> CreateFromConfigXmlAsync(ContentPackage owner, XElement src) { ImmutableArray assemblyResources = default; ImmutableArray configResources = default; ImmutableArray luaResources = default; var res = await Task.WhenAll(new[] { new Task(async () => assemblyResources = await GetAssembliesFromXml(owner, src)), new Task(async () => configResources = await GetConfigsFromXml(owner, src)), new Task(async () => luaResources = await GetLuaScriptsFromXml(owner, src)), }); bool isFaulted = false; foreach (var task in res) { if (task.IsFaulted) { _logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: {task.Exception?.ToString()}"); isFaulted = true; } } if (isFaulted) { _logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}"); return FluentResults.Result.Fail($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}"); } return FluentResults.Result.Ok(new ModConfigInfo() { Package = owner, Assemblies = assemblyResources, Configs = configResources, LuaScripts = luaResources }); async Task> GetLuaScriptsFromXml(ContentPackage contentPackage, XElement cfgElement) { return await GetResourceFromXml(contentPackage, cfgElement, "Lua", "FileGroup", _luaScriptParserService); } async Task> GetConfigsFromXml(ContentPackage contentPackage, XElement cfgElement) { return await GetResourceFromXml(contentPackage, cfgElement, "Config", "FileGroup", _configParserService); } async Task> GetAssembliesFromXml(ContentPackage contentPackage, XElement cfgElement) { return await GetResourceFromXml(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService); } async Task> GetResourceFromXml(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync resourceService) { var elems = GetResourceElementsWithName(owner, cfgElement, elemName, fileGroupName); if (elems.IsDefaultOrEmpty) return ImmutableArray.Empty; var results = await resourceService.TryParseResourcesAsync(elems); Guard.IsNotEmpty((IReadOnlyCollection>)results, nameof(results)); var resources = ImmutableArray.CreateBuilder(); foreach (var result in results) { if (result.Errors.Count > 0) { _logger.LogResults(result.ToResult()); continue; } resources.Add(result.Value); } return resources.MoveToImmutable(); } ImmutableArray GetResourceElementsWithName(ContentPackage package, XElement root, string elemName, string groupName) { var elems = ImmutableArray.CreateBuilder(); elems.AddRange(root.GetChildElements(elemName) .Select(e => new ResourceParserInfo(package, e, ImmutableArray.Empty, ImmutableArray.Empty)) .ToImmutableArray()); if (root.GetChildElements(groupName).ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroups) { foreach (var fileGroup in fileGroups) { if (fileGroup.GetChildElements(elemName).ToImmutableArray() is { IsDefaultOrEmpty: false } subLuaElems) { var cond = GetDependencyIdentifiers(fileGroup, true); var negCond = GetDependencyIdentifiers(fileGroup, false); foreach (var element in subLuaElems) { elems.Add(new ResourceParserInfo(package, element, cond, negCond)); } } } } return elems.MoveToImmutable(); } ImmutableArray GetDependencyIdentifiers(XElement fg, bool depsLoadedSetting) { return fg.GetChildElements("Conditional") .Where(cElem => bool.TryParse(cElem.GetAttribute("IsLoaded").Value, out bool isLoaded) && isLoaded == depsLoadedSetting) .SelectMany(cElem2 => cElem2.GetAttributeString("Dependencies", String.Empty) .Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) .Select(ident => new Identifier(ident))) .ToImmutableArray(); } } private async Task> CreateFromLegacyAsync(ContentPackage src) { return new ModConfigInfo() { Package = src, Assemblies = ImmutableArray.Empty, Configs = ImmutableArray.Empty, LuaScripts = ImmutableArray.Empty }; } }