From 9c237b231b84934eade275c63df21b895d730b28 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 13 Dec 2025 11:06:55 +0100 Subject: [PATCH 01/38] start verify target impl --- .../ModSelectors/VerifyInstallationData.cs | 28 --- src/ModVerify.CliApp/ModVerifyApplication.cs | 42 +--- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 220 +++++++++++++++--- src/ModVerify/VerificationTarget.cs | 41 ++++ src/ModVerify/Verifiers/GameVerifierBase.cs | 5 +- .../PG.StarWarsGame.Engine/GameLocations.cs | 21 +- .../IGameEngineInitializationReporter.cs | 10 + .../IPetroglyphStarWarsGameEngineService.cs | 5 +- .../PetroglyphStarWarsGameEngineService.cs | 22 +- 9 files changed, 276 insertions(+), 118 deletions(-) delete mode 100644 src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs create mode 100644 src/ModVerify/VerificationTarget.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/IGameEngineInitializationReporter.cs diff --git a/src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs b/src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs deleted file mode 100644 index 1a1fcd2..0000000 --- a/src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text; -using PG.StarWarsGame.Engine; - -namespace AET.ModVerify.App.ModSelectors; - -internal sealed class VerifyInstallationData -{ - public required string Name { get; init; } - - public required GameEngineType EngineType { get; init; } - - public required GameLocations GameLocations { get; init; } - - public override string ToString() - { - var sb = new StringBuilder(); - - sb.AppendLine($"ObjectToVerify={Name};EngineType={EngineType};Locations=["); - if (GameLocations.ModPaths.Count > 0) - sb.AppendLine($"Mods=[{string.Join(";", GameLocations.ModPaths)}];"); - sb.AppendLine($"Game=[{GameLocations.GamePath}];"); - if (GameLocations.FallbackPaths.Count > 0) - sb.AppendLine($"Fallbacks=[{string.Join(";", GameLocations.FallbackPaths)}];"); - sb.AppendLine("]"); - - return sb.ToString(); - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 7ea461c..43f3632 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -8,14 +8,12 @@ using AnakinRaW.ApplicationBase.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine; using Serilog; using System; using System.Collections.Generic; using System.IO; using System.IO.Abstractions; using System.Linq; -using System.Threading; using System.Threading.Tasks; using AET.ModVerify.App.GameFinder; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -114,47 +112,15 @@ private async Task> Verify( VerifyInstallationData installData, GlobalVerifyReportSettings reportSettings) { - var gameEngineService = services.GetRequiredService(); - var engineErrorReporter = new ConcurrentGameEngineErrorReporter(); - - IStarWarsGameEngine gameEngine; - - try - { - var initProgress = new Progress(); - var initProgressReporter = new EngineInitializeProgressReporter(initProgress); - - try - { - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Creating Game Engine '{Engine}'", installData.EngineType); - gameEngine = await gameEngineService.InitializeAsync( - installData.EngineType, - installData.GameLocations, - engineErrorReporter, - initProgress, - false, - CancellationToken.None).ConfigureAwait(false); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Game Engine created"); - } - finally - { - initProgressReporter.Dispose(); - } - } - catch (Exception e) - { - _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); - throw; - } - + var initProgressReporter = new EngineInitializeProgressReporter(null); var progressReporter = new VerifyConsoleProgressReporter(installData.Name); - using var verifyPipeline = new GameVerifyPipeline( - gameEngine, - engineErrorReporter, + using var verifyPipeline = new NewGameVerifyPipeline( + null, settings.VerifyPipelineSettings, reportSettings, progressReporter, + null, services); try diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index 810651a..ab62511 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -1,4 +1,5 @@ -using AET.ModVerify.Reporting; +using AET.ModVerify.Pipeline.Progress; +using AET.ModVerify.Reporting; using AET.ModVerify.Reporting.Settings; using AET.ModVerify.Settings; using AET.ModVerify.Utilities; @@ -12,72 +13,96 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using AET.ModVerify.Pipeline.Progress; +using Microsoft.Extensions.DependencyInjection; namespace AET.ModVerify.Pipeline; -public sealed class GameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline +public sealed class NewGameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline { private readonly List _verifiers = new(); private readonly List _verificationSteps = new(); - private readonly StepRunnerBase _verifyRunner; - - private readonly IStarWarsGameEngine _gameEngine; - private readonly IGameEngineErrorCollection _engineErrors; + private readonly ConcurrentGameEngineErrorReporter _engineErrorReporter = new(); + private readonly StepRunnerBase _verifyRunner; + private readonly VerificationTarget _verificationTarget; private readonly VerifyPipelineSettings _pipelineSettings; private readonly GlobalVerifyReportSettings _reportSettings; - private readonly IVerifyProgressReporter _progressReporter; + private readonly IGameEngineInitializationReporter? _engineInitializationReporter; + private readonly IPetroglyphStarWarsGameEngineService _gameEngineService; + private readonly ILogger? _logger; protected override bool FailFast { get; } public IReadOnlyCollection FilteredErrors { get; private set; } = []; - - public GameVerifyPipeline( - IStarWarsGameEngine gameEngine, - IGameEngineErrorCollection engineErrors, - VerifyPipelineSettings pipelineSettings, + + public NewGameVerifyPipeline( + VerificationTarget verificationTarget, + VerifyPipelineSettings pipelineSettings, GlobalVerifyReportSettings reportSettings, IVerifyProgressReporter progressReporter, + IGameEngineInitializationReporter? engineInitializationReporter, IServiceProvider serviceProvider) : base(serviceProvider) { - _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); - _engineErrors = engineErrors ?? throw new ArgumentNullException(nameof(gameEngine)); + _verificationTarget = verificationTarget ?? throw new ArgumentNullException(nameof(verificationTarget)); _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings)); _reportSettings = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); + _engineInitializationReporter = engineInitializationReporter; + _gameEngineService = serviceProvider.GetRequiredService(); + _logger = serviceProvider.GetService()?.CreateLogger(GetType()); - if (pipelineSettings.ParallelVerifiers is < 0 or > 64) - throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", nameof(pipelineSettings)); - - if (pipelineSettings.ParallelVerifiers == 1) - _verifyRunner = new SequentialStepRunner(serviceProvider); - else - _verifyRunner = new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider); + _verifyRunner = pipelineSettings.ParallelVerifiers switch + { + < 0 or > 64 => throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", + nameof(pipelineSettings)), + 1 => new SequentialStepRunner(serviceProvider), + _ => new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider) + }; FailFast = pipelineSettings.FailFast; } - protected override Task PrepareCoreAsync() + protected override async Task PrepareCoreAsync() { _verifiers.Clear(); - AddStep(new GameEngineErrorCollector(_engineErrors, _gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); - foreach (var gameVerificationStep in CreateVerificationSteps(_gameEngine)) + IStarWarsGameEngine gameEngine; + + try + { + gameEngine = await _gameEngineService.InitializeAsync( + _verificationTarget.Engine, + _verificationTarget.Location, + _engineErrorReporter, + _engineInitializationReporter, + false, + CancellationToken.None).ConfigureAwait(false); + } + catch (Exception e) + { + _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); + throw; + } + + + AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); + + foreach (var gameVerificationStep in CreateVerificationSteps(gameEngine)) AddStep(gameVerificationStep); - return Task.FromResult(true); + return true; } protected override async Task RunCoreAsync(CancellationToken token) - { + { var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); try { Logger?.LogInformation("Running game verifiers..."); + _progressReporter.Report(0.0, $"Verifing {_verificationTarget.Name}...", VerifyProgress.ProgressType, default); _verifyRunner.Error += OnError; await _verifyRunner.RunAsync(token); } @@ -97,6 +122,9 @@ protected override async Task RunCoreAsync(CancellationToken token) throw new StepFailureException(failedSteps); FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); + + + _progressReporter.Report(1.0, $"Finished Verifing {_verificationTarget.Name}", VerifyProgress.ProgressType, default); } protected override void OnError(object sender, StepRunnerErrorEventArgs e) @@ -110,11 +138,6 @@ protected override void OnError(object sender, StepRunnerErrorEventArgs e) base.OnError(sender, e); } - private IEnumerable CreateVerificationSteps(IStarWarsGameEngine database) - { - return _pipelineSettings.VerifiersProvider.GetVerifiers(database, _pipelineSettings.GameVerifySettings, ServiceProvider); - } - private void AddStep(GameVerifier verifier) { var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); @@ -131,4 +154,135 @@ private IEnumerable GetReportableErrors(IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) + { + return _pipelineSettings.VerifiersProvider + .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); + } +} + + + + + + + + + +//public sealed class GameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline +//{ +// private readonly List _verifiers = new(); +// private readonly List _verificationSteps = new(); +// private readonly StepRunnerBase _verifyRunner; + +// private readonly IStarWarsGameEngine _gameEngine; +// private readonly IGameEngineErrorCollection _engineErrors; + +// private readonly VerifyPipelineSettings _pipelineSettings; +// private readonly GlobalVerifyReportSettings _reportSettings; + +// private readonly IVerifyProgressReporter _progressReporter; + +// protected override bool FailFast { get; } + +// public IReadOnlyCollection FilteredErrors { get; private set; } = []; + +// public GameVerifyPipeline( +// IStarWarsGameEngine gameEngine, +// IGameEngineErrorCollection engineErrors, +// VerifyPipelineSettings pipelineSettings, +// GlobalVerifyReportSettings reportSettings, +// IVerifyProgressReporter progressReporter, +// IServiceProvider serviceProvider) : base(serviceProvider) +// { +// _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); +// _engineErrors = engineErrors ?? throw new ArgumentNullException(nameof(gameEngine)); +// _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings)); +// _reportSettings = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); +// _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); + +// if (pipelineSettings.ParallelVerifiers is < 0 or > 64) +// throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", nameof(pipelineSettings)); + +// if (pipelineSettings.ParallelVerifiers == 1) +// _verifyRunner = new SequentialStepRunner(serviceProvider); +// else +// _verifyRunner = new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider); + +// FailFast = pipelineSettings.FailFast; +// } + +// protected override Task PrepareCoreAsync() +// { +// _verifiers.Clear(); + +// AddStep(new GameEngineErrorCollector(_engineErrors, _gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); + +// foreach (var gameVerificationStep in CreateVerificationSteps(_gameEngine)) +// AddStep(gameVerificationStep); + +// return Task.FromResult(true); +// } + +// protected override async Task RunCoreAsync(CancellationToken token) +// { +// var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); + +// try +// { +// Logger?.LogInformation("Running game verifiers..."); +// _verifyRunner.Error += OnError; +// await _verifyRunner.RunAsync(token); +// } +// finally +// { +// aggregatedVerifyProgressReporter.Dispose(); +// _verifyRunner.Error -= OnError; +// Logger?.LogDebug("Game verifiers finished."); +// } + +// token.ThrowIfCancellationRequested(); + +// var failedSteps = _verifyRunner.ExecutedSteps.Where(p => +// p.Error != null && !p.Error.IsExceptionType()).ToList(); + +// if (failedSteps.Count != 0) +// throw new StepFailureException(failedSteps); + +// FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); +// } + +// protected override void OnError(object sender, StepRunnerErrorEventArgs e) +// { +// if (FailFast && e.Exception is GameVerificationException v) +// { +// // TODO: Apply globalMinSeverity +// if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) +// return; +// } +// base.OnError(sender, e); +// } + +// private IEnumerable CreateVerificationSteps(IStarWarsGameEngine database) +// { +// return _pipelineSettings.VerifiersProvider.GetVerifiers(database, _pipelineSettings.GameVerifySettings, ServiceProvider); +// } + +// private void AddStep(GameVerifier verifier) +// { +// var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); +// _verifyRunner.AddStep(verificationStep); +// _verificationSteps.Add(verificationStep); +// _verifiers.Add(verifier); +// } + +// private IEnumerable GetReportableErrors(IEnumerable errors) +// { +// Logger?.LogDebug("Applying baseline and suppressions."); +// // NB: We don't filter for severity here, as the individual reporters handle that. +// // This allows better control over what gets reported. +// return errors.ApplyBaseline(_reportSettings.Baseline) +// .ApplySuppressions(_reportSettings.Suppressions); +// } +//} \ No newline at end of file diff --git a/src/ModVerify/VerificationTarget.cs b/src/ModVerify/VerificationTarget.cs new file mode 100644 index 0000000..d3afdc7 --- /dev/null +++ b/src/ModVerify/VerificationTarget.cs @@ -0,0 +1,41 @@ +using System; +using System.Text; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify; + +public sealed class VerificationTarget +{ + public required GameEngineType Engine { get; init; } + + public required string Name + { + get; + init + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentNullException(nameof(value)); + field = value; + } + } + + public required GameLocations Location + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + public string? Version { get; init; } + + public bool IsGame => Location.ModPaths.Count == 0; + + public override string ToString() + { + var sb = new StringBuilder($"[Name={Name};EngineType={Engine};"); + if (!string.IsNullOrEmpty(Version)) + sb.Append($"Version={Version};"); + sb.Append($"Location={Location};"); + sb.Append("]"); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/GameVerifierBase.cs b/src/ModVerify/Verifiers/GameVerifierBase.cs index 8c9d67d..02bacc4 100644 --- a/src/ModVerify/Verifiers/GameVerifierBase.cs +++ b/src/ModVerify/Verifiers/GameVerifierBase.cs @@ -15,10 +15,8 @@ namespace AET.ModVerify.Verifiers; public abstract class GameVerifierBase : IGameVerifierInfo { public event EventHandler? Error; - public event EventHandler>? Progress; - private readonly IStarWarsGameEngine _gameEngine; private readonly ConcurrentDictionary _verifyErrors = new(); protected readonly IFileSystem FileSystem; @@ -35,7 +33,7 @@ public abstract class GameVerifierBase : IGameVerifierInfo protected IStarWarsGameEngine GameEngine { get; } - protected IGameRepository Repository => _gameEngine.GameRepository; + protected IGameRepository Repository => GameEngine.GameRepository; protected IReadOnlyList VerifierChain { get; } @@ -49,7 +47,6 @@ protected GameVerifierBase( throw new ArgumentNullException(nameof(serviceProvider)); FileSystem = serviceProvider.GetRequiredService(); Services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); Parent = parent; Settings = settings ?? throw new ArgumentNullException(nameof(settings)); GameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs index 3b77732..73aa400 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs @@ -1,7 +1,9 @@ -using System; +using AnakinRaW.CommonUtilities; +using System; using System.Collections.Generic; using System.Linq; -using AnakinRaW.CommonUtilities; +using System.Text; +using System.Xml.Linq; namespace PG.StarWarsGame.Engine; @@ -48,4 +50,19 @@ public GameLocations(IList modPaths, string gamePath, IList fall ? ModPaths[0] : GamePath; } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendLine("GameLocation=["); + if (ModPaths.Count > 0) + sb.AppendLine($"Mods=[{string.Join(";", ModPaths)}];"); + sb.AppendLine($"Game=[{GamePath}];"); + if (FallbackPaths.Count > 0) + sb.AppendLine($"Fallbacks=[{string.Join(";", FallbackPaths)}];"); + sb.AppendLine("]"); + + return sb.ToString(); + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameEngineInitializationReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameEngineInitializationReporter.cs new file mode 100644 index 0000000..2ff43bd --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameEngineInitializationReporter.cs @@ -0,0 +1,10 @@ +namespace PG.StarWarsGame.Engine; + +public interface IGameEngineInitializationReporter +{ + void ReportProgress(string message); + + void ReportStarted(); + + void ReportFinished(); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs index 314ce06..80aaa61 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using PG.StarWarsGame.Engine.ErrorReporting; @@ -11,7 +10,7 @@ public Task InitializeAsync( GameEngineType engineType, GameLocations gameLocations, IGameEngineErrorReporter? errorReporter = null, - IProgress? initProgress = null, + IGameEngineInitializationReporter? initReporter = null, bool cancelOnInitializationError = false, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs index 8ce1a5a..02a8d9f 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs @@ -27,7 +27,7 @@ public async Task InitializeAsync( GameEngineType engineType, GameLocations gameLocations, IGameEngineErrorReporter? errorReporter = null, - IProgress? progress = null, + IGameEngineInitializationReporter? initReporter = null, bool cancelOnInitializationError = false, CancellationToken cancellationToken = default) @@ -39,7 +39,7 @@ public async Task InitializeAsync( try { - return await InitializeEngine(engineType, gameLocations, errorListenerWrapper, progress, cts.Token) + return await InitializeEngineAsync(engineType, gameLocations, errorListenerWrapper, initReporter, cts.Token) .ConfigureAwait(false); } finally @@ -56,16 +56,17 @@ void OnInitializationError(object sender, InitializationError e) } } - private async Task InitializeEngine( + private async Task InitializeEngineAsync( GameEngineType engineType, GameLocations gameLocations, GameEngineErrorReporterWrapper errorReporter, - IProgress? progress, + IGameEngineInitializationReporter? initReporter, CancellationToken token) { try { _logger?.LogInformation("Initializing game engine for type '{GameEngineType}'.", engineType); + initReporter?.ReportStarted(); var repoFactory = _serviceProvider.GetRequiredService(); var repository = repoFactory.Create(engineType, gameLocations, errorReporter); @@ -73,7 +74,7 @@ private async Task InitializeEngine( var pgRender = new PGRender(repository, errorReporter, serviceProvider); var gameConstants = new GameConstants.GameConstants(repository, errorReporter, serviceProvider); - progress?.Report("Initializing GameConstants"); + initReporter?.ReportProgress("Initializing GameConstants"); await gameConstants.InitializeAsync(token); // AudioConstants @@ -81,23 +82,23 @@ private async Task InitializeEngine( // MousePointer var fontManger = new FontManager(repository, errorReporter, serviceProvider); - progress?.Report("Initializing FontManager"); + initReporter?.ReportProgress("Initializing FontManager"); await fontManger.InitializeAsync(token); var guiDialogs = new GuiDialogGameManager(repository, errorReporter, serviceProvider); - progress?.Report("Initializing GUIDialogManager"); + initReporter?.ReportProgress("Initializing GUIDialogManager"); await guiDialogs.InitializeAsync(token); var sfxGameManager = new SfxEventGameManager(repository, errorReporter, serviceProvider); - progress?.Report("Initializing SFXManager"); + initReporter?.ReportProgress("Initializing SFXManager"); await sfxGameManager.InitializeAsync(token); var commandBarManager = new CommandBarGameManager(repository, pgRender, gameConstants, fontManger, errorReporter, serviceProvider); - progress?.Report("Initializing CommandBar"); + initReporter?.ReportProgress("Initializing CommandBar"); await commandBarManager.InitializeAsync(token); var gameObjetTypeManager = new GameObjectTypeGameManager(repository, errorReporter, serviceProvider); - progress?.Report("Initializing GameObjectTypeManager"); + initReporter?.ReportProgress("Initializing GameObjectTypeManager"); await gameObjetTypeManager.InitializeAsync(token); token.ThrowIfCancellationRequested(); @@ -120,6 +121,7 @@ private async Task InitializeEngine( } finally { + initReporter?.ReportFinished(); _logger?.LogDebug("Finished initializing game database."); } } From b4829cb75fc93d81a0e26bb2b79c997cded1f444 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 13 Dec 2025 13:55:41 +0100 Subject: [PATCH 02/38] update deps --- .../ModSelectors/SettingsBasedModSelector.cs | 8 ++-- src/ModVerify.CliApp/ModVerify.CliApp.csproj | 12 ++--- src/ModVerify.CliApp/ModVerifyApplication.cs | 20 ++++---- .../Reporting/BaselineSelector.cs | 17 ++++--- src/ModVerify/ModVerify.csproj | 4 +- .../Reporting/Json/JsonBaselineParser.cs | 11 ++--- .../Reporting/Json/JsonBaselineSchema.cs | 46 +++++++++++-------- 7 files changed, 61 insertions(+), 57 deletions(-) diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs index 221bb9e..f6538c5 100644 --- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs @@ -9,7 +9,7 @@ namespace AET.ModVerify.App.ModSelectors; internal class SettingsBasedModSelector(IServiceProvider serviceProvider) { - public VerifyInstallationData CreateInstallationDataFromSettings(GameInstallationsSettings settings) + public VerificationTarget CreateInstallationDataFromSettings(GameInstallationsSettings settings) { var gameLocations = new ModSelectorFactory(serviceProvider) .CreateSelector(settings) @@ -21,10 +21,10 @@ public VerifyInstallationData CreateInstallationDataFromSettings(GameInstallatio if (engineType is null) throw new InvalidOperationException("Engine type not specified."); - return new VerifyInstallationData + return new VerificationTarget { - EngineType = engineType.Value, - GameLocations = gameLocations, + Engine = engineType.Value, + Location = gameLocations, Name = GetNameFromGameLocations(targetObject, gameLocations, engineType.Value) }; } diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj index 0073b2b..9874e8c 100644 --- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj +++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj @@ -30,18 +30,18 @@ - + - - - - - + + + + + diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 43f3632..754523b 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -68,10 +68,10 @@ private async Task RunCore() private async Task RunVerify() { - VerifyInstallationData installData; + VerificationTarget verificationTarget; try { - installData = new SettingsBasedModSelector(services) + verificationTarget = new SettingsBasedModSelector(services) .CreateInstallationDataFromSettings(settings.GameInstallationsSettings); } catch (GameNotFoundException ex) @@ -82,12 +82,12 @@ private async Task RunVerify() return ex.HResult; } - var reportSettings = CreateGlobalReportSettings(installData); + var reportSettings = CreateGlobalReportSettings(verificationTarget); - _logger?.LogDebug("Verify install data: {InstallData}", installData); + _logger?.LogDebug("Verification taget: {Target}", verificationTarget); _logger?.LogTrace("Verify settings: {Settings}", settings); - var allErrors = await Verify(installData, reportSettings) + var allErrors = await Verify(verificationTarget, reportSettings) .ConfigureAwait(false); try @@ -109,11 +109,11 @@ private async Task RunVerify() } private async Task> Verify( - VerifyInstallationData installData, + VerificationTarget verificationTarget, GlobalVerifyReportSettings reportSettings) { var initProgressReporter = new EngineInitializeProgressReporter(null); - var progressReporter = new VerifyConsoleProgressReporter(installData.Name); + var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name); using var verifyPipeline = new NewGameVerifyPipeline( null, @@ -127,7 +127,7 @@ private async Task> Verify( { try { - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verifying '{Target}'...", installData.Name); + _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verifying '{Target}'...", verificationTarget.Name); await verifyPipeline.RunAsync().ConfigureAwait(false); progressReporter.Report(string.Empty, 1.0); } @@ -184,10 +184,10 @@ private async Task WriteBaseline( await baseline.ToJsonAsync(fs); } - private GlobalVerifyReportSettings CreateGlobalReportSettings(VerifyInstallationData installData) + private GlobalVerifyReportSettings CreateGlobalReportSettings(VerificationTarget verificationTarget) { var baselineSelector = new BaselineSelector(settings, services); - var baseline = baselineSelector.SelectBaseline(installData, out var baselinePath); + var baseline = baselineSelector.SelectBaseline(verificationTarget, out var baselinePath); if (baseline.Count > 0) _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Using baseline '{Baseline}'", baselinePath); diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs index 95953f1..9fa637f 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs @@ -1,5 +1,4 @@ -using AET.ModVerify.App.ModSelectors; -using AET.ModVerify.App.Resources.Baselines; +using AET.ModVerify.App.Resources.Baselines; using AET.ModVerify.App.Settings; using AET.ModVerify.Reporting; using AnakinRaW.ApplicationBase; @@ -16,7 +15,7 @@ internal sealed class BaselineSelector(ModVerifyAppSettings settings, IServicePr private readonly ILogger? _logger = services.GetService()?.CreateLogger(typeof(ModVerifyApplication)); private readonly BaselineFactory _baselineFactory = new(services); - public VerificationBaseline SelectBaseline(VerifyInstallationData installationData, out string? usedBaselinePath) + public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget, out string? usedBaselinePath) { var baselinePath = settings.ReportSettings.BaselinePath; if (!string.IsNullOrEmpty(baselinePath)) @@ -49,14 +48,14 @@ public VerificationBaseline SelectBaseline(VerifyInstallationData installationDa } if (settings.Interactive) - return FindBaselineInteractive(installationData, out usedBaselinePath); + return FindBaselineInteractive(verificationTarget, out usedBaselinePath); // If the application is not interactive, we only use a baseline file present in the directory of the verification target. - return FindBaselineNonInteractive(installationData.GameLocations.TargetPath, out usedBaselinePath); + return FindBaselineNonInteractive(verificationTarget.Location.TargetPath, out usedBaselinePath); } - private VerificationBaseline FindBaselineInteractive(VerifyInstallationData installationData, out string? baselinePath) + private VerificationBaseline FindBaselineInteractive(VerificationTarget verificationTarget, out string? baselinePath) { // The application is in interactive mode. We apply the following lookup: // 1. Use a baseline found in the directory of the verification target. @@ -66,21 +65,21 @@ private VerificationBaseline FindBaselineInteractive(VerifyInstallationData inst _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Searching for local baseline files..."); - if (!_baselineFactory.TryCreateBaseline(installationData.GameLocations.TargetPath, out var baseline, + if (!_baselineFactory.TryCreateBaseline(verificationTarget.Location.TargetPath, out var baseline, out baselinePath)) { if (!_baselineFactory.TryCreateBaseline("./", out baseline, out baselinePath)) { // It does not make sense to load the game's default baselines if the user wants to verify the game, // as the verification result would always be empty (at least in a non-development scenario) - if (installationData.GameLocations.ModPaths.Count == 0) + if (verificationTarget.Location.ModPaths.Count == 0) { _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "No local baseline file found."); return VerificationBaseline.Empty; } Console.WriteLine("No baseline found locally."); - return TryGetDefaultBaseline(installationData.EngineType, out baselinePath); + return TryGetDefaultBaseline(verificationTarget.Engine, out baselinePath); } } diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index 427458d..fe15b1c 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -35,8 +35,8 @@ - - + + diff --git a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs b/src/ModVerify/Reporting/Json/JsonBaselineParser.cs index ef2f5d1..d3b4acd 100644 --- a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs +++ b/src/ModVerify/Reporting/Json/JsonBaselineParser.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text.Json; -using System.Text.Json.Nodes; namespace AET.ModVerify.Reporting.Json; @@ -13,7 +12,7 @@ public static VerificationBaseline Parse(Stream dataStream) throw new ArgumentNullException(nameof(dataStream)); try { - var jsonNode = JsonNode.Parse(dataStream); + var jsonNode = JsonDocument.Parse(dataStream); var jsonBaseline = ParseCore(jsonNode); if (jsonBaseline is null) @@ -27,12 +26,12 @@ public static VerificationBaseline Parse(Stream dataStream) } } - private static JsonVerificationBaseline? ParseCore(JsonNode? jsonData) + private static JsonVerificationBaseline? ParseCore(JsonDocument? json) { - if (jsonData is null) + if (json is null) return null; - JsonBaselineSchema.Evaluate(jsonData); - return jsonData.Deserialize(); + JsonBaselineSchema.Evaluate(json.RootElement); + return json.Deserialize(); } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs b/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs index 7c8b02a..12e3705 100644 --- a/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs +++ b/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs @@ -3,8 +3,9 @@ using System.Diagnostics; using System.Linq; using System.Text; -using System.Text.Json.Nodes; +using System.Text.Json; using Json.Schema; +using Json.Schema.Keywords; namespace AET.ModVerify.Reporting.Json; @@ -12,18 +13,20 @@ public static class JsonBaselineSchema { private static readonly JsonSchema Schema; private static readonly EvaluationOptions EvaluationOptions; - + private static readonly BuildOptions BuildOptions; + static JsonBaselineSchema() { - var evalvOptions = new EvaluationOptions + BuildOptions = new BuildOptions { - EvaluateAs = SpecVersion.Draft202012, - OutputFormat = OutputFormat.Hierarchical, - AllowReferencesIntoUnknownKeywords = false + Dialect = Dialect.Draft202012 }; Schema = GetCurrentSchema(); - EvaluationOptions = evalvOptions; + EvaluationOptions = new EvaluationOptions + { + OutputFormat = OutputFormat.Hierarchical + }; } /// @@ -31,11 +34,8 @@ static JsonBaselineSchema() /// /// The JSON node to evaluate. /// is not valid against the baseline JSON schema. - /// is . - public static void Evaluate(JsonNode json) + public static void Evaluate(JsonElement json) { - if (json == null) - throw new ArgumentNullException(nameof(json)); var result = Schema.Evaluate(json, EvaluationOptions); ThrowOnValidationError(result); } @@ -58,13 +58,17 @@ private static void ThrowOnValidationError(EvaluationResults result) private static KeyValuePair? GetFirstError(EvaluationResults result) { - if (result.HasErrors) - return result.Errors!.First(); - foreach (var child in result.Details) + if (result.Errors is not null) + return result.Errors.First(); + + if (result.Details is not null) { - var error = GetFirstError(child); - if (error is not null) - return error; + foreach (var child in result.Details) + { + var error = GetFirstError(child); + if (error is not null) + return error; + } } return null; } @@ -75,10 +79,12 @@ private static JsonSchema GetCurrentSchema() .Assembly.GetManifestResourceStream($"AET.ModVerify.Resources.Schemas.{GetVersionedPath()}.baseline.json"); Debug.Assert(resourceStream is not null); - var schema = JsonSchema.FromStream(resourceStream!).GetAwaiter().GetResult(); + var json = JsonDocument.Parse(resourceStream!).RootElement; + var schema = JsonSchema.Build(json, BuildOptions); + - var id = schema.GetId(); - if (id is null || !UriContainsVersion(id, VerificationBaseline.LatestVersionString)) + if (schema.Root.Keywords.FirstOrDefault(x => x.Handler is IdKeyword)?.Value is not Uri id + || !UriContainsVersion(id, VerificationBaseline.LatestVersionString)) throw new InvalidOperationException("Internal error: The embedded schema version does not match the expected baseline version!"); return schema; From 6b00425ad0cec566242fff66894e3fe494d21e8c Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 13 Dec 2025 13:55:46 +0100 Subject: [PATCH 03/38] update sub --- modules/ModdingToolBase | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index 479a088..da19380 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit 479a088a2b26dd4a3e2342b2e34f5359b0252e88 +Subproject commit da19380d04632302b3806af778e3e6696e14ddc3 From e1b7832793bf3c57a1683811548621664e2285b9 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 13 Dec 2025 14:07:51 +0100 Subject: [PATCH 04/38] make app running again --- ModVerify.slnx | 2 +- src/ModVerify.CliApp/ModVerifyApplication.cs | 5 ++-- .../EngineInitializeProgressReporter.cs | 27 ++++++++----------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/ModVerify.slnx b/ModVerify.slnx index 3527ff4..ebbde63 100644 --- a/ModVerify.slnx +++ b/ModVerify.slnx @@ -17,12 +17,12 @@ + - diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 754523b..44d494e 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -112,15 +112,14 @@ private async Task> Verify( VerificationTarget verificationTarget, GlobalVerifyReportSettings reportSettings) { - var initProgressReporter = new EngineInitializeProgressReporter(null); var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name); using var verifyPipeline = new NewGameVerifyPipeline( - null, + verificationTarget, settings.VerifyPipelineSettings, reportSettings, progressReporter, - null, + new EngineInitializeProgressReporter(verificationTarget.Engine), services); try diff --git a/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs index b994e97..d93462f 100644 --- a/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs +++ b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs @@ -1,30 +1,25 @@ using System; +using PG.StarWarsGame.Engine; namespace AET.ModVerify.App.Reporting; -internal sealed class EngineInitializeProgressReporter : IDisposable -{ - private Progress? _progress; - - public EngineInitializeProgressReporter(Progress? progress) +internal sealed class EngineInitializeProgressReporter(GameEngineType engine) : IGameEngineInitializationReporter +{ + public void ReportProgress(string message) { - if (progress is null) - return; - progress.ProgressChanged += OnProgress; + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine(message); + Console.ResetColor(); } - private void OnProgress(object sender, string e) + public void ReportStarted() { - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.WriteLine(e); - Console.ResetColor(); + Console.WriteLine($"Initializing game engine '{engine}'..."); } - public void Dispose() + public void ReportFinished() { + Console.WriteLine($"Game engine initialized."); Console.WriteLine(); - if (_progress is not null) - _progress.ProgressChanged -= OnProgress; - _progress = null; } } \ No newline at end of file From 2772bb5098566e11c16b11cd00ac16a7125b0f2e Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 13 Dec 2025 18:45:47 +0100 Subject: [PATCH 05/38] reorganize solution --- ModVerify.slnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModVerify.slnx b/ModVerify.slnx index ebbde63..3527ff4 100644 --- a/ModVerify.slnx +++ b/ModVerify.slnx @@ -17,12 +17,12 @@ - + From fdd9e38d944a178a89052b983977ef1da2e7a707 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 13 Dec 2025 18:46:36 +0100 Subject: [PATCH 06/38] basic support for VerificationTarget type --- modules/ModdingToolBase | 2 +- .../ModSelectors/AutomaticModSelector.cs | 2 +- .../ModSelectors/ConsoleModSelector.cs | 4 +- .../ModSelectors/ManualModSelector.cs | 5 +- .../ModSelectors/ModSelectorBase.cs | 2 +- .../ModSelectors/SettingsBasedModSelector.cs | 11 +- src/ModVerify.CliApp/ModVerifyApplication.cs | 5 +- .../Resources/Baselines/baseline-foc.json | 2045 ++++++++++++----- src/ModVerify/ModVerify.csproj | 4 +- .../Reporting/Json/JsonGameLocation.cs | 38 + .../Json/JsonVerificationBaseline.cs | 12 +- .../Reporting/Json/JsonVerificationTarget.cs | 51 + .../Reporting/VerificationBaseline.cs | 17 +- .../Schemas/{2.0 => 2.1}/baseline.json | 56 +- src/ModVerify/VerificationTarget.cs | 4 +- .../PG.StarWarsGame.Engine/GameLocations.cs | 7 +- 16 files changed, 1703 insertions(+), 562 deletions(-) create mode 100644 src/ModVerify/Reporting/Json/JsonGameLocation.cs create mode 100644 src/ModVerify/Reporting/Json/JsonVerificationTarget.cs rename src/ModVerify/Resources/Schemas/{2.0 => 2.1}/baseline.json (56%) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index da19380..a0ad12e 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit da19380d04632302b3806af778e3e6696e14ddc3 +Subproject commit a0ad12e651dcb92cd4f1067059824d8f2894c63f diff --git a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs index 717db7b..d12cd6b 100644 --- a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs @@ -39,7 +39,7 @@ internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelec catch (GameNotFoundException) { Logger?.LogError(ModVerifyConstants.ConsoleEventId, "Unable to find games based of the given location '{SettingsGamePath}'. Consider specifying all paths manually.", settings.GamePath); - targetObject = null!; + targetObject = null; return null; } diff --git a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs index c776d6d..a04cbea 100644 --- a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs @@ -14,7 +14,9 @@ namespace AET.ModVerify.App.ModSelectors; internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider) { - public override GameLocations Select(GameInstallationsSettings settings, out IPhysicalPlayableObject targetObject, + public override GameLocations Select( + GameInstallationsSettings settings, + out IPhysicalPlayableObject targetObject, out GameEngineType? actualEngineType) { var gameResult = GameFinderService.FindGames(); diff --git a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs index 34cf39d..a8b5ab7 100644 --- a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AET.ModVerify.App.Settings; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure; @@ -22,8 +23,8 @@ public override GameLocations Select( throw new ArgumentException("Argument --game must be set."); return new GameLocations( - settings.ModPaths, + settings.ModPaths.ToList(), settings.GamePath!, - GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths)); + GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths).ToList()); } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs index 8dd1d90..53b4b20 100644 --- a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs +++ b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs @@ -34,7 +34,7 @@ protected GameLocations GetLocations(IPhysicalPlayableObject playableObject, Gam { var fallbacks = GetFallbackPaths(finderResult, playableObject, additionalFallbackPaths); var modPaths = GetModPaths(playableObject); - return new GameLocations(modPaths, playableObject.Game.Directory.FullName, fallbacks); + return new GameLocations(modPaths.ToList(), playableObject.Game.Directory.FullName, fallbacks.ToList()); } private static IList GetFallbackPaths(GameFinderResult finderResult, IPlayableObject gameOrMod, IList additionalFallbackPaths) diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs index f6538c5..8ff890a 100644 --- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs @@ -25,11 +25,12 @@ public VerificationTarget CreateInstallationDataFromSettings(GameInstallationsSe { Engine = engineType.Value, Location = gameLocations, - Name = GetNameFromGameLocations(targetObject, gameLocations, engineType.Value) + Name = GetNameFromGameLocations(targetObject, gameLocations), + Version = GetTargetVersion(targetObject) }; } - private static string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations, GameEngineType engineType) + private static string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations) { if (targetObject is not null) return targetObject.Name; @@ -37,4 +38,10 @@ private static string GetNameFromGameLocations(IPlayableObject? targetObject, Ga var mod = gameLocations.ModPaths.FirstOrDefault(); return mod ?? gameLocations.GamePath; } + + private static string? GetTargetVersion(IPlayableObject? targetObject) + { + // TODO: Implement version retrieval from targetObject if possible + return null; + } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 44d494e..cd59804 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -102,7 +102,7 @@ private async Task RunVerify() if (!settings.CreateNewBaseline) return 0; - await WriteBaseline(reportSettings, allErrors, settings.NewBaselinePath).ConfigureAwait(false); + await WriteBaseline(verificationTarget, reportSettings, allErrors, settings.NewBaselinePath).ConfigureAwait(false); _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Baseline successfully created."); return 0; @@ -167,11 +167,12 @@ private async Task ReportErrors(IReadOnlyCollection errors) } private async Task WriteBaseline( + VerificationTarget target, GlobalVerifyReportSettings reportSettings, IEnumerable errors, string baselineFile) { - var baseline = new VerificationBaseline(reportSettings.MinimumReportSeverity, errors); + var baseline = new VerificationBaseline(reportSettings.MinimumReportSeverity, errors, target); var fullPath = _fileSystem.Path.GetFullPath(baselineFile); _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Writing Baseline to '{FullPath}'", fullPath); diff --git a/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json b/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json index c94d121..2cb5cac 100644 --- a/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json +++ b/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json @@ -1,5 +1,16 @@ { - "version": "2.0", + "version": "2.1", + "target": { + "name": "Forces of Corruption (SteamGold)", + "engine": "Foc", + "location": { + "modPaths": [], + "gamePath": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption", + "fallbackPaths": [ + "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\GameData" + ] + } + }, "minSeverity": "Information", "errors": [ { @@ -36,12 +47,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_SKIPRAY.ALO\u0027.", + "message": "Unable to find .ALO file \u0027CIN_Reb_CelebHall.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO" - ], - "asset": "Default.fx" + "context": [], + "asset": "CIN_Reb_CelebHall.alo" }, { "id": "FILE00", @@ -49,36 +58,26 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", + "message": "Proxy particle \u0027p_ssd_debris\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\NB_PRISON.ALO" - ], - "asset": "p_smoke_small_thin2" - }, - { - "id": "FILE00", - "verifiers": [ - "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO" ], - "message": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027", - "severity": "Error", - "context": [], - "asset": "W_Kamino_Reflect.ALO" + "asset": "p_ssd_debris" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Proxy particle \u0027p_ssd_debris\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO\u0027", + "message": "Could not find texture \u0027Cin_Reb_CelebHall_Wall.tga\u0027 for context: [W_SITH_LEFTHALL.ALO].", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO" + "W_SITH_LEFTHALL.ALO" ], - "asset": "p_ssd_debris" + "asset": "Cin_Reb_CelebHall_Wall.tga" }, { "id": "FILE00", @@ -86,10 +85,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_p_proton_torpedo.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_ImperialCraft.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_p_proton_torpedo.alo" + "asset": "Cin_ImperialCraft.alo" }, { "id": "FILE00", @@ -97,10 +96,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_DStar_LeverPanel.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_Officer.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_DStar_LeverPanel.alo" + "asset": "Cin_Officer.alo" }, { "id": "FILE00", @@ -108,12 +107,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027lookat\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE.ALO\u0027", + "message": "Unable to find .ALO file \u0027Cin_DStar_protons.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO" - ], - "asset": "lookat" + "context": [], + "asset": "Cin_DStar_protons.alo" }, { "id": "FILE00", @@ -122,12 +119,12 @@ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Could not find texture \u0027Cin_DeathStar.tga\u0027 for context: [ALTTEST.ALO].", + "message": "Could not find texture \u0027w_grenade.tga\u0027 for context: [W_GRENADE.ALO].", "severity": "Error", "context": [ - "ALTTEST.ALO" + "W_GRENADE.ALO" ], - "asset": "Cin_DeathStar.tga" + "asset": "w_grenade.tga" }, { "id": "FILE00", @@ -135,12 +132,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_prison_light\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_03_STATION_D.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\NB_PRISON.ALO" + "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO" ], - "asset": "p_prison_light" + "asset": "p_uwstation_death" }, { "id": "FILE00", @@ -148,26 +145,21 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO\u0027.", + "message": "Unable to find .ALO file \u0027Cin_EI_Vader.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO" - ], - "asset": "Default.fx" + "context": [], + "asset": "Cin_EI_Vader.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Could not find texture \u0027w_grenade.tga\u0027 for context: [W_GRENADE.ALO].", + "message": "Unable to find .ALO file \u0027MODELS\u0027", "severity": "Error", - "context": [ - "W_GRENADE.ALO" - ], - "asset": "w_grenade.tga" + "context": [], + "asset": "MODELS" }, { "id": "FILE00", @@ -175,10 +167,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Rbel_NavyRow.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_DeathStar_Wall.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Rbel_NavyRow.alo" + "asset": "Cin_DeathStar_Wall.alo" }, { "id": "FILE00", @@ -186,10 +178,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_Planet_Alderaan_High.alo\u0027", + "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", "severity": "Error", - "context": [], - "asset": "Cin_Planet_Alderaan_High.alo" + "context": [ + "DATA\\ART\\MODELS\\NB_PRISON.ALO" + ], + "asset": "p_smoke_small_thin2" }, { "id": "FILE00", @@ -197,12 +191,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027lookat\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO\u0027", + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_01_STATION_D.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO" + "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO" ], - "asset": "lookat" + "asset": "p_uwstation_death" }, { "id": "FILE00", @@ -210,12 +204,23 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\EI_MARAJADE.ALO\u0027", + "message": "Unable to find .ALO file \u0027CIN_Officer_Row.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Officer_Row.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\EI_MARAJADE.ALO" + "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO" ], - "asset": "p_desert_ground_dust" + "asset": "Lensflare0" }, { "id": "FILE00", @@ -223,10 +228,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027p_splash_wake_lava.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_DeathStar_Hangar.alo\u0027", "severity": "Error", "context": [], - "asset": "p_splash_wake_lava.alo" + "asset": "CIN_DeathStar_Hangar.alo" }, { "id": "FILE00", @@ -234,10 +239,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_rv_XWingProp.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_EV_lambdaShuttle_150.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_rv_XWingProp.alo" + "asset": "Cin_EV_lambdaShuttle_150.alo" }, { "id": "FILE00", @@ -245,10 +250,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Fire_Huge.alo\u0027", + "message": "Proxy particle \u0027p_smoke_small_thin4\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", "severity": "Error", - "context": [], - "asset": "CIN_Fire_Huge.alo" + "context": [ + "DATA\\ART\\MODELS\\NB_PRISON.ALO" + ], + "asset": "p_smoke_small_thin4" }, { "id": "FILE00", @@ -256,10 +263,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Probe_Droid.alo\u0027", + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_CINE.ALO\u0027", "severity": "Error", - "context": [], - "asset": "CIN_Probe_Droid.alo" + "context": [ + "DATA\\ART\\MODELS\\W_STARS_CINE.ALO" + ], + "asset": "Lensflare0" }, { "id": "FILE00", @@ -267,12 +276,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_05_STATION_D.ALO\u0027", + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_SKIPRAY.ALO\u0027.", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO" + "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO" ], - "asset": "p_uwstation_death" + "asset": "Default.fx" }, { "id": "FILE00", @@ -280,10 +289,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO\u0027", + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_IG88.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO" + "DATA\\ART\\MODELS\\UI_IG88.ALO" ], "asset": "p_desert_ground_dust" }, @@ -293,26 +302,34 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_steam_small\u0027 not found for model \u0027DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO\u0027", + "message": "Unable to find .ALO file \u0027CIN_p_proton_torpedo.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO" + "context": [], + "asset": "CIN_p_proton_torpedo.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "asset": "p_steam_small" + "message": "Unable to find .ALO file \u0027CIN_Fire_Huge.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Fire_Huge.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Could not find texture \u0027p_particle_master\u0027 for context: [P_DIRT_EMITTER_TEST1.ALO].", + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_04_STATION_D.ALO\u0027", "severity": "Error", "context": [ - "P_DIRT_EMITTER_TEST1.ALO" + "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO" ], - "asset": "p_particle_master" + "asset": "p_uwstation_death" }, { "id": "FILE00", @@ -320,12 +337,23 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_smoke_small_thin4\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", + "message": "Unable to find .ALO file \u0027RV_nebulonb_D_death_00.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "RV_nebulonb_D_death_00.ALO" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_02_STATION_D.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\NB_PRISON.ALO" + "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO" ], - "asset": "p_smoke_small_thin4" + "asset": "p_uwstation_death" }, { "id": "FILE00", @@ -333,10 +361,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_EI_Vader.alo\u0027", + "message": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027", "severity": "Error", "context": [], - "asset": "Cin_EI_Vader.alo" + "asset": "W_Kamino_Reflect.ALO" }, { "id": "FILE00", @@ -344,10 +372,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO\u0027", + "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO" + "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO" ], "asset": "p_smoke_small_thin2" }, @@ -357,12 +385,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO\u0027.", + "message": "Proxy particle \u0027p_steam_small\u0027 not found for model \u0027DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO" + "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO" ], - "asset": "Default.fx" + "asset": "p_steam_small" }, { "id": "FILE00", @@ -370,10 +398,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_DeathStar_Wall.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_EV_Stardestroyer_Warp.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_DeathStar_Wall.alo" + "asset": "Cin_EV_Stardestroyer_Warp.alo" }, { "id": "FILE00", @@ -381,10 +409,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027W_droid_steam.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_DStar_TurretLasers.alo\u0027", "severity": "Error", "context": [], - "asset": "W_droid_steam.alo" + "asset": "Cin_DStar_TurretLasers.alo" }, { "id": "FILE00", @@ -392,10 +420,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_DeathStar_High.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Rbel_GreyGroup.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_DeathStar_High.alo" + "asset": "CIN_Rbel_GreyGroup.alo" }, { "id": "FILE00", @@ -403,10 +431,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027MODELS\u0027", + "message": "Unable to find .ALO file \u0027Cin_Planet_Hoth_High.alo\u0027", "severity": "Error", "context": [], - "asset": "MODELS" + "asset": "Cin_Planet_Hoth_High.alo" }, { "id": "FILE00", @@ -414,10 +442,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027W_AllShaders.ALO\u0027", + "message": "Unable to find .ALO file \u0027CIN_Trooper_Row.alo\u0027", "severity": "Error", "context": [], - "asset": "W_AllShaders.ALO" + "asset": "CIN_Trooper_Row.alo" }, { "id": "FILE00", @@ -425,12 +453,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_bomb_spin\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO\u0027", + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_SABOTEUR.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO" + "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO" ], - "asset": "p_bomb_spin" + "asset": "p_desert_ground_dust" }, { "id": "FILE00", @@ -438,12 +466,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_03_STATION_D.ALO\u0027", + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO" + "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO" ], - "asset": "p_uwstation_death" + "asset": "Lensflare0" }, { "id": "FILE00", @@ -451,10 +479,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Rbel_GreyGroup.alo\u0027", + "message": "Unable to find .ALO file \u0027W_AllShaders.ALO\u0027", "severity": "Error", "context": [], - "asset": "CIN_Rbel_GreyGroup.alo" + "asset": "W_AllShaders.ALO" }, { "id": "FILE00", @@ -462,12 +490,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_cold_tiny01\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_SCH.ALO\u0027", + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO\u0027.", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\NB_SCH.ALO" + "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO" ], - "asset": "p_cold_tiny01" + "asset": "Default.fx" }, { "id": "FILE00", @@ -486,34 +514,38 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_hp_archammer-damage\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO\u0027", + "message": "Unable to find .ALO file \u0027Cin_EI_Palpatine.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO" - ], - "asset": "p_hp_archammer-damage" + "context": [], + "asset": "Cin_EI_Palpatine.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Unable to find .ALO file \u0027CIN_Rbel_grey.alo\u0027", + "message": "Could not find texture \u0027Cin_DeathStar.tga\u0027 for context: [ALTTEST.ALO].", "severity": "Error", - "context": [], - "asset": "CIN_Rbel_grey.alo" + "context": [ + "ALTTEST.ALO" + ], + "asset": "Cin_DeathStar.tga" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Unable to find .ALO file \u0027CIN_Reb_CelebHall.alo\u0027", + "message": "Could not find texture \u0027UB_girder_B.tga\u0027 for context: [UV_MDU_CAGE.ALO].", "severity": "Error", - "context": [], - "asset": "CIN_Reb_CelebHall.alo" + "context": [ + "UV_MDU_CAGE.ALO" + ], + "asset": "UB_girder_B.tga" }, { "id": "FILE00", @@ -534,10 +566,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_ImperialCraft.alo\u0027", + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO\u0027.", "severity": "Error", - "context": [], - "asset": "Cin_ImperialCraft.alo" + "context": [ + "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO" + ], + "asset": "Default.fx" }, { "id": "FILE00", @@ -545,10 +579,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_DStar_Dish_close.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_Planet_Alderaan_High.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_DStar_Dish_close.alo" + "asset": "Cin_Planet_Alderaan_High.alo" }, { "id": "FILE00", @@ -556,12 +590,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027pe_bwing_yellow\u0027 not found for model \u0027DATA\\ART\\MODELS\\RV_BWING.ALO\u0027", + "message": "Proxy particle \u0027p_hp_archammer-damage\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\RV_BWING.ALO" + "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO" ], - "asset": "pe_bwing_yellow" + "asset": "p_hp_archammer-damage" }, { "id": "FILE00", @@ -569,10 +603,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_bridge.alo\u0027", + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_05_STATION_D.ALO\u0027", "severity": "Error", - "context": [], - "asset": "Cin_bridge.alo" + "context": [ + "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO" + ], + "asset": "p_uwstation_death" }, { "id": "FILE00", @@ -580,12 +616,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_SABOTEUR.ALO\u0027", + "message": "Proxy particle \u0027p_explosion_smoke_small_thin5\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO" + "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO" ], - "asset": "p_desert_ground_dust" + "asset": "p_explosion_smoke_small_thin5" }, { "id": "FILE00", @@ -593,10 +629,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Trooper_Row.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Probe_Droid.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Trooper_Row.alo" + "asset": "CIN_Probe_Droid.alo" }, { "id": "FILE00", @@ -604,10 +640,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_EV_TieAdvanced.alo\u0027", + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_HIGH.ALO\u0027", "severity": "Error", - "context": [], - "asset": "Cin_EV_TieAdvanced.alo" + "context": [ + "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO" + ], + "asset": "Lensflare0" }, { "id": "FILE00", @@ -615,38 +653,58 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027w_sith_arch.alo\u0027", + "message": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027", "severity": "Error", "context": [], - "asset": "w_sith_arch.alo" + "asset": "W_Volcano_Rock02.ALO" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Could not find texture \u0027NB_YsalamiriTree_B.tga\u0027 for context: [UV_MDU_CAGE.ALO].", + "message": "Proxy particle \u0027lookat\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE.ALO\u0027", "severity": "Error", "context": [ - "UV_MDU_CAGE.ALO" + "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO" ], - "asset": "NB_YsalamiriTree_B.tga" + "asset": "lookat" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Could not find texture \u0027W_TE_Rock_f_02_b.tga\u0027 for context: [EV_TIE_PHANTOM.ALO].", + "message": "Unable to find .ALO file \u0027Cin_Shuttle_Tyderium.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_Shuttle_Tyderium.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_SwampGasEmit.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_SwampGasEmit.ALO" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027P_heat_small01\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_VCH.ALO\u0027", "severity": "Error", "context": [ - "EV_TIE_PHANTOM.ALO" + "DATA\\ART\\MODELS\\NB_VCH.ALO" ], - "asset": "W_TE_Rock_f_02_b.tga" + "asset": "P_heat_small01" }, { "id": "FILE00", @@ -654,10 +712,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_EI_Palpatine.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Rbel_NavyRow.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_EI_Palpatine.alo" + "asset": "CIN_Rbel_NavyRow.alo" }, { "id": "FILE00", @@ -665,10 +723,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Rbel_Soldier.alo" + "asset": "CIN_Fire_Medium.alo" }, { "id": "FILE00", @@ -676,12 +734,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO\u0027", + "message": "Proxy particle \u0027p_ewok_drag_dirt\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO" + "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO" ], - "asset": "p_smoke_small_thin2" + "asset": "p_ewok_drag_dirt" }, { "id": "FILE00", @@ -700,10 +758,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Officer_Row.alo\u0027", + "message": "Unable to find .ALO file \u0027W_droid_steam.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Officer_Row.alo" + "asset": "W_droid_steam.alo" }, { "id": "FILE00", @@ -711,26 +769,21 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_HIGH.ALO\u0027", + "message": "Unable to find .ALO file \u0027CIN_Biker_Row.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO" - ], - "asset": "Lensflare0" + "context": [], + "asset": "CIN_Biker_Row.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Could not find texture \u0027Cin_Reb_CelebHall_Wall_B.tga\u0027 for context: [W_SITH_LEFTHALL.ALO].", + "message": "Unable to find .ALO file \u0027w_planet_volcanic.alo\u0027", "severity": "Error", - "context": [ - "W_SITH_LEFTHALL.ALO" - ], - "asset": "Cin_Reb_CelebHall_Wall_B.tga" + "context": [], + "asset": "w_planet_volcanic.alo" }, { "id": "FILE00", @@ -738,10 +791,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_Coruscant.alo\u0027", + "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO\u0027", "severity": "Error", - "context": [], - "asset": "Cin_Coruscant.alo" + "context": [ + "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO" + ], + "asset": "p_smoke_small_thin2" }, { "id": "FILE00", @@ -749,12 +804,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_ewok_drag_dirt\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO\u0027", + "message": "Proxy particle \u0027lookat\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO" + "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO" ], - "asset": "p_ewok_drag_dirt" + "asset": "lookat" }, { "id": "FILE00", @@ -762,12 +817,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027P_heat_small01\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_VCH.ALO\u0027", + "message": "Unable to find .ALO file \u0027CIN_REb_CelebCharacters.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\NB_VCH.ALO" - ], - "asset": "P_heat_small01" + "context": [], + "asset": "CIN_REb_CelebCharacters.alo" }, { "id": "FILE00", @@ -775,36 +828,38 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027W_Vol_Steam01.ALO\u0027", + "message": "Unable to find .ALO file \u0027Cin_DeathStar_High.alo\u0027", "severity": "Error", "context": [], - "asset": "W_Vol_Steam01.ALO" + "asset": "Cin_DeathStar_High.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Proxy particle \u0027p_explosion_smoke_small_thin5\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO\u0027", + "message": "Could not find texture \u0027Cin_Reb_CelebHall_Wall_B.tga\u0027 for context: [W_SITH_LEFTHALL.ALO].", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO" + "W_SITH_LEFTHALL.ALO" ], - "asset": "p_explosion_smoke_small_thin5" + "asset": "Cin_Reb_CelebHall_Wall_B.tga" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO\u0027", + "message": "Could not find texture \u0027NB_YsalamiriTree_B.tga\u0027 for context: [UV_MDU_CAGE.ALO].", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO" + "UV_MDU_CAGE.ALO" ], - "asset": "Lensflare0" + "asset": "NB_YsalamiriTree_B.tga" }, { "id": "FILE00", @@ -812,12 +867,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_02_STATION_D.ALO\u0027", + "message": "Unable to find .ALO file \u0027Cin_Coruscant.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO" - ], - "asset": "p_uwstation_death" + "context": [], + "asset": "Cin_Coruscant.alo" }, { "id": "FILE00", @@ -825,10 +878,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_Officer.alo\u0027", + "message": "Proxy particle \u0027p_prison_light\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", "severity": "Error", - "context": [], - "asset": "Cin_Officer.alo" + "context": [ + "DATA\\ART\\MODELS\\NB_PRISON.ALO" + ], + "asset": "p_prison_light" }, { "id": "FILE00", @@ -836,12 +891,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_04_STATION_D.ALO\u0027", + "message": "Proxy particle \u0027p_cold_tiny01\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_SCH.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO" + "DATA\\ART\\MODELS\\NB_SCH.ALO" ], - "asset": "p_uwstation_death" + "asset": "p_cold_tiny01" }, { "id": "FILE00", @@ -849,10 +904,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Lambda_Head.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_NavyTrooper_Row.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Lambda_Head.alo" + "asset": "CIN_NavyTrooper_Row.alo" }, { "id": "FILE00", @@ -860,10 +915,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Biker_Row.alo\u0027", + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO\u0027.", "severity": "Error", - "context": [], - "asset": "CIN_Biker_Row.alo" + "context": [ + "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO" + ], + "asset": "Default.fx" }, { "id": "FILE00", @@ -871,10 +928,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_DStar_protons.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_DStar_protons.alo" + "asset": "CIN_Rbel_Soldier.alo" }, { "id": "FILE00", @@ -882,10 +939,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_IG88.ALO\u0027", + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UI_IG88.ALO" + "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO" ], "asset": "p_desert_ground_dust" }, @@ -904,12 +961,15 @@ "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Unable to find .ALO file \u0027Cin_Planet_Hoth_High.alo\u0027", + "message": "Could not find texture \u0027p_particle_master\u0027 for context: [P_DIRT_EMITTER_TEST1.ALO].", "severity": "Error", - "context": [], - "asset": "Cin_Planet_Hoth_High.alo" + "context": [ + "P_DIRT_EMITTER_TEST1.ALO" + ], + "asset": "p_particle_master" }, { "id": "FILE00", @@ -917,12 +977,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_LOW.ALO\u0027", + "message": "Unable to find .ALO file \u0027Cin_bridge.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\W_STARS_LOW.ALO" - ], - "asset": "Lensflare0" + "context": [], + "asset": "Cin_bridge.alo" }, { "id": "FILE00", @@ -930,10 +988,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_NavyTrooper_Row.alo\u0027", + "message": "Unable to find .ALO file \u0027W_Vol_Steam01.ALO\u0027", "severity": "Error", "context": [], - "asset": "CIN_NavyTrooper_Row.alo" + "asset": "W_Vol_Steam01.ALO" }, { "id": "FILE00", @@ -941,10 +999,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_DStar_TurretLasers.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Rbel_grey.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_DStar_TurretLasers.alo" + "asset": "CIN_Rbel_grey.alo" }, { "id": "FILE00", @@ -952,10 +1010,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027W_SwampGasEmit.ALO\u0027", + "message": "Unable to find .ALO file \u0027w_sith_arch.alo\u0027", "severity": "Error", "context": [], - "asset": "W_SwampGasEmit.ALO" + "asset": "w_sith_arch.alo" }, { "id": "FILE00", @@ -963,10 +1021,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_Shuttle_Tyderium.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_rv_XWingProp.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_Shuttle_Tyderium.alo" + "asset": "Cin_rv_XWingProp.alo" }, { "id": "FILE00", @@ -974,23 +1032,24 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_EV_Stardestroyer_Warp.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_DStar_Dish_close.alo\u0027", "severity": "Error", "context": [], - "asset": "Cin_EV_Stardestroyer_Warp.alo" + "asset": "Cin_DStar_Dish_close.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", + "AET.ModVerify.Verifiers.Commons.TextureVeifier" ], - "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO\u0027", + "message": "Could not find texture \u0027W_TE_Rock_f_02_b.tga\u0027 for context: [EV_TIE_PHANTOM.ALO].", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO" + "EV_TIE_PHANTOM.ALO" ], - "asset": "Lensflare0" + "asset": "W_TE_Rock_f_02_b.tga" }, { "id": "FILE00", @@ -998,10 +1057,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_DeathStar_Hangar.alo\u0027", + "message": "Proxy particle \u0027pe_bwing_yellow\u0027 not found for model \u0027DATA\\ART\\MODELS\\RV_BWING.ALO\u0027", "severity": "Error", - "context": [], - "asset": "CIN_DeathStar_Hangar.alo" + "context": [ + "DATA\\ART\\MODELS\\RV_BWING.ALO" + ], + "asset": "pe_bwing_yellow" }, { "id": "FILE00", @@ -1009,10 +1070,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027", + "message": "Unable to find .ALO file \u0027CIN_Lambda_Head.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Fire_Medium.alo" + "asset": "CIN_Lambda_Head.alo" }, { "id": "FILE00", @@ -1020,12 +1081,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_CINE.ALO\u0027", + "message": "Proxy particle \u0027p_explosion_small_delay00\u0027 not found for model \u0027DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO\u0027", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\W_STARS_CINE.ALO" + "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO" ], - "asset": "Lensflare0" + "asset": "p_explosion_small_delay00" }, { "id": "FILE00", @@ -1033,10 +1094,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier_Group.alo\u0027", + "message": "Unable to find .ALO file \u0027Cin_DStar_LeverPanel.alo\u0027", "severity": "Error", "context": [], - "asset": "CIN_Rbel_Soldier_Group.alo" + "asset": "Cin_DStar_LeverPanel.alo" }, { "id": "FILE00", @@ -1044,10 +1105,10 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027RV_nebulonb_D_death_00.ALO\u0027", + "message": "Unable to find .ALO file \u0027p_splash_wake_lava.alo\u0027", "severity": "Error", "context": [], - "asset": "RV_nebulonb_D_death_00.ALO" + "asset": "p_splash_wake_lava.alo" }, { "id": "FILE00", @@ -1055,10 +1116,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027", + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_LOW.ALO\u0027", "severity": "Error", - "context": [], - "asset": "W_Volcano_Rock02.ALO" + "context": [ + "DATA\\ART\\MODELS\\W_STARS_LOW.ALO" + ], + "asset": "Lensflare0" }, { "id": "FILE00", @@ -1066,10 +1129,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027w_planet_volcanic.alo\u0027", + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\EI_MARAJADE.ALO\u0027", "severity": "Error", - "context": [], - "asset": "w_planet_volcanic.alo" + "context": [ + "DATA\\ART\\MODELS\\EI_MARAJADE.ALO" + ], + "asset": "p_desert_ground_dust" }, { "id": "FILE00", @@ -1077,10 +1142,12 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Unable to find .ALO file \u0027CIN_REb_CelebCharacters.alo\u0027", + "message": "Proxy particle \u0027p_bomb_spin\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO\u0027", "severity": "Error", - "context": [], - "asset": "CIN_REb_CelebCharacters.alo" + "context": [ + "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO" + ], + "asset": "p_bomb_spin" }, { "id": "FILE00", @@ -1088,257 +1155,249 @@ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO\u0027.", + "message": "Unable to find .ALO file \u0027Cin_EV_TieAdvanced.alo\u0027", "severity": "Error", - "context": [ - "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO" - ], - "asset": "Default.fx" + "context": [], + "asset": "Cin_EV_TieAdvanced.alo" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" ], - "message": "Could not find texture \u0027Cin_Reb_CelebHall_Wall.tga\u0027 for context: [W_SITH_LEFTHALL.ALO].", + "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier_Group.alo\u0027", "severity": "Error", - "context": [ - "W_SITH_LEFTHALL.ALO" - ], - "asset": "Cin_Reb_CelebHall_Wall.tga" + "context": [], + "asset": "CIN_Rbel_Soldier_Group.alo" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Unable to find .ALO file \u0027Cin_EV_lambdaShuttle_150.alo\u0027", + "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.", "severity": "Error", - "context": [], - "asset": "Cin_EV_lambdaShuttle_150.alo" + "context": [ + "Unit_Move_Leia" + ], + "asset": "U000_LEI0213_ENG.WAV" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Proxy particle \u0027p_explosion_small_delay00\u0027 not found for model \u0027DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO\u0027", + "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO" + "Unit_Select_Leia" ], - "asset": "p_explosion_small_delay00" + "asset": "U000_LEI0113_ENG.WAV" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier", - "AET.ModVerify.Verifiers.Commons.TextureVeifier" + "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Could not find texture \u0027UB_girder_B.tga\u0027 for context: [UV_MDU_CAGE.ALO].", + "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "UV_MDU_CAGE.ALO" + "Unit_Increase_Production_Leia" ], - "asset": "UB_girder_B.tga" + "asset": "U000_LEI0603_ENG.WAV" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_01_STATION_D.ALO\u0027", + "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO" + "Unit_Attack_Leia" ], - "asset": "p_uwstation_death" + "asset": "U000_LEI0309_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0206_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Move_Leia" ], - "asset": "U000_LEI0206_ENG.WAV" + "asset": "U000_LEI0212_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Assist_Move_Missile_Launcher" ], - "asset": "U000_LEI0204_ENG.WAV" + "asset": "U000_MAL0503_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_StarDest_MC30_Frigate" ], - "asset": "U000_LEI0102_ENG.WAV" + "asset": "U000_MCF1601_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0111_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0215_ENG.WAV" + "asset": "U000_LEI0111_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Complete_Troops_Arc_Hammer" ], - "asset": "U000_LEI0107_ENG.WAV" + "asset": "U000_ARC3106_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Remove_Corruption_Leia" + "Unit_Attack_Leia" ], - "asset": "U000_LEI0504_ENG.WAV" + "asset": "U000_LEI0303_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Weather_Ambient_Clear_Sandstorm_Loop" + "Unit_Guard_Leia" ], - "asset": "AMB_DES_CLEAR_LOOP_1.WAV" + "asset": "U000_LEI0404_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Move_Gneneric_Test" ], - "asset": "U000_LEI0105_ENG.WAV" + "asset": "TESTUNITMOVE_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Guard_Leia" ], - "asset": "U000_LEI0213_ENG.WAV" + "asset": "U000_LEI0401_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Star_Viper_Spinning_By" ], - "asset": "U000_LEI0201_ENG.WAV" + "asset": "EGL_STAR_VIPER_SPINNING_1.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Unit_Move_Tie_Mauler" ], - "asset": "U000_LEI0303_ENG.WAV" + "asset": "U000_TMC0212_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Select_Leia" ], - "asset": "U000_LEI0103_ENG.WAV" + "asset": "U000_LEI0110_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Attack_Leia" ], - "asset": "U000_LEI0207_ENG.WAV" + "asset": "U000_LEI0314_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Corrupt_Sabateur" + "Unit_Attack_Leia" ], - "asset": "U000_DEF3006_ENG.WAV" + "asset": "U000_LEI0305_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0309_ENG.WAV" + "asset": "U000_LEI0112_ENG.WAV" }, { "id": "FILE00", @@ -1357,120 +1416,120 @@ "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Weaken_Sabateur" + "Unit_Move_Leia" ], - "asset": "U000_DEF3106_ENG.WAV" + "asset": "U000_LEI0211_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Remove_Corruption_Leia" + "Unit_Move_Leia" ], - "asset": "U000_LEI0503_ENG.WAV" + "asset": "U000_LEI0205_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Remove_Corruption_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0502_ENG.WAV" + "asset": "U000_LEI0115_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Increase_Production_Leia" ], - "asset": "U000_LEI0212_ENG.WAV" + "asset": "U000_LEI0604_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Weather_Ambient_Clear_Urban_Loop" + "Unit_Increase_Production_Leia" ], - "asset": "AMB_URB_CLEAR_LOOP_1.WAV" + "asset": "U000_LEI0602_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Attack_Leia" ], - "asset": "U000_LEI0311_ENG.WAV" + "asset": "U000_LEI0315_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Corrupt_Sabateur" ], - "asset": "U000_LEI0115_ENG.WAV" + "asset": "U000_DEF3006_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Move_Leia" ], - "asset": "U000_LEI0101_ENG.WAV" + "asset": "U000_LEI0210_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Guard_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0401_ENG.WAV" + "asset": "U000_LEI0105_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Unit_Move_Leia" ], - "asset": "U000_LEI0315_ENG.WAV" + "asset": "U000_LEI0208_ENG.WAV" }, { "id": "FILE00", @@ -1489,593 +1548,1505 @@ "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Increase_Production_Leia" + "Unit_Move_Leia" ], - "asset": "U000_LEI0603_ENG.WAV" + "asset": "U000_LEI0202_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Attack_Leia" ], - "asset": "U000_LEI0104_ENG.WAV" + "asset": "U000_LEI0306_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Remove_Corruption_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0501_ENG.WAV" + "asset": "U000_LEI0101_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Gneneric_Test" + "EHD_Death_Star_Activate" ], - "asset": "TESTUNITMOVE_ENG.WAV" + "asset": "C000_DST0102_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Select_Leia" ], - "asset": "U000_LEI0108_ENG.WAV" + "asset": "U000_LEI0103_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_StarDest_MC30_Frigate" + "Unit_Guard_Leia" ], - "asset": "U000_MCF1601_ENG.WAV" + "asset": "U000_LEI0403_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0111_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "SFX_Anim_Beetle_Footsteps" ], - "asset": "U000_LEI0111_ENG.WAV" + "asset": "FS_BEETLE_2.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Move_Leia" ], - "asset": "U000_LEI0211_ENG.WAV" + "asset": "U000_LEI0201_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Move_Leia" ], - "asset": "U000_LEI0110_ENG.WAV" + "asset": "U000_LEI0203_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Guard_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0403_ENG.WAV" + "asset": "U000_LEI0114_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "SFX_Anim_Beetle_Footsteps" ], - "asset": "U000_LEI0306_ENG.WAV" + "asset": "FS_BEETLE_1.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Attack_Leia" ], - "asset": "U000_LEI0308_ENG.WAV" + "asset": "U000_LEI0304_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Attack_Leia" ], - "asset": "U000_LEI0112_ENG.WAV" + "asset": "U000_LEI0301_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Unit_Remove_Corruption_Leia" ], - "asset": "U000_LEI0301_ENG.WAV" + "asset": "U000_LEI0503_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Guard_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0404_ENG.WAV" + "asset": "U000_LEI0109_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Tie_Mauler" + "Unit_Attack_Leia" ], - "asset": "U000_TMC0212_ENG.WAV" + "asset": "U000_LEI0308_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Star_Viper_Spinning_By" + "Unit_Guard_Leia" ], - "asset": "EGL_STAR_VIPER_SPINNING_1.WAV" + "asset": "U000_LEI0402_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0208_ENG.WAV" + "asset": "U000_LEI0108_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0307_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Increase_Production_Leia" + "Unit_Attack_Leia" ], - "asset": "U000_LEI0604_ENG.WAV" + "asset": "U000_LEI0307_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "SFX_Anim_Beetle_Footsteps" + "Unit_Attack_Leia" ], - "asset": "FS_BEETLE_3.WAV" + "asset": "U000_LEI0311_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Select_Leia" ], - "asset": "U000_LEI0109_ENG.WAV" + "asset": "U000_LEI0102_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Select_Leia" ], - "asset": "U000_LEI0202_ENG.WAV" + "asset": "U000_LEI0104_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Increase_Production_Leia" + "SFX_Anim_Beetle_Footsteps" ], - "asset": "U000_LEI0602_ENG.WAV" + "asset": "FS_BEETLE_3.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ "Unit_Attack_Leia" ], - "asset": "U000_LEI0305_ENG.WAV" + "asset": "U000_LEI0313_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0206_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Assist_Move_Missile_Launcher" + "Unit_Move_Leia" ], - "asset": "U000_MAL0503_ENG.WAV" + "asset": "U000_LEI0206_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Increase_Production_Leia" + "Unit_Produce_Troops_Arc_Hammer" ], - "asset": "U000_LEI0601_ENG.WAV" + "asset": "U000_ARC3104_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Complete_Troops_Arc_Hammer" + "Unit_Attack_Leia" ], - "asset": "U000_ARC3106_ENG.WAV" + "asset": "U000_LEI0312_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "SFX_Anim_Beetle_Footsteps" + "Unit_Move_Leia" ], - "asset": "FS_BEETLE_4.WAV" + "asset": "U000_LEI0215_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "SFX_Anim_Beetle_Footsteps" + "Unit_Select_Leia" ], - "asset": "FS_BEETLE_1.WAV" + "asset": "U000_LEI0107_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Remove_Corruption_Leia" ], - "asset": "U000_LEI0205_ENG.WAV" + "asset": "U000_LEI0501_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Weather_Ambient_Clear_Sandstorm_Loop" ], - "asset": "U000_LEI0113_ENG.WAV" + "asset": "AMB_DES_CLEAR_LOOP_1.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Unit_Remove_Corruption_Leia" ], - "asset": "U000_LEI0314_ENG.WAV" + "asset": "U000_LEI0504_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Unit_Remove_Corruption_Leia" ], - "asset": "U000_LEI0304_ENG.WAV" + "asset": "U000_LEI0502_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "Unit_Weaken_Sabateur" ], - "asset": "U000_LEI0203_ENG.WAV" + "asset": "U000_DEF3106_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "EHD_Death_Star_Activate" + "Unit_Complete_Troops_Arc_Hammer" ], - "asset": "C000_DST0102_ENG.WAV" + "asset": "U000_ARC3105_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Select_Leia" + "Unit_Increase_Production_Leia" ], - "asset": "U000_LEI0114_ENG.WAV" + "asset": "U000_LEI0601_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Produce_Troops_Arc_Hammer" + "Unit_Move_Leia" ], - "asset": "U000_ARC3104_ENG.WAV" + "asset": "U000_LEI0204_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.", + "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "SFX_Anim_Beetle_Footsteps" + "Unit_Move_Leia" ], - "asset": "FS_BEETLE_2.WAV" + "asset": "U000_LEI0207_ENG.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Complete_Troops_Arc_Hammer" + "SFX_Anim_Beetle_Footsteps" ], - "asset": "U000_ARC3105_ENG.WAV" + "asset": "FS_BEETLE_4.WAV" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.AudioFilesVerifier" ], - "message": "Audio file \u0027U000_LEI0307_ENG.WAV\u0027 could not be found.", + "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "Weather_Ambient_Clear_Urban_Loop" ], - "asset": "U000_LEI0307_ENG.WAV" + "asset": "AMB_URB_CLEAR_LOOP_1.WAV" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.AudioFilesVerifier" + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" ], - "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.", + "message": "Could not find GUI texture \u0027i_dialogue_button_large_middle_off.tga\u0027 at location \u0027Repository\u0027.", "severity": "Error", "context": [ - "Unit_Guard_Leia" + "IDC_PLAY_FACTION_B_BUTTON_BIG", + "Repository" ], - "asset": "U000_LEI0402_ENG.WAV" + "asset": "i_dialogue_button_large_middle_off.tga" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.AudioFilesVerifier" + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" ], - "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.", + "message": "Could not find GUI texture \u0027underworld_logo_selected.tga\u0027 at location \u0027MegaTexture\u0027.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "IDC_PLAY_FACTION_A_BUTTON_BIG", + "MegaTexture" ], - "asset": "U000_LEI0312_ENG.WAV" + "asset": "underworld_logo_selected.tga" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.AudioFilesVerifier" + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" ], - "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.", + "message": "Could not find GUI texture \u0027underworld_logo_off.tga\u0027 at location \u0027MegaTexture\u0027.", "severity": "Error", "context": [ - "Unit_Move_Leia" + "IDC_PLAY_FACTION_A_BUTTON_BIG", + "MegaTexture" ], - "asset": "U000_LEI0210_ENG.WAV" + "asset": "underworld_logo_off.tga" }, { "id": "FILE00", "verifiers": [ - "AET.ModVerify.Verifiers.AudioFilesVerifier" + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" ], - "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.", + "message": "Could not find GUI texture \u0027i_button_petro_sliver.tga\u0027 at location \u0027MegaTexture\u0027.", "severity": "Error", "context": [ - "Unit_Attack_Leia" + "IDC_MENU_PETRO_LOGO", + "MegaTexture" ], - "asset": "U000_LEI0313_ENG.WAV" + "asset": "i_button_petro_sliver.tga" }, { "id": "FILE00", "verifiers": [ "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" ], - "message": "Could not find GUI texture \u0027underworld_logo_selected.tga\u0027 at location \u0027MegaTexture\u0027.", + "message": "Could not find GUI texture \u0027underworld_logo_rollover.tga\u0027 at location \u0027MegaTexture\u0027.", "severity": "Error", "context": [ "IDC_PLAY_FACTION_A_BUTTON_BIG", "MegaTexture" ], - "asset": "underworld_logo_selected.tga" + "asset": "underworld_logo_rollover.tga" }, { - "id": "FILE00", + "id": "CMDBAR05", "verifiers": [ - "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "message": "Could not find GUI texture \u0027i_button_petro_sliver.tga\u0027 at location \u0027MegaTexture\u0027.", - "severity": "Error", - "context": [ - "IDC_MENU_PETRO_LOGO", - "MegaTexture" + "message": "The CommandBar component \u0027g_planet_land_forces\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_planet_land_forces" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "asset": "i_button_petro_sliver.tga" + "message": "The CommandBar component \u0027g_ground_sell\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_ground_sell" }, { - "id": "FILE00", + "id": "CMDBAR05", "verifiers": [ - "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "message": "Could not find GUI texture \u0027i_dialogue_button_large_middle_off.tga\u0027 at location \u0027Repository\u0027.", - "severity": "Error", - "context": [ - "IDC_PLAY_FACTION_B_BUTTON_BIG", - "Repository" + "message": "The CommandBar component \u0027st_bracket_medium\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_bracket_medium" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "asset": "i_dialogue_button_large_middle_off.tga" + "message": "The CommandBar component \u0027b_planet_right\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "b_planet_right" }, { - "id": "FILE00", + "id": "CMDBAR04", "verifiers": [ - "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "message": "Could not find GUI texture \u0027underworld_logo_rollover.tga\u0027 at location \u0027MegaTexture\u0027.", - "severity": "Error", - "context": [ - "IDC_PLAY_FACTION_A_BUTTON_BIG", - "MegaTexture" + "message": "The CommandBar component \u0027g_credit_bar\u0027 is not supported by the game.", + "severity": "Information", + "context": [], + "asset": "g_credit_bar" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "asset": "underworld_logo_rollover.tga" + "message": "The CommandBar component \u0027zoomed_header_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "zoomed_header_text" }, { - "id": "FILE00", + "id": "CMDBAR05", "verifiers": [ - "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "message": "Could not find GUI texture \u0027underworld_logo_off.tga\u0027 at location \u0027MegaTexture\u0027.", - "severity": "Error", - "context": [ - "IDC_PLAY_FACTION_A_BUTTON_BIG", - "MegaTexture" + "message": "The CommandBar component \u0027g_space_level_pips\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_space_level_pips" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" ], - "asset": "underworld_logo_off.tga" + "message": "The CommandBar component \u0027bribed_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "bribed_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_header_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_header_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tutorial_text_back\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tutorial_text_back" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_back\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_back" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027balance_pip\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "balance_pip" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027objective_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "objective_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027skirmish_upgrade\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "skirmish_upgrade" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027surface_mod_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "surface_mod_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_hero_health\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_hero_health" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027bribe_display\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "bribe_display" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_build\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_build" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027garrison_slot_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "garrison_slot_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_conflict\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_conflict" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tooltip_name\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tooltip_name" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027garrison_respawn_counter\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "garrison_respawn_counter" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_ability_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_ability_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_shields_medium\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_shields_medium" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_health\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_health" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_weather\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_weather" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_health_medium\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_health_medium" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_power\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_power" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_ground_level_pips\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_ground_level_pips" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027zoomed_cost_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "zoomed_cost_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027bm_title_4011\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "bm_title_4011" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_planet_name\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_planet_name" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_shields_large\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_shields_large" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_hero_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_hero_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027generic_flytext\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "generic_flytext" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027reinforcement_counter\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "reinforcement_counter" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_planet_value\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_planet_value" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027radar_blip\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "radar_blip" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_political_control\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_political_control" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_planet_ring\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_planet_ring" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_garrison_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_garrison_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_right_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_right_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027b_quick_ref\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "b_quick_ref" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027objective_back\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "objective_back" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_center_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_center_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_shields\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_shields" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_grab_bar\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_grab_bar" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_smuggler\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_smuggler" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_enemy_hero\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_enemy_hero" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027cs_ability_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "cs_ability_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_cost_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_cost_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_planet_ability\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_planet_ability" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_control_group\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_control_group" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027gui_dialog_tooltip\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "gui_dialog_tooltip" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027remote_bomb_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "remote_bomb_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tutorial_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tutorial_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_space_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_space_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_bracket_large\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_bracket_large" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027zoomed_back\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "zoomed_back" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027encyclopedia_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "encyclopedia_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027zoomed_right_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "zoomed_right_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027b_beacon_t\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "b_beacon_t" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_bounty_hunter\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_bounty_hunter" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_credit_bar\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_credit_bar" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_hero\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_hero" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027bm_title_4010\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "bm_title_4010" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_planet_fleet\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_planet_fleet" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_corruption_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_corruption_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_smuggled\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_smuggled" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027help_back\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "help_back" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_bracket_small\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_bracket_small" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027objective_header_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "objective_header_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_corruption_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_corruption_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_ground_level\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_ground_level" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027lt_weather_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "lt_weather_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027cs_ability_button\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "cs_ability_button" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_radar_view\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_radar_view" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027objective_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "objective_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tooltip_back\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tooltip_back" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027zoomed_center_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "zoomed_center_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_health_bar\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_health_bar" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027zoomed_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "zoomed_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027generic_collision\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "generic_collision" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tooltip_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tooltip_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_radar_blip\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_radar_blip" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_hero_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_hero_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_space_level\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_space_level" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027b_planet_left\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "b_planet_left" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tooltip_icon_land\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tooltip_icon_land" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_ground_icon\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_ground_icon" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tooltip_price\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tooltip_price" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tooltip_left_text\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tooltip_left_text" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027st_health_large\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "st_health_large" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027tactical_sell\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "tactical_sell" + }, + { + "id": "CMDBAR05", + "verifiers": [ + "AET.ModVerify.Verifiers.CommandBarVerifier" + ], + "message": "The CommandBar component \u0027g_special_ability\u0027 is not connected to a shell component.", + "severity": "Warning", + "context": [], + "asset": "g_special_ability" } ] } \ No newline at end of file diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index fe15b1c..89c4b64 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -25,11 +25,11 @@ - + - + diff --git a/src/ModVerify/Reporting/Json/JsonGameLocation.cs b/src/ModVerify/Reporting/Json/JsonGameLocation.cs new file mode 100644 index 0000000..3f088f1 --- /dev/null +++ b/src/ModVerify/Reporting/Json/JsonGameLocation.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Reporting.Json; + +internal class JsonGameLocation +{ + [JsonPropertyName("modPaths")] + public IReadOnlyList ModPaths { get; } + + [JsonPropertyName("gamePath")] + public string GamePath { get; } + + [JsonPropertyName("fallbackPaths")] + public IReadOnlyList FallbackPaths { get; } + + [JsonConstructor] + private JsonGameLocation(IReadOnlyList modPaths, string gamePath, IReadOnlyList fallbackPaths) + { + ModPaths = modPaths; + GamePath = gamePath; + FallbackPaths = fallbackPaths; + } + + public JsonGameLocation(GameLocations location) + { + ModPaths = location.ModPaths.ToArray(); + GamePath = location.GamePath; + FallbackPaths = location.FallbackPaths.ToArray(); + } + + public static GameLocations ToLocation(JsonGameLocation jsonLocation) + { + return new GameLocations(jsonLocation.ModPaths, jsonLocation.GamePath, jsonLocation.FallbackPaths); + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs b/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs index 4688c98..0d9a1b7 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs @@ -10,6 +10,10 @@ internal class JsonVerificationBaseline [JsonPropertyName("version")] public Version? Version { get; } + [JsonPropertyName("target")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public JsonVerificationTarget? Target { get; } + [JsonPropertyName("minSeverity")] [JsonConverter(typeof(JsonStringEnumConverter))] public VerificationSeverity MinimumSeverity { get; } @@ -22,11 +26,17 @@ public JsonVerificationBaseline(VerificationBaseline baseline) Errors = baseline.Select(x => new JsonVerificationError(x)); Version = baseline.Version; MinimumSeverity = baseline.MinimumSeverity; + Target = baseline.Target is not null ? new JsonVerificationTarget(baseline.Target) : null; } [JsonConstructor] - private JsonVerificationBaseline(Version version, VerificationSeverity minimumSeverity, IEnumerable errors) + private JsonVerificationBaseline( + JsonVerificationTarget target, + Version version, + VerificationSeverity minimumSeverity, + IEnumerable errors) { + Target = target; Errors = errors; Version = version; MinimumSeverity = minimumSeverity; diff --git a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs new file mode 100644 index 0000000..4b74b6f --- /dev/null +++ b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs @@ -0,0 +1,51 @@ +using System.Text.Json.Serialization; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Reporting.Json; + +internal class JsonVerificationTarget +{ + [JsonPropertyName("name")] + public string Name { get; } + + [JsonPropertyName("engine")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public GameEngineType Engine { get; } + + [JsonPropertyName("version")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Version{ get; } + + [JsonPropertyName("location")] + public JsonGameLocation Location { get; } + + [JsonConstructor] + private JsonVerificationTarget(string name, string? version, JsonGameLocation location, GameEngineType engine) + { + Name = name; + Version = version; + Engine = engine; + Location = location; + } + + public JsonVerificationTarget(VerificationTarget target) + { + Name = target.Name; + Version = target.Version; + Engine = target.Engine; + Location = new JsonGameLocation(target.Location); + } + + public static VerificationTarget? ToTarget(JsonVerificationTarget? jsonTarget) + { + if (jsonTarget is null) + return null!; + return new VerificationTarget + { + Engine = jsonTarget.Engine, + Name = jsonTarget.Name, + Location = JsonGameLocation.ToLocation(jsonTarget.Location), + Version = jsonTarget.Version + }; + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/VerificationBaseline.cs index c37539b..1cc8312 100644 --- a/src/ModVerify/Reporting/VerificationBaseline.cs +++ b/src/ModVerify/Reporting/VerificationBaseline.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; using System.Threading.Tasks; using AET.ModVerify.Reporting.Json; @@ -11,13 +12,15 @@ namespace AET.ModVerify.Reporting; public sealed class VerificationBaseline : IReadOnlyCollection { - public static readonly Version LatestVersion = new(2, 0); + public static readonly Version LatestVersion = new(2, 1); public static readonly string LatestVersionString = LatestVersion.ToString(2); - public static readonly VerificationBaseline Empty = new(VerificationSeverity.Information, []); + public static readonly VerificationBaseline Empty = new(VerificationSeverity.Information, [], null); private readonly HashSet _errors; + public VerificationTarget? Target { get; } + public Version? Version { get; } public VerificationSeverity MinimumSeverity { get; } @@ -30,13 +33,15 @@ internal VerificationBaseline(JsonVerificationBaseline baseline) _errors = [..baseline.Errors.Select(x => new VerificationError(x))]; Version = baseline.Version; MinimumSeverity = baseline.MinimumSeverity; + Target = JsonVerificationTarget.ToTarget(baseline.Target); } - public VerificationBaseline(VerificationSeverity minimumSeverity, IEnumerable errors) + public VerificationBaseline(VerificationSeverity minimumSeverity, IEnumerable errors, VerificationTarget? target) { _errors = [..errors]; Version = LatestVersion; MinimumSeverity = minimumSeverity; + Target = target; } public bool Contains(VerificationError error) @@ -77,6 +82,10 @@ IEnumerator IEnumerable.GetEnumerator() public override string ToString() { - return $"Baseline [Version={Version}, MinSeverity={MinimumSeverity}, NumErrors={Count}]"; + var sb = new StringBuilder($"Baseline [Version={Version}, MinSeverity={MinimumSeverity}, NumErrors={Count}"); + if (Target is not null) + sb.Append($", Target={Target}"); + sb.Append(']'); + return sb.ToString(); } } \ No newline at end of file diff --git a/src/ModVerify/Resources/Schemas/2.0/baseline.json b/src/ModVerify/Resources/Schemas/2.1/baseline.json similarity index 56% rename from src/ModVerify/Resources/Schemas/2.0/baseline.json rename to src/ModVerify/Resources/Schemas/2.1/baseline.json index 2520a58..56bfb86 100644 --- a/src/ModVerify/Resources/Schemas/2.0/baseline.json +++ b/src/ModVerify/Resources/Schemas/2.1/baseline.json @@ -1,9 +1,58 @@ { - "$id": "https://AlamoEngine-Tools.github.io/schemas/mod-verify/2.0/baseline", + "$id": "https://AlamoEngine-Tools.github.io/schemas/mod-verify/2.1/baseline", "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "Represents a baseline for AET ModVerify", "type": "object", "$defs": { + "location": { + "type": "object", + "properties": { + "modPaths": { + "type": "array", + "items": { + "type": "string" + } + }, + "gamePath": { + "type": "string" + }, + "fallbackPaths": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "modPaths", + "gamePath", + "fallbackPaths" + ], + "additionalProperties": false + }, + "target": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "engine": { + "enum": [ "Eaw", "Foc" ] + }, + "location": { + "$ref": "#/$defs/location" + } + }, + "required": [ + "name", + "engine", + "location" + ], + "additionalProperties": false + }, "severity": { "enum": [ "Information", "Warning", "Error", "Critical" ] }, @@ -48,7 +97,7 @@ }, "properties": { "version": { - "const": "2.0" + "const": "2.1" }, "minSeverity": { "$ref": "#/$defs/severity" @@ -59,6 +108,9 @@ "$ref": "#/$defs/error" }, "additionalItems": false + }, + "target": { + "$ref": "#/$defs/target" } }, "required": [ diff --git a/src/ModVerify/VerificationTarget.cs b/src/ModVerify/VerificationTarget.cs index d3afdc7..c0d5b97 100644 --- a/src/ModVerify/VerificationTarget.cs +++ b/src/ModVerify/VerificationTarget.cs @@ -26,7 +26,7 @@ public required GameLocations Location } public string? Version { get; init; } - + public bool IsGame => Location.ModPaths.Count == 0; public override string ToString() @@ -35,7 +35,7 @@ public override string ToString() if (!string.IsNullOrEmpty(Version)) sb.Append($"Version={Version};"); sb.Append($"Location={Location};"); - sb.Append("]"); + sb.Append(']'); return sb.ToString(); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs index 73aa400..f57b605 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Xml.Linq; namespace PG.StarWarsGame.Engine; @@ -29,13 +28,13 @@ public GameLocations(string modPath, string gamePath, string fallbackGamePath) : ThrowHelper.ThrowIfNullOrEmpty(modPath); } - public GameLocations(IList modPaths, string gamePath, string fallbackGamePath) : this(modPaths, - gamePath, [fallbackGamePath]) + public GameLocations(IReadOnlyList modPaths, string gamePath, string fallbackGamePath) + : this(modPaths, gamePath, [fallbackGamePath]) { ThrowHelper.ThrowIfNullOrEmpty(fallbackGamePath); } - public GameLocations(IList modPaths, string gamePath, IList fallbackPaths) + public GameLocations(IReadOnlyList modPaths, string gamePath, IReadOnlyList fallbackPaths) { if (modPaths == null) throw new ArgumentNullException(nameof(modPaths)); From 4e0425228f47527e7a5a0fd4f9f13435181e935d Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sun, 14 Dec 2025 15:11:03 +0100 Subject: [PATCH 07/38] start refactoring verification target selection --- modules/ModdingToolBase | 2 +- .../GameFinder/GameFinderService.cs | 106 +++++++++++------- ...ticModSelector.cs => AutomaticSelector.cs} | 92 ++++++++------- ...nsoleModSelector.cs => ConsoleSelector.cs} | 25 +++-- .../ModSelectors/IModSelector.cs | 13 --- .../IVerificationTargetSelector.cs | 8 ++ .../ModSelectors/ManualModSelector.cs | 30 ----- .../ModSelectors/ManualSelector.cs | 91 +++++++++++++++ .../ModSelectors/ModSelectorBase.cs | 75 ------------- .../ModSelectors/ModSelectorFactory.cs | 18 --- .../ModSelectors/SettingsBasedModSelector.cs | 47 -------- .../SettingsBasedVerificationTargetCreator.cs | 14 +++ .../VerificationTargetSelectorBase.cs | 96 ++++++++++++++++ .../VerificationTargetSelectorFactory.cs | 18 +++ src/ModVerify.CliApp/ModVerifyApplication.cs | 6 +- .../Properties/launchSettings.json | 2 +- .../CommandLine/BaseModVerifyOptions.cs | 6 +- .../Settings/CommandLine/VerifyVerbOption.cs | 2 + .../Settings/GameInstallationsSettings.cs | 6 +- .../Settings/SettingsBuilder.cs | 4 +- .../Utilities/ExtensionMethods.cs | 4 +- .../ModVerifyOptionsParserTest.cs | 3 + 22 files changed, 380 insertions(+), 288 deletions(-) rename src/ModVerify.CliApp/ModSelectors/{AutomaticModSelector.cs => AutomaticSelector.cs} (59%) rename src/ModVerify.CliApp/ModSelectors/{ConsoleModSelector.cs => ConsoleSelector.cs} (80%) delete mode 100644 src/ModVerify.CliApp/ModSelectors/IModSelector.cs create mode 100644 src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs delete mode 100644 src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs create mode 100644 src/ModVerify.CliApp/ModSelectors/ManualSelector.cs delete mode 100644 src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs delete mode 100644 src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs delete mode 100644 src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs create mode 100644 src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs create mode 100644 src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs create mode 100644 src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index a0ad12e..a6a05f7 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit a0ad12e651dcb92cd4f1067059824d8f2894c63f +Subproject commit a6a05f7c3ecc91d796afff91b9686bcded0f8030 diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs index a88ebd9..8cc2739 100644 --- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs +++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure.Clients.Steam; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; @@ -12,6 +13,17 @@ namespace AET.ModVerify.App.GameFinder; +internal class GameFinderSettings +{ + internal static readonly GameFinderSettings Default = new(); + + public bool InitMods { get; init; } = true; + + public bool SearchFallbackGame { get; init; } = true; + + public GameEngineType? Engine { get; init; } = null; +} + internal class GameFinderService { private readonly IServiceProvider _serviceProvider; @@ -29,7 +41,7 @@ public GameFinderService(IServiceProvider serviceProvider) _logger = _serviceProvider.GetService()?.CreateLogger(GetType()); } - public GameFinderResult FindGames() + public GameFinderResult FindGames(GameFinderSettings settings) { var detectors = new List { @@ -37,10 +49,19 @@ public GameFinderResult FindGames() new SteamPetroglyphStarWarsGameDetector(_serviceProvider), }; - return FindGames(detectors); + return FindGames(detectors, settings); + } + + public IGame FindGame(string gamePath, GameFinderSettings settings) + { + var detectors = new List + { + new DirectoryGameDetector(_fileSystem.DirectoryInfo.New(gamePath), _serviceProvider), + }; + return FindGames(detectors, settings).Game; } - public GameFinderResult FindGamesFromPathOrGlobal(string path) + public GameFinderResult FindGamesFromPathOrGlobal(string path, GameFinderSettings settings) { // There are four common situations: // 1. path points to the actual game directory @@ -62,29 +83,10 @@ public GameFinderResult FindGamesFromPathOrGlobal(string path) // Cases 3 & 4 detectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); - return FindGames(detectors); - } - - private bool TryDetectGame(GameType gameType, IList detectors, out GameDetectionResult result) - { - var gd = new CompositeGameDetector(detectors, _serviceProvider); - - try - { - result = gd.Detect(gameType); - if (result.GameLocation is null) - return false; - return true; - } - catch (Exception e) - { - result = GameDetectionResult.NotInstalled(gameType); - _logger?.LogTrace("Unable to find game installation: {Message}", e.Message); - return false; - } + return FindGames(detectors, settings); } - - private GameFinderResult FindGames(IList detectors) + + private GameFinderResult FindGames(IList detectors, GameFinderSettings settings) { // FoC needs to be tried first if (!TryDetectGame(GameType.Foc, detectors, out var result)) @@ -102,34 +104,58 @@ private GameFinderResult FindGames(IList detectors) var game = _gameFactory.CreateGame(result, CultureInfo.InvariantCulture); - SetupMods(game); + if (settings.InitMods) + SetupMods(game); IGame? fallbackGame = null; - // If the game is Foc we want to set up Eaw as well as the fallbackGame - if (game.Type == GameType.Foc) + if (settings.SearchFallbackGame) { - var fallbackDetectors = new List(); - - if (game.Platform == GamePlatform.SteamGold) - fallbackDetectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); - else - throw new NotImplementedException("Searching fallback game for non-Steam games is currently is not yet implemented."); + // If the game is Foc we want to set up Eaw as well as the fallbackGame + if (game.Type == GameType.Foc) + { + var fallbackDetectors = new List(); + + if (game.Platform == GamePlatform.SteamGold) + fallbackDetectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); + else + throw new NotImplementedException("Searching fallback game for non-Steam games is currently is not yet implemented."); - if (!TryDetectGame(GameType.Eaw, fallbackDetectors, out var fallbackResult) || fallbackResult.GameLocation is null) - throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); + if (!TryDetectGame(GameType.Eaw, fallbackDetectors, out var fallbackResult) || fallbackResult.GameLocation is null) + throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", fallbackResult.GameIdentity, fallbackResult.GameLocation.FullName); + _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", fallbackResult.GameIdentity, fallbackResult.GameLocation.FullName); - fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); + fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); - SetupMods(fallbackGame); + if (settings.InitMods) + SetupMods(fallbackGame); + } } return new GameFinderResult(game, fallbackGame); } + private bool TryDetectGame(GameType gameType, IList detectors, out GameDetectionResult result) + { + var gd = new CompositeGameDetector(detectors, _serviceProvider); + + try + { + result = gd.Detect(gameType); + if (result.GameLocation is null) + return false; + return true; + } + catch (Exception e) + { + result = GameDetectionResult.NotInstalled(gameType); + _logger?.LogTrace("Unable to find game installation: {Message}", e.Message); + return false; + } + } + private void SetupMods(IGame game) { var modFinder = _serviceProvider.GetRequiredService(); diff --git a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs b/src/ModVerify.CliApp/ModSelectors/AutomaticSelector.cs similarity index 59% rename from src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs rename to src/ModVerify.CliApp/ModSelectors/AutomaticSelector.cs index d12cd6b..216598a 100644 --- a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/AutomaticSelector.cs @@ -16,92 +16,102 @@ namespace AET.ModVerify.App.ModSelectors; -internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider) +internal class AutomaticSelector(IServiceProvider serviceProvider) : VerificationTargetSelectorBase(serviceProvider) { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public override GameLocations? Select( - GameInstallationsSettings settings, - out IPhysicalPlayableObject? targetObject, - out GameEngineType? actualEngineType) + public override VerificationTarget Select(GameInstallationsSettings settings) { - var pathToVerify = settings.AutoPath; - if (pathToVerify is null) + var targetPath = settings.AutoPath; + if (targetPath is null) throw new InvalidOperationException("path to verify cannot be null."); - actualEngineType = settings.EngineType; + var engine = settings.Engine; GameFinderResult finderResult; try { - finderResult = GameFinderService.FindGamesFromPathOrGlobal(pathToVerify); + finderResult = GameFinderService.FindGamesFromPathOrGlobal(targetPath, GameFinderSettings.Default); } catch (GameNotFoundException) { - Logger?.LogError(ModVerifyConstants.ConsoleEventId, "Unable to find games based of the given location '{SettingsGamePath}'. Consider specifying all paths manually.", settings.GamePath); - targetObject = null; - return null; + Logger?.LogError(ModVerifyConstants.ConsoleEventId, "Unable to find games based of the specified target path '{Path}'. Consider specifying all paths manually.", settings.GamePath); + throw; } - var modOrGame = GetAttachedModOrGame(finderResult, actualEngineType, pathToVerify); + GameLocations locations; + + var targetObject = GetAttachedModOrGame(finderResult, engine, targetPath); - if (modOrGame is not null) + + if (targetObject is null) { - var actualType = modOrGame.Game.Type.ToEngineType(); - actualEngineType ??= actualType; - if (actualEngineType != actualType) - throw new ArgumentException($"The specified game type '{actualEngineType}' does not match the actual type of the game or mod to verify."); + if (!settings.Engine.HasValue) + throw new ArgumentException("Game engine not specified. Use --engine argument to set it."); - targetObject = modOrGame; - return GetLocations(targetObject, finderResult, settings.AdditionalFallbackPaths); - } + Logger?.LogDebug("The requested mod at '{TargetPath}' is detached from its games.", targetPath); - if (!settings.EngineType.HasValue) - throw new ArgumentException("Unable to determine game type. Use --type argument to set the game type."); + // The path is a detached mod, that exists on a different location than the game. + locations = GetDetachedModLocations(targetPath, finderResult, settings, out var mod); + targetObject = mod; + } + else + { + var actualType = targetObject.Game.Type.ToEngineType(); + engine ??= actualType; + if (engine != actualType) + throw new ArgumentException($"The specified game type '{engine}' does not match the actual type of the game or mod to verify."); + locations = GetLocations(targetObject, finderResult.FallbackGame, settings.AdditionalFallbackPaths); + } - Logger?.LogDebug("The requested mod at '{PathToVerify}' is detached from its games.", pathToVerify); + return new VerificationTarget + { + Engine = engine.Value, + Location = locations, + Name = GetTargetName(targetObject, locations), + Version = GetTargetVersion(targetObject) + }; - // The path is a detached mod, that exists on a different location than the game. - var result = GetDetachedModLocations(pathToVerify, finderResult, settings, out var mod); - targetObject = mod; - return result; } - private IPhysicalPlayableObject? GetAttachedModOrGame(GameFinderResult finderResult, GameEngineType? requestedEngineType, string searchPath) + private IPhysicalPlayableObject? GetAttachedModOrGame(GameFinderResult finderResult, GameEngineType? requestedEngineType, string targetPath) { - var fullSearchPath = _fileSystem.Path.GetFullPath(searchPath); + var targetFullPath = _fileSystem.Path.GetFullPath(targetPath); - if (finderResult.Game.Directory.FullName.Equals(fullSearchPath, StringComparison.OrdinalIgnoreCase)) + // If the target is the game directory itself. + if (targetFullPath.Equals(finderResult.Game.Directory.FullName, StringComparison.OrdinalIgnoreCase)) { if (finderResult.Game.Type.ToEngineType() != requestedEngineType) - throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{searchPath}' to verify."); + throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{targetPath}' to verify."); return finderResult.Game; } if (finderResult.FallbackGame is not null && - finderResult.FallbackGame.Directory.FullName.Equals(fullSearchPath, StringComparison.OrdinalIgnoreCase)) + targetFullPath.Equals(finderResult.FallbackGame.Directory.FullName, StringComparison.OrdinalIgnoreCase)) { + throw new NotImplementedException("When does this actually happen???"); + if (finderResult.FallbackGame.Type.ToEngineType() != requestedEngineType) - throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{searchPath}' to verify."); + throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{targetPath}' to verify."); return finderResult.FallbackGame; } - return GetMatchingModFromGame(finderResult.Game, requestedEngineType, fullSearchPath) ?? - GetMatchingModFromGame(finderResult.FallbackGame, requestedEngineType, fullSearchPath); + return GetMatchingModFromGame(finderResult.Game, requestedEngineType, targetFullPath) ?? + GetMatchingModFromGame(finderResult.FallbackGame, requestedEngineType, targetFullPath); } private GameLocations GetDetachedModLocations(string modPath, GameFinderResult gameResult, GameInstallationsSettings settings, out IPhysicalMod mod) { IGame game = null!; - if (gameResult.Game.Type.ToEngineType() == settings.EngineType) + if (gameResult.Game.Type.ToEngineType() == settings.Engine) game = gameResult.Game; - if (gameResult.FallbackGame is not null && gameResult.FallbackGame.Type.ToEngineType() == settings.EngineType) + if (gameResult.FallbackGame is not null && gameResult.FallbackGame.Type.ToEngineType() == settings.Engine) game = gameResult.FallbackGame; if (game is null) - throw new GameNotFoundException($"Unable to find game of type '{settings.EngineType}'"); + throw new GameNotFoundException($"Unable to find game of type '{settings.Engine}'"); var modFinder = ServiceProvider.GetRequiredService(); var modRef = modFinder.FindMods(game, _fileSystem.DirectoryInfo.New(modPath)).FirstOrDefault(); @@ -116,7 +126,7 @@ private GameLocations GetDetachedModLocations(string modPath, GameFinderResult g mod.ResolveDependencies(); - return GetLocations(mod, gameResult, settings.AdditionalFallbackPaths); + return GetLocations(mod, gameResult.FallbackGame, settings.AdditionalFallbackPaths); } private static IPhysicalMod? GetMatchingModFromGame(IGame? game, GameEngineType? requestedEngineType, string modPath) @@ -132,7 +142,7 @@ private GameLocations GetDetachedModLocations(string modPath, GameFinderResult g if (physicalMod.Directory.FullName.Equals(modPath, StringComparison.OrdinalIgnoreCase)) { if (!isGameSupported) - throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the mod '{modPath}' to verify."); + throw new ArgumentException($"The specified engine type '{requestedEngineType}' does not match the required of the mod '{modPath}' to verify."); return physicalMod; } } diff --git a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ConsoleSelector.cs similarity index 80% rename from src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs rename to src/ModVerify.CliApp/ModSelectors/ConsoleSelector.cs index a04cbea..a1701f3 100644 --- a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/ConsoleSelector.cs @@ -5,24 +5,29 @@ using AET.ModVerify.App.Settings; using AET.ModVerify.App.Utilities; using AnakinRaW.ApplicationBase; -using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; namespace AET.ModVerify.App.ModSelectors; -internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider) +internal class ConsoleSelector(IServiceProvider serviceProvider) : VerificationTargetSelectorBase(serviceProvider) { - public override GameLocations Select( - GameInstallationsSettings settings, - out IPhysicalPlayableObject targetObject, - out GameEngineType? actualEngineType) + public override VerificationTarget Select(GameInstallationsSettings settings) { - var gameResult = GameFinderService.FindGames(); - targetObject = SelectPlayableObject(gameResult); - actualEngineType = targetObject.Game.Type.ToEngineType(); - return GetLocations(targetObject, gameResult, settings.AdditionalFallbackPaths); + var gameResult = GameFinderService.FindGames(GameFinderSettings.Default); + var targetObject = SelectPlayableObject(gameResult); + var engine = targetObject.Game.Type.ToEngineType(); + + var gameLocations = GetLocations(targetObject, gameResult.FallbackGame, settings.AdditionalFallbackPaths); + + return new VerificationTarget + { + Engine = engine, + Location = gameLocations, + Name = GetTargetName(targetObject, gameLocations), + Version = GetTargetVersion(targetObject) + }; } private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult) diff --git a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs deleted file mode 100644 index a04858c..0000000 --- a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AET.ModVerify.App.Settings; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Infrastructure; - -namespace AET.ModVerify.App.ModSelectors; - -internal interface IModSelector -{ - GameLocations? Select( - GameInstallationsSettings settings, - out IPhysicalPlayableObject? targetObject, - out GameEngineType? actualEngineType); -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs b/src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs new file mode 100644 index 0000000..d9fe438 --- /dev/null +++ b/src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs @@ -0,0 +1,8 @@ +using AET.ModVerify.App.Settings; + +namespace AET.ModVerify.App.ModSelectors; + +internal interface IVerificationTargetSelector +{ + VerificationTarget Select(GameInstallationsSettings settings); +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs deleted file mode 100644 index a8b5ab7..0000000 --- a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Linq; -using AET.ModVerify.App.Settings; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Infrastructure; - -namespace AET.ModVerify.App.ModSelectors; - -internal class ManualModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider) -{ - public override GameLocations Select( - GameInstallationsSettings settings, - out IPhysicalPlayableObject? targetObject, - out GameEngineType? actualEngineType) - { - actualEngineType = settings.EngineType; - targetObject = null; - - if (!actualEngineType.HasValue) - throw new ArgumentException("Unable to determine game type. Use --type argument to set the game type."); - - if (string.IsNullOrEmpty(settings.GamePath)) - throw new ArgumentException("Argument --game must be set."); - - return new GameLocations( - settings.ModPaths.ToList(), - settings.GamePath!, - GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths).ToList()); - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ManualSelector.cs b/src/ModVerify.CliApp/ModSelectors/ManualSelector.cs new file mode 100644 index 0000000..0cbd5e4 --- /dev/null +++ b/src/ModVerify.CliApp/ModSelectors/ManualSelector.cs @@ -0,0 +1,91 @@ +using System; +using System.Globalization; +using System.Linq; +using AET.ModVerify.App.GameFinder; +using AET.ModVerify.App.Settings; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Infrastructure; +using PG.StarWarsGame.Infrastructure.Games; +using PG.StarWarsGame.Infrastructure.Services; +using PG.StarWarsGame.Infrastructure.Services.Detection; + +namespace AET.ModVerify.App.ModSelectors; + +internal class ManualSelector(IServiceProvider serviceProvider) : VerificationTargetSelectorBase(serviceProvider) +{ + public override VerificationTarget Select(GameInstallationsSettings settings) + { + if (string.IsNullOrEmpty(settings.GamePath)) + throw new ArgumentException("Argument --game must be set."); + if (!settings.Engine.HasValue) + throw new ArgumentException("Unable to determine game type. Use --engine argument to set the game type."); + + var engine = settings.Engine.Value; + + var gameLocations = new GameLocations( + settings.ModPaths.ToList(), + settings.GamePath!, + GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths).ToList()); + + + IPlayableObject? target = null; + + // For the manual selector the whole game and mod detection is optional. + // This allows user to use the application for unusual scenarios, + // not known to the detection service. + try + { + var game = GameFinderService.FindGame(gameLocations.GamePath, new GameFinderSettings + { + Engine = engine, + InitMods = false, + SearchFallbackGame = false + }); + target = TryGetPlayableObject(game, gameLocations.ModPaths.FirstOrDefault()); + } + catch (GameNotFoundException e) + { + // TODO: Log + } + + // If the fallback game path is specified we simply try to detect the game and report a warning to the user if not found. + var fallbackGamePath = settings.FallbackGamePath; + if (!string.IsNullOrEmpty(fallbackGamePath)) + { + try + { + GameFinderService.FindGame(fallbackGamePath, new GameFinderSettings + { + InitMods = false, + SearchFallbackGame = false + }); + } + catch (GameNotFoundException e) + { + // TODO: Log + } + } + + return new VerificationTarget + { + Engine = engine, + Location = gameLocations, + Name = GetTargetName(target, gameLocations), + Version = GetTargetVersion(target) + }; + } + + private IPlayableObject TryGetPlayableObject(IGame game, string? modPath) + { + if (string.IsNullOrEmpty(modPath)) + return game; + + var modFinder = ServiceProvider.GetRequiredService(); + var modFactory = ServiceProvider.GetRequiredService(); + + var mods = modFinder.FindMods(game, FileSystem.DirectoryInfo.New(modPath)); + var mod = modFactory.CreatePhysicalMod(game, mods.First(), CultureInfo.InvariantCulture); + return mod; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs deleted file mode 100644 index 53b4b20..0000000 --- a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AET.ModVerify.App.GameFinder; -using AET.ModVerify.App.Settings; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Infrastructure; -using PG.StarWarsGame.Infrastructure.Mods; -using PG.StarWarsGame.Infrastructure.Services.Dependencies; - -namespace AET.ModVerify.App.ModSelectors; - -internal abstract class ModSelectorBase : IModSelector -{ - protected readonly ILogger? Logger; - protected readonly GameFinderService GameFinderService; - protected readonly IServiceProvider ServiceProvider; - - protected ModSelectorBase(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - Logger = serviceProvider.GetService()?.CreateLogger(GetType()); - GameFinderService = new GameFinderService(serviceProvider); - } - - public abstract GameLocations? Select( - GameInstallationsSettings settings, - out IPhysicalPlayableObject? targetObject, - out GameEngineType? actualEngineType); - - protected GameLocations GetLocations(IPhysicalPlayableObject playableObject, GameFinderResult finderResult, IList additionalFallbackPaths) - { - var fallbacks = GetFallbackPaths(finderResult, playableObject, additionalFallbackPaths); - var modPaths = GetModPaths(playableObject); - return new GameLocations(modPaths.ToList(), playableObject.Game.Directory.FullName, fallbacks.ToList()); - } - - private static IList GetFallbackPaths(GameFinderResult finderResult, IPlayableObject gameOrMod, IList additionalFallbackPaths) - { - var coercedFallbackGame = finderResult.FallbackGame; - if (gameOrMod.Equals(finderResult.FallbackGame)) - coercedFallbackGame = null; - else if (gameOrMod.Game.Equals(finderResult.FallbackGame)) - coercedFallbackGame = null; - - return GetFallbackPaths(coercedFallbackGame?.Directory.FullName, additionalFallbackPaths); - } - - - protected static IList GetFallbackPaths(string? fallbackGame, IList additionalFallbackPaths) - { - var fallbacks = new List(); - if (fallbackGame is not null) - fallbacks.Add(fallbackGame); - foreach (var fallback in additionalFallbackPaths) - fallbacks.Add(fallback); - - return fallbacks; - } - - - private IList GetModPaths(IPhysicalPlayableObject modOrGame) - { - if (modOrGame is not IMod mod) - return Array.Empty(); - - var traverser = ServiceProvider.GetRequiredService(); - return traverser.Traverse(mod) - .OfType().Select(x => x.Directory.FullName) - .ToList(); - } - -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs deleted file mode 100644 index 07ef263..0000000 --- a/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using AET.ModVerify.App.Settings; - -namespace AET.ModVerify.App.ModSelectors; - -internal class ModSelectorFactory(IServiceProvider serviceProvider) -{ - public IModSelector CreateSelector(GameInstallationsSettings settings) - { - if (settings.Interactive) - return new ConsoleModSelector(serviceProvider); - if (settings.UseAutoDetection) - return new AutomaticModSelector(serviceProvider); - if (settings.ManualSetup) - return new ManualModSelector(serviceProvider); - throw new ArgumentException("Unknown option configuration provided."); - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs deleted file mode 100644 index 8ff890a..0000000 --- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Linq; -using AET.ModVerify.App.GameFinder; -using AET.ModVerify.App.Settings; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Infrastructure; - -namespace AET.ModVerify.App.ModSelectors; - -internal class SettingsBasedModSelector(IServiceProvider serviceProvider) -{ - public VerificationTarget CreateInstallationDataFromSettings(GameInstallationsSettings settings) - { - var gameLocations = new ModSelectorFactory(serviceProvider) - .CreateSelector(settings) - .Select(settings, out var targetObject, out var engineType); - - if (gameLocations is null) - throw new GameNotFoundException("Unable to get game locations"); - - if (engineType is null) - throw new InvalidOperationException("Engine type not specified."); - - return new VerificationTarget - { - Engine = engineType.Value, - Location = gameLocations, - Name = GetNameFromGameLocations(targetObject, gameLocations), - Version = GetTargetVersion(targetObject) - }; - } - - private static string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations) - { - if (targetObject is not null) - return targetObject.Name; - - var mod = gameLocations.ModPaths.FirstOrDefault(); - return mod ?? gameLocations.GamePath; - } - - private static string? GetTargetVersion(IPlayableObject? targetObject) - { - // TODO: Implement version retrieval from targetObject if possible - return null; - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs new file mode 100644 index 0000000..9bb48cf --- /dev/null +++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs @@ -0,0 +1,14 @@ +using System; +using AET.ModVerify.App.Settings; + +namespace AET.ModVerify.App.ModSelectors; + +internal class SettingsBasedVerificationTargetCreator(IServiceProvider serviceProvider) +{ + public VerificationTarget CreateFromSettings(GameInstallationsSettings settings) + { + return new VerificationTargetSelectorFactory(serviceProvider) + .CreateSelector(settings) + .Select(settings); + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs b/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs new file mode 100644 index 0000000..5afbec2 --- /dev/null +++ b/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Linq; +using AET.ModVerify.App.GameFinder; +using AET.ModVerify.App.Settings; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Infrastructure; +using PG.StarWarsGame.Infrastructure.Games; +using PG.StarWarsGame.Infrastructure.Mods; +using PG.StarWarsGame.Infrastructure.Services.Dependencies; + +namespace AET.ModVerify.App.ModSelectors; + +internal abstract class VerificationTargetSelectorBase : IVerificationTargetSelector +{ + protected readonly ILogger? Logger; + protected readonly GameFinderService GameFinderService; + protected readonly IServiceProvider ServiceProvider; + protected readonly IFileSystem FileSystem; + + protected VerificationTargetSelectorBase(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + Logger = serviceProvider.GetService()?.CreateLogger(GetType()); + GameFinderService = new GameFinderService(serviceProvider); + FileSystem = serviceProvider.GetRequiredService(); + } + + public abstract VerificationTarget Select(GameInstallationsSettings settings); + + protected GameLocations GetLocations( + IPhysicalPlayableObject target, + IGame? fallbackGame, + IReadOnlyList additionalFallbackPaths) + { + var fallbacks = GetFallbackPaths(target, fallbackGame, additionalFallbackPaths); + var modPaths = GetModPaths(target); + return new GameLocations(modPaths, target.Game.Directory.FullName, fallbacks); + } + + private static IReadOnlyList GetFallbackPaths(IPlayableObject target, IGame? fallbackGame, IReadOnlyList additionalFallbackPaths) + { + var coercedFallbackGame = fallbackGame; + if (target is IGame tGame && tGame.Equals(fallbackGame)) + coercedFallbackGame = null; + else if (target.Game.Equals(fallbackGame)) + coercedFallbackGame = null; + return GetFallbackPaths(coercedFallbackGame?.Directory.FullName, additionalFallbackPaths); + } + + + protected static IReadOnlyList GetFallbackPaths(string? fallbackGame, IReadOnlyList additionalFallbackPaths) + { + var fallbacks = new List(); + if (fallbackGame is not null) + fallbacks.Add(fallbackGame); + foreach (var fallback in additionalFallbackPaths) + fallbacks.Add(fallback); + return fallbacks; + } + + + private IReadOnlyList GetModPaths(IPhysicalPlayableObject modOrGame) + { + if (modOrGame is not IMod mod) + return []; + + var traverser = ServiceProvider.GetRequiredService(); + return traverser.Traverse(mod) + .OfType().Select(x => x.Directory.FullName) + .ToList(); + } + + protected static string GetTargetName(IPlayableObject? targetObject, GameLocations gameLocations) + { + if (targetObject is not null) + return targetObject.Name; + + // TODO: Reuse name beautifier from GameInfrastructure lib + var mod = gameLocations.ModPaths.FirstOrDefault(); + return mod ?? gameLocations.GamePath; + } + + protected static string? GetTargetVersion(IPlayableObject? targetObject) + { + if (targetObject is null) + return null; + + // TODO: Implement version retrieval from targetObject if possible + return null; + } + +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs b/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs new file mode 100644 index 0000000..3dd6b9a --- /dev/null +++ b/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs @@ -0,0 +1,18 @@ +using System; +using AET.ModVerify.App.Settings; + +namespace AET.ModVerify.App.ModSelectors; + +internal sealed class VerificationTargetSelectorFactory(IServiceProvider serviceProvider) +{ + public IVerificationTargetSelector CreateSelector(GameInstallationsSettings settings) + { + if (settings.Interactive) + return new ConsoleSelector(serviceProvider); + if (settings.UseAutoDetection) + return new AutomaticSelector(serviceProvider); + if (settings.ManualSetup) + return new ManualSelector(serviceProvider); + throw new ArgumentException("Unknown option configuration provided."); + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index cd59804..2c3b602 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -71,8 +71,10 @@ private async Task RunVerify() VerificationTarget verificationTarget; try { - verificationTarget = new SettingsBasedModSelector(services) - .CreateInstallationDataFromSettings(settings.GameInstallationsSettings); + var targetSettings = settings.GameInstallationsSettings; + verificationTarget = new VerificationTargetSelectorFactory(services) + .CreateSelector(targetSettings) + .Select(targetSettings); } catch (GameNotFoundException ex) { diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index e47583a..bafb11d 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -15,7 +15,7 @@ "FromModPath": { "commandName": "Project", - "commandLineArgs": "-o verifyResults --baseline focBaseline.json --path C:/test --type Foc" + "commandLineArgs": "verify -o verifyResults --path \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\"" } } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs b/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs index 0e5e203..4590a9d 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs @@ -41,10 +41,10 @@ internal abstract class BaseModVerifyOptions public string? FallbackGamePath { get; init; } - [Option("type", Required = false, Default = null, - HelpText = "The game type of the mod that shall be verified. Skip this value to auto-determine the type. Valid values are 'Eaw' and 'Foc'. " + + [Option("engine", Required = false, Default = null, + HelpText = "The game engine of the target that shall be verified. Skip this value to auto-determine the type. Valid values are 'Eaw' and 'Foc'. " + "This argument is required, if the first mod of '--mods' points to a directory outside of the common folder hierarchy (e.g, /MODS/MOD_NAME or /32470/WORKSHOP_ID")] - public GameEngineType? GameType { get; init; } + public GameEngineType? Engine { get; init; } [Option("additionalFallbackPaths", Required = false, Separator = ';', diff --git a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs index 97f1536..d833f12 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs @@ -27,10 +27,12 @@ internal sealed class VerifyVerbOption : BaseModVerifyOptions HelpText = "When this flag is present, the application will not report engine assertions.")] public bool IgnoreAsserts { get; init; } + [Option("baseline", SetName = "baselineSelection", Required = false, HelpText = "Path to a JSON baseline file. Cannot be used together with --searchBaseline.")] public string? Baseline { get; init; } + // TODO: Ignore, if baseline is set [Option("searchBaseline", SetName = "baselineSelection", Required = false, HelpText = "When set, the application will search for baseline files and use them for verification. Cannot be used together with --baseline")] public bool SearchBaselineLocally { get; init; } diff --git a/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs b/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs index 00bc4f0..d928774 100644 --- a/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs +++ b/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs @@ -16,13 +16,13 @@ internal sealed record GameInstallationsSettings public string? AutoPath { get; init; } - public IList ModPaths { get; init; } = []; + public IReadOnlyList ModPaths { get; init; } = []; public string? GamePath { get; init; } public string? FallbackGamePath { get; init; } - public IList AdditionalFallbackPaths { get; init; } = []; + public IReadOnlyList AdditionalFallbackPaths { get; init; } = []; - public GameEngineType? EngineType { get; init; } + public GameEngineType? Engine { get; init; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 3c5535f..61f690b 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -107,7 +107,7 @@ static bool SearchLocally(BaseModVerifyOptions o) { if (o is not VerifyVerbOption v) return false; - return v.SearchBaselineLocally || v.LaunchedWithoutArguments(); + return v.SearchBaselineLocally; } } @@ -153,7 +153,7 @@ private GameInstallationsSettings BuildInstallationSettings(BaseModVerifyOptions GamePath = gamePath, FallbackGamePath = fallbackGamePath, AdditionalFallbackPaths = fallbackPaths, - EngineType = options.GameType + Engine = options.Engine }; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs index b2e7ab0..fb3c68a 100644 --- a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs +++ b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs @@ -10,12 +10,12 @@ internal static class ExtensionMethods { public static GameEngineType ToEngineType(this GameType type) { - return type == GameType.Foc ? GameEngineType.Foc : GameEngineType.Eaw; + return (GameEngineType)(int)type; } public static GameType FromEngineType(this GameEngineType type) { - return type == GameEngineType.Foc ? GameType.Foc : GameType.Eaw; + return (GameType)(int)type; } extension(ApplicationEnvironment modVerifyEnvironment) diff --git a/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs b/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs index 294baf7..8d28949 100644 --- a/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs +++ b/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs @@ -4,7 +4,10 @@ using System.IO.Abstractions; using ModVerify.CliApp.Test.TestData; using Testably.Abstractions; +#if NETFRAMEWORK using ModVerify.CliApp.Test.Utilities; +#endif + namespace ModVerify.CliApp.Test; From 3f799643aab87c492c3c81fc490f35ff250fd74b Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sun, 14 Dec 2025 15:38:32 +0100 Subject: [PATCH 08/38] rename stuff --- .../IVerificationTargetSelector.cs | 8 ----- .../SettingsBasedVerificationTargetCreator.cs | 14 --------- src/ModVerify.CliApp/ModVerifyApplication.cs | 6 ++-- .../CommandLine/BaseModVerifyOptions.cs | 4 +-- .../Settings/ModVerifyAppSettings.cs | 4 +-- .../Settings/SettingsBuilder.cs | 16 +++++----- ...tings.cs => VerificationTargetSettings.cs} | 10 +++---- .../AutomaticSelector.cs | 22 +++++--------- .../ConsoleSelector.cs | 18 ++++------- .../IVerificationTargetSelector.cs | 8 +++++ .../ManualSelector.cs | 22 +++++--------- .../VerificationTargetSelectorBase.cs | 30 +++++++++++++++---- .../VerificationTargetSelectorFactory.cs | 4 +-- .../EmbeddedBaselineTest.cs | 2 +- 14 files changed, 77 insertions(+), 91 deletions(-) delete mode 100644 src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs delete mode 100644 src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs rename src/ModVerify.CliApp/Settings/{GameInstallationsSettings.cs => VerificationTargetSettings.cs} (59%) rename src/ModVerify.CliApp/{ModSelectors => TargetSelectors}/AutomaticSelector.cs (92%) rename src/ModVerify.CliApp/{ModSelectors => TargetSelectors}/ConsoleSelector.cs (85%) create mode 100644 src/ModVerify.CliApp/TargetSelectors/IVerificationTargetSelector.cs rename src/ModVerify.CliApp/{ModSelectors => TargetSelectors}/ManualSelector.cs (83%) rename src/ModVerify.CliApp/{ModSelectors => TargetSelectors}/VerificationTargetSelectorBase.cs (74%) rename src/ModVerify.CliApp/{ModSelectors => TargetSelectors}/VerificationTargetSelectorFactory.cs (79%) diff --git a/src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs b/src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs deleted file mode 100644 index d9fe438..0000000 --- a/src/ModVerify.CliApp/ModSelectors/IVerificationTargetSelector.cs +++ /dev/null @@ -1,8 +0,0 @@ -using AET.ModVerify.App.Settings; - -namespace AET.ModVerify.App.ModSelectors; - -internal interface IVerificationTargetSelector -{ - VerificationTarget Select(GameInstallationsSettings settings); -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs deleted file mode 100644 index 9bb48cf..0000000 --- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedVerificationTargetCreator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using AET.ModVerify.App.Settings; - -namespace AET.ModVerify.App.ModSelectors; - -internal class SettingsBasedVerificationTargetCreator(IServiceProvider serviceProvider) -{ - public VerificationTarget CreateFromSettings(GameInstallationsSettings settings) - { - return new VerificationTargetSelectorFactory(serviceProvider) - .CreateSelector(settings) - .Select(settings); - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 2c3b602..69d0f68 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -1,5 +1,4 @@ -using AET.ModVerify.App.ModSelectors; -using AET.ModVerify.App.Reporting; +using AET.ModVerify.App.Reporting; using AET.ModVerify.App.Settings; using AET.ModVerify.Pipeline; using AET.ModVerify.Reporting; @@ -16,6 +15,7 @@ using System.Linq; using System.Threading.Tasks; using AET.ModVerify.App.GameFinder; +using AET.ModVerify.App.TargetSelectors; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace AET.ModVerify.App; @@ -71,7 +71,7 @@ private async Task RunVerify() VerificationTarget verificationTarget; try { - var targetSettings = settings.GameInstallationsSettings; + var targetSettings = settings.VerificationTargetSettings; verificationTarget = new VerificationTargetSelectorFactory(services) .CreateSelector(targetSettings) .Select(targetSettings); diff --git a/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs b/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs index 4590a9d..36c8509 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs @@ -21,9 +21,9 @@ internal abstract class BaseModVerifyOptions public string? Suppressions { get; init; } [Option("path", SetName = "autoDetection", Required = false, Default = null, - HelpText = "Specifies the path to verify. The path may be a game or mod. The application will try to find all necessary sub-mods or base games itself. " + + HelpText = "Specifies the path to verify. The path may be a game or mod. The application will try to find all necessary sub-mods and base games itself. " + "The argument cannot be combined with any of --mods, --game or --fallbackGame")] - public string? AutoPath { get; init; } + public string? TargetPath { get; init; } [Option("mods", SetName = "manualPaths", Required = false, Default = null, Separator = ';', HelpText = "The path of the mod to verify. To support submods, multiple paths can be separated using the ';' (semicolon) character. " + diff --git a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs index 3376354..e65464c 100644 --- a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs @@ -6,13 +6,13 @@ namespace AET.ModVerify.App.Settings; internal sealed class ModVerifyAppSettings { - public bool Interactive => GameInstallationsSettings.Interactive; + public bool Interactive => VerificationTargetSettings.Interactive; public required VerifyPipelineSettings VerifyPipelineSettings { get; init; } public required ModVerifyReportSettings ReportSettings { get; init; } - public required GameInstallationsSettings GameInstallationsSettings { get; init; } + public required VerificationTargetSettings VerificationTargetSettings { get; init; } public VerificationSeverity? AppThrowsOnMinimumSeverity { get; init; } diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 61f690b..984099b 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -44,7 +44,7 @@ private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) } }, AppThrowsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity, - GameInstallationsSettings = BuildInstallationSettings(verifyOptions), + VerificationTargetSettings = BuildInstallationSettings(verifyOptions), ReportSettings = BuildReportSettings(verifyOptions), }; @@ -85,7 +85,7 @@ private ModVerifyAppSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOptio FailFast = false, }, AppThrowsOnMinimumSeverity = null, - GameInstallationsSettings = BuildInstallationSettings(baselineVerb), + VerificationTargetSettings = BuildInstallationSettings(baselineVerb), ReportSettings = BuildReportSettings(baselineVerb), NewBaselinePath = baselineVerb.OutputFile, }; @@ -111,7 +111,7 @@ static bool SearchLocally(BaseModVerifyOptions o) } } - private GameInstallationsSettings BuildInstallationSettings(BaseModVerifyOptions options) + private VerificationTargetSettings BuildInstallationSettings(BaseModVerifyOptions options) { var modPaths = new List(); if (options.ModPaths is not null) @@ -142,13 +142,13 @@ private GameInstallationsSettings BuildInstallationSettings(BaseModVerifyOptions if (!string.IsNullOrEmpty(gamePath) && !string.IsNullOrEmpty(options.FallbackGamePath)) fallbackGamePath = _fileSystem.Path.GetFullPath(options.FallbackGamePath!); - var autoPath = options.AutoPath; - if (!string.IsNullOrEmpty(autoPath)) - autoPath = _fileSystem.Path.GetFullPath(autoPath!); + var targetPath = options.TargetPath; + if (!string.IsNullOrEmpty(targetPath)) + targetPath = _fileSystem.Path.GetFullPath(targetPath!); - return new GameInstallationsSettings + return new VerificationTargetSettings { - AutoPath = autoPath, + TargetPath = targetPath, ModPaths = modPaths, GamePath = gamePath, FallbackGamePath = fallbackGamePath, diff --git a/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs b/src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs similarity index 59% rename from src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs rename to src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs index d928774..6e205de 100644 --- a/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs +++ b/src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs @@ -4,17 +4,17 @@ namespace AET.ModVerify.App.Settings; -internal sealed record GameInstallationsSettings +internal sealed record VerificationTargetSettings { - public bool Interactive => string.IsNullOrEmpty(AutoPath) && ModPaths.Count == 0 && string.IsNullOrEmpty(GamePath); + public bool Interactive => string.IsNullOrEmpty(TargetPath) && ModPaths.Count == 0 && string.IsNullOrEmpty(GamePath) && string.IsNullOrEmpty(FallbackGamePath); - [MemberNotNullWhen(true, nameof(AutoPath))] - public bool UseAutoDetection => !string.IsNullOrEmpty(AutoPath); + [MemberNotNullWhen(true, nameof(TargetPath))] + public bool UseAutoDetection => !string.IsNullOrEmpty(TargetPath); [MemberNotNullWhen(true, nameof(GamePath))] public bool ManualSetup => !string.IsNullOrEmpty(GamePath); - public string? AutoPath { get; init; } + public string? TargetPath { get; init; } public IReadOnlyList ModPaths { get; init; } = []; diff --git a/src/ModVerify.CliApp/ModSelectors/AutomaticSelector.cs b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs similarity index 92% rename from src/ModVerify.CliApp/ModSelectors/AutomaticSelector.cs rename to src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs index 216598a..b301da4 100644 --- a/src/ModVerify.CliApp/ModSelectors/AutomaticSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs @@ -14,15 +14,16 @@ using PG.StarWarsGame.Infrastructure.Services; using PG.StarWarsGame.Infrastructure.Services.Detection; -namespace AET.ModVerify.App.ModSelectors; +namespace AET.ModVerify.App.TargetSelectors; internal class AutomaticSelector(IServiceProvider serviceProvider) : VerificationTargetSelectorBase(serviceProvider) { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public override VerificationTarget Select(GameInstallationsSettings settings) + internal override SelectionResult SelectTarget( + VerificationTargetSettings settings) { - var targetPath = settings.AutoPath; + var targetPath = settings.TargetPath; if (targetPath is null) throw new InvalidOperationException("path to verify cannot be null."); @@ -44,7 +45,7 @@ public override VerificationTarget Select(GameInstallationsSettings settings) var targetObject = GetAttachedModOrGame(finderResult, engine, targetPath); - + if (targetObject is null) { if (!settings.Engine.HasValue) @@ -64,15 +65,8 @@ public override VerificationTarget Select(GameInstallationsSettings settings) throw new ArgumentException($"The specified game type '{engine}' does not match the actual type of the game or mod to verify."); locations = GetLocations(targetObject, finderResult.FallbackGame, settings.AdditionalFallbackPaths); } - - return new VerificationTarget - { - Engine = engine.Value, - Location = locations, - Name = GetTargetName(targetObject, locations), - Version = GetTargetVersion(targetObject) - }; - + + return new(locations, engine.Value, targetObject); } private IPhysicalPlayableObject? GetAttachedModOrGame(GameFinderResult finderResult, GameEngineType? requestedEngineType, string targetPath) @@ -101,7 +95,7 @@ public override VerificationTarget Select(GameInstallationsSettings settings) GetMatchingModFromGame(finderResult.FallbackGame, requestedEngineType, targetFullPath); } - private GameLocations GetDetachedModLocations(string modPath, GameFinderResult gameResult, GameInstallationsSettings settings, out IPhysicalMod mod) + private GameLocations GetDetachedModLocations(string modPath, GameFinderResult gameResult, VerificationTargetSettings settings, out IPhysicalMod mod) { IGame game = null!; diff --git a/src/ModVerify.CliApp/ModSelectors/ConsoleSelector.cs b/src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs similarity index 85% rename from src/ModVerify.CliApp/ModSelectors/ConsoleSelector.cs rename to src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs index a1701f3..8c54cef 100644 --- a/src/ModVerify.CliApp/ModSelectors/ConsoleSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs @@ -9,25 +9,17 @@ using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; -namespace AET.ModVerify.App.ModSelectors; +namespace AET.ModVerify.App.TargetSelectors; internal class ConsoleSelector(IServiceProvider serviceProvider) : VerificationTargetSelectorBase(serviceProvider) { - public override VerificationTarget Select(GameInstallationsSettings settings) + internal override SelectionResult SelectTarget(VerificationTargetSettings settings) { var gameResult = GameFinderService.FindGames(GameFinderSettings.Default); - var targetObject = SelectPlayableObject(gameResult); + var targetObject = SelectPlayableObject(gameResult); var engine = targetObject.Game.Type.ToEngineType(); - - var gameLocations = GetLocations(targetObject, gameResult.FallbackGame, settings.AdditionalFallbackPaths); - - return new VerificationTarget - { - Engine = engine, - Location = gameLocations, - Name = GetTargetName(targetObject, gameLocations), - Version = GetTargetVersion(targetObject) - }; + var locations = GetLocations(targetObject, gameResult.FallbackGame, settings.AdditionalFallbackPaths); + return new(locations, engine, targetObject); } private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult) diff --git a/src/ModVerify.CliApp/TargetSelectors/IVerificationTargetSelector.cs b/src/ModVerify.CliApp/TargetSelectors/IVerificationTargetSelector.cs new file mode 100644 index 0000000..6d452e8 --- /dev/null +++ b/src/ModVerify.CliApp/TargetSelectors/IVerificationTargetSelector.cs @@ -0,0 +1,8 @@ +using AET.ModVerify.App.Settings; + +namespace AET.ModVerify.App.TargetSelectors; + +internal interface IVerificationTargetSelector +{ + VerificationTarget Select(VerificationTargetSettings settings); +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ManualSelector.cs b/src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs similarity index 83% rename from src/ModVerify.CliApp/ModSelectors/ManualSelector.cs rename to src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs index 0cbd5e4..410d10d 100644 --- a/src/ModVerify.CliApp/ModSelectors/ManualSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs @@ -10,11 +10,11 @@ using PG.StarWarsGame.Infrastructure.Services; using PG.StarWarsGame.Infrastructure.Services.Detection; -namespace AET.ModVerify.App.ModSelectors; +namespace AET.ModVerify.App.TargetSelectors; internal class ManualSelector(IServiceProvider serviceProvider) : VerificationTargetSelectorBase(serviceProvider) { - public override VerificationTarget Select(GameInstallationsSettings settings) + internal override SelectionResult SelectTarget(VerificationTargetSettings settings) { if (string.IsNullOrEmpty(settings.GamePath)) throw new ArgumentException("Argument --game must be set."); @@ -23,13 +23,13 @@ public override VerificationTarget Select(GameInstallationsSettings settings) var engine = settings.Engine.Value; - var gameLocations = new GameLocations( + var gameLocations = new GameLocations( settings.ModPaths.ToList(), settings.GamePath!, GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths).ToList()); - IPlayableObject? target = null; + IPhysicalPlayableObject? target = null; // For the manual selector the whole game and mod detection is optional. // This allows user to use the application for unusual scenarios, @@ -54,7 +54,7 @@ public override VerificationTarget Select(GameInstallationsSettings settings) if (!string.IsNullOrEmpty(fallbackGamePath)) { try - { + { GameFinderService.FindGame(fallbackGamePath, new GameFinderSettings { InitMods = false, @@ -67,16 +67,10 @@ public override VerificationTarget Select(GameInstallationsSettings settings) } } - return new VerificationTarget - { - Engine = engine, - Location = gameLocations, - Name = GetTargetName(target, gameLocations), - Version = GetTargetVersion(target) - }; + return new SelectionResult(gameLocations, engine, target); } - - private IPlayableObject TryGetPlayableObject(IGame game, string? modPath) + + private IPhysicalPlayableObject TryGetPlayableObject(IGame game, string? modPath) { if (string.IsNullOrEmpty(modPath)) return game; diff --git a/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs similarity index 74% rename from src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs rename to src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs index 5afbec2..7d353fa 100644 --- a/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorBase.cs +++ b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs @@ -12,10 +12,15 @@ using PG.StarWarsGame.Infrastructure.Mods; using PG.StarWarsGame.Infrastructure.Services.Dependencies; -namespace AET.ModVerify.App.ModSelectors; +namespace AET.ModVerify.App.TargetSelectors; internal abstract class VerificationTargetSelectorBase : IVerificationTargetSelector { + internal sealed record SelectionResult( + GameLocations Locations, + GameEngineType Engine, + IPhysicalPlayableObject? Target); + protected readonly ILogger? Logger; protected readonly GameFinderService GameFinderService; protected readonly IServiceProvider ServiceProvider; @@ -29,7 +34,22 @@ protected VerificationTargetSelectorBase(IServiceProvider serviceProvider) FileSystem = serviceProvider.GetRequiredService(); } - public abstract VerificationTarget Select(GameInstallationsSettings settings); + public VerificationTarget Select(VerificationTargetSettings settings) + { + var selectedTarget = SelectTarget(settings); + + return new VerificationTarget + { + Location = selectedTarget.Locations, + Engine = selectedTarget.Engine, + Name = GetTargetName(selectedTarget.Target, selectedTarget.Locations), + Version = GetTargetVersion(selectedTarget.Target) + }; + } + + + internal abstract SelectionResult SelectTarget(VerificationTargetSettings settings); + protected GameLocations GetLocations( IPhysicalPlayableObject target, @@ -41,7 +61,7 @@ protected GameLocations GetLocations( return new GameLocations(modPaths, target.Game.Directory.FullName, fallbacks); } - private static IReadOnlyList GetFallbackPaths(IPlayableObject target, IGame? fallbackGame, IReadOnlyList additionalFallbackPaths) + private static IReadOnlyList GetFallbackPaths(IPhysicalPlayableObject target, IGame? fallbackGame, IReadOnlyList additionalFallbackPaths) { var coercedFallbackGame = fallbackGame; if (target is IGame tGame && tGame.Equals(fallbackGame)) @@ -74,7 +94,7 @@ private IReadOnlyList GetModPaths(IPhysicalPlayableObject modOrGame) .ToList(); } - protected static string GetTargetName(IPlayableObject? targetObject, GameLocations gameLocations) + protected static string GetTargetName(IPhysicalPlayableObject? targetObject, GameLocations gameLocations) { if (targetObject is not null) return targetObject.Name; @@ -84,7 +104,7 @@ protected static string GetTargetName(IPlayableObject? targetObject, GameLocatio return mod ?? gameLocations.GamePath; } - protected static string? GetTargetVersion(IPlayableObject? targetObject) + protected static string? GetTargetVersion(IPhysicalPlayableObject? targetObject) { if (targetObject is null) return null; diff --git a/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorFactory.cs similarity index 79% rename from src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs rename to src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorFactory.cs index 3dd6b9a..5e106eb 100644 --- a/src/ModVerify.CliApp/ModSelectors/VerificationTargetSelectorFactory.cs +++ b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorFactory.cs @@ -1,11 +1,11 @@ using System; using AET.ModVerify.App.Settings; -namespace AET.ModVerify.App.ModSelectors; +namespace AET.ModVerify.App.TargetSelectors; internal sealed class VerificationTargetSelectorFactory(IServiceProvider serviceProvider) { - public IVerificationTargetSelector CreateSelector(GameInstallationsSettings settings) + public IVerificationTargetSelector CreateSelector(VerificationTargetSettings settings) { if (settings.Interactive) return new ConsoleSelector(serviceProvider); diff --git a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs index cde1dad..6bb8bb4 100644 --- a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs +++ b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs @@ -18,7 +18,7 @@ public class BaselineSelectorTest private static readonly ModVerifyAppSettings TestSettings = new() { ReportSettings = new(), - GameInstallationsSettings = new (), + VerificationTargetSettings = new (), VerifyPipelineSettings = new() { GameVerifySettings = new GameVerifySettings(), From 0a674e6545a3e0045acc7448d24d37d146ca5241 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Tue, 16 Dec 2025 14:06:27 +0100 Subject: [PATCH 09/38] start testing automatic selector --- .../TargetSelectors/AutomaticSelector.cs | 10 ++- test/ModVerify.CliApp.Test/CommonTestBase.cs | 10 ++- .../TargetSelectors/AutomaticSelectorTest.cs | 77 +++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs diff --git a/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs index b301da4..2915fc0 100644 --- a/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs @@ -20,8 +20,7 @@ internal class AutomaticSelector(IServiceProvider serviceProvider) : Verificatio { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - internal override SelectionResult SelectTarget( - VerificationTargetSettings settings) + internal override SelectionResult SelectTarget(VerificationTargetSettings settings) { var targetPath = settings.TargetPath; if (targetPath is null) @@ -76,7 +75,7 @@ internal override SelectionResult SelectTarget( // If the target is the game directory itself. if (targetFullPath.Equals(finderResult.Game.Directory.FullName, StringComparison.OrdinalIgnoreCase)) { - if (finderResult.Game.Type.ToEngineType() != requestedEngineType) + if (!IsEngineTypeSupported(requestedEngineType, finderResult.Game)) throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{targetPath}' to verify."); return finderResult.Game; } @@ -144,4 +143,9 @@ private GameLocations GetDetachedModLocations(string modPath, GameFinderResult g return null; } + + private static bool IsEngineTypeSupported(GameEngineType? requestedEngineType, IPhysicalPlayableObject target) + { + return !requestedEngineType.HasValue || target.Game.Type.ToEngineType() == requestedEngineType; + } } \ No newline at end of file diff --git a/test/ModVerify.CliApp.Test/CommonTestBase.cs b/test/ModVerify.CliApp.Test/CommonTestBase.cs index 7d6dc18..20325ab 100644 --- a/test/ModVerify.CliApp.Test/CommonTestBase.cs +++ b/test/ModVerify.CliApp.Test/CommonTestBase.cs @@ -1,8 +1,11 @@ -using System; -using System.IO.Abstractions; +using AET.SteamAbstraction; using AnakinRaW.CommonUtilities.Hashing; using Microsoft.Extensions.DependencyInjection; using PG.Commons; +using PG.StarWarsGame.Infrastructure; +using PG.StarWarsGame.Infrastructure.Clients.Steam; +using System; +using System.IO.Abstractions; using Testably.Abstractions.Testing; namespace ModVerify.CliApp.Test; @@ -18,6 +21,9 @@ protected CommonTestBase() sc.AddSingleton(sp => new HashingService(sp)); sc.AddSingleton(FileSystem); PetroglyphCommons.ContributeServices(sc); + PetroglyphGameInfrastructure.InitializeServices(sc); + SteamAbstractionLayer.InitializeServices(sc); + SteamPetroglyphStarWarsGameClients.InitializeServices(sc); // ReSharper disable once VirtualMemberCallInConstructor SetupServices(sc); ServiceProvider = sc.BuildServiceProvider(); diff --git a/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs b/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs new file mode 100644 index 0000000..d376010 --- /dev/null +++ b/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs @@ -0,0 +1,77 @@ +namespace ModVerify.CliApp.Test.TargetSelectors; + +public class AutomaticSelectorTest : CommonTestBase +{ + //private readonly AutomaticSelector _selector; + //private readonly IRegistry _registry = new InMemoryRegistry(InMemoryRegistryCreationFlags.WindowsLike); + + //public AutomaticSelectorTest() + //{ + // _selector = new AutomaticSelector(ServiceProvider); + //} + + //protected override void SetupServices(ServiceCollection serviceCollection) + //{ + // base.SetupServices(serviceCollection); + // serviceCollection.AddSingleton(_registry); + //} + + //[Fact] + //public void Test_Select_GameNotInstalled() + //{ + // var settings = new VerificationTargetSettings + // { + // TargetPath = "/test", + // }; + + // Assert.Throws(() => _selector.SelectTarget(settings)); + //} + + //[Theory] + //[MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + //public void Test_Select_Game(GameIdentity identity) + //{ + // var (eaw, foc) = InstallGames(identity.Platform); + // var game = identity.Type switch + // { + // GameType.Eaw => eaw, + // GameType.Foc => foc, + // _ => throw new ArgumentOutOfRangeException() + // }; + + // var settings = new VerificationTargetSettings + // { + // TargetPath = game.Directory.FullName, + // }; + + // var result = _selector.SelectTarget(settings); + // Assert.Equal(identity.Type, result.Engine.FromEngineType()); + // Assert.Equal(game, result.Target); + // Assert.Equal(game.Directory.FullName, result.Locations.GamePath); + //} + + //private (IGame eaw, IGame foc) InstallGames(GamePlatform platform) + //{ + // var eaw = FileSystem.InstallGame(new GameIdentity(GameType.Eaw, platform), ServiceProvider); + // var foc = FileSystem.InstallGame(new GameIdentity(GameType.Eaw, platform), ServiceProvider); + + + + // if (platform == GamePlatform.SteamGold) + // { + // InstallSteam(eaw, foc); + // } + + // return (eaw, foc); + //} + + //private void InstallSteam(IGame eaw, IGame foc) + //{ + // var registry = ServiceProvider.GetRequiredService().CreateRegistry(); + // FileSystem.InstallSteam(registry); + + // // Register Game to Steam + // var lib = FileSystem.InstallDefaultLibrary(ServiceProvider); + // lib.InstallGame(32470, "Star Wars Empire at War", [32472], SteamAppState.StateFullyInstalled); + //} +} \ No newline at end of file From 0887111e506f527abb2a74a08e4d1ddb68ac04e2 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Tue, 23 Dec 2025 19:21:38 +0100 Subject: [PATCH 10/38] fix and test automatic selector --- .../GameFinder/GameFinderService.cs | 92 +++- src/ModVerify.CliApp/ModVerifyApplication.cs | 13 + .../Settings/VerificationTargetSettings.cs | 2 +- .../TargetSelectors/AutomaticSelector.cs | 178 +++--- .../TargetSelectors/ManualSelector.cs | 40 +- .../TargetNotFoundException.cs | 6 + test/ModVerify.CliApp.Test/CommonTestBase.cs | 30 +- .../EmbeddedBaselineTest.cs | 1 + .../ModVerify.CliApp.Test.csproj | 29 +- .../ModVerifyOptionsParserTest.cs | 1 + .../TargetSelectors/AutomaticSelectorTest.cs | 508 +++++++++++++++--- 11 files changed, 678 insertions(+), 222 deletions(-) create mode 100644 src/ModVerify.CliApp/TargetSelectors/TargetNotFoundException.cs diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs index 8cc2739..aca0533 100644 --- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs +++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO.Abstractions; using Microsoft.Extensions.DependencyInjection; @@ -51,7 +52,7 @@ public GameFinderResult FindGames(GameFinderSettings settings) return FindGames(detectors, settings); } - + public IGame FindGame(string gamePath, GameFinderSettings settings) { var detectors = new List @@ -61,6 +62,25 @@ public IGame FindGame(string gamePath, GameFinderSettings settings) return FindGames(detectors, settings).Game; } + public bool TryFindGame(string gamePath, GameFinderSettings settings, [NotNullWhen(true)]out IGame? game) + { + var detectors = new List + { + new DirectoryGameDetector(_fileSystem.DirectoryInfo.New(gamePath), _serviceProvider), + }; + + try + { + game = FindGames(detectors, settings).Game; + return true; + } + catch (GameNotFoundException) + { + game = null; + return false; + } + } + public GameFinderResult FindGamesFromPathOrGlobal(string path, GameFinderSettings settings) { // There are four common situations: @@ -88,55 +108,79 @@ public GameFinderResult FindGamesFromPathOrGlobal(string path, GameFinderSetting private GameFinderResult FindGames(IList detectors, GameFinderSettings settings) { - // FoC needs to be tried first - if (!TryDetectGame(GameType.Foc, detectors, out var result)) + GameDetectionResult? detectionResult = null; + if (settings.Engine is GameEngineType.Eaw) + { + _logger?.LogTrace("Trying to find requested EaW installation."); + if (!TryDetectGame(GameType.Eaw, detectors, out detectionResult)) + { + var e = new GameNotFoundException($"Unable to find requested game installation '{settings.Engine}'. Wrong install path?"); + _logger?.LogTrace(e, e.Message); + throw e; + } + } + + if (detectionResult is null && !TryDetectGame(GameType.Foc, detectors, out detectionResult)) { + if (settings.Engine is GameEngineType.Foc) + { + var e = new GameNotFoundException($"Unable to find requested game installation '{settings.Engine}'. Wrong install path?"); + _logger?.LogTrace(e, e.Message); + throw e; + } + + // If the engine is unspecified, we also need to check for EaW. _logger?.LogTrace("Unable to find FoC installation. Trying again with EaW..."); - if (!TryDetectGame(GameType.Eaw, detectors, out result)) + if (!TryDetectGame(GameType.Eaw, detectors, out detectionResult)) throw new GameNotFoundException("Unable to find game installation: Wrong install path?"); } - if (result.GameLocation is null) + if (detectionResult.GameLocation is null) throw new GameNotFoundException("Unable to find game installation: Wrong install path?"); _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found game installation: {ResultGameIdentity} at {GameLocationFullName}", result.GameIdentity, result.GameLocation.FullName); + "Found game installation: {ResultGameIdentity} at {GameLocationFullName}", detectionResult.GameIdentity, detectionResult.GameLocation.FullName); - var game = _gameFactory.CreateGame(result, CultureInfo.InvariantCulture); + var game = _gameFactory.CreateGame(detectionResult, CultureInfo.InvariantCulture); if (settings.InitMods) SetupMods(game); IGame? fallbackGame = null; - if (settings.SearchFallbackGame) + if (SearchForFallbackGame(settings, detectionResult)) { - // If the game is Foc we want to set up Eaw as well as the fallbackGame - if (game.Type == GameType.Foc) - { - var fallbackDetectors = new List(); + var fallbackDetectors = new List(); - if (game.Platform == GamePlatform.SteamGold) - fallbackDetectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); - else - throw new NotImplementedException("Searching fallback game for non-Steam games is currently is not yet implemented."); + if (game.Platform == GamePlatform.SteamGold) + fallbackDetectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); + else + throw new NotImplementedException("Searching fallback game for non-Steam games is currently is not yet implemented."); - if (!TryDetectGame(GameType.Eaw, fallbackDetectors, out var fallbackResult) || fallbackResult.GameLocation is null) - throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); + if (!TryDetectGame(GameType.Eaw, fallbackDetectors, out var fallbackResult) || fallbackResult.GameLocation is null) + throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", fallbackResult.GameIdentity, fallbackResult.GameLocation.FullName); + _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", fallbackResult.GameIdentity, fallbackResult.GameLocation.FullName); - fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); + fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); - if (settings.InitMods) - SetupMods(fallbackGame); - } + if (settings.InitMods) + SetupMods(fallbackGame); } return new GameFinderResult(game, fallbackGame); } + private static bool SearchForFallbackGame(GameFinderSettings settings, GameDetectionResult? foundGame) + { + if (settings.Engine is GameEngineType.Eaw) + return false; + if (foundGame is { Installed: true, GameIdentity.Type: GameType.Eaw }) + return false; + return settings.SearchFallbackGame; + } + private bool TryDetectGame(GameType gameType, IList detectors, out GameDetectionResult result) { var gd = new CompositeGameDetector(detectors, _serviceProvider); diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 69d0f68..fb3ebab 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -76,6 +76,19 @@ private async Task RunVerify() .CreateSelector(targetSettings) .Select(targetSettings); } + catch (ArgumentException ex) + { + ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, + $"The specified arguments are not correct: {ex.Message}"); + _logger?.LogError(ex, "Invalid application arguments: {Message}", ex.Message); + return ex.HResult; + } + catch (TargetNotFoundException ex) + { + ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, ex.Message); + _logger?.LogError(ex, ex.Message); + return ex.HResult; + } catch (GameNotFoundException ex) { ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, diff --git a/src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs b/src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs index 6e205de..b5e1a40 100644 --- a/src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs +++ b/src/ModVerify.CliApp/Settings/VerificationTargetSettings.cs @@ -9,7 +9,7 @@ internal sealed record VerificationTargetSettings public bool Interactive => string.IsNullOrEmpty(TargetPath) && ModPaths.Count == 0 && string.IsNullOrEmpty(GamePath) && string.IsNullOrEmpty(FallbackGamePath); [MemberNotNullWhen(true, nameof(TargetPath))] - public bool UseAutoDetection => !string.IsNullOrEmpty(TargetPath); + public bool UseAutoDetection => !string.IsNullOrEmpty(TargetPath) && ModPaths.Count == 0 && string.IsNullOrEmpty(GamePath) && string.IsNullOrEmpty(FallbackGamePath); [MemberNotNullWhen(true, nameof(GamePath))] public bool ManualSetup => !string.IsNullOrEmpty(GamePath); diff --git a/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs index 2915fc0..f6b2b0c 100644 --- a/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs @@ -1,8 +1,4 @@ -using System; -using System.Globalization; -using System.IO.Abstractions; -using System.Linq; -using AET.ModVerify.App.GameFinder; +using AET.ModVerify.App.GameFinder; using AET.ModVerify.App.Settings; using AET.ModVerify.App.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -13,6 +9,13 @@ using PG.StarWarsGame.Infrastructure.Mods; using PG.StarWarsGame.Infrastructure.Services; using PG.StarWarsGame.Infrastructure.Services.Detection; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO.Abstractions; +using System.Linq; namespace AET.ModVerify.App.TargetSelectors; @@ -22,95 +25,130 @@ internal class AutomaticSelector(IServiceProvider serviceProvider) : Verificatio internal override SelectionResult SelectTarget(VerificationTargetSettings settings) { + if (!settings.UseAutoDetection) + throw new ArgumentException("wrong settings format provided.", nameof(settings)); + var targetPath = settings.TargetPath; - if (targetPath is null) - throw new InvalidOperationException("path to verify cannot be null."); + if (!_fileSystem.Directory.Exists(targetPath)) + { + Logger?.LogError(ModVerifyConstants.ConsoleEventId, "The specified path '{Path}' does not exist.", targetPath); + throw new TargetNotFoundException(targetPath); + } var engine = settings.Engine; GameFinderResult finderResult; try { - finderResult = GameFinderService.FindGamesFromPathOrGlobal(targetPath, GameFinderSettings.Default); + var finderSettings = new GameFinderSettings + { + Engine = engine, + InitMods = true, + SearchFallbackGame = true + }; + finderResult = GameFinderService.FindGamesFromPathOrGlobal(targetPath, finderSettings); } catch (GameNotFoundException) { - Logger?.LogError(ModVerifyConstants.ConsoleEventId, "Unable to find games based of the specified target path '{Path}'. Consider specifying all paths manually.", settings.GamePath); + Logger?.LogError(ModVerifyConstants.ConsoleEventId, + "Unable to find games based of the specified target path '{Path}'. Consider specifying all paths manually.", targetPath); throw; } + // In a Steam scenario, there is a chance that the user specified a FoC targetPath, + // but requested EaW engine. This does not make sense, and we need to check against this. + if (finderResult.Game.Platform == GamePlatform.SteamGold && engine is GameEngineType.Eaw) + { + var targetIsFoc = GameFinderService.TryFindGame(targetPath, + new GameFinderSettings { Engine = GameEngineType.Foc, InitMods = false, SearchFallbackGame = false }, + out _); + if (targetIsFoc) + ThrowEngineNotSupported(engine.Value, targetPath); + } - GameLocations locations; + if (engine.HasValue) + { + if (finderResult.Game.Type.ToEngineType() != engine.Value) + { + if (finderResult.FallbackGame?.Type.ToEngineType() != engine) + { + throw new InvalidOperationException(); + } + } + } + - var targetObject = GetAttachedModOrGame(finderResult, engine, targetPath); + GameLocations locations; + var targetObject = GetAttachedModOrGame(finderResult, targetPath, engine); - if (targetObject is null) + if (targetObject is not null) { - if (!settings.Engine.HasValue) + var actualType = targetObject.Game.Type; + Debug.Assert(IsEngineTypeSupported(engine, actualType)); + engine ??= actualType.ToEngineType(); + locations = GetLocations(targetObject, finderResult.FallbackGame, settings.AdditionalFallbackPaths); + } + else + { + if (!engine.HasValue) throw new ArgumentException("Game engine not specified. Use --engine argument to set it."); Logger?.LogDebug("The requested mod at '{TargetPath}' is detached from its games.", targetPath); // The path is a detached mod, that exists on a different location than the game. - locations = GetDetachedModLocations(targetPath, finderResult, settings, out var mod); + locations = GetDetachedModLocations(targetPath, finderResult, engine.Value, settings.AdditionalFallbackPaths, out var mod); targetObject = mod; } - else - { - var actualType = targetObject.Game.Type.ToEngineType(); - engine ??= actualType; - if (engine != actualType) - throw new ArgumentException($"The specified game type '{engine}' does not match the actual type of the game or mod to verify."); - locations = GetLocations(targetObject, finderResult.FallbackGame, settings.AdditionalFallbackPaths); - } - + return new(locations, engine.Value, targetObject); } - private IPhysicalPlayableObject? GetAttachedModOrGame(GameFinderResult finderResult, GameEngineType? requestedEngineType, string targetPath) + private IPhysicalPlayableObject? GetAttachedModOrGame(GameFinderResult finderResult, string targetPath, GameEngineType? requestedEngineType) { var targetFullPath = _fileSystem.Path.GetFullPath(targetPath); + IPhysicalPlayableObject? target = null; + // If the target is the game directory itself. - if (targetFullPath.Equals(finderResult.Game.Directory.FullName, StringComparison.OrdinalIgnoreCase)) + if (targetFullPath.Equals(finderResult.Game.Directory.FullName, StringComparison.OrdinalIgnoreCase)) + target = finderResult.Game; + else if (finderResult.FallbackGame is not null && targetFullPath.Equals(finderResult.FallbackGame.Directory.FullName, StringComparison.OrdinalIgnoreCase)) { - if (!IsEngineTypeSupported(requestedEngineType, finderResult.Game)) - throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{targetPath}' to verify."); - return finderResult.Game; + // The game detection identified both Foc and Eaw because either Steam is installed or requestedEngineType was not specified. + // The requested path points to a EaW installation. + Debug.Assert(finderResult.FallbackGame.Type is GameType.Eaw); + target = finderResult.FallbackGame; } - if (finderResult.FallbackGame is not null && - targetFullPath.Equals(finderResult.FallbackGame.Directory.FullName, StringComparison.OrdinalIgnoreCase)) - { - throw new NotImplementedException("When does this actually happen???"); + target ??= GetMatchingModFromGame(finderResult.Game, targetFullPath, requestedEngineType) ?? + GetMatchingModFromGame(finderResult.FallbackGame, targetFullPath, requestedEngineType); - if (finderResult.FallbackGame.Type.ToEngineType() != requestedEngineType) - throw new ArgumentException($"The specified game type '{requestedEngineType}' does not match the actual type of the game '{targetPath}' to verify."); - return finderResult.FallbackGame; + + if (target is not null) + { + if (!IsEngineTypeSupported(requestedEngineType, target.Game.Type)) + ThrowEngineNotSupported(requestedEngineType.Value, targetPath); } - return GetMatchingModFromGame(finderResult.Game, requestedEngineType, targetFullPath) ?? - GetMatchingModFromGame(finderResult.FallbackGame, requestedEngineType, targetFullPath); + return target; } - private GameLocations GetDetachedModLocations(string modPath, GameFinderResult gameResult, VerificationTargetSettings settings, out IPhysicalMod mod) + private GameLocations GetDetachedModLocations( + string modPath, + GameFinderResult gameResult, + GameEngineType requestedGameEngine, + IReadOnlyList additionalFallbackPaths, + out IPhysicalMod mod) { - IGame game = null!; - - if (gameResult.Game.Type.ToEngineType() == settings.Engine) - game = gameResult.Game; - if (gameResult.FallbackGame is not null && gameResult.FallbackGame.Type.ToEngineType() == settings.Engine) - game = gameResult.FallbackGame; - - if (game is null) - throw new GameNotFoundException($"Unable to find game of type '{settings.Engine}'"); + var game = GetTargetGame(gameResult, requestedGameEngine); + Debug.Assert(game is not null); var modFinder = ServiceProvider.GetRequiredService(); var modRef = modFinder.FindMods(game, _fileSystem.DirectoryInfo.New(modPath)).FirstOrDefault(); if (modRef is null) - throw new NotSupportedException($"The mod at '{modPath}' is not compatible to the found game '{game}'."); + ThrowEngineNotSupported(requestedGameEngine, modPath); var modFactory = ServiceProvider.GetRequiredService(); mod = modFactory.CreatePhysicalMod(game, modRef, CultureInfo.InvariantCulture); @@ -119,33 +157,45 @@ private GameLocations GetDetachedModLocations(string modPath, GameFinderResult g mod.ResolveDependencies(); - return GetLocations(mod, gameResult.FallbackGame, settings.AdditionalFallbackPaths); + return GetLocations(mod, gameResult.FallbackGame, additionalFallbackPaths); } - private static IPhysicalMod? GetMatchingModFromGame(IGame? game, GameEngineType? requestedEngineType, string modPath) + private static IPhysicalMod? GetMatchingModFromGame(IGame? game, string modPath, GameEngineType? requestedEngineType) { - if (game is null) + if (game is null || !IsEngineTypeSupported(requestedEngineType, game.Type)) return null; - var isGameSupported = !requestedEngineType.HasValue || game.Type.ToEngineType() == requestedEngineType; foreach (var mod in game.Game.Mods) { - if (mod is IPhysicalMod physicalMod) - { - if (physicalMod.Directory.FullName.Equals(modPath, StringComparison.OrdinalIgnoreCase)) - { - if (!isGameSupported) - throw new ArgumentException($"The specified engine type '{requestedEngineType}' does not match the required of the mod '{modPath}' to verify."); - return physicalMod; - } - } + if (mod is not IPhysicalMod physicalMod) + continue; + + if (physicalMod.Directory.FullName.Equals(modPath, StringComparison.OrdinalIgnoreCase)) + return physicalMod; } return null; } - - private static bool IsEngineTypeSupported(GameEngineType? requestedEngineType, IPhysicalPlayableObject target) + + private static IGame? GetTargetGame(GameFinderResult finderResult, GameEngineType? requestedEngine) + { + if (!requestedEngine.HasValue) + return null; + if (finderResult.Game.Type.ToEngineType() == requestedEngine) + return finderResult.Game; + if (finderResult.FallbackGame is not null && finderResult.FallbackGame.Type.ToEngineType() == requestedEngine) + return finderResult.FallbackGame; + return null; + } + + private static bool IsEngineTypeSupported([NotNullWhen(false)] GameEngineType? requestedEngineType, GameType actualGameType) + { + return !requestedEngineType.HasValue || actualGameType.ToEngineType() == requestedEngineType; + } + + [DoesNotReturn] + private static void ThrowEngineNotSupported(GameEngineType requested, string targetPath) { - return !requestedEngineType.HasValue || target.Game.Type.ToEngineType() == requestedEngineType; + throw new ArgumentException($"The specified game engine '{requested}' does not match engine of the verification target '{targetPath}'."); } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs b/src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs index 410d10d..f963b4f 100644 --- a/src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/ManualSelector.cs @@ -20,7 +20,7 @@ internal override SelectionResult SelectTarget(VerificationTargetSettings settin throw new ArgumentException("Argument --game must be set."); if (!settings.Engine.HasValue) throw new ArgumentException("Unable to determine game type. Use --engine argument to set the game type."); - + var engine = settings.Engine.Value; var gameLocations = new GameLocations( @@ -29,49 +29,37 @@ internal override SelectionResult SelectTarget(VerificationTargetSettings settin GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths).ToList()); - IPhysicalPlayableObject? target = null; - // For the manual selector the whole game and mod detection is optional. // This allows user to use the application for unusual scenarios, // not known to the detection service. - try - { - var game = GameFinderService.FindGame(gameLocations.GamePath, new GameFinderSettings - { - Engine = engine, - InitMods = false, - SearchFallbackGame = false - }); - target = TryGetPlayableObject(game, gameLocations.ModPaths.FirstOrDefault()); - } - catch (GameNotFoundException e) + if (!GameFinderService.TryFindGame(gameLocations.GamePath, + new GameFinderSettings { Engine = engine, InitMods = false, SearchFallbackGame = false }, + out var game)) { // TODO: Log } - + // If the fallback game path is specified we simply try to detect the game and report a warning to the user if not found. var fallbackGamePath = settings.FallbackGamePath; if (!string.IsNullOrEmpty(fallbackGamePath)) { - try - { - GameFinderService.FindGame(fallbackGamePath, new GameFinderSettings - { - InitMods = false, - SearchFallbackGame = false - }); - } - catch (GameNotFoundException e) + + if (!GameFinderService.TryFindGame(fallbackGamePath, + new GameFinderSettings { InitMods = false, SearchFallbackGame = false }, + out _)) { // TODO: Log } } - + + var target = TryGetPlayableObject(game, gameLocations.ModPaths.FirstOrDefault()); return new SelectionResult(gameLocations, engine, target); } - private IPhysicalPlayableObject TryGetPlayableObject(IGame game, string? modPath) + private IPhysicalPlayableObject? TryGetPlayableObject(IGame? game, string? modPath) { + if (game is null) + return null; if (string.IsNullOrEmpty(modPath)) return game; diff --git a/src/ModVerify.CliApp/TargetSelectors/TargetNotFoundException.cs b/src/ModVerify.CliApp/TargetSelectors/TargetNotFoundException.cs new file mode 100644 index 0000000..d6b052e --- /dev/null +++ b/src/ModVerify.CliApp/TargetSelectors/TargetNotFoundException.cs @@ -0,0 +1,6 @@ +using System.IO; + +namespace AET.ModVerify.App.TargetSelectors; + +internal class TargetNotFoundException(string path) + : DirectoryNotFoundException($"The target path '{path}' does not exist"); \ No newline at end of file diff --git a/test/ModVerify.CliApp.Test/CommonTestBase.cs b/test/ModVerify.CliApp.Test/CommonTestBase.cs index 20325ab..a93b970 100644 --- a/test/ModVerify.CliApp.Test/CommonTestBase.cs +++ b/test/ModVerify.CliApp.Test/CommonTestBase.cs @@ -4,32 +4,18 @@ using PG.Commons; using PG.StarWarsGame.Infrastructure; using PG.StarWarsGame.Infrastructure.Clients.Steam; -using System; -using System.IO.Abstractions; -using Testably.Abstractions.Testing; namespace ModVerify.CliApp.Test; -public abstract class CommonTestBase +public abstract class CommonTestBase : AET.Testing.TestBaseWithFileSystem { - protected readonly MockFileSystem FileSystem = new(); - protected readonly IServiceProvider ServiceProvider; - - protected CommonTestBase() - { - var sc = new ServiceCollection(); - sc.AddSingleton(sp => new HashingService(sp)); - sc.AddSingleton(FileSystem); - PetroglyphCommons.ContributeServices(sc); - PetroglyphGameInfrastructure.InitializeServices(sc); - SteamAbstractionLayer.InitializeServices(sc); - SteamPetroglyphStarWarsGameClients.InitializeServices(sc); - // ReSharper disable once VirtualMemberCallInConstructor - SetupServices(sc); - ServiceProvider = sc.BuildServiceProvider(); - } - - protected virtual void SetupServices(ServiceCollection serviceCollection) + protected override void SetupServices(IServiceCollection serviceCollection) { + base.SetupServices(serviceCollection); + serviceCollection.AddSingleton(sp => new HashingService(sp)); + PetroglyphCommons.ContributeServices(serviceCollection); + PetroglyphGameInfrastructure.InitializeServices(serviceCollection); + SteamAbstractionLayer.InitializeServices(serviceCollection); + SteamPetroglyphStarWarsGameClients.InitializeServices(serviceCollection); } } \ No newline at end of file diff --git a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs index 6bb8bb4..3272261 100644 --- a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs +++ b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs @@ -9,6 +9,7 @@ using System.IO.Abstractions; using ModVerify.CliApp.Test.TestData; using Testably.Abstractions; +using Xunit; namespace ModVerify.CliApp.Test; diff --git a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj index 84d1bba..2a54398 100644 --- a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj +++ b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj @@ -3,31 +3,36 @@ net10.0 $(TargetFrameworks);net481 - false preview + + false + true + Exe + + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - diff --git a/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs b/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs index 8d28949..c5caf92 100644 --- a/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs +++ b/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions; using ModVerify.CliApp.Test.TestData; using Testably.Abstractions; +using Xunit; #if NETFRAMEWORK using ModVerify.CliApp.Test.Utilities; #endif diff --git a/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs b/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs index d376010..10156ac 100644 --- a/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs +++ b/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs @@ -1,77 +1,439 @@ -namespace ModVerify.CliApp.Test.TargetSelectors; +using AET.Modinfo.Model; +using AET.Modinfo.Spec.Steam; +using AET.ModVerify.App.GameFinder; +using AET.ModVerify.App.Settings; +using AET.ModVerify.App.TargetSelectors; +using AET.ModVerify.App.Utilities; +using AET.SteamAbstraction.Testing; +using AnakinRaW.CommonUtilities.Registry; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Infrastructure.Games; +using PG.StarWarsGame.Infrastructure.Testing; +using PG.StarWarsGame.Infrastructure.Testing.Installations; +using PG.StarWarsGame.Infrastructure.Testing.Installations.Game; +using System; +using Xunit; + +namespace ModVerify.CliApp.Test.TargetSelectors; public class AutomaticSelectorTest : CommonTestBase { - //private readonly AutomaticSelector _selector; - //private readonly IRegistry _registry = new InMemoryRegistry(InMemoryRegistryCreationFlags.WindowsLike); - - //public AutomaticSelectorTest() - //{ - // _selector = new AutomaticSelector(ServiceProvider); - //} - - //protected override void SetupServices(ServiceCollection serviceCollection) - //{ - // base.SetupServices(serviceCollection); - // serviceCollection.AddSingleton(_registry); - //} - - //[Fact] - //public void Test_Select_GameNotInstalled() - //{ - // var settings = new VerificationTargetSettings - // { - // TargetPath = "/test", - // }; - - // Assert.Throws(() => _selector.SelectTarget(settings)); - //} - - //[Theory] - //[MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] - //public void Test_Select_Game(GameIdentity identity) - //{ - // var (eaw, foc) = InstallGames(identity.Platform); - // var game = identity.Type switch - // { - // GameType.Eaw => eaw, - // GameType.Foc => foc, - // _ => throw new ArgumentOutOfRangeException() - // }; - - // var settings = new VerificationTargetSettings - // { - // TargetPath = game.Directory.FullName, - // }; - - // var result = _selector.SelectTarget(settings); - // Assert.Equal(identity.Type, result.Engine.FromEngineType()); - // Assert.Equal(game, result.Target); - // Assert.Equal(game.Directory.FullName, result.Locations.GamePath); - //} - - //private (IGame eaw, IGame foc) InstallGames(GamePlatform platform) - //{ - // var eaw = FileSystem.InstallGame(new GameIdentity(GameType.Eaw, platform), ServiceProvider); - // var foc = FileSystem.InstallGame(new GameIdentity(GameType.Eaw, platform), ServiceProvider); - - - - // if (platform == GamePlatform.SteamGold) - // { - // InstallSteam(eaw, foc); - // } - - // return (eaw, foc); - //} - - //private void InstallSteam(IGame eaw, IGame foc) - //{ - // var registry = ServiceProvider.GetRequiredService().CreateRegistry(); - // FileSystem.InstallSteam(registry); - - // // Register Game to Steam - // var lib = FileSystem.InstallDefaultLibrary(ServiceProvider); - // lib.InstallGame(32470, "Star Wars Empire at War", [32472], SteamAppState.StateFullyInstalled); - //} + private readonly AutomaticSelector _selector; + private readonly IRegistry _registry = new InMemoryRegistry(InMemoryRegistryCreationFlags.WindowsLike); + + public AutomaticSelectorTest() + { + _selector = new AutomaticSelector(ServiceProvider); + } + + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.AddSingleton(_registry); + } + + [Fact] + public void Test_SelectTarget_GameNotInstalled() + { + var settings = new VerificationTargetSettings + { + TargetPath = "/test", + }; + Assert.Throws(() => _selector.SelectTarget(settings)); + } + + [Fact] + public void Test_SelectTarget_WrongSettings() + { + var settings = new VerificationTargetSettings + { + TargetPath = "/test", + FallbackGamePath = "does/not/exist", + ModPaths = ["also/does/not/exist"], + GamePath = "not/found" + }; + Assert.Throws(() => _selector.SelectTarget(settings)); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_FromGamePath(IGameIdentity identity) + { + TestSelectTarget( + identity, + i => i, + gi => new VerificationTargetSettings + { + TargetPath = gi.PlayableObject.Directory.FullName, + Engine = null + }); + TestSelectTarget( + identity, + i => i, + gi => new VerificationTargetSettings + { + TargetPath = gi.PlayableObject.Directory.FullName, + Engine = gi.PlayableObject.Game.Type.ToEngineType() + }); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_FromGamePath_OppositeEngine(IGameIdentity identity) + { + if (identity.Platform is GamePlatform.SteamGold) + { + TestSelectTarget(identity, + i => i, + gi => new VerificationTargetSettings + { + TargetPath = gi.PlayableObject.Directory.FullName, + Engine = gi.PlayableObject.Game.Type.Opposite().ToEngineType(), + }, + typeof(ArgumentException)); + } + else + { + TestSelectTarget(identity, + i => i, + gi => new VerificationTargetSettings + { + TargetPath = gi.PlayableObject.Directory.FullName, + Engine = gi.PlayableObject.Game.Type.Opposite().ToEngineType(), + }, + typeof(GameNotFoundException)); + } + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_ModInModsDir(IGameIdentity identity) + { + TestSelectTarget( + identity, + gameInstallation => gameInstallation.InstallMod("MyMod", false), + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = null + }); + TestSelectTarget( + identity, + gameInstallation => gameInstallation.InstallMod("MyMod", false), + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = ti.GameInstallation.Game.Type.ToEngineType() + }); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_ModInModsDir_WrongGameEngine_Throws(IGameIdentity identity) + { + var expectedExceptionType = identity.Platform == GamePlatform.SteamGold + ? typeof(ArgumentException) + : typeof(GameNotFoundException); + + TestSelectTarget( + identity, + gameInstallation => gameInstallation.InstallMod("MyMod", false), + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = ti.GameInstallation.Game.Type.Opposite().ToEngineType() + }, expectedExceptionType); + } + + + [Theory] + [InlineData(GameType.Eaw)] + [InlineData(GameType.Foc)] + public void Test_SelectTarget_Workshops_UnknownModEngineType(GameType gameType) + { + var identity = new GameIdentity(gameType, GamePlatform.SteamGold); + TestSelectTarget( + identity, + gameInstallation => gameInstallation.InstallMod("MyMod", true), + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = ti.GameInstallation.Game.Type.ToEngineType() + }); + + TestSelectTarget( + identity, + gameInstallation => gameInstallation.InstallMod("MyMod", true), + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = null // Causes fallback to FoC + }, + overrideAssertData: new OverrideAssertData { GameType = GameType.Foc }); + } + + [Theory] + [InlineData(GameType.Eaw)] + [InlineData(GameType.Foc)] + public void Test_SelectTarget_Workshops_UnspecifiedEngineIsCorrectEngineTyp_KnownModEngineType(GameType gameType) + { + var identity = new GameIdentity(gameType, GamePlatform.SteamGold); + + var modinfo = new ModinfoData("MyMod") + { + SteamData = new SteamData("123456", "123456", SteamWorkshopVisibility.Public, "MyMod", + [gameType.ToString().ToUpper()]) + }; + + TestSelectTarget( + identity, + gameInstallation => + { + var modInstallation = gameInstallation.InstallMod(modinfo, true); + modInstallation.InstallModinfoFile(modinfo); + return modInstallation; + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = null + }); + } + + [Theory] + [InlineData(GameType.Eaw)] + [InlineData(GameType.Foc)] + public void Test_SelectTarget_Workshops_IncompatibleKnownModEngineType_Throws(GameType gameType) + { + var identity = new GameIdentity(gameType, GamePlatform.SteamGold); + + var modinfo = new ModinfoData("MyMod") + { + SteamData = new SteamData("123456", "123456", SteamWorkshopVisibility.Public, "MyMod", + [gameType.ToString().ToUpper()]) + }; + + TestSelectTarget( + identity, + gameInstallation => + { + var modInstallation = gameInstallation.InstallMod(modinfo, true); + modInstallation.InstallModinfoFile(modinfo); + return modInstallation; + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = gameType.Opposite().ToEngineType() + }, + typeof(ArgumentException)); + } + + [Theory] + [InlineData(GameType.Eaw)] + [InlineData(GameType.Foc)] + public void Test_SelectTarget_Workshops_MultipleKnownModEngineTypes(GameType gameType) + { + var identity = new GameIdentity(gameType, GamePlatform.SteamGold); + + var modinfo = new ModinfoData("MyMod") + { + SteamData = new SteamData("123456", "123456", SteamWorkshopVisibility.Public, "MyMod", + [gameType.ToString().ToUpper(), gameType.Opposite().ToString().ToUpper()]) + }; + + TestSelectTarget( + identity, + gameInstallation => + { + var modInstallation = gameInstallation.InstallMod(modinfo, true); + modInstallation.InstallModinfoFile(modinfo); + return modInstallation; + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = gameType.ToEngineType() + }); + + TestSelectTarget( + identity, + gameInstallation => + { + var modInstallation = gameInstallation.InstallMod(modinfo, true); + modInstallation.InstallModinfoFile(modinfo); + return modInstallation; + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = null // Causes fallback to FoC + }, + overrideAssertData: new OverrideAssertData{GameType = GameType.Foc}); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_DetachedMod_NoEngineSpecified_Throws(IGameIdentity identity) + { + var exceptionType = identity.Platform is not GamePlatform.SteamGold + ? typeof(GameNotFoundException) + : typeof(ArgumentException); + + TestSelectTarget( + identity, + gameInstallation => + { + var modinfo = new ModinfoData("DetachedMod"); + var modPath = FileSystem.Directory.CreateDirectory("/detachedMod"); + return gameInstallation.InstallMod(modinfo, modPath, false); + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = null // No Engine means we cannot proceed + }, + expectedExceptionType: exceptionType); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_DetachedMod(IGameIdentity identity) + { + // Currently, only Steam is supported for detached mods. + var exceptionType = identity.Platform is not GamePlatform.SteamGold + ? typeof(GameNotFoundException) + : null; + + TestSelectTarget( + identity, + gameInstallation => + { + var modinfo = new ModinfoData("DetachedMod"); + var modPath = FileSystem.Directory.CreateDirectory("/detachedMod"); + return gameInstallation.InstallMod(modinfo, modPath, false); + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = identity.Type.ToEngineType() + }, + expectedExceptionType: exceptionType); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_AttachedMod_DoesNotExist(IGameIdentity identity) + { + // Currently, only Steam is supported for detached mods. + TestSelectTarget( + identity, + gameInstallation => + { + var modInstallation = gameInstallation.InstallMod("MyMod", GITestUtilities.GetRandomWorkshopFlag(identity)); + modInstallation.Mod.Directory.Delete(true); + return modInstallation; + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = identity.Type.ToEngineType() + }, + typeof(TargetNotFoundException)); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void Test_SelectTarget_DetachedMod_DoesNotExist(IGameIdentity identity) + { + TestSelectTarget( + identity, + gameInstallation => + { + var modinfo = new ModinfoData("DetachedMod"); + var modPath = FileSystem.DirectoryInfo.New("/detachedMod"); + var modInstallation = gameInstallation.InstallMod(modinfo, modPath, false); + modPath.Delete(true); + return modInstallation; + }, + ti => new VerificationTargetSettings + { + TargetPath = ti.PlayableObject.Directory.FullName, + Engine = identity.Type.ToEngineType() + }, + typeof(TargetNotFoundException)); + } + + private void TestSelectTarget( + IGameIdentity identity, + Func targetFactory, + Func settingsFactory, + Type? expectedExceptionType = null, + OverrideAssertData? overrideAssertData = null) + { + var (eaw, foc) = InstallGames(identity.Platform); + var gameInstallation = identity.Type switch + { + GameType.Eaw => eaw, + GameType.Foc => foc, + _ => throw new ArgumentOutOfRangeException() + }; + + var targetInstallation = targetFactory(gameInstallation); + + var settings = settingsFactory(targetInstallation); + + if (expectedExceptionType is not null) + { + Assert.Throws(expectedExceptionType, () => _selector.SelectTarget(settings)); + return; + } + + if (identity.Type == GameType.Foc && identity.Platform != GamePlatform.SteamGold) + Assert.Throws(() => _selector.SelectTarget(settings)); + else + { + var result = _selector.SelectTarget(settings); + Assert.Equal(overrideAssertData?.GameType ?? identity.Type, result.Engine.FromEngineType()); + Assert.Equal(targetInstallation.PlayableObject.GetType(), result.Target!.GetType()); + Assert.Equal(targetInstallation.PlayableObject.Directory.FullName, result.Locations.TargetPath); + + if (result.Engine == GameEngineType.Foc) + Assert.NotEmpty(result.Locations.FallbackPaths); + else + Assert.Empty(result.Locations.FallbackPaths); + } + } + + private (ITestingGameInstallation eaw, ITestingGameInstallation foc) InstallGames(GamePlatform platform) + { + var eaw = GameInfrastructureTesting.Game(new GameIdentity(GameType.Eaw, platform), ServiceProvider); + var foc = GameInfrastructureTesting.Game(new GameIdentity(GameType.Foc, platform), ServiceProvider); + + GameInfrastructureTesting.Registry(ServiceProvider).CreateInstalled(eaw.Game); + GameInfrastructureTesting.Registry(ServiceProvider).CreateInstalled(foc.Game); + + eaw.InstallMod("OtherEawMod"); + foc.InstallMod("OtherFocMod"); + + if (platform == GamePlatform.SteamGold) + InstallSteam(); + + return (eaw, foc); + } + + private void InstallSteam() + { + var steam = SteamTesting.Steam(ServiceProvider); + steam.Install(); + // Register Game to Steam + var lib = steam.InstallDefaultLibrary(); + lib.InstallGame(32470, "Star Wars Empire at War", [32472]); + } + + private class OverrideAssertData + { + public GameType? GameType { get; init; } + } } \ No newline at end of file From 327c0267e70faee4a1c9cb4c615fd59f727ae69c Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 16:22:46 +0100 Subject: [PATCH 11/38] update to new deps --- .../CommandBar/CommandBarGameManager.cs | 4 ++-- .../PG.StarWarsGame.Engine/GameManagerBase.cs | 5 ++--- .../GuiDialog/Xml/XmlComponentTextureData.cs | 6 +++--- .../PG.StarWarsGame.Engine/IGameManager.cs | 2 +- .../PG.StarWarsGame.Engine.csproj | 11 ++++------- .../Rendering/Animations/AnimationCollection.cs | 11 +++++------ .../Xml/Parsers/Data/CommandBarComponentParser.cs | 4 ++-- .../Xml/Parsers/Data/GameObjectParser.cs | 4 ++-- .../Xml/Parsers/Data/SfxEventParser.cs | 4 ++-- .../Xml/Parsers/File/CommandBarComponentFileParser.cs | 4 ++-- .../Xml/Parsers/File/GameObjectFileParser.cs | 4 ++-- .../Xml/Parsers/File/GuiDialogParser.cs | 4 ++-- .../Xml/Parsers/File/SfxEventFileParser.cs | 4 ++-- .../Xml/Parsers/XmlContainerContentParser.cs | 4 ++-- .../Xml/Parsers/XmlObjectParser.cs | 8 ++++---- .../PG.StarWarsGame.Files.ALO.csproj | 3 --- .../PG.StarWarsGame.Files.ChunkFiles.csproj | 5 +---- .../PG.StarWarsGame.Files.XML.csproj | 3 +-- .../Parsers/Base/IPetroglyphXmlFileContainerParser.cs | 4 ++-- .../Parsers/PetroglyphXmlFileContainerParser.cs | 6 +++--- 20 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs index 311c212..a007472 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using PG.Commons.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.CommandBar.Components; using PG.StarWarsGame.Engine.CommandBar.Xml; @@ -18,6 +17,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using AnakinRaW.CommonUtilities.Collections; namespace PG.StarWarsGame.Engine.CommandBar; @@ -77,7 +77,7 @@ protected override async Task InitializeCoreAsync(CancellationToken token) var contentParser = new XmlContainerContentParser(ServiceProvider, ErrorReporter); contentParser.XmlParseError += OnParseError; - var parsedCommandBarComponents = new ValueListDictionary(); + var parsedCommandBarComponents = new FrugalValueListDictionary(); try { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs index 130fb50..607121d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs @@ -6,7 +6,6 @@ using AnakinRaW.CommonUtilities.Collections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using PG.Commons.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.ErrorReporting; using PG.StarWarsGame.Engine.IO.Repositories; @@ -16,13 +15,13 @@ namespace PG.StarWarsGame.Engine; internal abstract class GameManagerBase(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) : GameManagerBase(repository, errorReporter, serviceProvider), IGameManager { - protected readonly ValueListDictionary NamedEntries = new(); + protected readonly FrugalValueListDictionary NamedEntries = new(); public ICollection Entries => NamedEntries.Values; public ICollection EntryKeys => NamedEntries.Keys; - public ReadOnlyFrugalList GetEntries(Crc32 key) + public ImmutableFrugalList GetEntries(Crc32 key) { return NamedEntries.GetValues(key); } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs index 2ed95ce..f102457 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs @@ -1,14 +1,14 @@ using System; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.StarWarsGame.Engine.Xml; using PG.StarWarsGame.Files.XML; namespace PG.StarWarsGame.Engine.GuiDialog.Xml; -public class XmlComponentTextureData(string componentId, IReadOnlyValueListDictionary textures, XmlLocationInfo location) +public class XmlComponentTextureData(string componentId, IReadOnlyFrugalValueListDictionary textures, XmlLocationInfo location) : XmlObject(location) { public string Component { get; } = componentId ?? throw new ArgumentNullException(componentId); - public IReadOnlyValueListDictionary Textures { get; } = textures ?? throw new ArgumentNullException(nameof(textures)); + public IReadOnlyFrugalValueListDictionary Textures { get; } = textures ?? throw new ArgumentNullException(nameof(textures)); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs index b9f60a7..750f00c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs @@ -10,5 +10,5 @@ public interface IGameManager ICollection EntryKeys { get; } - ReadOnlyFrugalList GetEntries(Crc32 key); + ImmutableFrugalList GetEntries(Crc32 key); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj index e54e6f0..19b4c1a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj @@ -23,10 +23,10 @@ - - - - + + + + @@ -39,7 +39,4 @@ - - - \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs index 03916fe..dfd43aa 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using AnakinRaW.CommonUtilities; using AnakinRaW.CommonUtilities.Collections; -using PG.Commons.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Files.ALO.Files.Animations; @@ -13,10 +12,10 @@ public sealed class AnimationCollection : DisposableObject, IEnumerable _animations = new(); - private readonly ValueListDictionary _animationCrc = new(); + private readonly FrugalValueListDictionary _animations = new(); + private readonly FrugalValueListDictionary _animationCrc = new(); - public int Cout => _animations.Count; + public int Cout => _animations.ValueCount; public Crc32 GetAnimationCrc(ModelAnimationType type, int subIndex) { @@ -28,12 +27,12 @@ public Crc32 GetAnimationCrc(ModelAnimationType type, int subIndex) return checksumsForType[subIndex]; } - public ReadOnlyFrugalList GetAnimations(ModelAnimationType type) + public ImmutableFrugalList GetAnimations(ModelAnimationType type) { return _animations.GetValues(type); } - public bool TryGetAnimations(ModelAnimationType type, out ReadOnlyFrugalList animations) + public bool TryGetAnimations(ModelAnimationType type, out ImmutableFrugalList animations) { return _animations.TryGetValues(type, out animations); } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs index 1e20a8b..297b474 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs @@ -1,7 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.CommandBar.Xml; using PG.StarWarsGame.Engine.Xml.Tags; @@ -12,7 +12,7 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; public sealed class CommandBarComponentParser( - IReadOnlyValueListDictionary parsedElements, + IReadOnlyFrugalValueListDictionary parsedElements, IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : XmlObjectParser(parsedElements, serviceProvider, errorReporter) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs index 4f445a7..5cac3e2 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs @@ -1,6 +1,6 @@ using System; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.GameObjects; using PG.StarWarsGame.Files.XML; @@ -26,7 +26,7 @@ public static class GameObjectXmlTags } public sealed class GameObjectParser( - IReadOnlyValueListDictionary parsedElements, + IReadOnlyFrugalValueListDictionary parsedElements, IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : XmlObjectParser(parsedElements, serviceProvider, errorReporter) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs index 4e3a5cb..bc3439f 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs @@ -1,7 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.Audio.Sfx; using PG.StarWarsGame.Engine.Xml.Tags; @@ -12,7 +12,7 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; public sealed class SfxEventParser( - IReadOnlyValueListDictionary parsedElements, + IReadOnlyFrugalValueListDictionary parsedElements, IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : XmlObjectParser(parsedElements, serviceProvider, errorReporter) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs index a32ff09..763d244 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs @@ -1,6 +1,6 @@ using System; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.CommandBar.Xml; using PG.StarWarsGame.Engine.Xml.Parsers.Data; @@ -12,7 +12,7 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers.File; internal class CommandBarComponentFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : PetroglyphXmlFileContainerParser(serviceProvider, errorReporter) { - protected override void Parse(XElement element, IValueListDictionary parsedElements, string fileName) + protected override void Parse(XElement element, IFrugalValueListDictionary parsedElements, string fileName) { var parser = new CommandBarComponentParser(parsedElements, ServiceProvider, ErrorReporter); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs index d2abfed..ffeafff 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs @@ -1,6 +1,6 @@ using System; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.GameObjects; using PG.StarWarsGame.Engine.Xml.Parsers.Data; @@ -12,7 +12,7 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers.File; internal class GameObjectFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : PetroglyphXmlFileContainerParser(serviceProvider, errorReporter) { - protected override void Parse(XElement element, IValueListDictionary parsedElements, string fileName) + protected override void Parse(XElement element, IFrugalValueListDictionary parsedElements, string fileName) { var parser = new GameObjectParser(parsedElements, ServiceProvider, ErrorReporter); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GuiDialogParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GuiDialogParser.cs index 7c23dd6..851aa9f 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GuiDialogParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GuiDialogParser.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.StarWarsGame.Engine.GuiDialog.Xml; using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; @@ -49,7 +49,7 @@ private GuiDialogsXmlTextureData ParseTextures(XElement? element, string fileNam private XmlComponentTextureData ParseTexture(XElement texture) { var componentId = GetTagName(texture); - var textures = new ValueListDictionary(); + var textures = new FrugalValueListDictionary(); foreach (var entry in texture.Elements()) textures.Add(entry.Name.ToString(), PetroglyphXmlStringParser.Instance.Parse(entry)); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs index f40dd1a..841d805 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs @@ -1,6 +1,6 @@ using System; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Engine.Audio.Sfx; using PG.StarWarsGame.Engine.Xml.Parsers.Data; @@ -12,7 +12,7 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers.File; internal class SfxEventFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : PetroglyphXmlFileContainerParser(serviceProvider, errorReporter) { - protected override void Parse(XElement element, IValueListDictionary parsedElements, string fileName) + protected override void Parse(XElement element, IFrugalValueListDictionary parsedElements, string fileName) { var parser = new SfxEventParser(parsedElements, ServiceProvider, ErrorReporter); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs index 0c911e2..00f4f61 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs @@ -1,9 +1,9 @@ using System; using System.Linq; using System.Xml; +using AnakinRaW.CommonUtilities.Collections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using PG.Commons.Collections; using PG.Commons.Hashing; using PG.Commons.Services; using PG.StarWarsGame.Engine.IO; @@ -34,7 +34,7 @@ public void ParseEntriesFromFileListXml( string xmlFile, IGameRepository gameRepository, string lookupPath, - ValueListDictionary entries, + FrugalValueListDictionary entries, Action? onFileParseAction = null) where T : notnull { Logger.LogDebug("Parsing container data '{XmlFile}'", xmlFile); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs index fead02d..1565e9e 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs @@ -1,7 +1,7 @@ using System; using System.Xml.Linq; +using AnakinRaW.CommonUtilities.Collections; using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers; public abstract class XmlObjectParser( - IReadOnlyValueListDictionary parsedElements, + IReadOnlyFrugalValueListDictionary parsedElements, IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : XmlObjectParser(parsedElements, serviceProvider, errorReporter) where TObject : XmlObject @@ -34,12 +34,12 @@ public readonly struct EmptyParseState public abstract class XmlObjectParser( - IReadOnlyValueListDictionary parsedElements, + IReadOnlyFrugalValueListDictionary parsedElements, IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : PetroglyphXmlElementParser(errorReporter) where TObject : XmlObject { - protected IReadOnlyValueListDictionary ParsedElements { get; } = + protected IReadOnlyFrugalValueListDictionary ParsedElements { get; } = parsedElements ?? throw new ArgumentNullException(nameof(parsedElements)); protected ICrc32HashingService HashingService { get; } = serviceProvider.GetRequiredService(); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj index c653074..052190c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj @@ -24,7 +24,4 @@ - - - \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj index 36221be..f49643c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj @@ -17,9 +17,6 @@ preview - - - - + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj index fbb055c..75fecfd 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj @@ -18,8 +18,7 @@ preview - - + diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs index f42a9a9..cd21e00 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs @@ -1,10 +1,10 @@ using System.IO; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; namespace PG.StarWarsGame.Files.XML.Parsers; public interface IPetroglyphXmlFileContainerParser : IPetroglyphXmlParser where T : notnull { - void ParseFile(Stream xmlStream, IValueListDictionary parsedEntries); + void ParseFile(Stream xmlStream, IFrugalValueListDictionary parsedEntries); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs index 4e371e2..d2b862a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Xml.Linq; -using PG.Commons.Collections; +using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; using PG.StarWarsGame.Files.XML.ErrorHandling; @@ -10,12 +10,12 @@ namespace PG.StarWarsGame.Files.XML.Parsers; public abstract class PetroglyphXmlFileContainerParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? listener = null) : PetroglyphXmlFileParserBase(serviceProvider, listener), IPetroglyphXmlFileContainerParser where T : notnull { - public void ParseFile(Stream xmlStream, IValueListDictionary parsedEntries) + public void ParseFile(Stream xmlStream, IFrugalValueListDictionary parsedEntries) { var root = GetRootElement(xmlStream, out var fileName); if (root is not null) Parse(root, parsedEntries, fileName); } - protected abstract void Parse(XElement element, IValueListDictionary parsedElements, string fileName); + protected abstract void Parse(XElement element, IFrugalValueListDictionary parsedElements, string fileName); } \ No newline at end of file From fd808567eff30308d9529a0cc054611eb4442179 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 16:38:24 +0100 Subject: [PATCH 12/38] update to new deps (just to make it compile) --- src/ModVerify/ModVerify.csproj | 12 +- .../Pipeline/GameVerifierPipelineStep.cs | 35 +-- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 240 ++++++++++-------- .../Verifiers/DuplicateNameFinder.cs | 10 +- 4 files changed, 158 insertions(+), 139 deletions(-) diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index 89c4b64..905b020 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -33,10 +33,10 @@ - - - - + + + + @@ -51,8 +51,4 @@ - - - - diff --git a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs index 6405dbd..f201d6b 100644 --- a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs +++ b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; using System.Threading; +using System.Threading.Tasks; using AET.ModVerify.Pipeline.Progress; namespace AET.ModVerify.Pipeline; @@ -22,23 +23,27 @@ public sealed class GameVerifierPipelineStep( public long Size => 1; - protected override void RunCore(CancellationToken token) + protected override async Task RunCoreAsync(CancellationToken token) { - try + await Task.Run(() => { - Logger?.LogDebug("Running verifier '{Name}'...", GameVerifier.FriendlyName); - ReportProgress(new ProgressEventArgs(0.0, "Started")); - - GameVerifier.Progress += OnVerifyProgress; - GameVerifier.Verify(token); - - Logger?.LogDebug("Finished verifier '{Name}'", GameVerifier.FriendlyName); - ReportProgress(new ProgressEventArgs(1.0, "Finished")); - } - finally - { - GameVerifier.Progress += OnVerifyProgress; - } + try + { + Logger?.LogDebug("Running verifier '{Name}'...", GameVerifier.FriendlyName); + ReportProgress(new ProgressEventArgs(0.0, "Started")); + + GameVerifier.Progress += OnVerifyProgress; + GameVerifier.Verify(token); + + Logger?.LogDebug("Finished verifier '{Name}'", GameVerifier.FriendlyName); + ReportProgress(new ProgressEventArgs(1.0, "Finished")); + } + finally + { + GameVerifier.Progress += OnVerifyProgress; + } + }, CancellationToken.None).ConfigureAwait(false); + } private void OnVerifyProgress(object _, ProgressEventArgs e) diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index ab62511..a450813 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -2,7 +2,6 @@ using AET.ModVerify.Reporting; using AET.ModVerify.Reporting.Settings; using AET.ModVerify.Settings; -using AET.ModVerify.Utilities; using AET.ModVerify.Verifiers; using AnakinRaW.CommonUtilities.SimplePipeline; using AnakinRaW.CommonUtilities.SimplePipeline.Runners; @@ -10,20 +9,19 @@ using PG.StarWarsGame.Engine; using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace AET.ModVerify.Pipeline; -public sealed class NewGameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline +public sealed class NewGameVerifyPipeline : StepRunnerPipelineBase { private readonly List _verifiers = new(); private readonly List _verificationSteps = new(); private readonly ConcurrentGameEngineErrorReporter _engineErrorReporter = new(); - private readonly StepRunnerBase _verifyRunner; + private readonly AsyncStepRunner _verifyRunner; private readonly VerificationTarget _verificationTarget; private readonly VerifyPipelineSettings _pipelineSettings; private readonly GlobalVerifyReportSettings _reportSettings; @@ -32,9 +30,24 @@ public sealed class NewGameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipe private readonly IPetroglyphStarWarsGameEngineService _gameEngineService; private readonly ILogger? _logger; - protected override bool FailFast { get; } + protected override Task PrepareCoreAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + protected override AsyncStepRunner CreateRunner() + { + throw new NotImplementedException(); + } - public IReadOnlyCollection FilteredErrors { get; private set; } = []; + //protected override IStepRunner CreateRunner() + //{ + // throw new NotImplementedException(); + //} + + //protected override bool FailFast { get; } + + //public IReadOnlyCollection FilteredErrors { get; private set; } = []; public NewGameVerifyPipeline( VerificationTarget verificationTarget, @@ -52,114 +65,119 @@ public NewGameVerifyPipeline( _gameEngineService = serviceProvider.GetRequiredService(); _logger = serviceProvider.GetService()?.CreateLogger(GetType()); - _verifyRunner = pipelineSettings.ParallelVerifiers switch - { - < 0 or > 64 => throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", - nameof(pipelineSettings)), - 1 => new SequentialStepRunner(serviceProvider), - _ => new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider) - }; - - FailFast = pipelineSettings.FailFast; - } - - protected override async Task PrepareCoreAsync() - { - _verifiers.Clear(); - - - IStarWarsGameEngine gameEngine; - - try - { - gameEngine = await _gameEngineService.InitializeAsync( - _verificationTarget.Engine, - _verificationTarget.Location, - _engineErrorReporter, - _engineInitializationReporter, - false, - CancellationToken.None).ConfigureAwait(false); - } - catch (Exception e) - { - _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); - throw; - } - - - AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); - - foreach (var gameVerificationStep in CreateVerificationSteps(gameEngine)) - AddStep(gameVerificationStep); - - return true; - } - - protected override async Task RunCoreAsync(CancellationToken token) - { - var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); - - try - { - Logger?.LogInformation("Running game verifiers..."); - _progressReporter.Report(0.0, $"Verifing {_verificationTarget.Name}...", VerifyProgress.ProgressType, default); - _verifyRunner.Error += OnError; - await _verifyRunner.RunAsync(token); - } - finally - { - aggregatedVerifyProgressReporter.Dispose(); - _verifyRunner.Error -= OnError; - Logger?.LogDebug("Game verifiers finished."); - } - - token.ThrowIfCancellationRequested(); - - var failedSteps = _verifyRunner.ExecutedSteps.Where(p => - p.Error != null && !p.Error.IsExceptionType()).ToList(); + //_verifyRunner = pipelineSettings.ParallelVerifiers switch + //{ + // < 0 or > 64 => throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", + // nameof(pipelineSettings)), + // 1 => new SequentialStepRunner(serviceProvider), + // _ => new AsyncStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider) + //}; - if (failedSteps.Count != 0) - throw new StepFailureException(failedSteps); - - FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); - - - _progressReporter.Report(1.0, $"Finished Verifing {_verificationTarget.Name}", VerifyProgress.ProgressType, default); + //FailFast = pipelineSettings.FailFast; } - protected override void OnError(object sender, StepRunnerErrorEventArgs e) - { - if (FailFast && e.Exception is GameVerificationException v) - { - // TODO: Apply globalMinSeverity - if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) - return; - } - base.OnError(sender, e); - } - - private void AddStep(GameVerifier verifier) - { - var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); - _verifyRunner.AddStep(verificationStep); - _verificationSteps.Add(verificationStep); - _verifiers.Add(verifier); - } - - private IEnumerable GetReportableErrors(IEnumerable errors) - { - Logger?.LogDebug("Applying baseline and suppressions."); - // NB: We don't filter for severity here, as the individual reporters handle that. - // This allows better control over what gets reported. - return errors.ApplyBaseline(_reportSettings.Baseline) - .ApplySuppressions(_reportSettings.Suppressions); - } - - private IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) - { - return _pipelineSettings.VerifiersProvider - .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); - } + //protected override async Task PrepareCoreAsync() + //{ + // _verifiers.Clear(); + + + // IStarWarsGameEngine gameEngine; + + // try + // { + // gameEngine = await _gameEngineService.InitializeAsync( + // _verificationTarget.Engine, + // _verificationTarget.Location, + // _engineErrorReporter, + // _engineInitializationReporter, + // false, + // CancellationToken.None).ConfigureAwait(false); + // } + // catch (Exception e) + // { + // _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); + // throw; + // } + + + // AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); + + // foreach (var gameVerificationStep in CreateVerificationSteps(gameEngine)) + // AddStep(gameVerificationStep); + + // return true; + //} + + //protected override Task> CreateRunnerSteps(CancellationToken token) + //{ + // throw new NotImplementedException(); + //} + + //protected override async Task RunCoreAsync(CancellationToken token) + //{ + // var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); + + // try + // { + // Logger?.LogInformation("Running game verifiers..."); + // _progressReporter.Report(0.0, $"Verifing {_verificationTarget.Name}...", VerifyProgress.ProgressType, default); + // _verifyRunner.Error += OnError; + // await _verifyRunner.RunAsync(token); + // } + // finally + // { + // aggregatedVerifyProgressReporter.Dispose(); + // _verifyRunner.Error -= OnError; + // Logger?.LogDebug("Game verifiers finished."); + // } + + // token.ThrowIfCancellationRequested(); + + // var failedSteps = _verifyRunner.ExecutedSteps.Where(p => + // p.Error != null && !p.Error.IsExceptionType()).ToList(); + + // if (failedSteps.Count != 0) + // throw new StepFailureException(failedSteps); + + // FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); + + + // _progressReporter.Report(1.0, $"Finished Verifing {_verificationTarget.Name}", VerifyProgress.ProgressType, default); + //} + + //protected override void OnError(object sender, StepRunnerErrorEventArgs e) + //{ + // if (FailFast && e.Exception is GameVerificationException v) + // { + // // TODO: Apply globalMinSeverity + // if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) + // return; + // } + // base.OnError(sender, e); + //} + + //private void AddStep(GameVerifier verifier) + //{ + // var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); + // _verifyRunner.AddStep(verificationStep); + // _verificationSteps.Add(verificationStep); + // _verifiers.Add(verifier); + //} + + //private IEnumerable GetReportableErrors(IEnumerable errors) + //{ + // Logger?.LogDebug("Applying baseline and suppressions."); + // // NB: We don't filter for severity here, as the individual reporters handle that. + // // This allows better control over what gets reported. + // return errors.ApplyBaseline(_reportSettings.Baseline) + // .ApplySuppressions(_reportSettings.Suppressions); + //} + + //private IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) + //{ + // return _pipelineSettings.VerifiersProvider + // .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); + //} } diff --git a/src/ModVerify/Verifiers/DuplicateNameFinder.cs b/src/ModVerify/Verifiers/DuplicateNameFinder.cs index 23ab1b8..0b3b585 100644 --- a/src/ModVerify/Verifiers/DuplicateNameFinder.cs +++ b/src/ModVerify/Verifiers/DuplicateNameFinder.cs @@ -40,10 +40,10 @@ private void CheckForDuplicateCrcEntries( string sourceName, TSource source, Func> crcSelector, - Func> entrySelector, + Func> entrySelector, Func entryToStringSelector, - Func, IEnumerable> contextSelector, - Func, string, string> errorMessageCreator) + Func, IEnumerable> contextSelector, + Func, string, string> errorMessageCreator) { foreach (var crc32 in crcSelector(source)) { @@ -87,13 +87,13 @@ private void CheckXmlObjectsForDuplicates(string databaseName, IGameManager entries, string fileName) + private static string CreateDuplicateMtdErrorMessage(ImmutableFrugalList entries, string fileName) { var firstEntry = entries.First(); return $"MTD File '{fileName}' has duplicate definitions for CRC ({firstEntry}): {string.Join(",", entries.Select(x => x.FileName))}"; } - private static string CreateDuplicateXmlErrorMessage(ReadOnlyFrugalList entries, string databaseName) where T : NamedXmlObject + private static string CreateDuplicateXmlErrorMessage(ImmutableFrugalList entries, string databaseName) where T : NamedXmlObject { var firstEntry = entries.First(); var message = $"{databaseName} '{firstEntry.Name}' ({firstEntry.Crc32}) has duplicate definitions: "; From 68453c2823481abf78d724ccdc00ce7caa08a3d2 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 17:28:03 +0100 Subject: [PATCH 13/38] make everything compile at least --- modules/ModdingToolBase | 2 +- src/ModVerify.CliApp/ModVerify.CliApp.csproj | 39 ++++++++++++------- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 2 +- test/ModVerify.CliApp.Test/CommonTestBase.cs | 3 +- .../ModVerify.CliApp.Test.csproj | 10 ++--- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index a6a05f7..9155aeb 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit a6a05f7c3ecc91d796afff91b9686bcded0f8030 +Subproject commit 9155aebd41f0dc24ed2e900139a9e6b75d631228 diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj index 9874e8c..2301a28 100644 --- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj +++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj @@ -21,6 +21,10 @@ en + + true + + @@ -30,18 +34,18 @@ - - + + - + - - - - - + + + + + @@ -51,10 +55,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - all @@ -64,9 +64,18 @@ - - true - + + + + compile + runtime; build; native; contentfiles; analyzers; buildtransitive + + + compile + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index a450813..a2ee0b5 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -47,7 +47,7 @@ protected override AsyncStepRunner CreateRunner() //protected override bool FailFast { get; } - //public IReadOnlyCollection FilteredErrors { get; private set; } = []; + public IReadOnlyCollection FilteredErrors { get; private set; } = []; public NewGameVerifyPipeline( VerificationTarget verificationTarget, diff --git a/test/ModVerify.CliApp.Test/CommonTestBase.cs b/test/ModVerify.CliApp.Test/CommonTestBase.cs index a93b970..8a93507 100644 --- a/test/ModVerify.CliApp.Test/CommonTestBase.cs +++ b/test/ModVerify.CliApp.Test/CommonTestBase.cs @@ -1,5 +1,6 @@ using AET.SteamAbstraction; using AnakinRaW.CommonUtilities.Hashing; +using AnakinRaW.CommonUtilities.Testing; using Microsoft.Extensions.DependencyInjection; using PG.Commons; using PG.StarWarsGame.Infrastructure; @@ -7,7 +8,7 @@ namespace ModVerify.CliApp.Test; -public abstract class CommonTestBase : AET.Testing.TestBaseWithFileSystem +public abstract class CommonTestBase : TestBaseWithFileSystem { protected override void SetupServices(IServiceCollection serviceCollection) { diff --git a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj index 2a54398..09e62a4 100644 --- a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj +++ b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj @@ -13,13 +13,13 @@ - - - + + + - + - + all From 371b5eb8e6e40424f72b376cb0cd016809502607 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 17:28:11 +0100 Subject: [PATCH 14/38] to weekly deps check --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b43ddf6..8c2061c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" groups: actions-deps: patterns: @@ -19,7 +19,7 @@ updates: - package-ecosystem: "nuget" directory: "/" schedule: - interval: "daily" + interval: "weekly" target-branch: "develop" open-pull-requests-limit: 1 groups: From 5ae5aedbc9685a448dcc527aff6b23cc2de79673 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 17:28:29 +0100 Subject: [PATCH 15/38] update license year --- Directory.Build.props | 4 ++-- LICENSE | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index d475e7f..5110cdb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ ModVerify Alamo Engine Tools and Contributors - Copyright © 2025 Alamo Engine Tools and contributors. All rights reserved. + Copyright © 2026 Alamo Engine Tools and contributors. All rights reserved. https://github.com/AlamoEngine-Tools/ModVerify $(RepoRootPath)LICENSE MIT @@ -33,7 +33,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/LICENSE b/LICENSE index 7159126..23fe869 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Alamo Engine Tools +Copyright (c) 2026 Alamo Engine Tools Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 30641ed85df1d10c5676d8e4f7db01076c1b089c Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 17:49:42 +0100 Subject: [PATCH 16/38] rename class --- src/ModVerify.CliApp/ModVerifyApplication.cs | 2 +- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 294 +++++-------------- 2 files changed, 75 insertions(+), 221 deletions(-) diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index fb3ebab..4d45979 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -129,7 +129,7 @@ private async Task> Verify( { var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name); - using var verifyPipeline = new NewGameVerifyPipeline( + using var verifyPipeline = new GameVerifyPipeline( verificationTarget, settings.VerifyPipelineSettings, reportSettings, diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index a2ee0b5..1260681 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -9,19 +9,20 @@ using PG.StarWarsGame.Engine; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using AET.ModVerify.Utilities; using Microsoft.Extensions.DependencyInjection; namespace AET.ModVerify.Pipeline; -public sealed class NewGameVerifyPipeline : StepRunnerPipelineBase +public sealed class GameVerifyPipeline : StepRunnerPipelineBase { private readonly List _verifiers = new(); private readonly List _verificationSteps = new(); private readonly ConcurrentGameEngineErrorReporter _engineErrorReporter = new(); - private readonly AsyncStepRunner _verifyRunner; private readonly VerificationTarget _verificationTarget; private readonly VerifyPipelineSettings _pipelineSettings; private readonly GlobalVerifyReportSettings _reportSettings; @@ -30,26 +31,9 @@ public sealed class NewGameVerifyPipeline : StepRunnerPipelineBase FilteredErrors { get; private set; } = []; - public NewGameVerifyPipeline( + public GameVerifyPipeline( VerificationTarget verificationTarget, VerifyPipelineSettings pipelineSettings, GlobalVerifyReportSettings reportSettings, @@ -65,53 +49,48 @@ public NewGameVerifyPipeline( _gameEngineService = serviceProvider.GetRequiredService(); _logger = serviceProvider.GetService()?.CreateLogger(GetType()); - //_verifyRunner = pipelineSettings.ParallelVerifiers switch - //{ - // < 0 or > 64 => throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", - // nameof(pipelineSettings)), - // 1 => new SequentialStepRunner(serviceProvider), - // _ => new AsyncStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider) - //}; - - //FailFast = pipelineSettings.FailFast; + FailFast = pipelineSettings.FailFast; } - //protected override async Task PrepareCoreAsync() - //{ - // _verifiers.Clear(); - - - // IStarWarsGameEngine gameEngine; - - // try - // { - // gameEngine = await _gameEngineService.InitializeAsync( - // _verificationTarget.Engine, - // _verificationTarget.Location, - // _engineErrorReporter, - // _engineInitializationReporter, - // false, - // CancellationToken.None).ConfigureAwait(false); - // } - // catch (Exception e) - // { - // _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); - // throw; - // } - - - // AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); - - // foreach (var gameVerificationStep in CreateVerificationSteps(gameEngine)) - // AddStep(gameVerificationStep); - - // return true; - //} + protected override AsyncStepRunner CreateRunner() + { + var requestedRunnerCount = _pipelineSettings.ParallelVerifiers; + return requestedRunnerCount switch + { + < 0 or > 64 => throw new InvalidOperationException( + $"Invalid parallel worker count ({requestedRunnerCount}) specified in verifier settings."), + 1 => new SequentialStepRunner(ServiceProvider), + _ => new AsyncStepRunner(requestedRunnerCount, ServiceProvider) + }; + } - //protected override Task> CreateRunnerSteps(CancellationToken token) - //{ - // throw new NotImplementedException(); - //} + protected override async Task PrepareCoreAsync(CancellationToken token) + { + _verifiers.Clear(); + + IStarWarsGameEngine gameEngine; + + try + { + gameEngine = await _gameEngineService.InitializeAsync( + _verificationTarget.Engine, + _verificationTarget.Location, + _engineErrorReporter, + _engineInitializationReporter, + false, + CancellationToken.None).ConfigureAwait(false); + } + catch (Exception e) + { + _logger?.LogError(e, "Creating game engine failed: {Message}", e.Message); + throw; + } + + AddStep(new GameEngineErrorCollector(_engineErrorReporter, gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); + + foreach (var gameVerificationStep in CreateVerificationSteps(gameEngine)) + AddStep(gameVerificationStep); + } //protected override async Task RunCoreAsync(CancellationToken token) //{ @@ -145,162 +124,37 @@ public NewGameVerifyPipeline( // _progressReporter.Report(1.0, $"Finished Verifing {_verificationTarget.Name}", VerifyProgress.ProgressType, default); //} - //protected override void OnError(object sender, StepRunnerErrorEventArgs e) - //{ - // if (FailFast && e.Exception is GameVerificationException v) - // { - // // TODO: Apply globalMinSeverity - // if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) - // return; - // } - // base.OnError(sender, e); - //} - - //private void AddStep(GameVerifier verifier) - //{ - // var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); - // _verifyRunner.AddStep(verificationStep); - // _verificationSteps.Add(verificationStep); - // _verifiers.Add(verifier); - //} - - //private IEnumerable GetReportableErrors(IEnumerable errors) - //{ - // Logger?.LogDebug("Applying baseline and suppressions."); - // // NB: We don't filter for severity here, as the individual reporters handle that. - // // This allows better control over what gets reported. - // return errors.ApplyBaseline(_reportSettings.Baseline) - // .ApplySuppressions(_reportSettings.Suppressions); - //} - - //private IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) - //{ - // return _pipelineSettings.VerifiersProvider - // .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); - //} -} - - - - - - - - - -//public sealed class GameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline -//{ -// private readonly List _verifiers = new(); -// private readonly List _verificationSteps = new(); -// private readonly StepRunnerBase _verifyRunner; - -// private readonly IStarWarsGameEngine _gameEngine; -// private readonly IGameEngineErrorCollection _engineErrors; - -// private readonly VerifyPipelineSettings _pipelineSettings; -// private readonly GlobalVerifyReportSettings _reportSettings; - -// private readonly IVerifyProgressReporter _progressReporter; - -// protected override bool FailFast { get; } - -// public IReadOnlyCollection FilteredErrors { get; private set; } = []; - -// public GameVerifyPipeline( -// IStarWarsGameEngine gameEngine, -// IGameEngineErrorCollection engineErrors, -// VerifyPipelineSettings pipelineSettings, -// GlobalVerifyReportSettings reportSettings, -// IVerifyProgressReporter progressReporter, -// IServiceProvider serviceProvider) : base(serviceProvider) -// { -// _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); -// _engineErrors = engineErrors ?? throw new ArgumentNullException(nameof(gameEngine)); -// _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings)); -// _reportSettings = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); -// _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); - -// if (pipelineSettings.ParallelVerifiers is < 0 or > 64) -// throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", nameof(pipelineSettings)); - -// if (pipelineSettings.ParallelVerifiers == 1) -// _verifyRunner = new SequentialStepRunner(serviceProvider); -// else -// _verifyRunner = new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider); - -// FailFast = pipelineSettings.FailFast; -// } - -// protected override Task PrepareCoreAsync() -// { -// _verifiers.Clear(); - -// AddStep(new GameEngineErrorCollector(_engineErrors, _gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); - -// foreach (var gameVerificationStep in CreateVerificationSteps(_gameEngine)) -// AddStep(gameVerificationStep); - -// return Task.FromResult(true); -// } - -// protected override async Task RunCoreAsync(CancellationToken token) -// { -// var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); - -// try -// { -// Logger?.LogInformation("Running game verifiers..."); -// _verifyRunner.Error += OnError; -// await _verifyRunner.RunAsync(token); -// } -// finally -// { -// aggregatedVerifyProgressReporter.Dispose(); -// _verifyRunner.Error -= OnError; -// Logger?.LogDebug("Game verifiers finished."); -// } - -// token.ThrowIfCancellationRequested(); - -// var failedSteps = _verifyRunner.ExecutedSteps.Where(p => -// p.Error != null && !p.Error.IsExceptionType()).ToList(); - -// if (failedSteps.Count != 0) -// throw new StepFailureException(failedSteps); - -// FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); -// } - -// protected override void OnError(object sender, StepRunnerErrorEventArgs e) -// { -// if (FailFast && e.Exception is GameVerificationException v) -// { -// // TODO: Apply globalMinSeverity -// if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) -// return; -// } -// base.OnError(sender, e); -// } + protected override void OnRunnerExecutionError(object sender, StepRunnerErrorEventArgs e) + { + if (FailFast && e.Exception is GameVerificationException v) + { + // TODO: Apply globalMinSeverity + if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) + return; + } + base.OnRunnerExecutionError(sender, e); + } -// private IEnumerable CreateVerificationSteps(IStarWarsGameEngine database) -// { -// return _pipelineSettings.VerifiersProvider.GetVerifiers(database, _pipelineSettings.GameVerifySettings, ServiceProvider); -// } + private void AddStep(GameVerifier verifier) + { + var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); + StepRunner.AddStep(verificationStep); + _verificationSteps.Add(verificationStep); + _verifiers.Add(verifier); + } -// private void AddStep(GameVerifier verifier) -// { -// var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); -// _verifyRunner.AddStep(verificationStep); -// _verificationSteps.Add(verificationStep); -// _verifiers.Add(verifier); -// } + private IEnumerable GetReportableErrors(IEnumerable errors) + { + Logger?.LogDebug("Applying baseline and suppressions."); + // NB: We don't filter for severity here, as the individual reporters handle that. + // This allows better control over what gets reported. + return errors.ApplyBaseline(_reportSettings.Baseline) + .ApplySuppressions(_reportSettings.Suppressions); + } -// private IEnumerable GetReportableErrors(IEnumerable errors) -// { -// Logger?.LogDebug("Applying baseline and suppressions."); -// // NB: We don't filter for severity here, as the individual reporters handle that. -// // This allows better control over what gets reported. -// return errors.ApplyBaseline(_reportSettings.Baseline) -// .ApplySuppressions(_reportSettings.Suppressions); -// } -//} \ No newline at end of file + private IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) + { + return _pipelineSettings.VerifiersProvider + .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); + } +} \ No newline at end of file From 47ae7024014d6f72e14faf77044f160372e06f9a Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 19:29:25 +0100 Subject: [PATCH 17/38] make compile and run again --- modules/ModdingToolBase | 2 +- src/ModVerify/ModVerify.csproj | 4 +- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 51 ++++++++----------- .../CommandBar/CommandBarGameManager.cs | 4 +- .../PG.StarWarsGame.Engine.csproj | 2 +- .../ModVerify.CliApp.Test.csproj | 2 +- 6 files changed, 27 insertions(+), 38 deletions(-) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index 9155aeb..9ab5f4b 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit 9155aebd41f0dc24ed2e900139a9e6b75d631228 +Subproject commit 9ab5f4b0be7541123dfe91c67aabd12711a25cfa diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index 905b020..634dbf7 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index 1260681..bc748e3 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -30,6 +30,7 @@ public sealed class GameVerifyPipeline : StepRunnerPipelineBase private readonly IGameEngineInitializationReporter? _engineInitializationReporter; private readonly IPetroglyphStarWarsGameEngineService _gameEngineService; private readonly ILogger? _logger; + private AggregatedVerifyProgressReporter? _aggregatedVerifyProgressReporter; public IReadOnlyCollection FilteredErrors { get; private set; } = []; @@ -92,37 +93,19 @@ protected override async Task PrepareCoreAsync(CancellationToken token) AddStep(gameVerificationStep); } - //protected override async Task RunCoreAsync(CancellationToken token) - //{ - // var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); - - // try - // { - // Logger?.LogInformation("Running game verifiers..."); - // _progressReporter.Report(0.0, $"Verifing {_verificationTarget.Name}...", VerifyProgress.ProgressType, default); - // _verifyRunner.Error += OnError; - // await _verifyRunner.RunAsync(token); - // } - // finally - // { - // aggregatedVerifyProgressReporter.Dispose(); - // _verifyRunner.Error -= OnError; - // Logger?.LogDebug("Game verifiers finished."); - // } - - // token.ThrowIfCancellationRequested(); - - // var failedSteps = _verifyRunner.ExecutedSteps.Where(p => - // p.Error != null && !p.Error.IsExceptionType()).ToList(); - - // if (failedSteps.Count != 0) - // throw new StepFailureException(failedSteps); - - // FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); - - - // _progressReporter.Report(1.0, $"Finished Verifing {_verificationTarget.Name}", VerifyProgress.ProgressType, default); - //} + protected override void OnExecuteStarted() + { + Logger?.LogInformation("Running game verifiers..."); + _aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); + _progressReporter.Report(0.0, $"Verifying {_verificationTarget.Name}...", VerifyProgress.ProgressType, default); + } + + protected override void OnExecuteCompleted() + { + Logger?.LogInformation("Game verifiers finished."); + FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); + _progressReporter.Report(1.0, $"Finished Verifying {_verificationTarget.Name}", VerifyProgress.ProgressType, default); + } protected override void OnRunnerExecutionError(object sender, StepRunnerErrorEventArgs e) { @@ -157,4 +140,10 @@ private IEnumerable CreateVerificationSteps(IStarWarsGameEngine en return _pipelineSettings.VerifiersProvider .GetVerifiers(engine, _pipelineSettings.GameVerifySettings, ServiceProvider); } + + protected override void DisposeResources() + { + base.DisposeResources(); + _aggregatedVerifyProgressReporter?.Dispose(); + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs index a007472..0b8687e 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs @@ -213,8 +213,8 @@ private void SetDefaultFont() if (_defaultFont is null) { // TODO: From GameConstants - string fontName = PGConstants.DefaultUnicodeFontName; - int size = 11; + var fontName = PGConstants.DefaultUnicodeFontName; + var size = 11; var font = fontManager.CreateFont(fontName, size, true, false, false, 1.0f); if (font is null) ErrorReporter.Assert(EngineAssert.FromNullOrEmpty([ToString()], $"Unable to create Default from name {fontName}")); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj index 19b4c1a..43301f5 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj @@ -25,7 +25,7 @@ - + diff --git a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj index 09e62a4..112201d 100644 --- a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj +++ b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj @@ -15,7 +15,7 @@ - + From b4ffd064425a5bca1387f0193ce5d22325900b4c Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 21:20:07 +0100 Subject: [PATCH 18/38] local deploy script --- deploy-local.ps1 | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 deploy-local.ps1 diff --git a/deploy-local.ps1 b/deploy-local.ps1 new file mode 100644 index 0000000..740c579 --- /dev/null +++ b/deploy-local.ps1 @@ -0,0 +1,75 @@ +# Local deployment script for ModVerify to test the update feature. +# This script builds the application, creates an update manifest, and "deploys" it to a local directory. + +$ErrorActionPreference = "Stop" + +$root = $PSScriptRoot +if ([string]::IsNullOrEmpty($root)) { $root = Get-Location } + +$deployRoot = Join-Path $root ".local_deploy" +$stagingDir = Join-Path $deployRoot "staging" +$serverDir = Join-Path $deployRoot "server" +$installDir = Join-Path $deployRoot "install" + +$toolProj = Join-Path $root "src\ModVerify.CliApp\ModVerify.CliApp.csproj" +$creatorProj = Join-Path $root "modules\ModdingToolBase\src\AnakinApps\ApplicationManifestCreator\ApplicationManifestCreator.csproj" +$uploaderProj = Join-Path $root "modules\ModdingToolBase\src\AnakinApps\FtpUploader\FtpUploader.csproj" + +$toolExe = "ModVerify.exe" +$updaterExe = "AnakinRaW.ExternalUpdater.exe" +$manifestCreatorDll = "AnakinRaW.ApplicationManifestCreator.dll" +$uploaderDll = "AnakinRaW.FtpUploader.dll" + +# 1. Clean and Create directories +if (Test-Path $deployRoot) { Remove-Item -Recurse -Force $deployRoot } +New-Item -ItemType Directory -Path $stagingDir | Out-Null +New-Item -ItemType Directory -Path $serverDir | Out-Null +New-Item -ItemType Directory -Path $installDir | Out-Null + +Write-Host "--- Building ModVerify (net481) ---" -ForegroundColor Cyan +dotnet build $toolProj --configuration Release -f net481 --output "$deployRoot\bin\tool" /p:DebugType=None /p:DebugSymbols=false + +Write-Host "--- Building Manifest Creator ---" -ForegroundColor Cyan +dotnet build $creatorProj --configuration Release --output "$deployRoot\bin\creator" + +Write-Host "--- Building Local Uploader ---" -ForegroundColor Cyan +dotnet build $uploaderProj --configuration Release --output "$deployRoot\bin\uploader" + +# 2. Prepare staging +Write-Host "--- Preparing Staging ---" -ForegroundColor Cyan +Copy-Item "$deployRoot\bin\tool\$toolExe" $stagingDir +Copy-Item "$deployRoot\bin\tool\$updaterExe" $stagingDir + +# 3. Create Manifest +# Origin must be an absolute URI for the manifest creator. +# Using 127.0.0.1 and file:// is tricky with Flurl/DownloadManager sometimes. +# We'll use the local path and ensure it's formatted correctly. +$serverPath = (Resolve-Path $serverDir).Path +$serverUri = "file:///$($serverPath.Replace('\', '/'))" +# If we have 3 slashes, Flurl/DownloadManager might still fail on Windows if it expects a certain format. +# However, the ManifestCreator just needs a valid URI for the 'Origin' field in the manifest. +Write-Host "--- Creating Manifest (Origin: $serverUri) ---" -ForegroundColor Cyan +dotnet "$deployRoot\bin\creator\$manifestCreatorDll" ` + -a "$stagingDir\$toolExe" ` + --appDataFiles "$stagingDir\$updaterExe" ` + --origin "$serverUri" ` + -o "$stagingDir" ` + -b "beta" + +# 4. "Deploy" to server using the local uploader +Write-Host "--- Deploying to Local Server ---" -ForegroundColor Cyan +dotnet "$deployRoot\bin\uploader\$uploaderDll" local --base "$serverDir" --source "$stagingDir" + +# 5. Setup a "test" installation +Write-Host "--- Setting up Test Installation ---" -ForegroundColor Cyan +Copy-Item "$deployRoot\bin\tool\*" $installDir -Recurse + +Write-Host "`nLocal deployment complete!" -ForegroundColor Green +Write-Host "Server directory: $serverDir" +Write-Host "Install directory: $installDir" +Write-Host "`nTo test the update:" +Write-Host "1. (Optional) Modify the version in version.json and run this script again to 'push' a new version to the local server." +Write-Host "2. Run ModVerify from the install directory with the following command:" +Write-Host " cd '$installDir'" +Write-Host " .\ModVerify.exe updateApplication --updateManifestUrl '$serverUri'" +Write-Host "`n Note: You can also specify a different branch using --updateBranch if needed." From c36a40636b707e597238650450712ed11097d9c2 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 21:20:18 +0100 Subject: [PATCH 19/38] update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8a30d25..7d20657 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,6 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +.idea + +.local_deploy \ No newline at end of file From 9721fa04eeee561a3489b8b9618dbef03a7954bd Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Fri, 16 Jan 2026 21:20:32 +0100 Subject: [PATCH 20/38] polishing --- .../ModVerifyAppEnvironment.cs | 11 ++++-- .../Settings/SettingsBuilder.cs | 1 - .../Pipeline/GameVerifierPipelineStep.cs | 35 +++++++++---------- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 4 +-- .../Reporting/Json/JsonVerificationTarget.cs | 2 +- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs b/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs index 86bfc40..90a41db 100644 --- a/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs +++ b/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs @@ -1,4 +1,5 @@ -using System.IO.Abstractions; +using System.IO; +using System.IO.Abstractions; using System.Reflection; using AnakinRaW.ApplicationBase.Environment; #if !NET @@ -26,11 +27,17 @@ internal sealed class ModVerifyAppEnvironment(Assembly assembly, IFileSystem fil public override ICollection UpdateMirrors { get; } = new List { #if DEBUG - new("C:\\Test\\ModVerify"), + new(CreateDebugPath()), #endif new($"https://republicatwar.com/downloads/{ModVerifyConstants.ModVerifyToolPath}") }; + private static string CreateDebugPath() + { + var dir = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "../../../../..")); + return Path.Combine(dir, ".local_deploy/server"); + } + public override string UpdateRegistryPath => $@"SOFTWARE\{ModVerifyConstants.ModVerifyToolPath}\Update"; protected override UpdateConfiguration CreateUpdateConfiguration() diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 984099b..02b2a01 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO.Abstractions; using AET.ModVerify.App.Settings.CommandLine; -using AET.ModVerify.App.Utilities; using AET.ModVerify.Pipeline; using AET.ModVerify.Reporting; using AET.ModVerify.Settings; diff --git a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs index f201d6b..e83cb25 100644 --- a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs +++ b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs @@ -23,27 +23,24 @@ public sealed class GameVerifierPipelineStep( public long Size => 1; - protected override async Task RunCoreAsync(CancellationToken token) + protected override Task RunCoreAsync(CancellationToken token) { - await Task.Run(() => + try { - try - { - Logger?.LogDebug("Running verifier '{Name}'...", GameVerifier.FriendlyName); - ReportProgress(new ProgressEventArgs(0.0, "Started")); - - GameVerifier.Progress += OnVerifyProgress; - GameVerifier.Verify(token); - - Logger?.LogDebug("Finished verifier '{Name}'", GameVerifier.FriendlyName); - ReportProgress(new ProgressEventArgs(1.0, "Finished")); - } - finally - { - GameVerifier.Progress += OnVerifyProgress; - } - }, CancellationToken.None).ConfigureAwait(false); - + Logger?.LogDebug("Running verifier '{Name}'...", GameVerifier.FriendlyName); + ReportProgress(new ProgressEventArgs(0.0, "Started")); + + GameVerifier.Progress += OnVerifyProgress; + GameVerifier.Verify(token); + + Logger?.LogDebug("Finished verifier '{Name}'", GameVerifier.FriendlyName); + ReportProgress(new ProgressEventArgs(1.0, "Finished")); + return Task.CompletedTask; + } + finally + { + GameVerifier.Progress += OnVerifyProgress; + } } private void OnVerifyProgress(object _, ProgressEventArgs e) diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index bc748e3..8e521eb 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -19,8 +19,8 @@ namespace AET.ModVerify.Pipeline; public sealed class GameVerifyPipeline : StepRunnerPipelineBase { - private readonly List _verifiers = new(); - private readonly List _verificationSteps = new(); + private readonly List _verifiers = []; + private readonly List _verificationSteps = []; private readonly ConcurrentGameEngineErrorReporter _engineErrorReporter = new(); private readonly VerificationTarget _verificationTarget; diff --git a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs index 4b74b6f..451f7f7 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs @@ -39,7 +39,7 @@ public JsonVerificationTarget(VerificationTarget target) public static VerificationTarget? ToTarget(JsonVerificationTarget? jsonTarget) { if (jsonTarget is null) - return null!; + return null; return new VerificationTarget { Engine = jsonTarget.Engine, From 95cfd743977629bd88e99bd3d52bc3fe7064067e Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 17 Jan 2026 10:49:24 +0100 Subject: [PATCH 21/38] update module --- modules/ModdingToolBase | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index 9ab5f4b..3552005 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit 9ab5f4b0be7541123dfe91c67aabd12711a25cfa +Subproject commit 3552005cbaa2b7199698924d17786550e370f7ba From 45ca2e67646f302cb0fe5f4d7b5dc4797c4889ee Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sun, 18 Jan 2026 10:35:52 +0100 Subject: [PATCH 22/38] various little changes --- src/ModVerify.CliApp/ModVerifyApplication.cs | 38 +++++++++---------- src/ModVerify.CliApp/Program.cs | 2 +- .../Properties/launchSettings.json | 2 +- .../Reporting/BaselineFactory.cs | 35 ++++++++++++++--- .../Reporting/BaselineSelector.cs | 10 ++--- .../Settings/CommandLine/VerifyVerbOption.cs | 2 +- .../Settings/ModVerifyAppSettings.cs | 26 +++++++++++++ .../Settings/SettingsBuilder.cs | 2 +- .../Reporting/Json/JsonBaselineParser.cs | 7 ++-- .../Reporting/VerificationBaseline.cs | 21 ++++++++++ .../Resources/Schemas/2.1/baseline.json | 3 +- .../EmbeddedBaselineTest.cs | 4 +- 12 files changed, 109 insertions(+), 43 deletions(-) diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs index 4d45979..ee71dcf 100644 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ b/src/ModVerify.CliApp/ModVerifyApplication.cs @@ -10,7 +10,6 @@ using Serilog; using System; using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; using System.Linq; using System.Threading.Tasks; @@ -26,21 +25,21 @@ internal sealed class ModVerifyApplication(ModVerifyAppSettings settings, IServi private readonly IFileSystem _fileSystem = services.GetRequiredService(); private readonly ModVerifyAppEnvironment _appEnvironment = services.GetRequiredService(); - public async Task Run() + public async Task RunAsync() { using (new UnhandledExceptionHandler(services)) using (new UnobservedTaskExceptionHandler(services)) - return await RunCore().ConfigureAwait(false); + return await RunCoreAsync().ConfigureAwait(false); } - private async Task RunCore() + private async Task RunCoreAsync() { _logger?.LogDebug("Raw command line: {CommandLine}", Environment.CommandLine); var interactive = settings.Interactive; try { - return await RunVerify().ConfigureAwait(false); + return await RunModVerifyAsync().ConfigureAwait(false); } catch (Exception e) { @@ -66,7 +65,7 @@ private async Task RunCore() } - private async Task RunVerify() + private async Task RunModVerifyAsync() { VerificationTarget verificationTarget; try @@ -102,28 +101,34 @@ private async Task RunVerify() _logger?.LogDebug("Verification taget: {Target}", verificationTarget); _logger?.LogTrace("Verify settings: {Settings}", settings); - var allErrors = await Verify(verificationTarget, reportSettings) + var allErrors = await VerifyTargetAsync(verificationTarget, reportSettings) .ConfigureAwait(false); + // TODO: Refactor method to represent "verify" and "baseline" mode of the app. + // Also display to user more prominently which mode is active. + try { await ReportErrors(allErrors).ConfigureAwait(false); } catch (GameVerificationException e) { + _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "The verification of {Target} completed with findings of the specified failure severity {Severity}", + verificationTarget.Name, settings.AppThrowsOnMinimumSeverity); return e.HResult; } if (!settings.CreateNewBaseline) return 0; - await WriteBaseline(verificationTarget, reportSettings, allErrors, settings.NewBaselinePath).ConfigureAwait(false); + await WriteBaselineAsync(verificationTarget, reportSettings, allErrors, settings.NewBaselinePath).ConfigureAwait(false); _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Baseline successfully created."); return 0; } - private async Task> Verify( + private async Task> VerifyTargetAsync( VerificationTarget verificationTarget, GlobalVerifyReportSettings reportSettings) { @@ -181,22 +186,15 @@ private async Task ReportErrors(IReadOnlyCollection errors) throw new GameVerificationException(errors); } - private async Task WriteBaseline( + private async Task WriteBaselineAsync( VerificationTarget target, GlobalVerifyReportSettings reportSettings, IEnumerable errors, string baselineFile) { - var baseline = new VerificationBaseline(reportSettings.MinimumReportSeverity, errors, target); - - var fullPath = _fileSystem.Path.GetFullPath(baselineFile); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Writing Baseline to '{FullPath}'", fullPath); - -#if NET - await -#endif - using var fs = _fileSystem.FileStream.New(fullPath, FileMode.Create, FileAccess.Write, FileShare.None); - await baseline.ToJsonAsync(fs); + var baselineFactory = services.GetRequiredService(); + var baseline = baselineFactory.CreateBaseline(target, reportSettings, errors); + await baselineFactory.WriteBaselineAsync(baseline, baselineFile); } private GlobalVerifyReportSettings CreateGlobalReportSettings(VerificationTarget verificationTarget) diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index cd4747b..2d293b9 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -173,7 +173,7 @@ protected override async Task RunAppAsync(string[] args, IServiceProvider a return e.HResult; } - return await new ModVerifyApplication(modVerifySettings, appServiceProvider).Run().ConfigureAwait(false); + return await new ModVerifyApplication(modVerifySettings, appServiceProvider).RunAsync().ConfigureAwait(false); } private void SetupVerifyReporting(IServiceCollection serviceCollection) diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index bafb11d..0dc1305 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -6,7 +6,7 @@ }, "Interactive Verify": { "commandName": "Project", - "commandLineArgs": "verify -o verifyResults --minFailSeverity Information --offline" + "commandLineArgs": "verify -o verifyResults --minFailSeverity Information --offline --failFast" }, "Interactive Baseline": { "commandName": "Project", diff --git a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs index 9fcc911..1b49e88 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs @@ -1,10 +1,13 @@ -using System; +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Settings; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Abstractions; -using AET.ModVerify.Reporting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using System.Threading.Tasks; namespace AET.ModVerify.App.Reporting; @@ -13,7 +16,7 @@ internal sealed class BaselineFactory(IServiceProvider serviceProvider) private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(BaselineFactory)); private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public bool TryCreateBaseline( + public bool TryFindBaselineInDirectory( string directory, out VerificationBaseline baseline, [NotNullWhen(true)] out string? path) @@ -58,11 +61,31 @@ public bool TryCreateBaseline( return false; } - public VerificationBaseline CreateBaseline(string filePath) + public VerificationBaseline ParseBaseline(string filePath) { return CreateBaselineFromFilePath(filePath); } + public VerificationBaseline CreateBaseline( + VerificationTarget target, + GlobalVerifyReportSettings reportSettings, + IEnumerable errors) + { + return new VerificationBaseline(reportSettings.MinimumReportSeverity, errors, target); + } + + public async Task WriteBaselineAsync(VerificationBaseline baseline, string filePath) + { + var fullPath = _fileSystem.Path.GetFullPath(filePath); + _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Writing Baseline to '{FullPath}'", fullPath); + +#if NET + await +#endif + using var fs = _fileSystem.FileStream.New(fullPath, FileMode.Create, FileAccess.Write, FileShare.None); + await baseline.ToJsonAsync(fs); + } + private VerificationBaseline CreateBaselineFromFilePath(string baselineFile) { using var fs = _fileSystem.FileStream.New(baselineFile, FileMode.Open, FileAccess.Read); diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs index 9fa637f..348c3a6 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs @@ -23,7 +23,7 @@ public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget try { usedBaselinePath = baselinePath; - return _baselineFactory.CreateBaseline(baselinePath!); + return _baselineFactory.ParseBaseline(baselinePath!); } catch (InvalidBaselineException e) { @@ -65,10 +65,10 @@ private VerificationBaseline FindBaselineInteractive(VerificationTarget verifica _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Searching for local baseline files..."); - if (!_baselineFactory.TryCreateBaseline(verificationTarget.Location.TargetPath, out var baseline, + if (!_baselineFactory.TryFindBaselineInDirectory(verificationTarget.Location.TargetPath, out var baseline, out baselinePath)) { - if (!_baselineFactory.TryCreateBaseline("./", out baseline, out baselinePath)) + if (!_baselineFactory.TryFindBaselineInDirectory("./", out baseline, out baselinePath)) { // It does not make sense to load the game's default baselines if the user wants to verify the game, // as the verification result would always be empty (at least in a non-development scenario) @@ -115,7 +115,7 @@ private VerificationBaseline TryGetDefaultBaseline(GameEngineType engineType, ou } } - internal VerificationBaseline LoadEmbeddedBaseline(GameEngineType engineType) + internal static VerificationBaseline LoadEmbeddedBaseline(GameEngineType engineType) { var baselineFileName = $"baseline-{engineType.ToString().ToLower()}.json"; var resourcePath = $"{typeof(BaselineResources).Namespace}.{baselineFileName}"; @@ -126,7 +126,7 @@ internal VerificationBaseline LoadEmbeddedBaseline(GameEngineType engineType) private VerificationBaseline FindBaselineNonInteractive(string targetPath, out string? usedPath) { - if (_baselineFactory.TryCreateBaseline(targetPath, out var baseline, out usedPath)) + if (_baselineFactory.TryFindBaselineInDirectory(targetPath, out var baseline, out usedPath)) { _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Automatically applying local baseline file '{Path}'.", usedPath); return baseline; diff --git a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs index d833f12..9581a9c 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs @@ -20,7 +20,7 @@ internal sealed class VerifyVerbOption : BaseModVerifyOptions public bool FailFast { get; init; } [Option("minFailSeverity", Required = false, Default = null, - HelpText = "When set, the application return with an error, if any finding has at least the specified severity value.")] + HelpText = "When set, the application returns with an error, if any finding has at least the specified severity value.")] public VerificationSeverity? MinimumFailureSeverity { get; set; } [Option("ignoreAsserts", Required = false, diff --git a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs index e65464c..0419f33 100644 --- a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs @@ -20,4 +20,30 @@ internal sealed class ModVerifyAppSettings public bool CreateNewBaseline => !string.IsNullOrEmpty(NewBaselinePath); public string? NewBaselinePath { get; init; } +} + + +internal enum AppMode +{ + Verify, + Baseline +} + +internal abstract class ModVerifyAppSettingsBase +{ + public abstract AppMode Mode { get; } + public bool IsInteractive => VerificationTargetSettings.Interactive; + public required VerificationTargetSettings VerificationTargetSettings { get; init; } +} + +internal sealed class VerifyAppSettings : ModVerifyAppSettingsBase +{ + public override AppMode Mode => AppMode.Verify; + public VerificationSeverity? AppThrowsOnMinimumSeverity { get; init; } +} + +internal sealed class BaselineAppSettings : ModVerifyAppSettingsBase +{ + public override AppMode Mode => AppMode.Baseline; + public required string NewBaselinePath { get; init; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 02b2a01..8a31301 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -55,7 +55,7 @@ private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) if (minFailSeverity == null) { _logger?.LogWarning(ModVerifyConstants.ConsoleEventId, - "Verification is configured to fail fast but 'minFailSeverity' is not specified. Using severity '{Info}'.", VerificationSeverity.Information); + "Verification is configured to fail fast but 'minFailSeverity' is not specified. Using severity '{Severity}'.", VerificationSeverity.Information); minFailSeverity = VerificationSeverity.Information; } diff --git a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs b/src/ModVerify/Reporting/Json/JsonBaselineParser.cs index d3b4acd..669ef53 100644 --- a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs +++ b/src/ModVerify/Reporting/Json/JsonBaselineParser.cs @@ -4,7 +4,7 @@ namespace AET.ModVerify.Reporting.Json; -public static class JsonBaselineParser +internal static class JsonBaselineParser { public static VerificationBaseline Parse(Stream dataStream) { @@ -13,7 +13,7 @@ public static VerificationBaseline Parse(Stream dataStream) try { var jsonNode = JsonDocument.Parse(dataStream); - var jsonBaseline = ParseCore(jsonNode); + var jsonBaseline = EvaluateAndDeserialize(jsonNode); if (jsonBaseline is null) throw new InvalidBaselineException($"Unable to parse input from stream to {nameof(VerificationBaseline)}. Unknown Error!"); @@ -26,11 +26,10 @@ public static VerificationBaseline Parse(Stream dataStream) } } - private static JsonVerificationBaseline? ParseCore(JsonDocument? json) + private static JsonVerificationBaseline? EvaluateAndDeserialize(JsonDocument? json) { if (json is null) return null; - JsonBaselineSchema.Evaluate(json.RootElement); return json.Deserialize(); } diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/VerificationBaseline.cs index 1cc8312..ca8700d 100644 --- a/src/ModVerify/Reporting/VerificationBaseline.cs +++ b/src/ModVerify/Reporting/VerificationBaseline.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading.Tasks; using AET.ModVerify.Reporting.Json; +using PG.StarWarsGame.Engine; namespace AET.ModVerify.Reporting; @@ -88,4 +89,24 @@ public override string ToString() sb.Append(']'); return sb.ToString(); } +} + +public sealed class BaselineVerificationTarget +{ + public required GameEngineType Engine { get; init; } + public required string Name { get; init; } + public GameLocations? Location { get; init; } + public string? Version { get; init; } + public bool IsGame => Location.ModPaths.Count == 0; + + + + public override string ToString() + { + var sb = new StringBuilder($"[Name={Name};EngineType={Engine};"); + if (!string.IsNullOrEmpty(Version)) sb.Append($"Version={Version};"); + sb.Append($"Location={Location};"); + sb.Append(']'); + return sb.ToString(); + } } \ No newline at end of file diff --git a/src/ModVerify/Resources/Schemas/2.1/baseline.json b/src/ModVerify/Resources/Schemas/2.1/baseline.json index 56bfb86..ed5a014 100644 --- a/src/ModVerify/Resources/Schemas/2.1/baseline.json +++ b/src/ModVerify/Resources/Schemas/2.1/baseline.json @@ -48,8 +48,7 @@ }, "required": [ "name", - "engine", - "location" + "engine" ], "additionalProperties": false }, diff --git a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs index 3272261..1747677 100644 --- a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs +++ b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs @@ -16,6 +16,7 @@ namespace ModVerify.CliApp.Test; public class BaselineSelectorTest { private static readonly IFileSystem FileSystem = new RealFileSystem(); + private readonly IServiceProvider _serviceProvider; private static readonly ModVerifyAppSettings TestSettings = new() { ReportSettings = new(), @@ -27,7 +28,6 @@ public class BaselineSelectorTest } }; - private readonly IServiceProvider _serviceProvider; public BaselineSelectorTest() { @@ -43,6 +43,6 @@ public BaselineSelectorTest() public void LoadEmbeddedBaseline(GameEngineType engineType) { // Ensure this operation does not crash, meaning the embedded baseline is at least compatible. - new BaselineSelector(TestSettings, _serviceProvider).LoadEmbeddedBaseline(engineType); + BaselineSelector.LoadEmbeddedBaseline(engineType); } } \ No newline at end of file From 628342491148765187fca4a2344b0dbf94838d24 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Mon, 19 Jan 2026 11:49:20 +0100 Subject: [PATCH 23/38] update deps --- src/ModVerify/ModVerify.csproj | 4 ++-- .../PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj | 2 +- test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index 634dbf7..73d45e0 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj index 43301f5..1376246 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj @@ -25,7 +25,7 @@ - + diff --git a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj index 112201d..826987d 100644 --- a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj +++ b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj @@ -15,7 +15,7 @@ - + From 641b1c7f42335fe3bc7bd3fb579de43a828ede9a Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Mon, 19 Jan 2026 11:52:27 +0100 Subject: [PATCH 24/38] use filed keyword --- src/ModVerify/GameVerificationException.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ModVerify/GameVerificationException.cs b/src/ModVerify/GameVerificationException.cs index 1096179..dba312b 100644 --- a/src/ModVerify/GameVerificationException.cs +++ b/src/ModVerify/GameVerificationException.cs @@ -7,23 +7,21 @@ namespace AET.ModVerify; public sealed class GameVerificationException : Exception { - private readonly string? _errorMessage = null; - public IReadOnlyCollection Errors { get; } private string ErrorMessage { get { - if (_errorMessage != null) - return _errorMessage; + if (field != null) + return field; var stringBuilder = new StringBuilder(); foreach (var error in Errors) stringBuilder.AppendLine($"Verification error: {error.Id}: {error.Message};"); return stringBuilder.ToString().TrimEnd(';'); } - } + } = null; /// public override string Message => ErrorMessage; From 9fa87e43191dcff194e4d5006edbfb042017e888 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Mon, 19 Jan 2026 11:52:53 +0100 Subject: [PATCH 25/38] do not thorw stepfailure exception if verification errors occured. --- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index 8e521eb..099297c 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -118,6 +118,11 @@ protected override void OnRunnerExecutionError(object sender, StepRunnerErrorEve base.OnRunnerExecutionError(sender, e); } + protected override IEnumerable GetFailedSteps(IEnumerable steps) + { + return base.GetFailedSteps(steps).Where(s => s.Error is not GameVerificationException); + } + private void AddStep(GameVerifier verifier) { var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); From 8dc022d7adb65d581d79bc9d6119b139d0fcad7f Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Mon, 19 Jan 2026 11:53:23 +0100 Subject: [PATCH 26/38] Fail fast should print any error to console --- src/ModVerify.CliApp/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index 2d293b9..49e1bbe 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -184,12 +184,14 @@ private void SetupVerifyReporting(IServiceCollection serviceCollection) var verifyVerb = options as VerifyVerbOption; - // Console should be in minimal summary mode if we are not in verify mode. + // Console should be in minimal summary mode if we are in a different mode than verify. var printOnlySummary = verifyVerb is null; serviceCollection.RegisterConsoleReporter(new VerifyReportSettings { - MinimumReportSeverity = VerificationSeverity.Error + MinimumReportSeverity = verifyVerb?.FailFast is true + ? VerificationSeverity.Information + : VerificationSeverity.Error }, printOnlySummary); if (verifyVerb == null) From 1cb12753c5017dcf319eb88f37cfc3856e5bcfff Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 09:51:37 +0100 Subject: [PATCH 27/38] major refactorings --- modules/ModdingToolBase | 2 +- .../App/CreateBaselineAction.cs | 53 ++++ .../App/IModVerifyAppAction.cs | 8 + .../App/ModVerifyApplication.cs | 68 ++++++ .../App/ModVerifyApplicationAction.cs | 142 +++++++++++ src/ModVerify.CliApp/App/VerifyAction.cs | 55 +++++ .../ModVerify.CliApp.csproj.DotSettings | 2 + src/ModVerify.CliApp/ModVerifyApplication.cs | 230 ------------------ src/ModVerify.CliApp/ModVerifyConstants.cs | 4 + src/ModVerify.CliApp/Program.cs | 136 ++++++----- .../Reporting/BaselineFactory.cs | 32 ++- .../Reporting/BaselineSelector.cs | 43 ++-- .../Reporting/IBaselineFactory.cs | 24 ++ .../Settings/ModVerifyAppSettings.cs | 78 ++++-- .../Settings/ModVerifyReportSettings.cs | 14 -- .../Settings/SettingsBuilder.cs | 109 +++++---- src/ModVerify/Pipeline/GameVerifyPipeline.cs | 24 +- .../Reporting/BaselineVerificationTarget.cs | 22 ++ .../Reporting/Json/JsonGameLocation.cs | 6 +- .../Reporting/Json/JsonVerificationTarget.cs | 13 +- .../Reporting/Reporters/ConsoleReporter.cs | 4 +- .../Reporting/Reporters/JSON/JsonReporter.cs | 1 - .../Reporting/Reporters/ReporterBase.cs | 2 +- .../VerificationReportersExtensions.cs | 8 +- .../Settings/FileBasedReporterSettings.cs | 10 +- .../Settings/GlobalVerifyReportSettings.cs | 12 +- ...yReportSettings.cs => ReporterSettings.cs} | 2 +- .../Reporting/VerificationBaseline.cs | 27 +- .../Settings/VerifyPipelineSettings.cs | 18 +- 29 files changed, 671 insertions(+), 478 deletions(-) create mode 100644 src/ModVerify.CliApp/App/CreateBaselineAction.cs create mode 100644 src/ModVerify.CliApp/App/IModVerifyAppAction.cs create mode 100644 src/ModVerify.CliApp/App/ModVerifyApplication.cs create mode 100644 src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs create mode 100644 src/ModVerify.CliApp/App/VerifyAction.cs create mode 100644 src/ModVerify.CliApp/ModVerify.CliApp.csproj.DotSettings delete mode 100644 src/ModVerify.CliApp/ModVerifyApplication.cs create mode 100644 src/ModVerify.CliApp/Reporting/IBaselineFactory.cs delete mode 100644 src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs create mode 100644 src/ModVerify/Reporting/BaselineVerificationTarget.cs rename src/ModVerify/Reporting/Settings/{VerifyReportSettings.cs => ReporterSettings.cs} (81%) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index 3552005..ba1c70b 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit 3552005cbaa2b7199698924d17786550e370f7ba +Subproject commit ba1c70b2e3b9442eaa4b83af00ae0d9b7569b1d4 diff --git a/src/ModVerify.CliApp/App/CreateBaselineAction.cs b/src/ModVerify.CliApp/App/CreateBaselineAction.cs new file mode 100644 index 0000000..cc56ea2 --- /dev/null +++ b/src/ModVerify.CliApp/App/CreateBaselineAction.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Threading.Tasks; +using AET.ModVerify.App.Reporting; +using AET.ModVerify.App.Settings; +using AET.ModVerify.Reporting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace AET.ModVerify.App; + +internal sealed class CreateBaselineAction(AppBaselineSettings settings, IServiceProvider serviceProvider) + : ModVerifyApplicationAction(settings, serviceProvider) +{ + private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); + + protected override void PrintAction(VerificationTarget target) + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine($"Creating baseline for {target.Name}..."); + Console.ResetColor(); + Console.WriteLine(); + } + + protected override async Task ProcessVerifyFindings( + VerificationTarget verificationTarget, + IReadOnlyCollection allErrors) + { + var baselineFactory = ServiceProvider.GetRequiredService(); + var baseline = baselineFactory.CreateBaseline(verificationTarget, Settings.ReportSettings, allErrors); + + var fullPath = _fileSystem.Path.GetFullPath(Settings.NewBaselinePath); + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "Writing Baseline to '{FullPath}' with {Number} findings", fullPath, allErrors.Count); + + await baselineFactory.WriteBaselineAsync(baseline, Settings.NewBaselinePath); + + Logger?.LogDebug("Baseline successfully created."); + + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine($"Baseline for {verificationTarget.Name} created."); + Console.ResetColor(); + + return ModVerifyConstants.Success; + } + + protected override VerificationBaseline GetBaseline(VerificationTarget verificationTarget) + { + return VerificationBaseline.Empty; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/App/IModVerifyAppAction.cs b/src/ModVerify.CliApp/App/IModVerifyAppAction.cs new file mode 100644 index 0000000..1a75b84 --- /dev/null +++ b/src/ModVerify.CliApp/App/IModVerifyAppAction.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace AET.ModVerify.App; + +internal interface IModVerifyAppAction +{ + Task ExecuteAsync(); +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/App/ModVerifyApplication.cs b/src/ModVerify.CliApp/App/ModVerifyApplication.cs new file mode 100644 index 0000000..eae7851 --- /dev/null +++ b/src/ModVerify.CliApp/App/ModVerifyApplication.cs @@ -0,0 +1,68 @@ +using AET.ModVerify.App.Settings; +using AnakinRaW.ApplicationBase; +using AnakinRaW.ApplicationBase.Utilities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using System; +using System.Threading.Tasks; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace AET.ModVerify.App; + +internal sealed class ModVerifyApplication(AppSettingsBase settings, IServiceProvider services) +{ + private readonly ILogger? _logger = services.GetService()?.CreateLogger(typeof(ModVerifyApplication)); + + public async Task RunAsync() + { + using (new UnhandledExceptionHandler(services)) + using (new UnobservedTaskExceptionHandler(services)) + return await RunCoreAsync().ConfigureAwait(false); + } + + private async Task RunCoreAsync() + { + _logger?.LogDebug("Raw command line: {CommandLine}", Environment.CommandLine); + + try + { + var action = CreateAppAction(); + return await action.ExecuteAsync().ConfigureAwait(false); + } + catch (Exception e) + { + _logger?.LogCritical(e, e.Message); + ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e); + return e.HResult; + } + finally + { +#if NET + await Log.CloseAndFlushAsync(); +#else + Log.CloseAndFlush(); +#endif + if (settings is AppVerifySettings { IsInteractive: true }) + { + Console.WriteLine(); + ConsoleUtilities.WriteHorizontalLine('-'); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + } + } + } + + private IModVerifyAppAction CreateAppAction() + { + switch (settings) + { + case AppVerifySettings verifySettings: + return new VerifyAction(verifySettings, services); + case AppBaselineSettings baselineSettings: + return new CreateBaselineAction(baselineSettings, services); + default: + throw new InvalidOperationException("Unknown settings"); + } + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs new file mode 100644 index 0000000..89af482 --- /dev/null +++ b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Threading.Tasks; +using AET.ModVerify.App.GameFinder; +using AET.ModVerify.App.Reporting; +using AET.ModVerify.App.Settings; +using AET.ModVerify.App.TargetSelectors; +using AET.ModVerify.Pipeline; +using AET.ModVerify.Reporting; +using AnakinRaW.ApplicationBase; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace AET.ModVerify.App; + +internal abstract class ModVerifyApplicationAction : IModVerifyAppAction where T : AppSettingsBase +{ + private readonly ModVerifyAppEnvironment _appEnvironment; + private readonly IFileSystem _fileSystem; + + protected T Settings { get; } + + protected IServiceProvider ServiceProvider { get; } + + protected ILogger? Logger { get; } + + protected ModVerifyApplicationAction(T settings, IServiceProvider serviceProvider) + { + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + Logger = serviceProvider.GetService()?.CreateLogger(GetType()); + _appEnvironment = ServiceProvider.GetRequiredService(); + _fileSystem = ServiceProvider.GetRequiredService(); + } + + protected virtual void PrintAction(VerificationTarget target) + { + } + + public async Task ExecuteAsync() + { + VerificationTarget verificationTarget; + try + { + var targetSettings = Settings.VerificationTargetSettings; + verificationTarget = new VerificationTargetSelectorFactory(ServiceProvider) + .CreateSelector(targetSettings) + .Select(targetSettings); + } + catch (ArgumentException ex) + { + ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, + $"The specified arguments are not correct: {ex.Message}"); + Logger?.LogError(ex, "Invalid application arguments: {Message}", ex.Message); + return ex.HResult; + } + catch (TargetNotFoundException ex) + { + ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, ex.Message); + Logger?.LogError(ex, ex.Message); + return ex.HResult; + } + catch (GameNotFoundException ex) + { + ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, + "Unable to find an installation of Empire at War or Forces of Corruption."); + Logger?.LogError(ex, "Game not found: {Message}", ex.Message); + return ex.HResult; + } + + PrintAction(verificationTarget); + + var allErrors = await VerifyTargetAsync(verificationTarget) + .ConfigureAwait(false); + + return await ProcessVerifyFindings(verificationTarget, allErrors); + } + + protected abstract Task ProcessVerifyFindings( + VerificationTarget verificationTarget, + IReadOnlyCollection allErrors); + + protected abstract VerificationBaseline GetBaseline(VerificationTarget verificationTarget); + + private async Task> VerifyTargetAsync(VerificationTarget verificationTarget) + { + var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name); + + var baseline = GetBaseline(verificationTarget); + var suppressions = GetSuppressions(); + + using var verifyPipeline = new GameVerifyPipeline( + verificationTarget, + Settings.VerifyPipelineSettings, + progressReporter, + new EngineInitializeProgressReporter(verificationTarget.Engine), + baseline, + suppressions, + ServiceProvider); + + try + { + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verifying '{Target}'...", verificationTarget.Name); + await verifyPipeline.RunAsync().ConfigureAwait(false); + progressReporter.Report(string.Empty, 1.0); + } + catch (OperationCanceledException) + { + Logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Verification stopped due to enabled failFast setting."); + } + catch (Exception e) + { + progressReporter.ReportError("Verification failed!", e.Message); + Logger?.LogError(e, "Verification failed: {Message}", e.Message); + throw; + } + finally + { + progressReporter.Dispose(); + } + + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Finished verification"); + return verifyPipeline.FilteredErrors; + } + + private SuppressionList GetSuppressions() + { + var suppressionsFile = Settings.ReportSettings.SuppressionsPath; + SuppressionList suppressions; + if (string.IsNullOrEmpty(suppressionsFile)) + suppressions = SuppressionList.Empty; + else + { + using var fileStream = _fileSystem.File.OpenRead(suppressionsFile); + suppressions = SuppressionList.FromJson(fileStream); + if (suppressions.Count > 0) + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Using suppressions from '{Suppressions}'", suppressionsFile); + } + return suppressions; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/App/VerifyAction.cs b/src/ModVerify.CliApp/App/VerifyAction.cs new file mode 100644 index 0000000..c295768 --- /dev/null +++ b/src/ModVerify.CliApp/App/VerifyAction.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AET.ModVerify.App.Reporting; +using AET.ModVerify.App.Settings; +using AET.ModVerify.Reporting; +using Microsoft.Extensions.Logging; + +namespace AET.ModVerify.App; + +internal sealed class VerifyAction(AppVerifySettings settings, IServiceProvider services) + : ModVerifyApplicationAction(settings, services) +{ + protected override void PrintAction(VerificationTarget target) + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine($"Verifying {target.Name} for issues..."); + Console.ResetColor(); + Console.WriteLine(); + } + + protected override async Task ProcessVerifyFindings( + VerificationTarget verificationTarget, + IReadOnlyCollection allErrors) + { + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Reporting Errors..."); + var reportBroker = new VerificationReportBroker(ServiceProvider); + await reportBroker.ReportAsync(allErrors); + + if (Settings.AppFailsOnMinimumSeverity.HasValue && + allErrors.Any(x => x.Severity >= Settings.AppFailsOnMinimumSeverity)) + { + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "The verification of {Target} completed with findings of the specified failure severity {Severity}", + verificationTarget.Name, Settings.AppFailsOnMinimumSeverity); + + return ModVerifyConstants.CompletedWithFindings; + } + + return ModVerifyConstants.Success; + } + + protected override VerificationBaseline GetBaseline(VerificationTarget verificationTarget) + { + var baselineSelector = new BaselineSelector(Settings, ServiceProvider); + var baseline = baselineSelector.SelectBaseline(verificationTarget, out var baselinePath); + if (!baseline.IsEmpty) + { + // TODO: Handle nullable path + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Using baseline '{Baseline}'", baselinePath); + } + return baseline; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj.DotSettings b/src/ModVerify.CliApp/ModVerify.CliApp.csproj.DotSettings new file mode 100644 index 0000000..0bcf4ed --- /dev/null +++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs deleted file mode 100644 index ee71dcf..0000000 --- a/src/ModVerify.CliApp/ModVerifyApplication.cs +++ /dev/null @@ -1,230 +0,0 @@ -using AET.ModVerify.App.Reporting; -using AET.ModVerify.App.Settings; -using AET.ModVerify.Pipeline; -using AET.ModVerify.Reporting; -using AET.ModVerify.Reporting.Settings; -using AnakinRaW.ApplicationBase; -using AnakinRaW.ApplicationBase.Utilities; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; -using System; -using System.Collections.Generic; -using System.IO.Abstractions; -using System.Linq; -using System.Threading.Tasks; -using AET.ModVerify.App.GameFinder; -using AET.ModVerify.App.TargetSelectors; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace AET.ModVerify.App; - -internal sealed class ModVerifyApplication(ModVerifyAppSettings settings, IServiceProvider services) -{ - private readonly ILogger? _logger = services.GetService()?.CreateLogger(typeof(ModVerifyApplication)); - private readonly IFileSystem _fileSystem = services.GetRequiredService(); - private readonly ModVerifyAppEnvironment _appEnvironment = services.GetRequiredService(); - - public async Task RunAsync() - { - using (new UnhandledExceptionHandler(services)) - using (new UnobservedTaskExceptionHandler(services)) - return await RunCoreAsync().ConfigureAwait(false); - } - - private async Task RunCoreAsync() - { - _logger?.LogDebug("Raw command line: {CommandLine}", Environment.CommandLine); - - var interactive = settings.Interactive; - try - { - return await RunModVerifyAsync().ConfigureAwait(false); - } - catch (Exception e) - { - _logger?.LogCritical(e, e.Message); - ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e); - return e.HResult; - } - finally - { -#if NET - await Log.CloseAndFlushAsync(); -#else - Log.CloseAndFlush(); -#endif - if (interactive) - { - Console.WriteLine(); - ConsoleUtilities.WriteHorizontalLine('-'); - Console.WriteLine("Press any key to exit"); - Console.ReadLine(); - } - } - } - - - private async Task RunModVerifyAsync() - { - VerificationTarget verificationTarget; - try - { - var targetSettings = settings.VerificationTargetSettings; - verificationTarget = new VerificationTargetSelectorFactory(services) - .CreateSelector(targetSettings) - .Select(targetSettings); - } - catch (ArgumentException ex) - { - ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, - $"The specified arguments are not correct: {ex.Message}"); - _logger?.LogError(ex, "Invalid application arguments: {Message}", ex.Message); - return ex.HResult; - } - catch (TargetNotFoundException ex) - { - ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, ex.Message); - _logger?.LogError(ex, ex.Message); - return ex.HResult; - } - catch (GameNotFoundException ex) - { - ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, - "Unable to find an installation of Empire at War or Forces of Corruption."); - _logger?.LogError(ex, "Game not found: {Message}", ex.Message); - return ex.HResult; - } - - var reportSettings = CreateGlobalReportSettings(verificationTarget); - - _logger?.LogDebug("Verification taget: {Target}", verificationTarget); - _logger?.LogTrace("Verify settings: {Settings}", settings); - - var allErrors = await VerifyTargetAsync(verificationTarget, reportSettings) - .ConfigureAwait(false); - - // TODO: Refactor method to represent "verify" and "baseline" mode of the app. - // Also display to user more prominently which mode is active. - - try - { - await ReportErrors(allErrors).ConfigureAwait(false); - } - catch (GameVerificationException e) - { - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "The verification of {Target} completed with findings of the specified failure severity {Severity}", - verificationTarget.Name, settings.AppThrowsOnMinimumSeverity); - return e.HResult; - } - - if (!settings.CreateNewBaseline) - return 0; - - await WriteBaselineAsync(verificationTarget, reportSettings, allErrors, settings.NewBaselinePath).ConfigureAwait(false); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Baseline successfully created."); - - return 0; - } - - private async Task> VerifyTargetAsync( - VerificationTarget verificationTarget, - GlobalVerifyReportSettings reportSettings) - { - var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name); - - using var verifyPipeline = new GameVerifyPipeline( - verificationTarget, - settings.VerifyPipelineSettings, - reportSettings, - progressReporter, - new EngineInitializeProgressReporter(verificationTarget.Engine), - services); - - try - { - try - { - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verifying '{Target}'...", verificationTarget.Name); - await verifyPipeline.RunAsync().ConfigureAwait(false); - progressReporter.Report(string.Empty, 1.0); - } - catch - { - progressReporter.ReportError("Verification failed", null); - throw; - } - finally - { - progressReporter.Dispose(); - } - } - catch (OperationCanceledException) - { - _logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Verification stopped due to enabled failFast setting."); - } - catch (Exception e) - { - _logger?.LogError(e, "Verification failed: {Message}", e.Message); - throw; - } - - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Finished verification"); - return verifyPipeline.FilteredErrors; - } - - private async Task ReportErrors(IReadOnlyCollection errors) - { - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Reporting Errors..."); - - var reportBroker = new VerificationReportBroker(services); - - await reportBroker.ReportAsync(errors); - - if (errors.Any(x => x.Severity >= settings.AppThrowsOnMinimumSeverity)) - throw new GameVerificationException(errors); - } - - private async Task WriteBaselineAsync( - VerificationTarget target, - GlobalVerifyReportSettings reportSettings, - IEnumerable errors, - string baselineFile) - { - var baselineFactory = services.GetRequiredService(); - var baseline = baselineFactory.CreateBaseline(target, reportSettings, errors); - await baselineFactory.WriteBaselineAsync(baseline, baselineFile); - } - - private GlobalVerifyReportSettings CreateGlobalReportSettings(VerificationTarget verificationTarget) - { - var baselineSelector = new BaselineSelector(settings, services); - var baseline = baselineSelector.SelectBaseline(verificationTarget, out var baselinePath); - - if (baseline.Count > 0) - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Using baseline '{Baseline}'", baselinePath); - - var suppressionsFile = settings.ReportSettings.SuppressionsPath; - SuppressionList suppressions; - - if (string.IsNullOrEmpty(suppressionsFile)) - suppressions = SuppressionList.Empty; - else - { - using var fs = _fileSystem.File.OpenRead(suppressionsFile); - suppressions = SuppressionList.FromJson(fs); - - if (suppressions.Count > 0) - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Using suppressions from '{Suppressions}'", suppressionsFile); - } - - - return new GlobalVerifyReportSettings - { - Baseline = baseline, - Suppressions = suppressions, - MinimumReportSeverity = settings.ReportSettings.MinimumReportSeverity, - }; - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyConstants.cs b/src/ModVerify.CliApp/ModVerifyConstants.cs index 6b60f06..041a4a5 100644 --- a/src/ModVerify.CliApp/ModVerifyConstants.cs +++ b/src/ModVerify.CliApp/ModVerifyConstants.cs @@ -9,5 +9,9 @@ internal static class ModVerifyConstants public const string ModVerifyToolPath = "ModVerify"; public const int ConsoleEventIdValue = 1138; + public const int Success = 0; + public const int CompletedWithFindings = 1; + public const int ErrorBadArguments = 0xA0; + public static readonly EventId ConsoleEventId = new(ConsoleEventIdValue, "LogToConsole"); } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index 49e1bbe..26ef1fd 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -40,6 +40,7 @@ using System.IO.Abstractions; using System.Runtime.InteropServices; using System.Threading.Tasks; +using AET.ModVerify.App.Reporting; using Testably.Abstractions; using ILogger = Serilog.ILogger; @@ -61,21 +62,22 @@ internal class Program : SelfUpdateableAppLifecycle private static readonly string ModVerifyRootNameSpace = typeof(Program).Namespace!; private static readonly CompiledExpression PrintToConsoleExpression = SerilogExpression.Compile($"EventId.Id = {ModVerifyConstants.ConsoleEventIdValue}"); - private static ModVerifyOptionsContainer _optionsContainer = null!; - - protected override async Task InitializeAppAsync(IReadOnlyList args) + private AppSettingsBase? _modVerifyAppSettings; + private ApplicationUpdateOptions _updateOptions = new(); + private bool _offlineMode; + private bool _verboseMode; + private bool _isLaunchedWithoutArguments; + + protected override async Task InitializeAppAsync(IReadOnlyList args, IServiceProvider bootstrapServices) { + await base.InitializeAppAsync(args, bootstrapServices); + ModVerifyConsoleUtilities.WriteHeader(ApplicationEnvironment.AssemblyInfo.InformationalVersion); - await base.InitializeAppAsync(args); - + ModVerifyOptionsContainer parsedOptions; try { - var settings = new ModVerifyOptionsParser(ApplicationEnvironment, BootstrapLoggerFactory).Parse(args); - if (!settings.HasOptions) - return 0xA0; - _optionsContainer = settings; - return 0; + parsedOptions = new ModVerifyOptionsParser(ApplicationEnvironment, BootstrapLoggerFactory).Parse(args); } catch (Exception e) { @@ -83,6 +85,34 @@ protected override async Task InitializeAppAsync(IReadOnlyList args ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e); return e.HResult; } + + if (!parsedOptions.HasOptions) + return ModVerifyConstants.ErrorBadArguments; + + if (parsedOptions.UpdateOptions is not null) + _updateOptions = parsedOptions.UpdateOptions; + + if (parsedOptions.ModVerifyOptions?.Verbose is true || parsedOptions.UpdateOptions?.Verbose is true) + _verboseMode = true; + + if (parsedOptions.ModVerifyOptions is null) + return ModVerifyConstants.Success; + + _offlineMode = parsedOptions.ModVerifyOptions.OfflineMode; + _isLaunchedWithoutArguments = parsedOptions.ModVerifyOptions.LaunchedWithoutArguments(); + + try + { + _modVerifyAppSettings = new SettingsBuilder(bootstrapServices).BuildSettings(parsedOptions.ModVerifyOptions); + } + catch (Exception e) + { + Logger?.LogCritical(e, "Failed to create settings form commandline arguments: {EMessage}", e.Message); + ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e); + return e.HResult; + } + + return ModVerifyConstants.Success; } protected override void CreateAppServices(IServiceCollection services, IReadOnlyList args) @@ -106,8 +136,8 @@ protected override void CreateAppServices(IServiceCollection services, IReadOnly sp => new CosturaApplicationProductService(ApplicationEnvironment, sp), sp => new JsonManifestLoader(sp)); } - - if (_optionsContainer.ModVerifyOptions is null) + + if (_modVerifyAppSettings is null) return; SteamAbstractionLayer.InitializeServices(services); @@ -122,10 +152,11 @@ protected override void CreateAppServices(IServiceCollection services, IReadOnly PetroglyphEngineServiceContribution.ContributeServices(services); services.RegisterVerifierCache(); - + services.AddSingleton(sp => new BaselineFactory(sp)); + SetupVerifyReporting(services); - if (_optionsContainer.ModVerifyOptions.OfflineMode) + if (_offlineMode) { services.AddSingleton(sp => new OfflineModNameResolver(sp)); services.AddSingleton(sp => new OfflineModGameTypeResolver(sp)); @@ -157,61 +188,39 @@ protected override IRegistry CreateRegistry() protected override async Task RunAppAsync(string[] args, IServiceProvider appServiceProvider) { var result = await HandleUpdate(appServiceProvider); - if (result != 0 || _optionsContainer.ModVerifyOptions is null) + if (result != 0 || _modVerifyAppSettings is null) return result; - - ModVerifyAppSettings modVerifySettings; - - try - { - modVerifySettings = new SettingsBuilder(appServiceProvider).BuildSettings(_optionsContainer.ModVerifyOptions); - } - catch (Exception e) - { - Logger?.LogCritical(e, "Failed to create settings form commandline arguments: {EMessage}", e.Message); - ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e); - return e.HResult; - } - - return await new ModVerifyApplication(modVerifySettings, appServiceProvider).RunAsync().ConfigureAwait(false); + return await new ModVerifyApplication(_modVerifyAppSettings, appServiceProvider).RunAsync().ConfigureAwait(false); } private void SetupVerifyReporting(IServiceCollection serviceCollection) { - var options = _optionsContainer.ModVerifyOptions; - Debug.Assert(options is not null); - + Debug.Assert(_modVerifyAppSettings is not null); - var verifyVerb = options as VerifyVerbOption; + var verifySettings = _modVerifyAppSettings as AppVerifySettings; // Console should be in minimal summary mode if we are in a different mode than verify. - var printOnlySummary = verifyVerb is null; - - serviceCollection.RegisterConsoleReporter(new VerifyReportSettings + serviceCollection.RegisterConsoleReporter(new ReporterSettings { - MinimumReportSeverity = verifyVerb?.FailFast is true + MinimumReportSeverity = verifySettings?.VerifyPipelineSettings.FailFastSettings.IsFailFast is true ? VerificationSeverity.Information : VerificationSeverity.Error - }, printOnlySummary); + }, summaryOnly: verifySettings is null); - if (verifyVerb == null) + if (verifySettings == null) return; - var outputDirectory = Environment.CurrentDirectory; - - if (!string.IsNullOrEmpty(verifyVerb.OutputDirectory)) - outputDirectory = FileSystem.Path.GetFullPath(FileSystem.Path.Combine(Environment.CurrentDirectory, verifyVerb.OutputDirectory!)); - + var outputDirectory = verifySettings.ReportDirectory; serviceCollection.RegisterJsonReporter(new JsonReporterSettings { OutputDirectory = outputDirectory!, - MinimumReportSeverity = options.MinimumSeverity + MinimumReportSeverity = _modVerifyAppSettings.ReportSettings.MinimumReportSeverity }); serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings { OutputDirectory = outputDirectory!, - MinimumReportSeverity = options.MinimumSeverity + MinimumReportSeverity = _modVerifyAppSettings.ReportSettings.MinimumReportSeverity }); } @@ -226,7 +235,7 @@ private void ConfigureLogging(ILoggingBuilder loggingBuilder) loggingBuilder.AddDebug(); #endif - if (_optionsContainer.ModVerifyOptions?.Verbose == true || _optionsContainer.UpdateOptions?.Verbose == true) + if (_verboseMode) { logLevel = LogEventLevel.Verbose; loggingBuilder.AddDebug(); @@ -299,31 +308,30 @@ static bool IsXmlParserLogging(LogEvent logEvent) private async Task HandleUpdate(IServiceProvider serviceProvider) { - var updateOptions = _optionsContainer.UpdateOptions ?? new ApplicationUpdateOptions(); + if (_offlineMode) + { + Logger?.LogTrace("Running in offline mode. There will be nothing to update."); + return ModVerifyConstants.Success; + } + ModVerifyUpdateMode updateMode; - if (_optionsContainer.ModVerifyOptions is not null) + if (_isLaunchedWithoutArguments) + updateMode = ModVerifyUpdateMode.InteractiveUpdate; + else { - if (_optionsContainer.ModVerifyOptions.OfflineMode) - { - Logger?.LogTrace("Running in offline mode. There will be nothing to update."); - return 0; - } - - updateMode = _optionsContainer.ModVerifyOptions.LaunchedWithoutArguments() - ? ModVerifyUpdateMode.InteractiveUpdate - : ModVerifyUpdateMode.CheckOnly; + updateMode = _modVerifyAppSettings is not null + ? ModVerifyUpdateMode.CheckOnly + : ModVerifyUpdateMode.AutoUpdate; } - else - updateMode = ModVerifyUpdateMode.AutoUpdate; - + try { Logger?.LogDebug("Running update with mode '{ModVerifyUpdateMode}'", updateMode); var modVerifyUpdater = new ModVerifyUpdater(serviceProvider); - await modVerifyUpdater.RunUpdateProcedure(updateOptions, updateMode).ConfigureAwait(false); + await modVerifyUpdater.RunUpdateProcedure(_updateOptions, updateMode).ConfigureAwait(false); Logger?.LogDebug("Update procedure completed successfully."); - return 0; + return ModVerifyConstants.Success; } catch (Exception e) { diff --git a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs index 1b49e88..88294de 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs @@ -1,5 +1,5 @@ -using AET.ModVerify.Reporting; -using AET.ModVerify.Reporting.Settings; +using AET.ModVerify.App.Settings; +using AET.ModVerify.Reporting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; @@ -11,17 +11,17 @@ namespace AET.ModVerify.App.Reporting; -internal sealed class BaselineFactory(IServiceProvider serviceProvider) +internal sealed class BaselineFactory(IServiceProvider serviceProvider) : IBaselineFactory { private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(BaselineFactory)); private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); public bool TryFindBaselineInDirectory( string directory, - out VerificationBaseline baseline, + [NotNullWhen(true)] out VerificationBaseline? baseline, [NotNullWhen(true)] out string? path) { - baseline = VerificationBaseline.Empty; + baseline = null; path = null; if (!_fileSystem.Directory.Exists(directory)) @@ -46,7 +46,7 @@ public bool TryFindBaselineInDirectory( try { baseline = CreateBaselineFromFilePath(jsonFile); - path = jsonFile; + path = _fileSystem.Path.GetFullPath(jsonFile); _logger?.LogDebug("Create baseline from file: {JsonFile}", jsonFile); return true; } @@ -57,6 +57,7 @@ public bool TryFindBaselineInDirectory( } } + baseline = null; path = null; return false; } @@ -68,21 +69,28 @@ public VerificationBaseline ParseBaseline(string filePath) public VerificationBaseline CreateBaseline( VerificationTarget target, - GlobalVerifyReportSettings reportSettings, + AppReportSettings reportSettings, IEnumerable errors) { - return new VerificationBaseline(reportSettings.MinimumReportSeverity, errors, target); + // TODO: Add option to not write location + // TODO: Mask username in locations + var baselineTarget = new BaselineVerificationTarget + { + Engine = target.Engine, + Name = target.Name, + Version = target.Version, + Location = target.Location + }; + + return new VerificationBaseline(reportSettings.MinimumReportSeverity, errors, baselineTarget); } public async Task WriteBaselineAsync(VerificationBaseline baseline, string filePath) { - var fullPath = _fileSystem.Path.GetFullPath(filePath); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Writing Baseline to '{FullPath}'", fullPath); - #if NET await #endif - using var fs = _fileSystem.FileStream.New(fullPath, FileMode.Create, FileAccess.Write, FileShare.None); + using var fs = _fileSystem.FileStream.New(filePath, FileMode.Create, FileAccess.Write, FileShare.None); await baseline.ToJsonAsync(fs); } diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs index 348c3a6..82b99eb 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs @@ -7,13 +7,15 @@ using PG.StarWarsGame.Engine; using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; namespace AET.ModVerify.App.Reporting; -internal sealed class BaselineSelector(ModVerifyAppSettings settings, IServiceProvider services) +internal sealed class BaselineSelector(AppVerifySettings settings, IServiceProvider services) { private readonly ILogger? _logger = services.GetService()?.CreateLogger(typeof(ModVerifyApplication)); - private readonly BaselineFactory _baselineFactory = new(services); + private readonly IBaselineFactory _baselineFactory = services.GetRequiredService(); public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget, out string? usedBaselinePath) { @@ -47,7 +49,7 @@ public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget return VerificationBaseline.Empty; } - if (settings.Interactive) + if (settings.IsInteractive) return FindBaselineInteractive(verificationTarget, out usedBaselinePath); // If the application is not interactive, we only use a baseline file present in the directory of the verification target. @@ -79,34 +81,47 @@ private VerificationBaseline FindBaselineInteractive(VerificationTarget verifica } Console.WriteLine("No baseline found locally."); - return TryGetDefaultBaseline(verificationTarget.Engine, out baselinePath); + TryGetDefaultBaseline(verificationTarget.Engine, out baseline); + return baseline ?? VerificationBaseline.Empty; } } - Debug.Assert(baselinePath is not null); + Debug.Assert(baselinePath is not null && baseline is not null); - return ConsoleUtilities.UserYesNoQuestion($"ModVerify found the baseline file '{baselinePath}'. Do you want to use it?") - ? baseline + var sb = new StringBuilder("Found baseline "); + if (baseline.Target is not null) + { + sb.Append($"for '{baseline.Target.Name}' "); + } + + sb.Append($"at '{baselinePath}'."); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(sb.ToString()); + + return ConsoleUtilities.UserYesNoQuestion("Do you want to use it?") + ? baseline : VerificationBaseline.Empty; } - private VerificationBaseline TryGetDefaultBaseline(GameEngineType engineType, out string? baselinePath) + private static bool TryGetDefaultBaseline( + GameEngineType engineType, + [NotNullWhen(true)] out VerificationBaseline? baseline) { - baselinePath = null; + baseline = null; if (engineType == GameEngineType.Eaw) { // TODO: EAW currently not implemented - return VerificationBaseline.Empty; + return false; } if (!ConsoleUtilities.UserYesNoQuestion($"Do you want to load the default baseline for game engine '{engineType}'?")) - return VerificationBaseline.Empty; - - baselinePath = $"{engineType} (Default)"; + return false; try { - return LoadEmbeddedBaseline(engineType); + baseline = LoadEmbeddedBaseline(engineType); + return true; } catch (InvalidBaselineException) { diff --git a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs new file mode 100644 index 0000000..6b6777a --- /dev/null +++ b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using AET.ModVerify.App.Settings; +using AET.ModVerify.Reporting; + +namespace AET.ModVerify.App.Reporting; + +internal interface IBaselineFactory +{ + bool TryFindBaselineInDirectory( + string directory, + [NotNullWhen(true)] out VerificationBaseline? baseline, + [NotNullWhen(true)] out string? path); + + VerificationBaseline ParseBaseline(string filePath); + + Task WriteBaselineAsync(VerificationBaseline baseline, string filePath); + + VerificationBaseline CreateBaseline( + VerificationTarget target, + AppReportSettings reportSettings, + IEnumerable errors); +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs index 0419f33..e31119f 100644 --- a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs @@ -1,49 +1,73 @@ -using System.Diagnostics.CodeAnalysis; +using System; using AET.ModVerify.Reporting; using AET.ModVerify.Settings; namespace AET.ModVerify.App.Settings; -internal sealed class ModVerifyAppSettings +public class AppReportSettings { - public bool Interactive => VerificationTargetSettings.Interactive; - - public required VerifyPipelineSettings VerifyPipelineSettings { get; init; } + public VerificationSeverity MinimumReportSeverity { get; init; } + + public string? SuppressionsPath { get; init; } +} - public required ModVerifyReportSettings ReportSettings { get; init; } +public sealed class VerifyReportSettings : AppReportSettings +{ + public string? BaselinePath { get; init; } + public bool SearchBaselineLocally { get; init; } +} - public required VerificationTargetSettings VerificationTargetSettings { get; init; } +internal abstract class AppSettingsBase(AppReportSettings reportSettings) +{ + public bool IsInteractive => VerificationTargetSettings.Interactive; - public VerificationSeverity? AppThrowsOnMinimumSeverity { get; init; } + public required VerificationTargetSettings VerificationTargetSettings + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } - [MemberNotNullWhen(true, nameof(NewBaselinePath))] - public bool CreateNewBaseline => !string.IsNullOrEmpty(NewBaselinePath); + public required VerifyPipelineSettings VerifyPipelineSettings + { + get; + init => field = value ?? throw new ArgumentNullException(nameof(value)); + } - public string? NewBaselinePath { get; init; } + public AppReportSettings ReportSettings { get; } = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); } - -internal enum AppMode +internal abstract class AppSettingsBase(T reportSettings) : AppSettingsBase(reportSettings) + where T : AppReportSettings { - Verify, - Baseline + public new T ReportSettings { get; } = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); } -internal abstract class ModVerifyAppSettingsBase +internal sealed class AppVerifySettings(VerifyReportSettings reportSettings) : AppSettingsBase(reportSettings) { - public abstract AppMode Mode { get; } - public bool IsInteractive => VerificationTargetSettings.Interactive; - public required VerificationTargetSettings VerificationTargetSettings { get; init; } -} + public VerificationSeverity? AppFailsOnMinimumSeverity { get; init; } -internal sealed class VerifyAppSettings : ModVerifyAppSettingsBase -{ - public override AppMode Mode => AppMode.Verify; - public VerificationSeverity? AppThrowsOnMinimumSeverity { get; init; } + public required string ReportDirectory + { + get; + init + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentNullException(nameof(value)); + field = value; + } + } } -internal sealed class BaselineAppSettings : ModVerifyAppSettingsBase +internal sealed class AppBaselineSettings(AppReportSettings reportSettings) : AppSettingsBase(reportSettings) { - public override AppMode Mode => AppMode.Baseline; - public required string NewBaselinePath { get; init; } + public required string NewBaselinePath + { + get; + init + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentNullException(nameof(value)); + field = value; + } + } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs deleted file mode 100644 index 482b844..0000000 --- a/src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AET.ModVerify.Reporting; - -namespace AET.ModVerify.App.Settings; - -internal sealed class ModVerifyReportSettings -{ - public VerificationSeverity MinimumReportSeverity { get; init; } - - public string? SuppressionsPath { get; init; } - - public string? BaselinePath { get; init; } - - public bool SearchBaselineLocally { get; init; } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 8a31301..3a34ae0 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; -using System.IO.Abstractions; -using AET.ModVerify.App.Settings.CommandLine; +using AET.ModVerify.App.Settings.CommandLine; using AET.ModVerify.Pipeline; using AET.ModVerify.Reporting; using AET.ModVerify.Settings; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO.Abstractions; namespace AET.ModVerify.App.Settings; @@ -15,7 +15,7 @@ internal sealed class SettingsBuilder(IServiceProvider serviceProvider) private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(SettingsBuilder)); private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public ModVerifyAppSettings BuildSettings(BaseModVerifyOptions options) + public AppSettingsBase BuildSettings(BaseModVerifyOptions options) { switch (options) { @@ -27,90 +27,91 @@ public ModVerifyAppSettings BuildSettings(BaseModVerifyOptions options) throw new NotSupportedException($"The option '{options.GetType().Name}' is not supported!"); } - private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) + private AppVerifySettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) { - return new ModVerifyAppSettings + var failFastSetting = GetFailFastSetting(); + return new AppVerifySettings(BuildReportSettings(verifyOptions)) { + ReportDirectory = GetReportDirectory(), VerifyPipelineSettings = new VerifyPipelineSettings { ParallelVerifiers = verifyOptions.Parallel ? 4 : 1, VerifiersProvider = new DefaultGameVerifiersProvider(), - FailFast = verifyOptions.FailFast, + FailFastSettings = failFastSetting, GameVerifySettings = new GameVerifySettings { IgnoreAsserts = verifyOptions.IgnoreAsserts, - ThrowsOnMinimumSeverity = GetVerifierMinimumThrowSeverity() + ThrowsOnMinimumSeverity = failFastSetting.IsFailFast + ? failFastSetting.MinumumSeverity + : verifyOptions.MinimumFailureSeverity } }, - AppThrowsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity, - VerificationTargetSettings = BuildInstallationSettings(verifyOptions), - ReportSettings = BuildReportSettings(verifyOptions), + AppFailsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity, + VerificationTargetSettings = BuildTargetSettings(verifyOptions), }; - VerificationSeverity? GetVerifierMinimumThrowSeverity() + + FailFastSetting GetFailFastSetting() { + if (!verifyOptions.FailFast) + return FailFastSetting.NoFailFast; + var minFailSeverity = verifyOptions.MinimumFailureSeverity; - if (verifyOptions.FailFast) + if (!minFailSeverity.HasValue) { - if (minFailSeverity == null) - { - _logger?.LogWarning(ModVerifyConstants.ConsoleEventId, - "Verification is configured to fail fast but 'minFailSeverity' is not specified. Using severity '{Severity}'.", VerificationSeverity.Information); - minFailSeverity = VerificationSeverity.Information; - } - - return minFailSeverity; + _logger?.LogWarning(ModVerifyConstants.ConsoleEventId, + "Verification is configured to fail fast but 'minFailSeverity' is not specified. Using severity '{Severity}'.", VerificationSeverity.Information); + minFailSeverity = VerificationSeverity.Information; } - // Only in a failFast scenario we want the verifier to throw. - // In a normal run, the verifier should simply store the error. - return null; + return new FailFastSetting(minFailSeverity.Value); + } + + string GetReportDirectory() + { + return _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine( + Environment.CurrentDirectory, + verifyOptions.OutputDirectory ?? "ModVerifyResults")); + } + + VerifyReportSettings BuildReportSettings(VerifyVerbOption options) + { + return new VerifyReportSettings + { + BaselinePath = options.Baseline, + MinimumReportSeverity = options.MinimumSeverity, + SearchBaselineLocally = options.SearchBaselineLocally, + SuppressionsPath = options.Suppressions + }; } } - private ModVerifyAppSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOption baselineVerb) + private AppBaselineSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOption baselineVerb) { - return new ModVerifyAppSettings + return new AppBaselineSettings(BuildReportSettings()) { VerifyPipelineSettings = new VerifyPipelineSettings { ParallelVerifiers = baselineVerb.Parallel ? 4 : 1, - GameVerifySettings = new GameVerifySettings - { - IgnoreAsserts = false, - ThrowsOnMinimumSeverity = null, - }, VerifiersProvider = new DefaultGameVerifiersProvider(), - FailFast = false, + GameVerifySettings = GameVerifySettings.Default, + FailFastSettings = FailFastSetting.NoFailFast, }, - AppThrowsOnMinimumSeverity = null, - VerificationTargetSettings = BuildInstallationSettings(baselineVerb), - ReportSettings = BuildReportSettings(baselineVerb), + VerificationTargetSettings = BuildTargetSettings(baselineVerb), NewBaselinePath = baselineVerb.OutputFile, }; - } - - private static ModVerifyReportSettings BuildReportSettings(BaseModVerifyOptions options) - { - var baselinePath = (options as VerifyVerbOption)?.Baseline; - - return new ModVerifyReportSettings - { - BaselinePath = baselinePath, - MinimumReportSeverity = options.MinimumSeverity, - SearchBaselineLocally = SearchLocally(options), - SuppressionsPath = options.Suppressions - }; - static bool SearchLocally(BaseModVerifyOptions o) + AppReportSettings BuildReportSettings() { - if (o is not VerifyVerbOption v) - return false; - return v.SearchBaselineLocally; + return new AppReportSettings + { + MinimumReportSeverity = baselineVerb.MinimumSeverity, + SuppressionsPath = baselineVerb.Suppressions + }; } } - private VerificationTargetSettings BuildInstallationSettings(BaseModVerifyOptions options) + private VerificationTargetSettings BuildTargetSettings(BaseModVerifyOptions options) { var modPaths = new List(); if (options.ModPaths is not null) diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs index 099297c..493aa8c 100644 --- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -1,6 +1,5 @@ using AET.ModVerify.Pipeline.Progress; using AET.ModVerify.Reporting; -using AET.ModVerify.Reporting.Settings; using AET.ModVerify.Settings; using AET.ModVerify.Verifiers; using AnakinRaW.CommonUtilities.SimplePipeline; @@ -25,7 +24,6 @@ public sealed class GameVerifyPipeline : StepRunnerPipelineBase private readonly VerificationTarget _verificationTarget; private readonly VerifyPipelineSettings _pipelineSettings; - private readonly GlobalVerifyReportSettings _reportSettings; private readonly IVerifyProgressReporter _progressReporter; private readonly IGameEngineInitializationReporter? _engineInitializationReporter; private readonly IPetroglyphStarWarsGameEngineService _gameEngineService; @@ -33,24 +31,28 @@ public sealed class GameVerifyPipeline : StepRunnerPipelineBase private AggregatedVerifyProgressReporter? _aggregatedVerifyProgressReporter; public IReadOnlyCollection FilteredErrors { get; private set; } = []; + public VerificationBaseline Baseline { get; } + public SuppressionList Suppressions { get; } public GameVerifyPipeline( VerificationTarget verificationTarget, VerifyPipelineSettings pipelineSettings, - GlobalVerifyReportSettings reportSettings, IVerifyProgressReporter progressReporter, IGameEngineInitializationReporter? engineInitializationReporter, + VerificationBaseline baseline, + SuppressionList suppressions, IServiceProvider serviceProvider) : base(serviceProvider) { + Baseline = baseline ?? throw new ArgumentNullException(nameof(baseline)); + Suppressions = suppressions ?? throw new ArgumentNullException(nameof(suppressions)); _verificationTarget = verificationTarget ?? throw new ArgumentNullException(nameof(verificationTarget)); _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings)); - _reportSettings = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); _engineInitializationReporter = engineInitializationReporter; _gameEngineService = serviceProvider.GetRequiredService(); _logger = serviceProvider.GetService()?.CreateLogger(GetType()); - FailFast = pipelineSettings.FailFast; + FailFast = pipelineSettings.FailFastSettings.IsFailFast; } protected override AsyncStepRunner CreateRunner() @@ -109,10 +111,13 @@ protected override void OnExecuteCompleted() protected override void OnRunnerExecutionError(object sender, StepRunnerErrorEventArgs e) { - if (FailFast && e.Exception is GameVerificationException v) + if (FailFast && e.Exception is GameVerificationException verificationException) { - // TODO: Apply globalMinSeverity - if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) + var minSeverity = _pipelineSettings.FailFastSettings.MinumumSeverity; + var ignoreError = verificationException.Errors + .Where(error => error.Severity >= minSeverity) + .All(error => Baseline.Contains(error) || Suppressions.Suppresses(error)); + if (ignoreError) return; } base.OnRunnerExecutionError(sender, e); @@ -136,8 +141,7 @@ private IEnumerable GetReportableErrors(IEnumerable CreateVerificationSteps(IStarWarsGameEngine engine) diff --git a/src/ModVerify/Reporting/BaselineVerificationTarget.cs b/src/ModVerify/Reporting/BaselineVerificationTarget.cs new file mode 100644 index 0000000..43fb440 --- /dev/null +++ b/src/ModVerify/Reporting/BaselineVerificationTarget.cs @@ -0,0 +1,22 @@ +using System.Text; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Reporting; + +public sealed class BaselineVerificationTarget +{ + public required GameEngineType Engine { get; init; } + public required string Name { get; init; } + public GameLocations? Location { get; init; } // Optional compared to Verification Target + public string? Version { get; init; } + + public override string ToString() + { + var sb = new StringBuilder($"[Name={Name};EngineType={Engine};"); + if (!string.IsNullOrEmpty(Version)) sb.Append($"Version={Version};"); + if (Location is not null) + sb.Append($"Location={Location};"); + sb.Append(']'); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonGameLocation.cs b/src/ModVerify/Reporting/Json/JsonGameLocation.cs index 3f088f1..bd51993 100644 --- a/src/ModVerify/Reporting/Json/JsonGameLocation.cs +++ b/src/ModVerify/Reporting/Json/JsonGameLocation.cs @@ -31,8 +31,10 @@ public JsonGameLocation(GameLocations location) FallbackPaths = location.FallbackPaths.ToArray(); } - public static GameLocations ToLocation(JsonGameLocation jsonLocation) + public static GameLocations? ToLocation(JsonGameLocation? jsonLocation) { - return new GameLocations(jsonLocation.ModPaths, jsonLocation.GamePath, jsonLocation.FallbackPaths); + return jsonLocation is null + ? null + : new GameLocations(jsonLocation.ModPaths, jsonLocation.GamePath, jsonLocation.FallbackPaths); } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs index 451f7f7..b371c74 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs @@ -17,10 +17,11 @@ internal class JsonVerificationTarget public string? Version{ get; } [JsonPropertyName("location")] - public JsonGameLocation Location { get; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public JsonGameLocation? Location { get; } [JsonConstructor] - private JsonVerificationTarget(string name, string? version, JsonGameLocation location, GameEngineType engine) + private JsonVerificationTarget(string name, string? version, JsonGameLocation? location, GameEngineType engine) { Name = name; Version = version; @@ -28,19 +29,19 @@ private JsonVerificationTarget(string name, string? version, JsonGameLocation lo Location = location; } - public JsonVerificationTarget(VerificationTarget target) + public JsonVerificationTarget(BaselineVerificationTarget target) { Name = target.Name; Version = target.Version; Engine = target.Engine; - Location = new JsonGameLocation(target.Location); + Location = target.Location is null ? null : new JsonGameLocation(target.Location); } - public static VerificationTarget? ToTarget(JsonVerificationTarget? jsonTarget) + public static BaselineVerificationTarget? ToTarget(JsonVerificationTarget? jsonTarget) { if (jsonTarget is null) return null; - return new VerificationTarget + return new BaselineVerificationTarget { Engine = jsonTarget.Engine, Name = jsonTarget.Name, diff --git a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs index 7ee435d..dbee50d 100644 --- a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs +++ b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs @@ -7,10 +7,10 @@ namespace AET.ModVerify.Reporting.Reporters; internal class ConsoleReporter( - VerifyReportSettings settings, + ReporterSettings settings, bool summaryOnly, IServiceProvider serviceProvider) : - ReporterBase(settings, serviceProvider) + ReporterBase(settings, serviceProvider) { public override Task ReportAsync(IReadOnlyCollection errors) { diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs index 941a9d5..348fbb1 100644 --- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs +++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs @@ -12,7 +12,6 @@ internal class JsonReporter(JsonReporterSettings settings, IServiceProvider serv { public const string FileName = "VerificationResult.json"; - public override async Task ReportAsync(IReadOnlyCollection errors) { var report = new JsonVerificationReport(errors.Select(x => new JsonVerificationError(x))); diff --git a/src/ModVerify/Reporting/Reporters/ReporterBase.cs b/src/ModVerify/Reporting/Reporters/ReporterBase.cs index fd86119..df444e3 100644 --- a/src/ModVerify/Reporting/Reporters/ReporterBase.cs +++ b/src/ModVerify/Reporting/Reporters/ReporterBase.cs @@ -6,7 +6,7 @@ namespace AET.ModVerify.Reporting.Reporters; -public abstract class ReporterBase(T settings, IServiceProvider serviceProvider) : IVerificationReporter where T : VerifyReportSettings +public abstract class ReporterBase(T settings, IServiceProvider serviceProvider) : IVerificationReporter where T : ReporterSettings { protected IServiceProvider ServiceProvider { get; } = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); diff --git a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs index 7de81eb..b8906ac 100644 --- a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs +++ b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs @@ -11,7 +11,7 @@ public static class VerificationReportersExtensions { public IServiceCollection RegisterJsonReporter() { - return RegisterJsonReporter(serviceCollection, new JsonReporterSettings + return serviceCollection.RegisterJsonReporter(new JsonReporterSettings { OutputDirectory = "." }); @@ -19,7 +19,7 @@ public IServiceCollection RegisterJsonReporter() public IServiceCollection RegisterTextFileReporter() { - return RegisterTextFileReporter(serviceCollection, new TextFileReporterSettings + return serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings { OutputDirectory = "." }); @@ -27,7 +27,7 @@ public IServiceCollection RegisterTextFileReporter() public IServiceCollection RegisterConsoleReporter(bool summaryOnly = false) { - return RegisterConsoleReporter(serviceCollection, new VerifyReportSettings + return serviceCollection.RegisterConsoleReporter(new ReporterSettings { MinimumReportSeverity = VerificationSeverity.Error }, summaryOnly); @@ -43,7 +43,7 @@ public IServiceCollection RegisterTextFileReporter(TextFileReporterSettings sett return serviceCollection.AddSingleton(sp => new TextFileReporter(settings, sp)); } - public IServiceCollection RegisterConsoleReporter(VerifyReportSettings settings, + public IServiceCollection RegisterConsoleReporter(ReporterSettings settings, bool summaryOnly = false) { return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, summaryOnly, sp)); diff --git a/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs index c6233a1..759a6ab 100644 --- a/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs +++ b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs @@ -2,13 +2,11 @@ namespace AET.ModVerify.Reporting.Settings; -public record FileBasedReporterSettings : VerifyReportSettings +public record FileBasedReporterSettings : ReporterSettings { - private readonly string _outputDirectory = Environment.CurrentDirectory; - public string OutputDirectory { - get => _outputDirectory; - init => _outputDirectory = string.IsNullOrEmpty(value) ? Environment.CurrentDirectory : value; - } + get; + init => field = string.IsNullOrEmpty(value) ? Environment.CurrentDirectory : value; + } = Environment.CurrentDirectory; } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs index b376fe3..5a51436 100644 --- a/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs +++ b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs @@ -1,8 +1,10 @@ namespace AET.ModVerify.Reporting.Settings; -public record GlobalVerifyReportSettings : VerifyReportSettings -{ - public VerificationBaseline Baseline { get; init; } = VerificationBaseline.Empty; +//public record GlobalVerifyReportSettings +//{ +// //public VerificationSeverity MinimumReportSeverity { get; init; } = VerificationSeverity.Information; - public SuppressionList Suppressions { get; init; } = SuppressionList.Empty; -} \ No newline at end of file +// public VerificationBaseline Baseline { get; init; } = VerificationBaseline.Empty; + +// public SuppressionList Suppressions { get; init; } = SuppressionList.Empty; +//} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Settings/VerifyReportSettings.cs b/src/ModVerify/Reporting/Settings/ReporterSettings.cs similarity index 81% rename from src/ModVerify/Reporting/Settings/VerifyReportSettings.cs rename to src/ModVerify/Reporting/Settings/ReporterSettings.cs index 1289822..ef33857 100644 --- a/src/ModVerify/Reporting/Settings/VerifyReportSettings.cs +++ b/src/ModVerify/Reporting/Settings/ReporterSettings.cs @@ -1,6 +1,6 @@ namespace AET.ModVerify.Reporting.Settings; -public record VerifyReportSettings +public record ReporterSettings { public VerificationSeverity MinimumReportSeverity { get; init; } = VerificationSeverity.Information; } \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/VerificationBaseline.cs index ca8700d..3f9c274 100644 --- a/src/ModVerify/Reporting/VerificationBaseline.cs +++ b/src/ModVerify/Reporting/VerificationBaseline.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading.Tasks; using AET.ModVerify.Reporting.Json; -using PG.StarWarsGame.Engine; namespace AET.ModVerify.Reporting; @@ -20,7 +19,7 @@ public sealed class VerificationBaseline : IReadOnlyCollection _errors; - public VerificationTarget? Target { get; } + public BaselineVerificationTarget? Target { get; } public Version? Version { get; } @@ -29,6 +28,8 @@ public sealed class VerificationBaseline : IReadOnlyCollection public int Count => _errors.Count; + public bool IsEmpty => Count == 0; + internal VerificationBaseline(JsonVerificationBaseline baseline) { _errors = [..baseline.Errors.Select(x => new VerificationError(x))]; @@ -37,7 +38,7 @@ internal VerificationBaseline(JsonVerificationBaseline baseline) Target = JsonVerificationTarget.ToTarget(baseline.Target); } - public VerificationBaseline(VerificationSeverity minimumSeverity, IEnumerable errors, VerificationTarget? target) + public VerificationBaseline(VerificationSeverity minimumSeverity, IEnumerable errors, BaselineVerificationTarget? target) { _errors = [..errors]; Version = LatestVersion; @@ -89,24 +90,4 @@ public override string ToString() sb.Append(']'); return sb.ToString(); } -} - -public sealed class BaselineVerificationTarget -{ - public required GameEngineType Engine { get; init; } - public required string Name { get; init; } - public GameLocations? Location { get; init; } - public string? Version { get; init; } - public bool IsGame => Location.ModPaths.Count == 0; - - - - public override string ToString() - { - var sb = new StringBuilder($"[Name={Name};EngineType={Engine};"); - if (!string.IsNullOrEmpty(Version)) sb.Append($"Version={Version};"); - sb.Append($"Location={Location};"); - sb.Append(']'); - return sb.ToString(); - } } \ No newline at end of file diff --git a/src/ModVerify/Settings/VerifyPipelineSettings.cs b/src/ModVerify/Settings/VerifyPipelineSettings.cs index 2e11c74..db65969 100644 --- a/src/ModVerify/Settings/VerifyPipelineSettings.cs +++ b/src/ModVerify/Settings/VerifyPipelineSettings.cs @@ -1,4 +1,5 @@ using AET.ModVerify.Pipeline; +using AET.ModVerify.Reporting; namespace AET.ModVerify.Settings; @@ -8,7 +9,22 @@ public sealed class VerifyPipelineSettings public required IGameVerifiersProvider VerifiersProvider { get; init; } - public bool FailFast { get; init; } + public FailFastSetting FailFastSettings { get; init; } = FailFastSetting.NoFailFast; public int ParallelVerifiers { get; init; } = 4; +} + +public readonly struct FailFastSetting +{ + public static readonly FailFastSetting NoFailFast = default; + + public readonly bool IsFailFast; + + public readonly VerificationSeverity MinumumSeverity; + + public FailFastSetting(VerificationSeverity severity) + { + IsFailFast = true; + MinumumSeverity = severity; + } } \ No newline at end of file From abf5a38ac3ae851486b66f2ac258da3001d2a256 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 10:31:39 +0100 Subject: [PATCH 28/38] allow skip location in baselines --- .../App/CreateBaselineAction.cs | 2 +- .../Properties/launchSettings.json | 2 +- .../Reporting/BaselineFactory.cs | 21 +++++++--- .../Reporting/IBaselineFactory.cs | 2 +- .../CommandLine/CreateBaselineVerbOption.cs | 3 ++ .../Settings/ModVerifyAppSettings.cs | 2 + .../Settings/SettingsBuilder.cs | 1 + .../Utilities/PathUtilities.cs | 39 +++++++++++++++++++ 8 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 src/ModVerify.CliApp/Utilities/PathUtilities.cs diff --git a/src/ModVerify.CliApp/App/CreateBaselineAction.cs b/src/ModVerify.CliApp/App/CreateBaselineAction.cs index cc56ea2..c1cdb89 100644 --- a/src/ModVerify.CliApp/App/CreateBaselineAction.cs +++ b/src/ModVerify.CliApp/App/CreateBaselineAction.cs @@ -28,7 +28,7 @@ protected override async Task ProcessVerifyFindings( IReadOnlyCollection allErrors) { var baselineFactory = ServiceProvider.GetRequiredService(); - var baseline = baselineFactory.CreateBaseline(verificationTarget, Settings.ReportSettings, allErrors); + var baseline = baselineFactory.CreateBaseline(verificationTarget, Settings, allErrors); var fullPath = _fileSystem.Path.GetFullPath(Settings.NewBaselinePath); Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index 0dc1305..9c1a6a2 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -10,7 +10,7 @@ }, "Interactive Baseline": { "commandName": "Project", - "commandLineArgs": "createBaseline -o focBaseline.json --offline" + "commandLineArgs": "createBaseline -o focBaseline.json --offline --skipLocation" }, "FromModPath": { diff --git a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs index 88294de..e388867 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs @@ -7,7 +7,10 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Abstractions; +using System.Linq; using System.Threading.Tasks; +using PG.StarWarsGame.Engine; +using AET.ModVerify.App.Utilities; namespace AET.ModVerify.App.Reporting; @@ -68,21 +71,27 @@ public VerificationBaseline ParseBaseline(string filePath) } public VerificationBaseline CreateBaseline( - VerificationTarget target, - AppReportSettings reportSettings, + VerificationTarget target, + AppBaselineSettings settings, IEnumerable errors) { - // TODO: Add option to not write location - // TODO: Mask username in locations var baselineTarget = new BaselineVerificationTarget { Engine = target.Engine, Name = target.Name, Version = target.Version, - Location = target.Location + Location = settings.WriteLocations ? MaskUsername(target.Location) : null }; - return new VerificationBaseline(reportSettings.MinimumReportSeverity, errors, baselineTarget); + return new VerificationBaseline(settings.ReportSettings.MinimumReportSeverity, errors, baselineTarget); + } + + private static GameLocations MaskUsername(GameLocations targetLocation) + { + return new GameLocations( + targetLocation.ModPaths.Select(PathUtilities.MaskUsername).ToList(), + PathUtilities.MaskUsername(targetLocation.GamePath), + targetLocation.FallbackPaths.Select(PathUtilities.MaskUsername).ToList()); } public async Task WriteBaselineAsync(VerificationBaseline baseline, string filePath) diff --git a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs index 6b6777a..19bdbda 100644 --- a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs @@ -19,6 +19,6 @@ bool TryFindBaselineInDirectory( VerificationBaseline CreateBaseline( VerificationTarget target, - AppReportSettings reportSettings, + AppBaselineSettings settings, IEnumerable errors); } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs index dc60a73..6593245 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs @@ -7,4 +7,7 @@ internal sealed class CreateBaselineVerbOption : BaseModVerifyOptions { [Option('o', "outFile", Required = true, HelpText = "The file path of the new baseline file.")] public required string OutputFile { get; init; } + + [Option("skipLocation", Required = false, HelpText = "Skips writing the target location to the baseline.")] + public bool SkipLocation { get; init; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs index e31119f..c142bd4 100644 --- a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs @@ -70,4 +70,6 @@ public required string NewBaselinePath field = value; } } + + public bool WriteLocations { get; init; } = true; } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 3a34ae0..cae1a61 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -99,6 +99,7 @@ private AppBaselineSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOption }, VerificationTargetSettings = BuildTargetSettings(baselineVerb), NewBaselinePath = baselineVerb.OutputFile, + WriteLocations = !baselineVerb.SkipLocation }; AppReportSettings BuildReportSettings() diff --git a/src/ModVerify.CliApp/Utilities/PathUtilities.cs b/src/ModVerify.CliApp/Utilities/PathUtilities.cs new file mode 100644 index 0000000..561630f --- /dev/null +++ b/src/ModVerify.CliApp/Utilities/PathUtilities.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; +using AnakinRaW.CommonUtilities.FileSystem.Normalization; + +namespace AET.ModVerify.App.Utilities; + +internal static class PathUtilities +{ + private static readonly string HomeVariable; + private static readonly string HomePath; + private static readonly StringComparison StringComparer; + + static PathUtilities() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + HomeVariable = "%USERPROFILE%"; + StringComparer = StringComparison.OrdinalIgnoreCase; + } + else + { + HomeVariable = "$HOME"; + StringComparer = StringComparison.Ordinal; + } + + HomePath = PathNormalizer.Normalize( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + PathNormalizeOptions.EnsureTrailingSeparator); + } + + internal static string MaskUsername(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return path; + + var index = path.IndexOf(HomePath, StringComparer); + return index >= 0 ? path.Remove(index, HomePath.Length).Insert(index, HomeVariable) : path; + } +} \ No newline at end of file From c2b11dacc5b6b21d02d308a96c7734a9b075790f Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 12:51:06 +0100 Subject: [PATCH 29/38] only search for baselines of the correct engine type --- src/ModVerify.CliApp/App/VerifyAction.cs | 23 +++++- .../Reporting/BaselineFactory.cs | 16 ++++- .../Reporting/BaselineSelector.cs | 70 +++++++++++-------- .../Reporting/IBaselineFactory.cs | 8 ++- .../Reporting/BaselineVerificationTarget.cs | 3 +- .../Reporting/Json/JsonVerificationTarget.cs | 15 +++- .../Resources/Schemas/2.1/baseline.json | 3 + 7 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/ModVerify.CliApp/App/VerifyAction.cs b/src/ModVerify.CliApp/App/VerifyAction.cs index c295768..1d99768 100644 --- a/src/ModVerify.CliApp/App/VerifyAction.cs +++ b/src/ModVerify.CliApp/App/VerifyAction.cs @@ -47,8 +47,27 @@ protected override VerificationBaseline GetBaseline(VerificationTarget verificat var baseline = baselineSelector.SelectBaseline(verificationTarget, out var baselinePath); if (!baseline.IsEmpty) { - // TODO: Handle nullable path - Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Using baseline '{Baseline}'", baselinePath); + if (string.IsNullOrEmpty(baselinePath)) + { + if (baseline.Target?.Engine is null) + Logger?.LogDebug("Target for default baseline should not be null!"); + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "Using default baseline for engine '{Engine}'", + baseline.Target?.Engine); + } + else + { + if (string.IsNullOrEmpty(baseline.Target?.Name)) + { + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "Using baseline '{Baseline}'", baselinePath); + } + else + { + Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, + "Using baseline for target {Target} from location '{Baseline}'", baseline.Target.Name, baselinePath); + } + } } return baseline; } diff --git a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs index e388867..a83cea9 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs @@ -21,6 +21,7 @@ internal sealed class BaselineFactory(IServiceProvider serviceProvider) : IBasel public bool TryFindBaselineInDirectory( string directory, + Predicate baselineSelector, [NotNullWhen(true)] out VerificationBaseline? baseline, [NotNullWhen(true)] out string? path) { @@ -48,9 +49,17 @@ public bool TryFindBaselineInDirectory( { try { - baseline = CreateBaselineFromFilePath(jsonFile); + var parsedBaseline = CreateBaselineFromFilePath(jsonFile); + if (!baselineSelector(parsedBaseline)) + { + _logger?.LogDebug("Baseline '{JsonFile}' was denied by selector.", jsonFile); + continue; + } + + baseline = parsedBaseline; path = _fileSystem.Path.GetFullPath(jsonFile); - _logger?.LogDebug("Create baseline from file: {JsonFile}", jsonFile); + + _logger?.LogDebug("Create baseline from file '{JsonFile}'", jsonFile); return true; } catch (InvalidBaselineException e) @@ -80,7 +89,8 @@ public VerificationBaseline CreateBaseline( Engine = target.Engine, Name = target.Name, Version = target.Version, - Location = settings.WriteLocations ? MaskUsername(target.Location) : null + Location = settings.WriteLocations ? MaskUsername(target.Location) : null, + IsGame = target.IsGame, }; return new VerificationBaseline(settings.ReportSettings.MinimumReportSeverity, errors, baselineTarget); diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs index 82b99eb..c34b467 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs @@ -25,7 +25,7 @@ public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget try { usedBaselinePath = baselinePath; - return _baselineFactory.ParseBaseline(baselinePath!); + return _baselineFactory.ParseBaseline(baselinePath); } catch (InvalidBaselineException e) { @@ -44,7 +44,8 @@ public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget if (!settings.ReportSettings.SearchBaselineLocally) { - _logger?.LogDebug(ModVerifyConstants.ConsoleEventId, "No baseline path specified and local search is not enabled. Using empty baseline."); + _logger?.LogDebug(ModVerifyConstants.ConsoleEventId, + "No baseline path specified and local search is not enabled. Using empty baseline."); usedBaselinePath = null; return VerificationBaseline.Empty; } @@ -53,7 +54,7 @@ public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget return FindBaselineInteractive(verificationTarget, out usedBaselinePath); // If the application is not interactive, we only use a baseline file present in the directory of the verification target. - return FindBaselineNonInteractive(verificationTarget.Location.TargetPath, out usedBaselinePath); + return FindBaselineNonInteractive(verificationTarget, out usedBaselinePath); } @@ -67,20 +68,20 @@ private VerificationBaseline FindBaselineInteractive(VerificationTarget verifica _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Searching for local baseline files..."); - if (!_baselineFactory.TryFindBaselineInDirectory(verificationTarget.Location.TargetPath, out var baseline, + if (!_baselineFactory.TryFindBaselineInDirectory( + verificationTarget.Location.TargetPath, + b => IsBaselineCompatible(b, verificationTarget), + out var baseline, out baselinePath)) { - if (!_baselineFactory.TryFindBaselineInDirectory("./", out baseline, out baselinePath)) + if (!_baselineFactory.TryFindBaselineInDirectory( + Environment.CurrentDirectory, + b => IsBaselineCompatible(b, verificationTarget), + out baseline, + out baselinePath)) { - // It does not make sense to load the game's default baselines if the user wants to verify the game, - // as the verification result would always be empty (at least in a non-development scenario) - if (verificationTarget.Location.ModPaths.Count == 0) - { - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "No local baseline file found."); - return VerificationBaseline.Empty; - } - Console.WriteLine("No baseline found locally."); + baselinePath = null; TryGetDefaultBaseline(verificationTarget.Engine, out baseline); return baseline ?? VerificationBaseline.Empty; } @@ -88,18 +89,7 @@ private VerificationBaseline FindBaselineInteractive(VerificationTarget verifica Debug.Assert(baselinePath is not null && baseline is not null); - var sb = new StringBuilder("Found baseline "); - if (baseline.Target is not null) - { - sb.Append($"for '{baseline.Target.Name}' "); - } - - sb.Append($"at '{baselinePath}'."); - - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine(sb.ToString()); - - return ConsoleUtilities.UserYesNoQuestion("Do you want to use it?") + return ShouldUseBaseline(baseline, baselinePath) ? baseline : VerificationBaseline.Empty; } @@ -139,15 +129,39 @@ internal static VerificationBaseline LoadEmbeddedBaseline(GameEngineType engineT return VerificationBaseline.FromJson(baselineStream); } - private VerificationBaseline FindBaselineNonInteractive(string targetPath, out string? usedPath) + private VerificationBaseline FindBaselineNonInteractive(VerificationTarget target, out string? usedPath) { - if (_baselineFactory.TryFindBaselineInDirectory(targetPath, out var baseline, out usedPath)) + if (_baselineFactory.TryFindBaselineInDirectory( + target.Location.TargetPath, + b => IsBaselineCompatible(b, target), + out var baseline, + out usedPath)) { _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Automatically applying local baseline file '{Path}'.", usedPath); return baseline; } - _logger?.LogTrace("No baseline file found in taget path '{TargetPath}'.", targetPath); + _logger?.LogTrace("No baseline file found in taget path '{TargetPath}'.", target.Location.TargetPath); usedPath = null; return VerificationBaseline.Empty; } + + + private static bool IsBaselineCompatible(VerificationBaseline baseline, VerificationTarget target) + { + return baseline.Target?.Engine == target.Engine; + } + + private static bool ShouldUseBaseline(VerificationBaseline baseline, string baselinePath) + { + var sb = new StringBuilder("Found baseline "); + if (baseline.Target is not null) + sb.Append($"for '{baseline.Target.Name}' "); + + sb.Append($"at '{baselinePath}'."); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(sb.ToString()); + + return ConsoleUtilities.UserYesNoQuestion("Do you want to use it?"); + } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs index 19bdbda..721a7ce 100644 --- a/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs +++ b/src/ModVerify.CliApp/Reporting/IBaselineFactory.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using AET.ModVerify.App.Settings; +using AET.ModVerify.Reporting; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; -using AET.ModVerify.App.Settings; -using AET.ModVerify.Reporting; namespace AET.ModVerify.App.Reporting; @@ -10,6 +11,7 @@ internal interface IBaselineFactory { bool TryFindBaselineInDirectory( string directory, + Predicate baselineSelector, [NotNullWhen(true)] out VerificationBaseline? baseline, [NotNullWhen(true)] out string? path); diff --git a/src/ModVerify/Reporting/BaselineVerificationTarget.cs b/src/ModVerify/Reporting/BaselineVerificationTarget.cs index 43fb440..6e21ddd 100644 --- a/src/ModVerify/Reporting/BaselineVerificationTarget.cs +++ b/src/ModVerify/Reporting/BaselineVerificationTarget.cs @@ -9,10 +9,11 @@ public sealed class BaselineVerificationTarget public required string Name { get; init; } public GameLocations? Location { get; init; } // Optional compared to Verification Target public string? Version { get; init; } + public bool IsGame { get; init; } public override string ToString() { - var sb = new StringBuilder($"[Name={Name};EngineType={Engine};"); + var sb = new StringBuilder($"[Name={Name};EngineType={Engine};IsGame={IsGame};"); if (!string.IsNullOrEmpty(Version)) sb.Append($"Version={Version};"); if (Location is not null) sb.Append($"Location={Location};"); diff --git a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs index b371c74..9495456 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationTarget.cs @@ -12,6 +12,9 @@ internal class JsonVerificationTarget [JsonConverter(typeof(JsonStringEnumConverter))] public GameEngineType Engine { get; } + [JsonPropertyName("isGame")] + public bool IsGame { get; } + [JsonPropertyName("version")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Version{ get; } @@ -21,12 +24,18 @@ internal class JsonVerificationTarget public JsonGameLocation? Location { get; } [JsonConstructor] - private JsonVerificationTarget(string name, string? version, JsonGameLocation? location, GameEngineType engine) + private JsonVerificationTarget( + string name, + string? version, + JsonGameLocation? location, + GameEngineType engine, + bool isGame) { Name = name; Version = version; Engine = engine; Location = location; + IsGame = isGame; } public JsonVerificationTarget(BaselineVerificationTarget target) @@ -35,6 +44,7 @@ public JsonVerificationTarget(BaselineVerificationTarget target) Version = target.Version; Engine = target.Engine; Location = target.Location is null ? null : new JsonGameLocation(target.Location); + IsGame = target.IsGame; } public static BaselineVerificationTarget? ToTarget(JsonVerificationTarget? jsonTarget) @@ -46,7 +56,8 @@ public JsonVerificationTarget(BaselineVerificationTarget target) Engine = jsonTarget.Engine, Name = jsonTarget.Name, Location = JsonGameLocation.ToLocation(jsonTarget.Location), - Version = jsonTarget.Version + Version = jsonTarget.Version, + IsGame = jsonTarget.IsGame }; } } \ No newline at end of file diff --git a/src/ModVerify/Resources/Schemas/2.1/baseline.json b/src/ModVerify/Resources/Schemas/2.1/baseline.json index ed5a014..da37c4d 100644 --- a/src/ModVerify/Resources/Schemas/2.1/baseline.json +++ b/src/ModVerify/Resources/Schemas/2.1/baseline.json @@ -44,6 +44,9 @@ }, "location": { "$ref": "#/$defs/location" + }, + "isGame": { + "type": "boolean" } }, "required": [ From c1506c72346bf5ede1603fcffba765c33e0466d4 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 13:23:45 +0100 Subject: [PATCH 30/38] Support argument validation --- src/ModVerify.CliApp/AppArgumentException.cs | 5 +++++ src/ModVerify.CliApp/Program.cs | 11 +++++++++-- .../Settings/CommandLine/VerifyVerbOption.cs | 1 - .../Settings/CommandLineHelper.cs | 18 ++++++++++++++++++ .../Settings/SettingsBuilder.cs | 11 +++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/ModVerify.CliApp/AppArgumentException.cs create mode 100644 src/ModVerify.CliApp/Settings/CommandLineHelper.cs diff --git a/src/ModVerify.CliApp/AppArgumentException.cs b/src/ModVerify.CliApp/AppArgumentException.cs new file mode 100644 index 0000000..8ead9ed --- /dev/null +++ b/src/ModVerify.CliApp/AppArgumentException.cs @@ -0,0 +1,5 @@ +using System; + +namespace AET.ModVerify.App; + +internal class AppArgumentException(string message) : ArgumentException(message); \ No newline at end of file diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index 26ef1fd..2d23b3b 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -103,11 +103,18 @@ protected override async Task InitializeAppAsync(IReadOnlyList args try { - _modVerifyAppSettings = new SettingsBuilder(bootstrapServices).BuildSettings(parsedOptions.ModVerifyOptions); + _modVerifyAppSettings = new SettingsBuilder(bootstrapServices) + .BuildSettings(parsedOptions.ModVerifyOptions); + } + catch (AppArgumentException e) + { + Logger?.LogCritical(e, "Invalid arguments specified by the user: {Message}", e.Message); + ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e.Message); + return e.HResult; } catch (Exception e) { - Logger?.LogCritical(e, "Failed to create settings form commandline arguments: {EMessage}", e.Message); + Logger?.LogCritical(e, "Failed to create settings form commandline arguments: {Message}", e.Message); ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e); return e.HResult; } diff --git a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs index 9581a9c..895a7f1 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs @@ -32,7 +32,6 @@ internal sealed class VerifyVerbOption : BaseModVerifyOptions HelpText = "Path to a JSON baseline file. Cannot be used together with --searchBaseline.")] public string? Baseline { get; init; } - // TODO: Ignore, if baseline is set [Option("searchBaseline", SetName = "baselineSelection", Required = false, HelpText = "When set, the application will search for baseline files and use them for verification. Cannot be used together with --baseline")] public bool SearchBaselineLocally { get; init; } diff --git a/src/ModVerify.CliApp/Settings/CommandLineHelper.cs b/src/ModVerify.CliApp/Settings/CommandLineHelper.cs new file mode 100644 index 0000000..f799591 --- /dev/null +++ b/src/ModVerify.CliApp/Settings/CommandLineHelper.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; +using System.Reflection; +using CommandLine; + +namespace AET.ModVerify.App.Settings; + +internal static class CommandLineHelper +{ + public static string GetOptionName(this Type type, string optionPropertyName) + { + var property = type.GetProperties().FirstOrDefault(p => p.Name.Equals(optionPropertyName)); + var optionAttribute = property?.GetCustomAttribute(); + return optionAttribute is null + ? throw new InvalidOperationException($"Unable to get option data for {type}:{optionAttribute}") + : $"--{optionAttribute.LongName}"; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index cae1a61..63c969c 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -29,6 +29,7 @@ public AppSettingsBase BuildSettings(BaseModVerifyOptions options) private AppVerifySettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) { + ValidateVerb(); var failFastSetting = GetFailFastSetting(); return new AppVerifySettings(BuildReportSettings(verifyOptions)) { @@ -50,6 +51,16 @@ private AppVerifySettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) VerificationTargetSettings = BuildTargetSettings(verifyOptions), }; + void ValidateVerb() + { + if (verifyOptions.SearchBaselineLocally && !string.IsNullOrEmpty(verifyOptions.Baseline)) + { + var searchOption = typeof(VerifyVerbOption).GetOptionName(nameof(VerifyVerbOption.SearchBaselineLocally)); + var baselineOption = typeof(VerifyVerbOption).GetOptionName(nameof(VerifyVerbOption.Baseline)); + throw new AppArgumentException($"Options {searchOption} and {baselineOption} cannot be used together."); + } + } + FailFastSetting GetFailFastSetting() { From 30c1381041b75be91e522c50108c7b2019b28b2a Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 14:01:48 +0100 Subject: [PATCH 31/38] Setting appFailure does not terminate the verifiers on first error. --- .../App/ModVerifyApplicationAction.cs | 2 +- .../Properties/launchSettings.json | 2 +- .../VerifyConsoleProgressReporter.cs | 13 +++-- .../Settings/CommandLine/VerifyVerbOption.cs | 3 +- .../Settings/ModVerifyAppSettings.cs | 2 + .../Settings/SettingsBuilder.cs | 47 +++++++++---------- .../AggregatedVerifyProgressReporter.cs | 1 + .../Pipeline/Progress/VerifyProgressInfo.cs | 2 +- 8 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs index 89af482..607b6ea 100644 --- a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs +++ b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs @@ -85,7 +85,7 @@ protected abstract Task ProcessVerifyFindings( private async Task> VerifyTargetAsync(VerificationTarget verificationTarget) { - var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name); + var progressReporter = new VerifyConsoleProgressReporter(verificationTarget.Name, Settings.ReportSettings); var baseline = GetBaseline(verificationTarget); var suppressions = GetSuppressions(); diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index 9c1a6a2..7d4048e 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -6,7 +6,7 @@ }, "Interactive Verify": { "commandName": "Project", - "commandLineArgs": "verify -o verifyResults --minFailSeverity Information --offline --failFast" + "commandLineArgs": "verify -o verifyResults --offline --minFailSeverity Information --verbose" }, "Interactive Baseline": { "commandName": "Project", diff --git a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs index 4700457..b2ce170 100644 --- a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs +++ b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using AET.ModVerify.App.Settings; using AET.ModVerify.Pipeline.Progress; using AnakinRaW.CommonUtilities; using AnakinRaW.CommonUtilities.SimplePipeline.Progress; @@ -7,7 +8,8 @@ namespace AET.ModVerify.App.Reporting; -public sealed class VerifyConsoleProgressReporter(string toVerifyName) : DisposableObject, IVerifyProgressReporter +public sealed class VerifyConsoleProgressReporter(string toVerifyName, AppReportSettings reportSettings) + : DisposableObject, IVerifyProgressReporter { private static readonly ProgressBarOptions ProgressBarOptions = new() { @@ -17,6 +19,7 @@ public sealed class VerifyConsoleProgressReporter(string toVerifyName) : Disposa WriteQueuedMessage = WriteQueuedMessage, }; + private readonly bool _verbose = reportSettings.Verbose; private ProgressBar? _progressBar; public void ReportError(string message, string? errorLine) @@ -38,8 +41,8 @@ public void Report(double progress, string? progressText, ProgressType type, Ver var progressBar = EnsureProgressBar(); - // TODO: Only recognize detailed mode - progressBar.Message = progressText; + if (detailedProgress.IsDetailed) + progressBar.Message = progressText; if (progress >= 1.0) progressBar.Message = $"Verified '{toVerifyName}'"; @@ -47,8 +50,8 @@ public void Report(double progress, string? progressText, ProgressType type, Ver var cpb = progressBar.AsProgress(); cpb.Report(progress); - // TODO: Only in verbose mode - //progressBar.WriteLine(progressText); + if (_verbose) + progressBar.WriteLine(progressText); } protected override void DisposeResources() diff --git a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs index 895a7f1..e3be836 100644 --- a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs +++ b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs @@ -16,7 +16,8 @@ internal sealed class VerifyVerbOption : BaseModVerifyOptions public string? OutputDirectory { get; init; } [Option("failFast", Required = false, Default = false, - HelpText = "When set, the application will abort on the first failure. The option also recognized the 'MinimumFailureSeverity' setting.")] + HelpText = "When set, the application will abort on the first failure. " + + "The option requires 'minFailSeverity' to be set.")] public bool FailFast { get; init; } [Option("minFailSeverity", Required = false, Default = null, diff --git a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs index c142bd4..e4488a0 100644 --- a/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs @@ -9,6 +9,8 @@ public class AppReportSettings public VerificationSeverity MinimumReportSeverity { get; init; } public string? SuppressionsPath { get; init; } + + public bool Verbose { get; init; } } public sealed class VerifyReportSettings : AppReportSettings diff --git a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs index 63c969c..23a89ff 100644 --- a/src/ModVerify.CliApp/Settings/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs @@ -1,9 +1,7 @@ using AET.ModVerify.App.Settings.CommandLine; using AET.ModVerify.Pipeline; -using AET.ModVerify.Reporting; using AET.ModVerify.Settings; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO.Abstractions; @@ -12,7 +10,6 @@ namespace AET.ModVerify.App.Settings; internal sealed class SettingsBuilder(IServiceProvider serviceProvider) { - private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(SettingsBuilder)); private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); public AppSettingsBase BuildSettings(BaseModVerifyOptions options) @@ -31,7 +28,7 @@ private AppVerifySettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) { ValidateVerb(); var failFastSetting = GetFailFastSetting(); - return new AppVerifySettings(BuildReportSettings(verifyOptions)) + return new AppVerifySettings(BuildReportSettings()) { ReportDirectory = GetReportDirectory(), VerifyPipelineSettings = new VerifyPipelineSettings @@ -43,8 +40,9 @@ private AppVerifySettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) { IgnoreAsserts = verifyOptions.IgnoreAsserts, ThrowsOnMinimumSeverity = failFastSetting.IsFailFast - ? failFastSetting.MinumumSeverity - : verifyOptions.MinimumFailureSeverity + ? failFastSetting.MinumumSeverity + // The app shall not make a specific verifier throw, but it should always run to completion. + : null } }, AppFailsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity, @@ -59,23 +57,20 @@ void ValidateVerb() var baselineOption = typeof(VerifyVerbOption).GetOptionName(nameof(VerifyVerbOption.Baseline)); throw new AppArgumentException($"Options {searchOption} and {baselineOption} cannot be used together."); } - } - - - FailFastSetting GetFailFastSetting() - { - if (!verifyOptions.FailFast) - return FailFastSetting.NoFailFast; - var minFailSeverity = verifyOptions.MinimumFailureSeverity; - if (!minFailSeverity.HasValue) + if (verifyOptions is { FailFast: true, MinimumFailureSeverity: null }) { - _logger?.LogWarning(ModVerifyConstants.ConsoleEventId, - "Verification is configured to fail fast but 'minFailSeverity' is not specified. Using severity '{Severity}'.", VerificationSeverity.Information); - minFailSeverity = VerificationSeverity.Information; + var failFast = typeof(VerifyVerbOption).GetOptionName(nameof(VerifyVerbOption.FailFast)); + var minThrowSeverity = typeof(VerifyVerbOption).GetOptionName(nameof(VerifyVerbOption.MinimumFailureSeverity)); + throw new AppArgumentException($"Option {failFast} requires to set {minThrowSeverity}."); } + } - return new FailFastSetting(minFailSeverity.Value); + FailFastSetting GetFailFastSetting() + { + return !verifyOptions.FailFast + ? FailFastSetting.NoFailFast + : new FailFastSetting(verifyOptions.MinimumFailureSeverity!.Value); } string GetReportDirectory() @@ -85,14 +80,15 @@ string GetReportDirectory() verifyOptions.OutputDirectory ?? "ModVerifyResults")); } - VerifyReportSettings BuildReportSettings(VerifyVerbOption options) + VerifyReportSettings BuildReportSettings() { return new VerifyReportSettings { - BaselinePath = options.Baseline, - MinimumReportSeverity = options.MinimumSeverity, - SearchBaselineLocally = options.SearchBaselineLocally, - SuppressionsPath = options.Suppressions + BaselinePath = verifyOptions.Baseline, + MinimumReportSeverity = verifyOptions.MinimumSeverity, + SearchBaselineLocally = verifyOptions.SearchBaselineLocally, + SuppressionsPath = verifyOptions.Suppressions, + Verbose = verifyOptions.Verbose }; } } @@ -118,7 +114,8 @@ AppReportSettings BuildReportSettings() return new AppReportSettings { MinimumReportSeverity = baselineVerb.MinimumSeverity, - SuppressionsPath = baselineVerb.Suppressions + SuppressionsPath = baselineVerb.Suppressions, + Verbose = baselineVerb.Verbose }; } } diff --git a/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs b/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs index 361febe..cfdae25 100644 --- a/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs +++ b/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs @@ -59,6 +59,7 @@ protected override ProgressEventArgs CalculateAggregatedProg var progressInfo = new VerifyProgressInfo { TotalVerifiers = TotalStepCount, + IsDetailed = true }; return new ProgressEventArgs(totalProgress, progress.ProgressText, progressInfo); } diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs b/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs index 1409239..cadeb0d 100644 --- a/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs +++ b/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs @@ -4,5 +4,5 @@ public struct VerifyProgressInfo { public bool IsDetailed { get; init; } - public int TotalVerifiers { get; internal set; } + public int TotalVerifiers { get; internal init; } } \ No newline at end of file From ae1273be311628a12790aa80d638fa339e8bec2d Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 14:30:34 +0100 Subject: [PATCH 32/38] udpate assert reporting --- .../Properties/launchSettings.json | 2 +- .../Reporters/Engine/EngineErrorReporterBase.cs | 5 +++-- .../Reporters/Engine/GameAssertErrorReporter.cs | 14 +++----------- src/ModVerify/Reporting/VerificationError.cs | 3 ++- .../Rendering/Font/WindowsFontManager.cs | 4 ++-- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index 7d4048e..d2f4f0e 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -6,7 +6,7 @@ }, "Interactive Verify": { "commandName": "Project", - "commandLineArgs": "verify -o verifyResults --offline --minFailSeverity Information --verbose" + "commandLineArgs": "verify -o verifyResults --offline --minFailSeverity Information" }, "Interactive Baseline": { "commandName": "Project", diff --git a/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs b/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs index 5ecbb6f..455212d 100644 --- a/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs +++ b/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs @@ -38,12 +38,13 @@ public ErrorData(string identifier, string message, IEnumerable context, ThrowHelper.ThrowIfNullOrEmpty(message); Identifier = identifier; Message = message; - Context = context; + Context = context ?? throw new ArgumentNullException(nameof(context)); Asset = asset; Severity = severity; } - public ErrorData(string identifier, string message, string asset, VerificationSeverity severity) : this(identifier, message, [], asset, severity) + public ErrorData(string identifier, string message, string asset, VerificationSeverity severity) + : this(identifier, message, [], asset, severity) { } } diff --git a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs index cb6b258..6c5fb17 100644 --- a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs +++ b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs @@ -14,18 +14,10 @@ internal sealed class GameAssertErrorReporter(IGameRepository gameRepository, IS protected override ErrorData CreateError(EngineAssert assert) { - // TODO: Why is context not used atm? var context = new List(); - - if (assert.Value is not null) - context.Add($"value='{assert.Value}'"); - if (assert.Context is not null) - context.Add($"context='{assert.Context}'"); - - // The location is the only identifiable thing of an assert. 'Value' might be null, thus we cannot use it. - var asset = GetLocation(assert); - - return new ErrorData(GetIdFromError(assert.Kind), assert.Message, asset, VerificationSeverity.Warning); + context.AddRange(assert.Context); + context.Add($"location='{GetLocation(assert)}'"); + return new ErrorData(GetIdFromError(assert.Kind), assert.Message, context, assert.Value, VerificationSeverity.Warning); } private static string GetLocation(EngineAssert assert) diff --git a/src/ModVerify/Reporting/VerificationError.cs b/src/ModVerify/Reporting/VerificationError.cs index 4174f32..538a8fd 100644 --- a/src/ModVerify/Reporting/VerificationError.cs +++ b/src/ModVerify/Reporting/VerificationError.cs @@ -117,6 +117,7 @@ public override int GetHashCode() public override string ToString() { - return $"[{Severity}] [{string.Join(" --> ", VerifierChain)}] {Id}: Message={Message}; Asset='{Asset}'; Context=[{string.Join(",", ContextEntries)}];"; + return $"[{Severity}] [{string.Join(" --> ", VerifierChain)}] " + + $"{Id}: Message={Message}; Asset='{Asset}'; Context=[{string.Join(",", ContextEntries)}];"; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs index f11374e..fe8f546 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs @@ -28,8 +28,8 @@ public IEnumerable GetFontFamilies() return fonts; } - static IEnumerable<(Gdi32.ENUMLOGFONTEXDV lpelfe, Gdi32.ENUMTEXTMETRIC lpntme, Gdi32.FontType FontType)> GetFonts(Gdi32.SafeHDC hdc) + static IEnumerable<(Gdi32.ENUMLOGFONTEXDV lpelfe, Gdi32.ENUMTEXTMETRIC _, Gdi32.FontType __)> GetFonts(Gdi32.SafeHDC hdc) { - return Gdi32.EnumFontFamiliesEx(hdc, CharacterSet.DEFAULT_CHARSET); + return Gdi32.EnumFontFamiliesEx(hdc, lfCharSet: CharacterSet.DEFAULT_CHARSET); } } \ No newline at end of file From 6e632601f1d4072a1842da717465e80e3d290094 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Thu, 22 Jan 2026 15:40:14 +0100 Subject: [PATCH 33/38] make tests compile again --- test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs index 1747677..ecf3253 100644 --- a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs +++ b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs @@ -1,13 +1,10 @@ using AET.ModVerify.App; using AET.ModVerify.App.Reporting; -using AET.ModVerify.App.Settings; -using AET.ModVerify.Settings; using AnakinRaW.ApplicationBase.Environment; using Microsoft.Extensions.DependencyInjection; using PG.StarWarsGame.Engine; using System; using System.IO.Abstractions; -using ModVerify.CliApp.Test.TestData; using Testably.Abstractions; using Xunit; @@ -17,17 +14,6 @@ public class BaselineSelectorTest { private static readonly IFileSystem FileSystem = new RealFileSystem(); private readonly IServiceProvider _serviceProvider; - private static readonly ModVerifyAppSettings TestSettings = new() - { - ReportSettings = new(), - VerificationTargetSettings = new (), - VerifyPipelineSettings = new() - { - GameVerifySettings = new GameVerifySettings(), - VerifiersProvider = new NoVerifierProvider() - } - }; - public BaselineSelectorTest() { From 685093b1e4649b3885435992f4e5fd86ca81cb4f Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 24 Jan 2026 11:40:59 +0100 Subject: [PATCH 34/38] pretty print verify info --- modules/ModdingToolBase | 2 +- .../App/CreateBaselineAction.cs | 14 +++-- .../App/ModVerifyApplicationAction.cs | 2 +- src/ModVerify.CliApp/App/VerifyAction.cs | 39 ++++--------- .../VerificationTargetSelectorBase.cs | 32 +++++++++-- .../Utilities/ModVerifyConsoleUtilities.cs | 56 +++++++++++++++++++ 6 files changed, 104 insertions(+), 41 deletions(-) diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase index ba1c70b..18cfb7a 160000 --- a/modules/ModdingToolBase +++ b/modules/ModdingToolBase @@ -1 +1 @@ -Subproject commit ba1c70b2e3b9442eaa4b83af00ae0d9b7569b1d4 +Subproject commit 18cfb7a31a091ebc57708ca394b0d2e406343b31 diff --git a/src/ModVerify.CliApp/App/CreateBaselineAction.cs b/src/ModVerify.CliApp/App/CreateBaselineAction.cs index c1cdb89..0e951d7 100644 --- a/src/ModVerify.CliApp/App/CreateBaselineAction.cs +++ b/src/ModVerify.CliApp/App/CreateBaselineAction.cs @@ -1,12 +1,13 @@ -using System; -using System.Collections.Generic; -using System.IO.Abstractions; -using System.Threading.Tasks; -using AET.ModVerify.App.Reporting; +using AET.ModVerify.App.Reporting; using AET.ModVerify.App.Settings; +using AET.ModVerify.App.Utilities; using AET.ModVerify.Reporting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Threading.Tasks; namespace AET.ModVerify.App; @@ -19,7 +20,8 @@ protected override void PrintAction(VerificationTarget target) { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine($"Creating baseline for {target.Name}..."); - Console.ResetColor(); + Console.WriteLine(); + ModVerifyConsoleUtilities.WriteSelectedTarget(target); Console.WriteLine(); } diff --git a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs index 607b6ea..e10bfde 100644 --- a/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs +++ b/src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs @@ -68,7 +68,7 @@ public async Task ExecuteAsync() Logger?.LogError(ex, "Game not found: {Message}", ex.Message); return ex.HResult; } - + PrintAction(verificationTarget); var allErrors = await VerifyTargetAsync(verificationTarget) diff --git a/src/ModVerify.CliApp/App/VerifyAction.cs b/src/ModVerify.CliApp/App/VerifyAction.cs index 1d99768..2d476d0 100644 --- a/src/ModVerify.CliApp/App/VerifyAction.cs +++ b/src/ModVerify.CliApp/App/VerifyAction.cs @@ -1,11 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AET.ModVerify.App.Reporting; +using AET.ModVerify.App.Reporting; using AET.ModVerify.App.Settings; using AET.ModVerify.Reporting; using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AET.ModVerify.App.Utilities; namespace AET.ModVerify.App; @@ -16,7 +17,8 @@ protected override void PrintAction(VerificationTarget target) { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine($"Verifying {target.Name} for issues..."); - Console.ResetColor(); + Console.WriteLine(); + ModVerifyConsoleUtilities.WriteSelectedTarget(target); Console.WriteLine(); } @@ -47,27 +49,10 @@ protected override VerificationBaseline GetBaseline(VerificationTarget verificat var baseline = baselineSelector.SelectBaseline(verificationTarget, out var baselinePath); if (!baseline.IsEmpty) { - if (string.IsNullOrEmpty(baselinePath)) - { - if (baseline.Target?.Engine is null) - Logger?.LogDebug("Target for default baseline should not be null!"); - Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Using default baseline for engine '{Engine}'", - baseline.Target?.Engine); - } - else - { - if (string.IsNullOrEmpty(baseline.Target?.Name)) - { - Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Using baseline '{Baseline}'", baselinePath); - } - else - { - Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Using baseline for target {Target} from location '{Baseline}'", baseline.Target.Name, baselinePath); - } - } + Console.WriteLine(); + ModVerifyConsoleUtilities.WriteBaselineInfo(baseline, baselinePath); + Logger?.LogDebug("Using baseline {Baseline} from location '{Path}'", baseline.ToString(), baselinePath); + Console.WriteLine(); } return baseline; } diff --git a/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs index 7d353fa..68f0f39 100644 --- a/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs +++ b/src/ModVerify.CliApp/TargetSelectors/VerificationTargetSelectorBase.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; +using System.Runtime.InteropServices; using AET.ModVerify.App.GameFinder; using AET.ModVerify.App.Settings; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure; +using PG.StarWarsGame.Infrastructure.Clients; +using PG.StarWarsGame.Infrastructure.Clients.Utilities; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; using PG.StarWarsGame.Infrastructure.Services.Dependencies; @@ -104,13 +107,30 @@ protected static string GetTargetName(IPhysicalPlayableObject? targetObject, Gam return mod ?? gameLocations.GamePath; } - protected static string? GetTargetVersion(IPhysicalPlayableObject? targetObject) + protected string? GetTargetVersion(IPhysicalPlayableObject? targetObject) { - if (targetObject is null) - return null; - - // TODO: Implement version retrieval from targetObject if possible - return null; + return targetObject switch + { + IMod mod => mod.Version?.ToString(), + IGame game => GetVersionFromGame(game), + _ => null + }; } + private string? GetVersionFromGame(IGame game) + { + var exeFile = GameExecutableFileUtilities.GetExecutableForGame(game, GameBuildType.Release); + if (exeFile is null) + { + Logger?.LogWarning(ModVerifyConstants.ConsoleEventId, + "Unable to get game version of target path '{Path}'. Is this a game directory?", + game.Directory.FullName); + return null; + } + + var versionInfo = FileSystem.FileVersionInfo.GetVersionInfo(exeFile.FullName); + var version = + $"{versionInfo.FileMajorPart}.{versionInfo.FileMinorPart}.{versionInfo.FileBuildPart}.{versionInfo.FilePrivatePart}"; + return version; + } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs index 6e6664a..e354803 100644 --- a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs +++ b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs @@ -1,6 +1,8 @@ using AnakinRaW.ApplicationBase; using Figgle; using System; +using System.Collections.Generic; +using AET.ModVerify.Reporting; namespace AET.ModVerify.App.Utilities; @@ -21,10 +23,64 @@ public static void WriteHeader(string? version = null) Console.ResetColor(); Console.WriteLine(); } + ConsoleUtilities.WriteHorizontalLine('*', lineLength); ConsoleUtilities.WriteLineRight(author, lineLength); Console.WriteLine(); Console.WriteLine(); } + + public static void WriteSelectedTarget(VerificationTarget target) + { + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Selected Target:"); + Console.ForegroundColor = ConsoleColor.DarkGray; + ConsoleUtilities.PrintAsTable([ + ("Name", target.Name), + ("Type", target.IsGame ? "Game" : "Mod"), + ("Engine", target.Engine), + ("Version", target.Version ?? "n/a"), + ("Location", target.Location.TargetPath), + ], 120); + Console.ResetColor(); + } + + public static void WriteBaselineInfo(VerificationBaseline baseline, string? filePath) + { + if (baseline.IsEmpty) + return; + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Using Baseline:"); + Console.ForegroundColor = ConsoleColor.DarkGray; + + IList<(string, object)> baselineData = + [ + ("Version", baseline.Version?.ToString(2) ?? "n/a"), + ("Is Default", filePath is null), + ("Minimum Severity", baseline.MinimumSeverity.ToString()), + ("Entries", baseline.Count.ToString()), + ]; + if (!string.IsNullOrEmpty(filePath)) + baselineData.Add(("File Path", filePath)); + + ConsoleUtilities.PrintAsTable(baselineData, 120); + + if (baseline.Target is not null) + { + Console.ForegroundColor = ConsoleColor.DarkMagenta; + Console.WriteLine("Baseline Target:"); + Console.ForegroundColor = ConsoleColor.DarkGray; + ConsoleUtilities.PrintAsTable( + [ + ("Name", baseline.Target.Name), + ("Type", baseline.Target.IsGame ? "Game" : "Mod"), + ("Engine", baseline.Target.Engine), + ("Version", baseline.Target.Version ?? "n/a"), + ("Location", baseline.Target.Location?.TargetPath ?? "n/a"), + ], 120); + } + Console.ResetColor(); + } } \ No newline at end of file From 28e34a31b66d43e3d6240e04832a13a68056de60 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 24 Jan 2026 12:33:05 +0100 Subject: [PATCH 35/38] do not print location if not exists --- .../Utilities/ModVerifyConsoleUtilities.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs index e354803..b9ebff0 100644 --- a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs +++ b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs @@ -60,7 +60,7 @@ public static void WriteBaselineInfo(VerificationBaseline baseline, string? file ("Version", baseline.Version?.ToString(2) ?? "n/a"), ("Is Default", filePath is null), ("Minimum Severity", baseline.MinimumSeverity.ToString()), - ("Entries", baseline.Count.ToString()), + ("Entries", baseline.Count.ToString()) ]; if (!string.IsNullOrEmpty(filePath)) baselineData.Add(("File Path", filePath)); @@ -72,14 +72,18 @@ public static void WriteBaselineInfo(VerificationBaseline baseline, string? file Console.ForegroundColor = ConsoleColor.DarkMagenta; Console.WriteLine("Baseline Target:"); Console.ForegroundColor = ConsoleColor.DarkGray; - ConsoleUtilities.PrintAsTable( - [ - ("Name", baseline.Target.Name), + + IList<(string, object)> targetData = [ + ("Name", baseline.Target.Name), ("Type", baseline.Target.IsGame ? "Game" : "Mod"), ("Engine", baseline.Target.Engine), ("Version", baseline.Target.Version ?? "n/a"), - ("Location", baseline.Target.Location?.TargetPath ?? "n/a"), - ], 120); + ]; + + if (baseline.Target.Location is not null) + targetData.Add(("Location", baseline.Target.Location.TargetPath)); + + ConsoleUtilities.PrintAsTable(targetData, 120); } Console.ResetColor(); } From 972131cabac49b458a6047b6a744f964c59d34ae Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 24 Jan 2026 16:50:31 +0100 Subject: [PATCH 36/38] simply automatic selector --- .../GameFinder/GameFinderService.cs | 130 ++++++++++++------ .../Properties/launchSettings.json | 9 +- .../Resources/Baselines/baseline-foc.json | 9 +- .../TargetSelectors/AutomaticSelector.cs | 56 ++------ .../Utilities/ExtensionMethods.cs | 17 ++- .../TargetSelectors/AutomaticSelectorTest.cs | 73 +++------- .../TargetSelectors/GameFinderServiceTest.cs | 72 ++++++++++ 7 files changed, 210 insertions(+), 156 deletions(-) create mode 100644 test/ModVerify.CliApp.Test/TargetSelectors/GameFinderServiceTest.cs diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs index aca0533..92e7d62 100644 --- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs +++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs @@ -1,20 +1,24 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO.Abstractions; +using System.Runtime.InteropServices; +using AET.ModVerify.App.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure.Clients.Steam; using PG.StarWarsGame.Infrastructure.Games; +using PG.StarWarsGame.Infrastructure.Games.Registry; using PG.StarWarsGame.Infrastructure.Mods; using PG.StarWarsGame.Infrastructure.Services; using PG.StarWarsGame.Infrastructure.Services.Detection; namespace AET.ModVerify.App.GameFinder; -internal class GameFinderSettings +internal sealed class GameFinderSettings { internal static readonly GameFinderSettings Default = new(); @@ -50,6 +54,15 @@ public GameFinderResult FindGames(GameFinderSettings settings) new SteamPetroglyphStarWarsGameDetector(_serviceProvider), }; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var registryFactory = _serviceProvider.GetRequiredService(); + detectors.Add(new RegistryGameDetector( + registryFactory.CreateRegistry(GameType.Eaw), + registryFactory.CreateRegistry(GameType.Foc), + false, _serviceProvider)); + } + return FindGames(detectors, settings); } @@ -80,7 +93,7 @@ public bool TryFindGame(string gamePath, GameFinderSettings settings, [NotNullWh return false; } } - + public GameFinderResult FindGamesFromPathOrGlobal(string path, GameFinderSettings settings) { // There are four common situations: @@ -90,22 +103,63 @@ public GameFinderResult FindGamesFromPathOrGlobal(string path, GameFinderSetting // 4. path points to a "detached mod" at a completely different location var givenDirectory = _fileSystem.DirectoryInfo.New(path); var possibleGameDir = givenDirectory.Parent?.Parent; + + + // We need to check the local paths first, before falling back to global detectors, + // to ensure that we always find the correct game installation, + // especially if the user did not request a specific engine. - var detectors = new List + var localDetectors = new List { - // Case 1 - new DirectoryGameDetector(givenDirectory, _serviceProvider) + new DirectoryGameDetector(givenDirectory, _serviceProvider), }; + if (possibleGameDir is not null) + localDetectors.Add(new DirectoryGameDetector(possibleGameDir, _serviceProvider)); + + // Case 1 & 2 + if (TryFindGames(localDetectors, settings, out var finderResult)) + return finderResult; - // Case 2 - if (possibleGameDir is not null) - detectors.Add(new DirectoryGameDetector(possibleGameDir, _serviceProvider)); + + // There is the rare scenario where the user specified a specific engine, + // but path points to a game with the opposite engine. + // This does not make sense and the global detectors can not handle this. + // Thus, we need to check against this. + if (settings.Engine is not null) + { + if (TryFindGame(path, new GameFinderSettings + { + Engine = settings.Engine.Value.Opposite(), + InitMods = false, + SearchFallbackGame = false + }, out _)) + { + var e = new GameNotFoundException( + $"The specified game engine '{settings.Engine.Value}' does not match engine of the specified path '{path}'."); + _logger?.LogTrace(e, e.Message); + throw e; + } + } // Cases 3 & 4 - detectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); - return FindGames(detectors, settings); + return FindGames(CreateGlobalDetectors(), settings); } - + + private bool TryFindGames(IList detectors, GameFinderSettings settings, + [NotNullWhen(true)] out GameFinderResult? finderResult) + { + try + { + finderResult = FindGames(detectors, settings); + return true; + } + catch (GameNotFoundException) + { + finderResult = null; + return false; + } + } + private GameFinderResult FindGames(IList detectors, GameFinderSettings settings) { GameDetectionResult? detectionResult = null; @@ -135,33 +189,26 @@ private GameFinderResult FindGames(IList detectors, GameFinderSet throw new GameNotFoundException("Unable to find game installation: Wrong install path?"); } - if (detectionResult.GameLocation is null) - throw new GameNotFoundException("Unable to find game installation: Wrong install path?"); + Debug.Assert(detectionResult.GameLocation is not null); _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found game installation: {ResultGameIdentity} at {GameLocationFullName}", detectionResult.GameIdentity, detectionResult.GameLocation.FullName); + "Found game installation: {ResultGameIdentity} at {GameLocationFullName}", + detectionResult.GameIdentity, detectionResult.GameLocation!.FullName); var game = _gameFactory.CreateGame(detectionResult, CultureInfo.InvariantCulture); if (settings.InitMods) SetupMods(game); - IGame? fallbackGame = null; if (SearchForFallbackGame(settings, detectionResult)) { - var fallbackDetectors = new List(); - - if (game.Platform == GamePlatform.SteamGold) - fallbackDetectors.Add(new SteamPetroglyphStarWarsGameDetector(_serviceProvider)); - else - throw new NotImplementedException("Searching fallback game for non-Steam games is currently is not yet implemented."); - - if (!TryDetectGame(GameType.Eaw, fallbackDetectors, out var fallbackResult) || fallbackResult.GameLocation is null) + if (!TryDetectGame(GameType.Eaw, CreateGlobalDetectors(), out var fallbackResult)) throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", fallbackResult.GameIdentity, fallbackResult.GameLocation.FullName); + "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", + fallbackResult.GameIdentity, fallbackResult.GameLocation!.FullName); fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); @@ -183,21 +230,9 @@ private static bool SearchForFallbackGame(GameFinderSettings settings, GameDetec private bool TryDetectGame(GameType gameType, IList detectors, out GameDetectionResult result) { - var gd = new CompositeGameDetector(detectors, _serviceProvider); - - try - { - result = gd.Detect(gameType); - if (result.GameLocation is null) - return false; - return true; - } - catch (Exception e) - { - result = GameDetectionResult.NotInstalled(gameType); - _logger?.LogTrace("Unable to find game installation: {Message}", e.Message); - return false; - } + var gd = new CompositeGameDetector(detectors, _serviceProvider, true); + result = gd.Detect(gameType); + return result.GameLocation is not null; } private void SetupMods(IGame game) @@ -222,4 +257,21 @@ private void SetupMods(IGame game) mod.ResolveDependencies(); } } + + private IList CreateGlobalDetectors() + { + var detectors = new List + { + new SteamPetroglyphStarWarsGameDetector(_serviceProvider), + }; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var registryFactory = _serviceProvider.GetRequiredService(); + detectors.Add(new RegistryGameDetector( + registryFactory.CreateRegistry(GameType.Eaw), + registryFactory.CreateRegistry(GameType.Foc), + false, _serviceProvider)); + } + return detectors; + } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index d2f4f0e..7e99153 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -1,18 +1,17 @@ { "profiles": { - "Run": { + "Verify": { "commandName": "Project", "commandLineArgs": "" }, - "Interactive Verify": { + "Verify (Interactive)": { "commandName": "Project", "commandLineArgs": "verify -o verifyResults --offline --minFailSeverity Information" }, - "Interactive Baseline": { + "Create Baseline Interactive": { "commandName": "Project", - "commandLineArgs": "createBaseline -o focBaseline.json --offline --skipLocation" + "commandLineArgs": "createBaseline -o baseline.json --offline --skipLocation" }, - "FromModPath": { "commandName": "Project", "commandLineArgs": "verify -o verifyResults --path \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\"" diff --git a/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json b/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json index 2cb5cac..ce70f8a 100644 --- a/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json +++ b/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json @@ -3,13 +3,8 @@ "target": { "name": "Forces of Corruption (SteamGold)", "engine": "Foc", - "location": { - "modPaths": [], - "gamePath": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption", - "fallbackPaths": [ - "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\GameData" - ] - } + "isGame": true, + "version": "1.121.13.7360" }, "minSeverity": "Information", "errors": [ diff --git a/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs index f6b2b0c..5250ec4 100644 --- a/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/AutomaticSelector.cs @@ -16,6 +16,7 @@ using System.Globalization; using System.IO.Abstractions; using System.Linq; +using AnakinRaW.CommonUtilities.FileSystem; namespace AET.ModVerify.App.TargetSelectors; @@ -54,30 +55,7 @@ internal override SelectionResult SelectTarget(VerificationTargetSettings settin "Unable to find games based of the specified target path '{Path}'. Consider specifying all paths manually.", targetPath); throw; } - - // In a Steam scenario, there is a chance that the user specified a FoC targetPath, - // but requested EaW engine. This does not make sense, and we need to check against this. - if (finderResult.Game.Platform == GamePlatform.SteamGold && engine is GameEngineType.Eaw) - { - var targetIsFoc = GameFinderService.TryFindGame(targetPath, - new GameFinderSettings { Engine = GameEngineType.Foc, InitMods = false, SearchFallbackGame = false }, - out _); - if (targetIsFoc) - ThrowEngineNotSupported(engine.Value, targetPath); - } - - if (engine.HasValue) - { - if (finderResult.Game.Type.ToEngineType() != engine.Value) - { - if (finderResult.FallbackGame?.Type.ToEngineType() != engine) - { - throw new InvalidOperationException(); - } - } - } - GameLocations locations; var targetObject = GetAttachedModOrGame(finderResult, targetPath, engine); @@ -113,24 +91,10 @@ internal override SelectionResult SelectTarget(VerificationTargetSettings settin // If the target is the game directory itself. if (targetFullPath.Equals(finderResult.Game.Directory.FullName, StringComparison.OrdinalIgnoreCase)) target = finderResult.Game; - else if (finderResult.FallbackGame is not null && targetFullPath.Equals(finderResult.FallbackGame.Directory.FullName, StringComparison.OrdinalIgnoreCase)) - { - // The game detection identified both Foc and Eaw because either Steam is installed or requestedEngineType was not specified. - // The requested path points to a EaW installation. - Debug.Assert(finderResult.FallbackGame.Type is GameType.Eaw); - target = finderResult.FallbackGame; - } target ??= GetMatchingModFromGame(finderResult.Game, targetFullPath, requestedEngineType) ?? GetMatchingModFromGame(finderResult.FallbackGame, targetFullPath, requestedEngineType); - - - if (target is not null) - { - if (!IsEngineTypeSupported(requestedEngineType, target.Game.Type)) - ThrowEngineNotSupported(requestedEngineType.Value, targetPath); - } - + return target; } @@ -141,9 +105,9 @@ private GameLocations GetDetachedModLocations( IReadOnlyList additionalFallbackPaths, out IPhysicalMod mod) { - var game = GetTargetGame(gameResult, requestedGameEngine); - Debug.Assert(game is not null); - + // Because requestedGameEngine must be set, GameFinderService already ensures + // gameResult.Game is the correct type. + var game = gameResult.Game; var modFinder = ServiceProvider.GetRequiredService(); var modRef = modFinder.FindMods(game, _fileSystem.DirectoryInfo.New(modPath)).FirstOrDefault(); @@ -160,7 +124,7 @@ private GameLocations GetDetachedModLocations( return GetLocations(mod, gameResult.FallbackGame, additionalFallbackPaths); } - private static IPhysicalMod? GetMatchingModFromGame(IGame? game, string modPath, GameEngineType? requestedEngineType) + private IPhysicalMod? GetMatchingModFromGame(IGame? game, string modPath, GameEngineType? requestedEngineType) { if (game is null || !IsEngineTypeSupported(requestedEngineType, game.Type)) return null; @@ -169,9 +133,9 @@ private GameLocations GetDetachedModLocations( { if (mod is not IPhysicalMod physicalMod) continue; - - if (physicalMod.Directory.FullName.Equals(modPath, StringComparison.OrdinalIgnoreCase)) - return physicalMod; + + if (_fileSystem.Path.AreEqual(modPath, physicalMod.Directory.FullName)) + return physicalMod; } return null; @@ -179,8 +143,6 @@ private GameLocations GetDetachedModLocations( private static IGame? GetTargetGame(GameFinderResult finderResult, GameEngineType? requestedEngine) { - if (!requestedEngine.HasValue) - return null; if (finderResult.Game.Type.ToEngineType() == requestedEngine) return finderResult.Game; if (finderResult.FallbackGame is not null && finderResult.FallbackGame.Type.ToEngineType() == requestedEngine) diff --git a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs index fb3c68a..fc07469 100644 --- a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs +++ b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs @@ -8,16 +8,23 @@ namespace AET.ModVerify.App.Utilities; internal static class ExtensionMethods { - public static GameEngineType ToEngineType(this GameType type) + extension(GameEngineType type) { - return (GameEngineType)(int)type; + public GameType FromEngineType() + { + return (GameType)(int)type; + } + + public GameEngineType Opposite() + { + return (GameEngineType)((int)type ^ 1); + } } - public static GameType FromEngineType(this GameEngineType type) + public static GameEngineType ToEngineType(this GameType type) { - return (GameType)(int)type; + return (GameEngineType)(int)type; } - extension(ApplicationEnvironment modVerifyEnvironment) { public bool IsUpdatable() diff --git a/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs b/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs index 10156ac..f01d5cd 100644 --- a/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs +++ b/test/ModVerify.CliApp.Test/TargetSelectors/AutomaticSelectorTest.cs @@ -80,30 +80,16 @@ public void Test_SelectTarget_FromGamePath(IGameIdentity identity) [Theory] [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] - public void Test_SelectTarget_FromGamePath_OppositeEngine(IGameIdentity identity) + public void Test_SelectTarget_FromGamePath_OppositeEngine_ThrowsGameNotFoundException(IGameIdentity identity) { - if (identity.Platform is GamePlatform.SteamGold) - { - TestSelectTarget(identity, - i => i, - gi => new VerificationTargetSettings - { - TargetPath = gi.PlayableObject.Directory.FullName, - Engine = gi.PlayableObject.Game.Type.Opposite().ToEngineType(), - }, - typeof(ArgumentException)); - } - else - { - TestSelectTarget(identity, - i => i, - gi => new VerificationTargetSettings - { - TargetPath = gi.PlayableObject.Directory.FullName, - Engine = gi.PlayableObject.Game.Type.Opposite().ToEngineType(), - }, - typeof(GameNotFoundException)); - } + TestSelectTarget(identity, + i => i, + gi => new VerificationTargetSettings + { + TargetPath = gi.PlayableObject.Directory.FullName, + Engine = gi.PlayableObject.Game.Type.Opposite().ToEngineType(), + }, + typeof(GameNotFoundException)); } [Theory] @@ -132,10 +118,6 @@ public void Test_SelectTarget_ModInModsDir(IGameIdentity identity) [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] public void Test_SelectTarget_ModInModsDir_WrongGameEngine_Throws(IGameIdentity identity) { - var expectedExceptionType = identity.Platform == GamePlatform.SteamGold - ? typeof(ArgumentException) - : typeof(GameNotFoundException); - TestSelectTarget( identity, gameInstallation => gameInstallation.InstallMod("MyMod", false), @@ -143,7 +125,7 @@ public void Test_SelectTarget_ModInModsDir_WrongGameEngine_Throws(IGameIdentity { TargetPath = ti.PlayableObject.Directory.FullName, Engine = ti.GameInstallation.Game.Type.Opposite().ToEngineType() - }, expectedExceptionType); + }, typeof(ArgumentException)); } @@ -277,10 +259,6 @@ public void Test_SelectTarget_Workshops_MultipleKnownModEngineTypes(GameType gam [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] public void Test_SelectTarget_DetachedMod_NoEngineSpecified_Throws(IGameIdentity identity) { - var exceptionType = identity.Platform is not GamePlatform.SteamGold - ? typeof(GameNotFoundException) - : typeof(ArgumentException); - TestSelectTarget( identity, gameInstallation => @@ -294,18 +272,13 @@ public void Test_SelectTarget_DetachedMod_NoEngineSpecified_Throws(IGameIdentity TargetPath = ti.PlayableObject.Directory.FullName, Engine = null // No Engine means we cannot proceed }, - expectedExceptionType: exceptionType); + expectedExceptionType: typeof(ArgumentException)); } [Theory] [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] public void Test_SelectTarget_DetachedMod(IGameIdentity identity) { - // Currently, only Steam is supported for detached mods. - var exceptionType = identity.Platform is not GamePlatform.SteamGold - ? typeof(GameNotFoundException) - : null; - TestSelectTarget( identity, gameInstallation => @@ -318,8 +291,7 @@ public void Test_SelectTarget_DetachedMod(IGameIdentity identity) { TargetPath = ti.PlayableObject.Directory.FullName, Engine = identity.Type.ToEngineType() - }, - expectedExceptionType: exceptionType); + }); } [Theory] @@ -390,20 +362,15 @@ private void TestSelectTarget( return; } - if (identity.Type == GameType.Foc && identity.Platform != GamePlatform.SteamGold) - Assert.Throws(() => _selector.SelectTarget(settings)); + var result = _selector.SelectTarget(settings); + Assert.Equal(overrideAssertData?.GameType ?? identity.Type, result.Engine.FromEngineType()); + Assert.Equal(targetInstallation.PlayableObject.GetType(), result.Target!.GetType()); + Assert.Equal(targetInstallation.PlayableObject.Directory.FullName, result.Locations.TargetPath); + + if (result.Engine == GameEngineType.Foc) + Assert.NotEmpty(result.Locations.FallbackPaths); else - { - var result = _selector.SelectTarget(settings); - Assert.Equal(overrideAssertData?.GameType ?? identity.Type, result.Engine.FromEngineType()); - Assert.Equal(targetInstallation.PlayableObject.GetType(), result.Target!.GetType()); - Assert.Equal(targetInstallation.PlayableObject.Directory.FullName, result.Locations.TargetPath); - - if (result.Engine == GameEngineType.Foc) - Assert.NotEmpty(result.Locations.FallbackPaths); - else - Assert.Empty(result.Locations.FallbackPaths); - } + Assert.Empty(result.Locations.FallbackPaths); } private (ITestingGameInstallation eaw, ITestingGameInstallation foc) InstallGames(GamePlatform platform) diff --git a/test/ModVerify.CliApp.Test/TargetSelectors/GameFinderServiceTest.cs b/test/ModVerify.CliApp.Test/TargetSelectors/GameFinderServiceTest.cs new file mode 100644 index 0000000..334d43f --- /dev/null +++ b/test/ModVerify.CliApp.Test/TargetSelectors/GameFinderServiceTest.cs @@ -0,0 +1,72 @@ +using AET.ModVerify.App.GameFinder; +using AnakinRaW.CommonUtilities.Registry; +using AnakinRaW.CommonUtilities.Testing.Extensions; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Infrastructure.Games; +using PG.StarWarsGame.Infrastructure.Testing; +using PG.StarWarsGame.Infrastructure.Testing.Installations; +using Xunit; + +namespace ModVerify.CliApp.Test.TargetSelectors; + +public class GameFinderServiceTest : CommonTestBase +{ + private readonly IRegistry _registry = new InMemoryRegistry(InMemoryRegistryCreationFlags.WindowsLike); + private readonly GameFinderService _finderService; + public GameFinderServiceTest() + { + _finderService = new GameFinderService(ServiceProvider); + } + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.AddSingleton(_registry); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void FindGame_SearchFallbackGame_FocInstalledButEawNot_ThrowsGameNotFoundException(IGameIdentity identity) + { + if (identity.Type == GameType.Eaw) + return; + + var foc = GameInfrastructureTesting.Game(identity, ServiceProvider); + GameInfrastructureTesting.Registry(ServiceProvider).CreateInstalled(foc.Game); + + Assert.Throws(() => _finderService.FindGame(foc.Game.Directory.FullName, new GameFinderSettings + { + Engine = GameEngineType.Foc, + SearchFallbackGame = true + })); + + Assert.Throws(() => _finderService.FindGame(foc.Game.Directory.FullName, new GameFinderSettings + { + Engine = null, + SearchFallbackGame = true + })); + } + + [Theory] + [MemberData(nameof(GITestUtilities.RealGameIdentities), MemberType = typeof(GITestUtilities))] + public void FindGame_EngineEaw_SearchFallbackGameIsIgnored(IGameIdentity identity) + { + if (identity.Type == GameType.Foc) + return; + + var eaw = GameInfrastructureTesting.Game(identity, ServiceProvider); + GameInfrastructureTesting.Registry(ServiceProvider).CreateInstalled(eaw.Game); + + Assert.DoesNotThrowException(() => _finderService.FindGame(eaw.Game.Directory.FullName, new GameFinderSettings + { + Engine = GameEngineType.Eaw, + SearchFallbackGame = true + })); + + Assert.DoesNotThrowException(() => _finderService.FindGame(eaw.Game.Directory.FullName, new GameFinderSettings + { + Engine = null, + SearchFallbackGame = true + })); + } +} \ No newline at end of file From f81f1de706e7c3db354940e74d346361fecab816 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 24 Jan 2026 16:52:42 +0100 Subject: [PATCH 37/38] extract to file --- .../GameFinder/GameFinderService.cs | 11 ----------- .../GameFinder/GameFinderSettings.cs | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 src/ModVerify.CliApp/GameFinder/GameFinderSettings.cs diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs index 92e7d62..b978727 100644 --- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs +++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs @@ -18,17 +18,6 @@ namespace AET.ModVerify.App.GameFinder; -internal sealed class GameFinderSettings -{ - internal static readonly GameFinderSettings Default = new(); - - public bool InitMods { get; init; } = true; - - public bool SearchFallbackGame { get; init; } = true; - - public GameEngineType? Engine { get; init; } = null; -} - internal class GameFinderService { private readonly IServiceProvider _serviceProvider; diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderSettings.cs b/src/ModVerify.CliApp/GameFinder/GameFinderSettings.cs new file mode 100644 index 0000000..fc57b24 --- /dev/null +++ b/src/ModVerify.CliApp/GameFinder/GameFinderSettings.cs @@ -0,0 +1,14 @@ +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.App.GameFinder; + +internal sealed class GameFinderSettings +{ + internal static readonly GameFinderSettings Default = new(); + + public bool InitMods { get; init; } = true; + + public bool SearchFallbackGame { get; init; } = true; + + public GameEngineType? Engine { get; init; } = null; +} \ No newline at end of file From 3e1f33ea53ac2b39ba3b953696c629c4278693c8 Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 24 Jan 2026 17:08:33 +0100 Subject: [PATCH 38/38] nicer console output --- src/ModVerify.CliApp/App/CreateBaselineAction.cs | 1 + src/ModVerify.CliApp/App/VerifyAction.cs | 1 + src/ModVerify.CliApp/GameFinder/GameFinderService.cs | 6 ++---- src/ModVerify.CliApp/Properties/launchSettings.json | 8 ++++---- src/ModVerify.CliApp/Reporting/BaselineSelector.cs | 2 ++ src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs | 5 ++++- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/ModVerify.CliApp/App/CreateBaselineAction.cs b/src/ModVerify.CliApp/App/CreateBaselineAction.cs index 0e951d7..b0eb880 100644 --- a/src/ModVerify.CliApp/App/CreateBaselineAction.cs +++ b/src/ModVerify.CliApp/App/CreateBaselineAction.cs @@ -18,6 +18,7 @@ internal sealed class CreateBaselineAction(AppBaselineSettings settings, IServic protected override void PrintAction(VerificationTarget target) { + Console.WriteLine(); Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine($"Creating baseline for {target.Name}..."); Console.WriteLine(); diff --git a/src/ModVerify.CliApp/App/VerifyAction.cs b/src/ModVerify.CliApp/App/VerifyAction.cs index 2d476d0..0cfea0e 100644 --- a/src/ModVerify.CliApp/App/VerifyAction.cs +++ b/src/ModVerify.CliApp/App/VerifyAction.cs @@ -15,6 +15,7 @@ internal sealed class VerifyAction(AppVerifySettings settings, IServiceProvider { protected override void PrintAction(VerificationTarget target) { + Console.WriteLine(); Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine($"Verifying {target.Name} for issues..."); Console.WriteLine(); diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs index b978727..0ac8346 100644 --- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs +++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs @@ -180,8 +180,7 @@ private GameFinderResult FindGames(IList detectors, GameFinderSet Debug.Assert(detectionResult.GameLocation is not null); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found game installation: {ResultGameIdentity} at {GameLocationFullName}", + _logger?.LogDebug("Found game installation: {ResultGameIdentity} at {GameLocationFullName}", detectionResult.GameIdentity, detectionResult.GameLocation!.FullName); var game = _gameFactory.CreateGame(detectionResult, CultureInfo.InvariantCulture); @@ -195,8 +194,7 @@ private GameFinderResult FindGames(IList detectors, GameFinderSet if (!TryDetectGame(GameType.Eaw, CreateGlobalDetectors(), out var fallbackResult)) throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); - _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, - "Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", + _logger?.LogDebug("Found fallback game installation: {FallbackResultGameIdentity} at {GameLocationFullName}", fallbackResult.GameIdentity, fallbackResult.GameLocation!.FullName); fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index 7e99153..299ce46 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -8,13 +8,13 @@ "commandName": "Project", "commandLineArgs": "verify -o verifyResults --offline --minFailSeverity Information" }, - "Create Baseline Interactive": { + "Verify (Automatic Target Selection)": { "commandName": "Project", - "commandLineArgs": "createBaseline -o baseline.json --offline --skipLocation" + "commandLineArgs": "verify -o verifyResults --path \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\"" }, - "FromModPath": { + "Create Baseline Interactive": { "commandName": "Project", - "commandLineArgs": "verify -o verifyResults --path \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\"" + "commandLineArgs": "createBaseline -o baseline.json --offline --skipLocation" } } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs index c34b467..791eaae 100644 --- a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs +++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs @@ -80,7 +80,9 @@ private VerificationBaseline FindBaselineInteractive(VerificationTarget verifica out baseline, out baselinePath)) { + Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("No baseline found locally."); + Console.ResetColor(); baselinePath = null; TryGetDefaultBaseline(verificationTarget.Engine, out baseline); return baseline ?? VerificationBaseline.Empty; diff --git a/src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs b/src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs index 8c54cef..17b9791 100644 --- a/src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs +++ b/src/ModVerify.CliApp/TargetSelectors/ConsoleSelector.cs @@ -19,7 +19,7 @@ internal override SelectionResult SelectTarget(VerificationTargetSettings settin var targetObject = SelectPlayableObject(gameResult); var engine = targetObject.Game.Type.ToEngineType(); var locations = GetLocations(targetObject, gameResult.FallbackGame, settings.AdditionalFallbackPaths); - return new(locations, engine, targetObject); + return new SelectionResult(locations, engine, targetObject); } private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult) @@ -30,6 +30,9 @@ private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult fin list.Add(finderResult.Game); Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Listing Games and Mods:"); + Console.ResetColor(); ConsoleUtilities.WriteHorizontalLine(); Console.WriteLine($"0: {game.Name}");