diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7369175..7a9b844 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,8 +13,8 @@ on:
env:
TOOL_PROJ_PATH: ./src/ModVerify.CliApp/ModVerify.CliApp.csproj
- CREATOR_PROJ_PATH: ./Modules/ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj
- UPLOADER_PROJ_PATH: ./Modules/ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj
+ CREATOR_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj
+ UPLOADER_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj
TOOL_EXE: ModVerify.exe
UPDATER_EXE: AnakinRaW.ExternalUpdater.exe
MANIFEST_CREATOR: AnakinRaW.ApplicationManifestCreator.dll
@@ -35,18 +35,20 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout sources
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- name: Setup .NET
- uses: actions/setup-dotnet@v4
+ uses: actions/setup-dotnet@v5
- name: Create NetFramework Release
- run: dotnet publish ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net48 --output ./releases/net48 /p:DebugType=None /p:DebugSymbols=false
+ # use build for .NET Framework to enusre external updatere .EXE is included
+ run: dotnet build ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net481 --output ./releases/net481 /p:DebugType=None /p:DebugSymbols=false
- name: Create Net Core Release
- run: dotnet publish ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net9.0 --output ./releases/net9.0 /p:DebugType=None /p:DebugSymbols=false
+ # use publish for .NET Core
+ run: dotnet publish ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net10.0 --output ./releases/net10.0 /p:DebugType=None /p:DebugSymbols=false
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: Binary Releases
path: ./releases
@@ -62,17 +64,45 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- - uses: actions/download-artifact@v5
+ submodules: recursive
+ - uses: actions/download-artifact@v6
with:
name: Binary Releases
path: ./releases
+
+ # Deploy .NET Framework self-update release
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: 10.0.x
+ - name: Build Creator
+ run: dotnet build ${{env.CREATOR_PROJ_PATH}} --configuration Release --output ./dev
+ - name: Build Uploader
+ run: dotnet build ${{env.UPLOADER_PROJ_PATH}} --configuration Release --output ./dev
+ - name: Create binaries directory
+ run: mkdir -p ./deploy
+ - name: Copy self-update files
+ run: |
+ cp ./releases/net481/${{env.TOOL_EXE}} ./deploy/
+ cp ./releases/net481/${{env.UPDATER_EXE}} ./deploy/
+ - name: Create Manifest
+ run: dotnet ./dev/${{env.MANIFEST_CREATOR}} -a deploy/${{env.TOOL_EXE}} --appDataFiles deploy/${{env.UPDATER_EXE}} --origin ${{env.ORIGIN_BASE}} -o ./deploy -b ${{env.BRANCH_NAME}}
+ - name: Upload Build
+ run: dotnet ./dev/${{env.SFTP_UPLOADER}} ftp --host $host --port $port -u ${{secrets.SFTP_USER}} -p ${{secrets.SFTP_PASSWORD}} --base $base_path -s $source
+ env:
+ host: republicatwar.com
+ port: 1579
+ base_path: ${{env.ORIGIN_BASE_PART}}
+ source: ./deploy
+
+ # Deploy .NET Core and .NET Framework apps to Github
- name: Create NET Core .zip
# Change into the artifacts directory to avoid including the directory itself in the zip archive
- working-directory: ./releases/net9.0
- run: zip -r ../ModVerify-Net9.zip .
+ working-directory: ./releases/net10.0
+ run: zip -r ../ModVerify-Net10.zip .
- uses: dotnet/nbgv@v0.4.2
id: nbgv
- name: Create GitHub release
@@ -86,5 +116,5 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
files: |
- ./releases/net48/ModVerify.exe
- ./releases/ModVerify-Net9.zip
\ No newline at end of file
+ ./releases/net481/ModVerify.exe
+ ./releases/ModVerify-Net10.zip
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ed2af48..6e60fde 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -19,12 +19,12 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-dotnet@v4
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Build & Test in Release Mode
run: dotnet test --configuration Release
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..87124a4
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "modules/ModdingToolBase"]
+ path = modules/ModdingToolBase
+ url = https://github.com/AnakinRaW/ModdingToolBase
diff --git a/Directory.Build.props b/Directory.Build.props
index 49ca26f..d475e7f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -24,7 +24,7 @@
- latest
+ preview
disable
enable
True
@@ -39,7 +39,7 @@
all
- 3.7.115
+ 3.9.50
diff --git a/ModVerify.sln b/ModVerify.sln
deleted file mode 100644
index d09a64e..0000000
--- a/ModVerify.sln
+++ /dev/null
@@ -1,63 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.11.34909.67
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PetroglyphTools", "PetroglyphTools", "{15F8B753-814A-406E-9147-EB048DADAC96}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModVerify", "src\ModVerify\ModVerify.csproj", "{22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModVerify.CliApp", "src\ModVerify.CliApp\ModVerify.CliApp.csproj", "{84479931-A329-4113-9BE5-90B71E5486E6}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Files.ChunkFiles", "src\PetroglyphTools\PG.StarWarsGame.Files.ChunkFiles\PG.StarWarsGame.Files.ChunkFiles.csproj", "{92F2A0C8-61B6-424B-99D5-7898CDBA7CA6}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Files.ALO", "src\PetroglyphTools\PG.StarWarsGame.Files.ALO\PG.StarWarsGame.Files.ALO.csproj", "{DF76A383-C94E-4D03-A07C-22D61ED37059}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Files.XML", "src\PetroglyphTools\PG.StarWarsGame.Files.XML\PG.StarWarsGame.Files.XML.csproj", "{418C68FA-531B-432E-8459-6433181C8AD3}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Engine", "src\PetroglyphTools\PG.StarWarsGame.Engine\PG.StarWarsGame.Engine.csproj", "{DFD62F61-3455-44BE-BB7C-E954FF48534B}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Release|Any CPU.Build.0 = Release|Any CPU
- {84479931-A329-4113-9BE5-90B71E5486E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {84479931-A329-4113-9BE5-90B71E5486E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {84479931-A329-4113-9BE5-90B71E5486E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {84479931-A329-4113-9BE5-90B71E5486E6}.Release|Any CPU.Build.0 = Release|Any CPU
- {92F2A0C8-61B6-424B-99D5-7898CDBA7CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {92F2A0C8-61B6-424B-99D5-7898CDBA7CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {92F2A0C8-61B6-424B-99D5-7898CDBA7CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {92F2A0C8-61B6-424B-99D5-7898CDBA7CA6}.Release|Any CPU.Build.0 = Release|Any CPU
- {DF76A383-C94E-4D03-A07C-22D61ED37059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DF76A383-C94E-4D03-A07C-22D61ED37059}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DF76A383-C94E-4D03-A07C-22D61ED37059}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DF76A383-C94E-4D03-A07C-22D61ED37059}.Release|Any CPU.Build.0 = Release|Any CPU
- {418C68FA-531B-432E-8459-6433181C8AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {418C68FA-531B-432E-8459-6433181C8AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {418C68FA-531B-432E-8459-6433181C8AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {418C68FA-531B-432E-8459-6433181C8AD3}.Release|Any CPU.Build.0 = Release|Any CPU
- {DFD62F61-3455-44BE-BB7C-E954FF48534B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DFD62F61-3455-44BE-BB7C-E954FF48534B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DFD62F61-3455-44BE-BB7C-E954FF48534B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DFD62F61-3455-44BE-BB7C-E954FF48534B}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {92F2A0C8-61B6-424B-99D5-7898CDBA7CA6} = {15F8B753-814A-406E-9147-EB048DADAC96}
- {DF76A383-C94E-4D03-A07C-22D61ED37059} = {15F8B753-814A-406E-9147-EB048DADAC96}
- {418C68FA-531B-432E-8459-6433181C8AD3} = {15F8B753-814A-406E-9147-EB048DADAC96}
- {DFD62F61-3455-44BE-BB7C-E954FF48534B} = {15F8B753-814A-406E-9147-EB048DADAC96}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {D74A22E2-91F1-4BC7-9630-3CF930B45408}
- EndGlobalSection
-EndGlobal
diff --git a/ModVerify.slnx b/ModVerify.slnx
new file mode 100644
index 0000000..3527ff4
--- /dev/null
+++ b/ModVerify.slnx
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/ModdingToolBase b/modules/ModdingToolBase
new file mode 160000
index 0000000..479a088
--- /dev/null
+++ b/modules/ModdingToolBase
@@ -0,0 +1 @@
+Subproject commit 479a088a2b26dd4a3e2342b2e34f5359b0252e88
diff --git a/src/ModVerify.CliApp/ConsoleUtilities.cs b/src/ModVerify.CliApp/ConsoleUtilities.cs
deleted file mode 100644
index db3e741..0000000
--- a/src/ModVerify.CliApp/ConsoleUtilities.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-
-namespace AET.ModVerifyTool;
-
-internal static class ConsoleUtilities
-{
- public delegate bool ConsoleQuestionValueFactory(string input, out T value);
-
- public static void WriteHorizontalLine(char lineChar = '─', int length = 20)
- {
- var line = new string(lineChar, length);
- Console.WriteLine(line);
- }
-
- public static void WriteHeader()
- {
- Console.WriteLine("***********************************");
- Console.WriteLine("***********************************");
- Console.WriteLine(Figgle.FiggleFonts.Standard.Render("Mod Verify"));
- Console.WriteLine("***********************************");
- Console.WriteLine("***********************************");
- Console.WriteLine(" by AnakinRaW");
- Console.WriteLine();
- Console.WriteLine();
- }
-
- public static void WriteApplicationFailure()
- {
- Console.WriteLine();
- WriteHorizontalLine('*');
- Console.ForegroundColor = ConsoleColor.DarkRed;
- Console.WriteLine(" ModVerify Failure! ");
- Console.ResetColor();
- WriteHorizontalLine('*');
- Console.WriteLine();
- Console.WriteLine("The application encountered an unexpected error and will terminate now!");
- Console.WriteLine();
- }
-
- public static T UserQuestionOnSameLine(string question, ConsoleQuestionValueFactory inputCorrect)
- {
- while (true)
- {
- var promptLeft = 0;
- var promptTop = Console.CursorTop;
-
- Console.SetCursorPosition(promptLeft, promptTop);
- Console.Write(question);
- Console.SetCursorPosition(promptLeft + question.Length, promptTop);
-
- var input = ReadLineInline();
-
- if (!inputCorrect(input, out var result))
- {
- Console.SetCursorPosition(0, promptTop);
- Console.Write(new string(' ', Console.WindowWidth - 1));
- continue;
- }
-
- Console.WriteLine();
- return result;
- }
- }
-
- private static string ReadLineInline()
- {
- var input = "";
- while (true)
- {
- var key = Console.ReadKey(intercept: true);
-
- if (key.Key == ConsoleKey.Enter)
- break;
-
- if (key.Key == ConsoleKey.Backspace)
- {
- if (input.Length > 0)
- {
- input = input[..^1];
- Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
- Console.Write(' ');
- Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
- }
- }
- else if (!char.IsControl(key.KeyChar))
- {
- input += key.KeyChar;
- Console.Write(key.KeyChar);
- }
- }
-
- return input;
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ExtensionMethods.cs b/src/ModVerify.CliApp/ExtensionMethods.cs
deleted file mode 100644
index b7e20c8..0000000
--- a/src/ModVerify.CliApp/ExtensionMethods.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using PG.StarWarsGame.Engine;
-using PG.StarWarsGame.Infrastructure.Games;
-
-namespace AET.ModVerifyTool;
-
-internal static class ExtensionMethods
-{
- public static GameEngineType ToEngineType(this GameType type)
- {
- return type == GameType.Foc ? GameEngineType.Foc : GameEngineType.Eaw;
- }
-
- public static GameType FromEngineType(this GameEngineType type)
- {
- return type == GameEngineType.Foc ? GameType.Foc : GameType.Eaw;
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderResult.cs b/src/ModVerify.CliApp/GameFinder/GameFinderResult.cs
index 3135955..beb3964 100644
--- a/src/ModVerify.CliApp/GameFinder/GameFinderResult.cs
+++ b/src/ModVerify.CliApp/GameFinder/GameFinderResult.cs
@@ -1,5 +1,5 @@
using PG.StarWarsGame.Infrastructure.Games;
-namespace AET.ModVerifyTool.GameFinder;
+namespace AET.ModVerify.App.GameFinder;
internal record GameFinderResult(IGame Game, IGame? FallbackGame);
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
index 78fe408..a88ebd9 100644
--- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
+++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
@@ -10,7 +10,7 @@
using PG.StarWarsGame.Infrastructure.Services;
using PG.StarWarsGame.Infrastructure.Services.Detection;
-namespace AET.ModVerifyTool.GameFinder;
+namespace AET.ModVerify.App.GameFinder;
internal class GameFinderService
{
@@ -79,7 +79,7 @@ private bool TryDetectGame(GameType gameType, IList detectors, ou
catch (Exception e)
{
result = GameDetectionResult.NotInstalled(gameType);
- _logger?.LogTrace($"Unable to find game installation: {e.Message}");
+ _logger?.LogTrace("Unable to find game installation: {Message}", e.Message);
return false;
}
}
@@ -97,7 +97,8 @@ private GameFinderResult FindGames(IList detectors)
if (result.GameLocation is null)
throw new GameNotFoundException("Unable to find game installation: Wrong install path?");
- _logger?.LogInformation($"Found game installation: {result.GameIdentity} at {result.GameLocation.FullName}");
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId,
+ "Found game installation: {ResultGameIdentity} at {GameLocationFullName}", result.GameIdentity, result.GameLocation.FullName);
var game = _gameFactory.CreateGame(result, CultureInfo.InvariantCulture);
@@ -118,7 +119,8 @@ private GameFinderResult FindGames(IList detectors)
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($"Found fallback game installation: {fallbackResult.GameIdentity} at {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);
diff --git a/src/ModVerify.CliApp/GameNotFoundException.cs b/src/ModVerify.CliApp/GameFinder/GameNotFoundException.cs
similarity index 76%
rename from src/ModVerify.CliApp/GameNotFoundException.cs
rename to src/ModVerify.CliApp/GameFinder/GameNotFoundException.cs
index 21cdc81..37b0386 100644
--- a/src/ModVerify.CliApp/GameNotFoundException.cs
+++ b/src/ModVerify.CliApp/GameFinder/GameNotFoundException.cs
@@ -1,5 +1,5 @@
using PG.StarWarsGame.Infrastructure.Games;
-namespace AET.ModVerifyTool;
+namespace AET.ModVerify.App.GameFinder;
internal class GameNotFoundException(string message) : GameException(message);
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs
index 00fc4ae..717db7b 100644
--- a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs
@@ -2,8 +2,9 @@
using System.Globalization;
using System.IO.Abstractions;
using System.Linq;
-using AET.ModVerifyTool.GameFinder;
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.GameFinder;
+using AET.ModVerify.App.Settings;
+using AET.ModVerify.App.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PG.StarWarsGame.Engine;
@@ -13,7 +14,7 @@
using PG.StarWarsGame.Infrastructure.Services;
using PG.StarWarsGame.Infrastructure.Services.Detection;
-namespace AET.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider)
{
@@ -37,7 +38,7 @@ internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelec
}
catch (GameNotFoundException)
{
- Logger?.LogError($"Unable to find games based of the given location '{settings.GamePath}'. Consider specifying all paths manually.");
+ 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;
}
@@ -59,7 +60,7 @@ internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelec
if (!settings.EngineType.HasValue)
throw new ArgumentException("Unable to determine game type. Use --type argument to set the game type.");
- Logger?.LogDebug($"The requested mod at '{pathToVerify}' is detached from its games.");
+ Logger?.LogDebug("The requested mod at '{PathToVerify}' is detached from its games.", pathToVerify);
// The path is a detached mod, that exists on a different location than the game.
var result = GetDetachedModLocations(pathToVerify, finderResult, settings, out var mod);
diff --git a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
index 7a29368..c776d6d 100644
--- a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using AET.Modinfo.Spec;
-using AET.ModVerifyTool.GameFinder;
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.GameFinder;
+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.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider)
{
@@ -100,7 +102,7 @@ private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult fin
if (!int.TryParse(input, out value))
return false;
- return value <= list.Count;
+ return value <= list.Count && value >= 0;
});
return list[selected];
}
diff --git a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs
index f0b30a0..a04858c 100644
--- a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs
@@ -1,8 +1,8 @@
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.Settings;
using PG.StarWarsGame.Engine;
using PG.StarWarsGame.Infrastructure;
-namespace AET.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal interface IModSelector
{
diff --git a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs
index d5913c6..34cf39d 100644
--- a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs
@@ -1,9 +1,9 @@
using System;
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.Settings;
using PG.StarWarsGame.Engine;
using PG.StarWarsGame.Infrastructure;
-namespace AET.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal class ManualModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider)
{
diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs
index 0eec285..8dd1d90 100644
--- a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using AET.ModVerifyTool.GameFinder;
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.GameFinder;
+using AET.ModVerify.App.Settings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PG.StarWarsGame.Engine;
@@ -10,7 +10,7 @@
using PG.StarWarsGame.Infrastructure.Mods;
using PG.StarWarsGame.Infrastructure.Services.Dependencies;
-namespace AET.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal abstract class ModSelectorBase : IModSelector
{
diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs
index 8335a86..07ef263 100644
--- a/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ModSelectorFactory.cs
@@ -1,7 +1,7 @@
using System;
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.Settings;
-namespace AET.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal class ModSelectorFactory(IServiceProvider serviceProvider)
{
diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
index 12f7861..221bb9e 100644
--- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
@@ -1,14 +1,15 @@
using System;
using System.Linq;
-using AET.ModVerifyTool.Options;
+using AET.ModVerify.App.GameFinder;
+using AET.ModVerify.App.Settings;
using PG.StarWarsGame.Engine;
using PG.StarWarsGame.Infrastructure;
-namespace AET.ModVerifyTool.ModSelectors;
+namespace AET.ModVerify.App.ModSelectors;
internal class SettingsBasedModSelector(IServiceProvider serviceProvider)
{
- public VerifyInstallationInformation CreateInstallationDataFromSettings(GameInstallationsSettings settings)
+ public VerifyInstallationData CreateInstallationDataFromSettings(GameInstallationsSettings settings)
{
var gameLocations = new ModSelectorFactory(serviceProvider)
.CreateSelector(settings)
@@ -20,7 +21,7 @@ public VerifyInstallationInformation CreateInstallationDataFromSettings(GameInst
if (engineType is null)
throw new InvalidOperationException("Engine type not specified.");
- return new VerifyInstallationInformation
+ return new VerifyInstallationData
{
EngineType = engineType.Value,
GameLocations = gameLocations,
diff --git a/src/ModVerify.CliApp/VerifyInstallationInformation.cs b/src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs
similarity index 90%
rename from src/ModVerify.CliApp/VerifyInstallationInformation.cs
rename to src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs
index 66816ab..1a1fcd2 100644
--- a/src/ModVerify.CliApp/VerifyInstallationInformation.cs
+++ b/src/ModVerify.CliApp/ModSelectors/VerifyInstallationData.cs
@@ -1,9 +1,9 @@
using System.Text;
using PG.StarWarsGame.Engine;
-namespace AET.ModVerifyTool;
+namespace AET.ModVerify.App.ModSelectors;
-internal sealed class VerifyInstallationInformation
+internal sealed class VerifyInstallationData
{
public required string Name { get; init; }
diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj
index 3ca41b6..0073b2b 100644
--- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj
+++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj
@@ -1,9 +1,9 @@
- net9.0;net48
+ net10.0;net481
Exe
- AET.ModVerifyTool
+ AET.ModVerify.App
ModVerify
$(RepoRootPath)aet.ico
AlamoEngineTools.ModVerify.CliApp
@@ -22,42 +22,46 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
@@ -65,11 +69,16 @@
+
+
+
+
+
-
-
+
+
diff --git a/src/ModVerify.CliApp/ModVerifyApp.cs b/src/ModVerify.CliApp/ModVerifyApp.cs
deleted file mode 100644
index 5cefc40..0000000
--- a/src/ModVerify.CliApp/ModVerifyApp.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using AET.ModVerify;
-using AET.ModVerify.Reporting;
-using AET.ModVerifyTool.ModSelectors;
-using AET.ModVerifyTool.Options;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-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.Pipeline;
-using AET.ModVerifyTool.Reporting;
-using PG.StarWarsGame.Engine;
-
-namespace AET.ModVerifyTool;
-
-internal class ModVerifyApp(ModVerifyAppSettings settings, IServiceProvider services)
-{
- private readonly ILogger? _logger = services.GetService()?.CreateLogger(typeof(ModVerifyApp));
- private readonly IFileSystem _fileSystem = services.GetRequiredService();
-
- public async Task RunApplication()
- {
- var installData = new SettingsBasedModSelector(services)
- .CreateInstallationDataFromSettings(settings.GameInstallationsSettings);
-
- _logger?.LogDebug($"Verify install data: {installData}");
- _logger?.LogTrace($"Verify settings: {settings}");
-
- var allErrors = await Verify(installData).ConfigureAwait(false);
-
- try
- {
- await ReportErrors(allErrors).ConfigureAwait(false);
- }
- catch (GameVerificationException e)
- {
- return e.HResult;
- }
-
- if (!settings.CreateNewBaseline)
- return 0;
-
- await WriteBaseline(allErrors, settings.NewBaselinePath).ConfigureAwait(false);
- _logger?.LogInformation("Baseline successfully created.");
-
- return 0;
- }
-
- private async Task> Verify(VerifyInstallationInformation installInformation)
- {
- var gameEngineService = services.GetRequiredService();
- var engineErrorReporter = new ConcurrentGameEngineErrorReporter();
-
- IStarWarsGameEngine gameEngine;
-
- try
- {
- var initProgress = new Progress();
- var initProgressReporter = new EngineInitializeProgressReporter(initProgress);
-
- try
- {
- _logger?.LogInformation($"Creating Game Engine '{installInformation.EngineType}'");
- gameEngine = await gameEngineService.InitializeAsync(
- installInformation.EngineType,
- installInformation.GameLocations,
- engineErrorReporter,
- initProgress,
- false,
- CancellationToken.None).ConfigureAwait(false);
- _logger?.LogInformation("Game Engine created");
- }
- finally
- {
- initProgressReporter.Dispose();
- }
- }
- catch (Exception e)
- {
- _logger?.LogError(e, $"Creating game engine failed: {e.Message}");
- throw;
- }
-
- var progressReporter = new VerifyConsoleProgressReporter(installInformation.Name);
-
- using var verifyPipeline = new GameVerifyPipeline(
- gameEngine,
- engineErrorReporter,
- settings.VerifyPipelineSettings,
- settings.GlobalReportSettings,
- progressReporter,
- services);
-
- try
- {
- try
- {
- _logger?.LogInformation($"Verifying '{installInformation.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("Verification stopped due to enabled failFast setting.");
- }
- catch (Exception e)
- {
- _logger?.LogError(e, $"Verification failed: {e.Message}");
- throw;
- }
-
- _logger?.LogInformation("Finished verification");
- return verifyPipeline.FilteredErrors;
- }
-
- private async Task ReportErrors(IReadOnlyCollection errors)
- {
- _logger?.LogInformation("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 WriteBaseline(IEnumerable errors, string baselineFile)
- {
- var baseline = new VerificationBaseline(settings.GlobalReportSettings.MinimumReportSeverity, errors);
-
- var fullPath = _fileSystem.Path.GetFullPath(baselineFile);
- _logger?.LogInformation($"Writing Baseline to '{fullPath}'");
-
-#if NET
- await
-#endif
- using var fs = _fileSystem.FileStream.New(fullPath, FileMode.Create, FileAccess.Write, FileShare.None);
- await baseline.ToJsonAsync(fs);
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs b/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs
new file mode 100644
index 0000000..86bfc40
--- /dev/null
+++ b/src/ModVerify.CliApp/ModVerifyAppEnvironment.cs
@@ -0,0 +1,71 @@
+using System.IO.Abstractions;
+using System.Reflection;
+using AnakinRaW.ApplicationBase.Environment;
+#if !NET
+using System;
+using System.Collections.Generic;
+using AnakinRaW.AppUpdaterFramework.Configuration;
+using AnakinRaW.CommonUtilities.DownloadManager.Configuration;
+#endif
+
+namespace AET.ModVerify.App;
+
+internal sealed class ModVerifyAppEnvironment(Assembly assembly, IFileSystem fileSystem)
+#if NET
+ : ApplicationEnvironment(assembly, fileSystem)
+#else
+ : UpdatableApplicationEnvironment(assembly, fileSystem)
+#endif
+{
+ public override string ApplicationName => ModVerifyConstants.AppNameString;
+
+ protected override string ApplicationLocalDirectoryName => ModVerifyConstants.ModVerifyToolPath;
+
+#if NETFRAMEWORK
+
+ public override ICollection UpdateMirrors { get; } = new List
+ {
+#if DEBUG
+ new("C:\\Test\\ModVerify"),
+#endif
+ new($"https://republicatwar.com/downloads/{ModVerifyConstants.ModVerifyToolPath}")
+ };
+
+ public override string UpdateRegistryPath => $@"SOFTWARE\{ModVerifyConstants.ModVerifyToolPath}\Update";
+
+ protected override UpdateConfiguration CreateUpdateConfiguration()
+ {
+ return new UpdateConfiguration
+ {
+ DownloadLocation = FileSystem.Path.Combine(ApplicationLocalPath, "downloads"),
+ BackupLocation = FileSystem.Path.Combine(ApplicationLocalPath, "backups"),
+ BackupPolicy = BackupPolicy.Required,
+ ComponentDownloadConfiguration = new DownloadManagerConfiguration
+ {
+ AllowEmptyFileDownload = false,
+ DownloadRetryDelay = 500,
+ ValidationPolicy = ValidationPolicy.Required
+ },
+ ManifestDownloadConfiguration = new DownloadManagerConfiguration
+ {
+ AllowEmptyFileDownload = false,
+ DownloadRetryDelay = 500,
+ ValidationPolicy = ValidationPolicy.Optional
+ },
+ BranchDownloadConfiguration = new DownloadManagerConfiguration
+ {
+ AllowEmptyFileDownload = false,
+ DownloadRetryDelay = 500,
+ ValidationPolicy = ValidationPolicy.NoValidation
+ },
+ DownloadRetryCount = 3,
+ RestartConfiguration = new UpdateRestartConfiguration
+ {
+ SupportsRestart = true,
+ PassCurrentArgumentsForRestart = true
+ },
+ ValidateInstallation = true
+ };
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModVerifyApplication.cs b/src/ModVerify.CliApp/ModVerifyApplication.cs
new file mode 100644
index 0000000..7ea461c
--- /dev/null
+++ b/src/ModVerify.CliApp/ModVerifyApplication.cs
@@ -0,0 +1,251 @@
+using AET.ModVerify.App.ModSelectors;
+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 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;
+
+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 Run()
+ {
+ using (new UnhandledExceptionHandler(services))
+ using (new UnobservedTaskExceptionHandler(services))
+ return await RunCore().ConfigureAwait(false);
+ }
+
+ private async Task RunCore()
+ {
+ _logger?.LogDebug("Raw command line: {CommandLine}", Environment.CommandLine);
+
+ var interactive = settings.Interactive;
+ try
+ {
+ return await RunVerify().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 RunVerify()
+ {
+ VerifyInstallationData installData;
+ try
+ {
+ installData = new SettingsBasedModSelector(services)
+ .CreateInstallationDataFromSettings(settings.GameInstallationsSettings);
+ }
+ 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(installData);
+
+ _logger?.LogDebug("Verify install data: {InstallData}", installData);
+ _logger?.LogTrace("Verify settings: {Settings}", settings);
+
+ var allErrors = await Verify(installData, reportSettings)
+ .ConfigureAwait(false);
+
+ try
+ {
+ await ReportErrors(allErrors).ConfigureAwait(false);
+ }
+ catch (GameVerificationException e)
+ {
+ return e.HResult;
+ }
+
+ if (!settings.CreateNewBaseline)
+ return 0;
+
+ await WriteBaseline(reportSettings, allErrors, settings.NewBaselinePath).ConfigureAwait(false);
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Baseline successfully created.");
+
+ return 0;
+ }
+
+ 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 progressReporter = new VerifyConsoleProgressReporter(installData.Name);
+
+ using var verifyPipeline = new GameVerifyPipeline(
+ gameEngine,
+ engineErrorReporter,
+ settings.VerifyPipelineSettings,
+ reportSettings,
+ progressReporter,
+ services);
+
+ try
+ {
+ try
+ {
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verifying '{Target}'...", installData.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 WriteBaseline(
+ GlobalVerifyReportSettings reportSettings,
+ IEnumerable errors,
+ string baselineFile)
+ {
+ var baseline = new VerificationBaseline(reportSettings.MinimumReportSeverity, errors);
+
+ 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);
+ }
+
+ private GlobalVerifyReportSettings CreateGlobalReportSettings(VerifyInstallationData installData)
+ {
+ var baselineSelector = new BaselineSelector(settings, services);
+ var baseline = baselineSelector.SelectBaseline(installData, 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
new file mode 100644
index 0000000..6b60f06
--- /dev/null
+++ b/src/ModVerify.CliApp/ModVerifyConstants.cs
@@ -0,0 +1,13 @@
+using Microsoft.Extensions.Logging;
+
+namespace AET.ModVerify.App;
+
+internal static class ModVerifyConstants
+{
+ public const string AppNameString = "AET Mod Verify";
+ public const string ModVerifyToolId = "AET.ModVerify";
+ public const string ModVerifyToolPath = "ModVerify";
+ public const int ConsoleEventIdValue = 1138;
+
+ public static readonly EventId ConsoleEventId = new(ConsoleEventIdValue, "LogToConsole");
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs
deleted file mode 100644
index 517ceda..0000000
--- a/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using AET.ModVerify.Reporting;
-using CommandLine;
-
-namespace AET.ModVerifyTool.Options.CommandLine;
-
-[Verb("verify", true, HelpText = "Verifies the specified game and reports the findings.")]
-internal class VerifyVerbOption : BaseModVerifyOptions
-{
- [Option('o', "outDir", Required = false, HelpText = "Directory where result files shall be stored to.")]
- public string? OutputDirectory { get; set; }
-
- [Option("failFast", Required = false, Default = false,
- HelpText = "When set, the application will abort on the first failure. The option also recognized the 'MinimumFailureSeverity' setting.")]
- public bool FailFast { get; set; }
-
- [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.")]
- public VerificationSeverity? MinimumFailureSeverity { get; set; }
-
- [Option("ignoreAsserts", Required = false,
- HelpText = "When this flag is present, the application will not report engine assertions.")]
- public bool IgnoreAsserts { get; set; }
-
- [Option("baseline", Required = false, HelpText = "Path to a JSON baseline file.")]
- public string? Baseline { get; set; }
-}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs
index 3ac92ba..cd4747b 100644
--- a/src/ModVerify.CliApp/Program.cs
+++ b/src/ModVerify.CliApp/Program.cs
@@ -1,18 +1,21 @@
-using AET.ModVerify;
+using AET.ModVerify.App.Settings;
+using AET.ModVerify.App.Settings.CommandLine;
+using AET.ModVerify.App.Updates;
+using AET.ModVerify.App.Utilities;
using AET.ModVerify.Reporting;
using AET.ModVerify.Reporting.Reporters;
using AET.ModVerify.Reporting.Reporters.JSON;
using AET.ModVerify.Reporting.Reporters.Text;
using AET.ModVerify.Reporting.Settings;
-using AET.ModVerifyTool.Options;
-using AET.ModVerifyTool.Options.CommandLine;
-using AET.ModVerifyTool.Updates;
using AET.SteamAbstraction;
+using AnakinRaW.ApplicationBase;
+using AnakinRaW.ApplicationBase.Environment;
+using AnakinRaW.ApplicationBase.Update;
+using AnakinRaW.ApplicationBase.Update.Options;
+using AnakinRaW.AppUpdaterFramework.Json;
using AnakinRaW.CommonUtilities.Hashing;
using AnakinRaW.CommonUtilities.Registry;
using AnakinRaW.CommonUtilities.Registry.Windows;
-using CommandLine;
-using CommandLine.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PG.Commons;
@@ -28,194 +31,189 @@
using PG.StarWarsGame.Infrastructure.Services.Name;
using Serilog;
using Serilog.Events;
+using Serilog.Expressions;
using Serilog.Filters;
using Serilog.Sinks.SystemConsole.Themes;
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.IO.Abstractions;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Testably.Abstractions;
using ILogger = Serilog.ILogger;
-namespace AET.ModVerifyTool;
+namespace AET.ModVerify.App;
-internal class Program
+internal class MainClass
+{
+ // Fody/Costura application with .NET Core apparently don't work well when the class containing the Main method are derived by a type in an embedded assembly.
+ private static Task Main(string[] args)
+ {
+ return new Program().StartAsync(args);
+ }
+}
+
+internal class Program : SelfUpdateableAppLifecycle
{
private static readonly string EngineParserNamespace = typeof(XmlObjectParser<>).Namespace!;
private static readonly string ParserNamespace = typeof(PetroglyphXmlFileParser<>).Namespace!;
private static readonly string ModVerifyRootNameSpace = typeof(Program).Namespace!;
+ private static readonly CompiledExpression PrintToConsoleExpression = SerilogExpression.Compile($"EventId.Id = {ModVerifyConstants.ConsoleEventIdValue}");
- private static async Task Main(string[] args)
- {
- ConsoleUtilities.WriteHeader();
+ private static ModVerifyOptionsContainer _optionsContainer = null!;
- var result = 0;
-
- Type[] programVerbs =
- [
- typeof(VerifyVerbOption),
- typeof(CreateBaselineVerbOption),
- ];
-
- var parseResult = Parser.Default.ParseArguments(args, programVerbs);
-
- await parseResult.WithParsedAsync(async o =>
- {
- result = await Run((BaseModVerifyOptions)o);
- });
- await parseResult.WithNotParsedAsync(e =>
- {
- Console.WriteLine(HelpText.AutoBuild(parseResult).ToString());
- result = 0xA0;
- return Task.CompletedTask;
- });
-
- return result;
- }
-
- private static async Task Run(BaseModVerifyOptions options)
+ protected override async Task InitializeAppAsync(IReadOnlyList args)
{
- var coreServiceCollection = CreateCoreServices(options.Verbose);
- var coreServices = coreServiceCollection.BuildServiceProvider();
- var logger = coreServices.GetService()?.CreateLogger(typeof(Program));
+ ModVerifyConsoleUtilities.WriteHeader(ApplicationEnvironment.AssemblyInfo.InformationalVersion);
- logger?.LogDebug($"Raw command line: {Environment.CommandLine}");
+ await base.InitializeAppAsync(args);
- var interactive = false;
try
{
- var settings = new SettingsBuilder(coreServices).BuildSettings(options);
- interactive = settings.Interactive;
- var services = CreateAppServices(coreServiceCollection, settings);
-
- if (!settings.Offline)
- await CheckForUpdate(services, logger);
-
- var verifier = new ModVerifyApp(settings, services);
- return await verifier.RunApplication().ConfigureAwait(false);
+ var settings = new ModVerifyOptionsParser(ApplicationEnvironment, BootstrapLoggerFactory).Parse(args);
+ if (!settings.HasOptions)
+ return 0xA0;
+ _optionsContainer = settings;
+ return 0;
}
catch (Exception e)
{
- ConsoleUtilities.WriteApplicationFailure();
- logger?.LogCritical(e, e.Message);
+ Logger?.LogCritical(e, "Failed to parse commandline arguments: {Message}", e.Message);
+ ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e);
return e.HResult;
}
- finally
+ }
+
+ protected override void CreateAppServices(IServiceCollection services, IReadOnlyList args)
+ {
+ base.CreateAppServices(services, args);
+
+ services.AddSingleton((ApplicationEnvironment as ModVerifyAppEnvironment)!);
+
+ services.AddLogging(ConfigureLogging);
+
+ services.AddSingleton(sp => new HashingService(sp));
+
+
+ if (IsUpdateableApplication)
{
#if NET
- await Log.CloseAndFlushAsync();
-#else
- Log.CloseAndFlush();
+ throw new NotSupportedException();
#endif
- if (interactive)
- {
- Console.WriteLine();
- ConsoleUtilities.WriteHorizontalLine('-');
- Console.WriteLine("Press any key to exit");
- Console.ReadLine();
- }
+ services.MakeAppUpdateable(
+ UpdatableApplicationEnvironment,
+ sp => new CosturaApplicationProductService(ApplicationEnvironment, sp),
+ sp => new JsonManifestLoader(sp));
}
- }
- private static async Task CheckForUpdate(IServiceProvider services, Microsoft.Extensions.Logging.ILogger? logger)
- {
- var updateChecker = new ModVerifyUpdaterChecker(services);
+ if (_optionsContainer.ModVerifyOptions is null)
+ return;
- logger?.LogDebug("Checking for available update");
+ SteamAbstractionLayer.InitializeServices(services);
+ PetroglyphGameInfrastructure.InitializeServices(services);
- try
- {
- var updateInfo = await updateChecker.CheckForUpdateAsync().ConfigureAwait(false);
- if (updateInfo.IsUpdateAvailable)
- {
- ConsoleUtilities.WriteHorizontalLine();
-
- Console.ForegroundColor = ConsoleColor.DarkGreen;
- Console.WriteLine("New Update Available!");
- Console.ResetColor();
-
- Console.WriteLine($"Version: {updateInfo.NewVersion}, Download here: {updateInfo.DownloadLink}");
- ConsoleUtilities.WriteHorizontalLine();
- Console.WriteLine();
+ services.SupportMTD();
+ services.SupportMEG();
+ services.SupportALO();
+ services.SupportXML();
+ PetroglyphCommons.ContributeServices(services);
- }
+ PetroglyphEngineServiceContribution.ContributeServices(services);
+ services.RegisterVerifierCache();
+
+
+ SetupVerifyReporting(services);
+
+ if (_optionsContainer.ModVerifyOptions.OfflineMode)
+ {
+ services.AddSingleton(sp => new OfflineModNameResolver(sp));
+ services.AddSingleton(sp => new OfflineModGameTypeResolver(sp));
}
- catch(Exception e)
+ else
{
- logger?.LogWarning($"Unable to check for updates due to an internal error: {e.Message}");
- logger?.LogTrace(e, "Checking for update failed: " + e.Message);
+ services.AddSingleton(sp => new OnlineModNameResolver(sp));
+ services.AddSingleton(sp => new OnlineModGameTypeResolver(sp));
}
}
- private static IServiceCollection CreateCoreServices(bool verboseLogging)
+ protected override ApplicationEnvironment CreateAppEnvironment()
{
- var fileSystem = new RealFileSystem();
- var serviceCollection = new ServiceCollection();
-
- serviceCollection.AddSingleton(new WindowsRegistry());
- serviceCollection.AddSingleton(fileSystem);
-
- serviceCollection.AddLogging(builder => ConfigureLogging(builder, fileSystem, verboseLogging));
-
- return serviceCollection;
+ return new ModVerifyAppEnvironment(typeof(Program).Assembly, FileSystem);
}
- private static IServiceProvider CreateAppServices(IServiceCollection serviceCollection, ModVerifyAppSettings settings)
+ protected override IFileSystem CreateFileSystem()
{
- serviceCollection.AddSingleton(sp => new HashingService(sp));
-
- SteamAbstractionLayer.InitializeServices(serviceCollection);
- PetroglyphGameInfrastructure.InitializeServices(serviceCollection);
+ return new RealFileSystem();
+ }
- serviceCollection.SupportMTD();
- serviceCollection.SupportMEG();
- serviceCollection.SupportALO();
- serviceCollection.SupportXML();
- PetroglyphCommons.ContributeServices(serviceCollection);
+ protected override IRegistry CreateRegistry()
+ {
+ return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+ ? new InMemoryRegistry(InMemoryRegistryCreationFlags.WindowsLike)
+ : new WindowsRegistry();
+ }
- PetroglyphEngineServiceContribution.ContributeServices(serviceCollection);
- serviceCollection.RegisterVerifierCache();
+ protected override async Task RunAppAsync(string[] args, IServiceProvider appServiceProvider)
+ {
+ var result = await HandleUpdate(appServiceProvider);
+ if (result != 0 || _optionsContainer.ModVerifyOptions is null)
+ return result;
- SetupVerifyReporting(serviceCollection, settings);
+ ModVerifyAppSettings modVerifySettings;
- if (settings.Offline)
+ try
{
- serviceCollection.AddSingleton(sp => new OfflineModNameResolver(sp));
- serviceCollection.AddSingleton(sp => new OfflineModGameTypeResolver(sp));
+ modVerifySettings = new SettingsBuilder(appServiceProvider).BuildSettings(_optionsContainer.ModVerifyOptions);
}
- else
+ catch (Exception e)
{
- serviceCollection.AddSingleton(sp => new OnlineModNameResolver(sp));
- serviceCollection.AddSingleton(sp => new OnlineModGameTypeResolver(sp));
+ Logger?.LogCritical(e, "Failed to create settings form commandline arguments: {EMessage}", e.Message);
+ ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, e);
+ return e.HResult;
}
-
- return serviceCollection.BuildServiceProvider();
+
+ return await new ModVerifyApplication(modVerifySettings, appServiceProvider).Run().ConfigureAwait(false);
}
- private static void SetupVerifyReporting(IServiceCollection serviceCollection, ModVerifyAppSettings settings)
+ private void SetupVerifyReporting(IServiceCollection serviceCollection)
{
- var printOnlySummary = settings.CreateNewBaseline;
+ var options = _optionsContainer.ModVerifyOptions;
+ Debug.Assert(options is not null);
+
+
+ var verifyVerb = options as VerifyVerbOption;
+
+ // Console should be in minimal summary mode if we are not in verify mode.
+ var printOnlySummary = verifyVerb is null;
+
serviceCollection.RegisterConsoleReporter(new VerifyReportSettings
{
MinimumReportSeverity = VerificationSeverity.Error
}, printOnlySummary);
- if (string.IsNullOrEmpty(settings.ReportOutput))
+ if (verifyVerb == null)
return;
+ var outputDirectory = Environment.CurrentDirectory;
+
+ if (!string.IsNullOrEmpty(verifyVerb.OutputDirectory))
+ outputDirectory = FileSystem.Path.GetFullPath(FileSystem.Path.Combine(Environment.CurrentDirectory, verifyVerb.OutputDirectory!));
+
serviceCollection.RegisterJsonReporter(new JsonReporterSettings
{
- OutputDirectory = settings.ReportOutput!,
- MinimumReportSeverity = settings.GlobalReportSettings.MinimumReportSeverity
+ OutputDirectory = outputDirectory!,
+ MinimumReportSeverity = options.MinimumSeverity
});
serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings
{
- OutputDirectory = settings.ReportOutput!,
- MinimumReportSeverity = settings.GlobalReportSettings.MinimumReportSeverity
+ OutputDirectory = outputDirectory!,
+ MinimumReportSeverity = options.MinimumSeverity
});
}
- private static void ConfigureLogging(ILoggingBuilder loggingBuilder, IFileSystem fileSystem, bool verbose)
+ private void ConfigureLogging(ILoggingBuilder loggingBuilder)
{
loggingBuilder.ClearProviders();
@@ -226,53 +224,116 @@ private static void ConfigureLogging(ILoggingBuilder loggingBuilder, IFileSystem
loggingBuilder.AddDebug();
#endif
- if (verbose)
+ if (_optionsContainer.ModVerifyOptions?.Verbose == true || _optionsContainer.UpdateOptions?.Verbose == true)
{
logLevel = LogEventLevel.Verbose;
loggingBuilder.AddDebug();
}
- var fileLogger = SetupFileLogging(fileSystem, logLevel);
+ var fileLogger = SetupFileLogging();
loggingBuilder.AddSerilog(fileLogger);
- var cLogger = new LoggerConfiguration()
- .WriteTo.Console(
- logLevel,
- theme: AnsiConsoleTheme.Code,
- outputTemplate: "[{Level:u3}] {Message:lj}{NewLine}{Exception}")
- .Filter.ByIncludingOnly(x =>
- {
- if (!x.Properties.TryGetValue("SourceContext", out var value))
- return true;
-
- var source = value.ToString().AsSpan().Trim('\"');
-
- return source.StartsWith(ModVerifyRootNameSpace.AsSpan());
- })
- .CreateLogger();
- loggingBuilder.AddSerilog(cLogger);
+ var consoleLogger = SetupConsoleLogging();
+ loggingBuilder.AddSerilog(consoleLogger);
+
+ return;
+
+ ILogger SetupConsoleLogging()
+ {
+ return new LoggerConfiguration()
+ .WriteTo.Console(
+ logLevel,
+ theme: AnsiConsoleTheme.Code,
+ outputTemplate: "[{Level:u3}] {Message:lj}{NewLine}{Exception}")
+ .MinimumLevel.Is(logLevel)
+ .Filter.ByIncludingOnly(x =>
+ {
+ // Fatal errors are handled by a global exception handler
+ if (x.Level == LogEventLevel.Fatal)
+ return false;
+
+ // Verbose should print everything we get
+ if (logLevel == LogEventLevel.Verbose)
+ return true;
+
+ // Debug should print everything that has something to do with ModVerify
+ if (logLevel == LogEventLevel.Debug)
+ {
+ if (!x.Properties.TryGetValue("SourceContext", out var value))
+ return false;
+ var source = value.ToString().AsSpan().Trim('\"');
+ return source.StartsWith(ModVerifyRootNameSpace.AsSpan());
+ }
+
+ // In normal operation, we only print logs, which have the print-to-console EventId set.
+ return ExpressionResult.IsTrue(PrintToConsoleExpression(x));
+ })
+ .CreateLogger();
+ }
+
+ ILogger SetupFileLogging()
+ {
+ var logPath = FileSystem.Path.Combine(ApplicationEnvironment.ApplicationLocalPath, "ModVerify_log.txt");
+
+ return new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .MinimumLevel.Is(logLevel)
+ .Filter.ByExcluding(IsXmlParserLogging)
+ .WriteTo.Async(c =>
+ {
+ c.RollingFile(
+ logPath,
+ outputTemplate:
+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}");
+ })
+ .CreateLogger();
+ }
+
+ static bool IsXmlParserLogging(LogEvent logEvent)
+ {
+ return Matching.FromSource(ParserNamespace)(logEvent) || Matching.FromSource(EngineParserNamespace)(logEvent);
+ }
}
- private static ILogger SetupFileLogging(IFileSystem fileSystem, LogEventLevel minLevel)
+ private async Task HandleUpdate(IServiceProvider serviceProvider)
{
- var logPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), "ModVerify_log.txt");
+ var updateOptions = _optionsContainer.UpdateOptions ?? new ApplicationUpdateOptions();
+ ModVerifyUpdateMode updateMode;
+
+ if (_optionsContainer.ModVerifyOptions is not null)
+ {
+ if (_optionsContainer.ModVerifyOptions.OfflineMode)
+ {
+ Logger?.LogTrace("Running in offline mode. There will be nothing to update.");
+ return 0;
+ }
- return new LoggerConfiguration()
- .Enrich.FromLogContext()
- .MinimumLevel.Is(minLevel)
- .Filter.ByExcluding(IsXmlParserLogging)
- .WriteTo.Async(c =>
+ updateMode = _optionsContainer.ModVerifyOptions.LaunchedWithoutArguments()
+ ? ModVerifyUpdateMode.InteractiveUpdate
+ : ModVerifyUpdateMode.CheckOnly;
+ }
+ 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);
+ Logger?.LogDebug("Update procedure completed successfully.");
+ return 0;
+ }
+ catch (Exception e)
+ {
+ Logger?.LogCritical(e, e.Message);
+ var action = updateMode switch
{
- c.RollingFile(
- logPath,
- outputTemplate:
- "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}");
- })
- .CreateLogger();
- }
+ ModVerifyUpdateMode.CheckOnly => "checking for updates",
+ _ => "updating"
+ };
+ ConsoleUtilities.WriteApplicationFatalError(ModVerifyConstants.AppNameString, $"Error while {action}: {e.Message}", e.StackTrace);
+ return e.HResult;
+ }
- private static bool IsXmlParserLogging(LogEvent logEvent)
- {
- return Matching.FromSource(ParserNamespace)(logEvent) || Matching.FromSource(EngineParserNamespace)(logEvent);
}
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Properties/AssemblyAttributes.cs b/src/ModVerify.CliApp/Properties/AssemblyAttributes.cs
new file mode 100644
index 0000000..af943d8
--- /dev/null
+++ b/src/ModVerify.CliApp/Properties/AssemblyAttributes.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("ModVerify.CliApp.Test")]
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json
index 1e01ece..e47583a 100644
--- a/src/ModVerify.CliApp/Properties/launchSettings.json
+++ b/src/ModVerify.CliApp/Properties/launchSettings.json
@@ -1,8 +1,12 @@
{
"profiles": {
+ "Run": {
+ "commandName": "Project",
+ "commandLineArgs": ""
+ },
"Interactive Verify": {
"commandName": "Project",
- "commandLineArgs": "verify -o verifyResults --minFailSeverity Information -v --baseline focBaseline.json --offline"
+ "commandLineArgs": "verify -o verifyResults --minFailSeverity Information --offline"
},
"Interactive Baseline": {
"commandName": "Project",
diff --git a/src/ModVerify.CliApp/Reporting/BaselineFactory.cs b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs
new file mode 100644
index 0000000..9fcc911
--- /dev/null
+++ b/src/ModVerify.CliApp/Reporting/BaselineFactory.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.IO.Abstractions;
+using AET.ModVerify.Reporting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace AET.ModVerify.App.Reporting;
+
+internal sealed class BaselineFactory(IServiceProvider serviceProvider)
+{
+ private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(BaselineFactory));
+ private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
+
+ public bool TryCreateBaseline(
+ string directory,
+ out VerificationBaseline baseline,
+ [NotNullWhen(true)] out string? path)
+ {
+ baseline = VerificationBaseline.Empty;
+ path = null;
+
+ if (!_fileSystem.Directory.Exists(directory))
+ return false;
+
+ _logger?.LogDebug(ModVerifyConstants.ConsoleEventId, "Searching for baseline file at '{Directory}'", directory);
+
+ var jsonFiles = _fileSystem.Directory.EnumerateFiles(
+ directory,
+ "*.json"
+#if NET || NETSTANDARD2_1_OR_GREATER
+ , new EnumerationOptions
+ {
+ MatchCasing = MatchCasing.CaseInsensitive,
+ RecurseSubdirectories = false
+ }
+#endif
+ );
+
+ foreach (var jsonFile in jsonFiles)
+ {
+ try
+ {
+ baseline = CreateBaselineFromFilePath(jsonFile);
+ path = jsonFile;
+ _logger?.LogDebug("Create baseline from file: {JsonFile}", jsonFile);
+ return true;
+ }
+ catch (InvalidBaselineException e)
+ {
+ _logger?.LogDebug("'{JsonFile}' is not a valid baseline file: {Message}", jsonFile, e.Message);
+ // Ignore this exception
+ }
+ }
+
+ path = null;
+ return false;
+ }
+
+ public VerificationBaseline CreateBaseline(string filePath)
+ {
+ return CreateBaselineFromFilePath(filePath);
+ }
+
+ private VerificationBaseline CreateBaselineFromFilePath(string baselineFile)
+ {
+ using var fs = _fileSystem.FileStream.New(baselineFile, FileMode.Open, FileAccess.Read);
+ return VerificationBaseline.FromJson(fs);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Reporting/BaselineSelector.cs b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs
new file mode 100644
index 0000000..95953f1
--- /dev/null
+++ b/src/ModVerify.CliApp/Reporting/BaselineSelector.cs
@@ -0,0 +1,139 @@
+using AET.ModVerify.App.ModSelectors;
+using AET.ModVerify.App.Resources.Baselines;
+using AET.ModVerify.App.Settings;
+using AET.ModVerify.Reporting;
+using AnakinRaW.ApplicationBase;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine;
+using System;
+using System.Diagnostics;
+
+namespace AET.ModVerify.App.Reporting;
+
+internal sealed class BaselineSelector(ModVerifyAppSettings settings, IServiceProvider services)
+{
+ private readonly ILogger? _logger = services.GetService()?.CreateLogger(typeof(ModVerifyApplication));
+ private readonly BaselineFactory _baselineFactory = new(services);
+
+ public VerificationBaseline SelectBaseline(VerifyInstallationData installationData, out string? usedBaselinePath)
+ {
+ var baselinePath = settings.ReportSettings.BaselinePath;
+ if (!string.IsNullOrEmpty(baselinePath))
+ {
+ try
+ {
+ usedBaselinePath = baselinePath;
+ return _baselineFactory.CreateBaseline(baselinePath!);
+ }
+ catch (InvalidBaselineException e)
+ {
+ using (ConsoleUtilities.HorizontalLineSeparatedBlock('*'))
+ {
+ Console.WriteLine($"The baseline '{baselinePath}' is not a valid baseline file: {e.Message}" +
+ $"{Environment.NewLine}Please generate a new baseline file or download the latest version." +
+ $"{Environment.NewLine}");
+ }
+
+ // For now, we bubble up this exception because we except users
+ // to correctly specify their baselines through command line arguments.
+ throw;
+ }
+ }
+
+ if (!settings.ReportSettings.SearchBaselineLocally)
+ {
+ _logger?.LogDebug(ModVerifyConstants.ConsoleEventId, "No baseline path specified and local search is not enabled. Using empty baseline.");
+ usedBaselinePath = null;
+ return VerificationBaseline.Empty;
+ }
+
+ if (settings.Interactive)
+ return FindBaselineInteractive(installationData, 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);
+
+ }
+
+ private VerificationBaseline FindBaselineInteractive(VerifyInstallationData installationData, 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.
+ // 2. Use a baseline found in the directory ModVerify executable.
+ // 3. If the verification target is a mod, ask the user to apply the default game's baseline.
+ // In any case ask the use if they want to use the located baseline file, or they wish to continue using none/empty.
+
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Searching for local baseline files...");
+
+ if (!_baselineFactory.TryCreateBaseline(installationData.GameLocations.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)
+ {
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "No local baseline file found.");
+ return VerificationBaseline.Empty;
+ }
+
+ Console.WriteLine("No baseline found locally.");
+ return TryGetDefaultBaseline(installationData.EngineType, out baselinePath);
+ }
+ }
+
+ Debug.Assert(baselinePath is not null);
+
+ return ConsoleUtilities.UserYesNoQuestion($"ModVerify found the baseline file '{baselinePath}'. Do you want to use it?")
+ ? baseline
+ : VerificationBaseline.Empty;
+ }
+
+ private VerificationBaseline TryGetDefaultBaseline(GameEngineType engineType, out string? baselinePath)
+ {
+ baselinePath = null;
+ if (engineType == GameEngineType.Eaw)
+ {
+ // TODO: EAW currently not implemented
+ return VerificationBaseline.Empty;
+ }
+
+ if (!ConsoleUtilities.UserYesNoQuestion($"Do you want to load the default baseline for game engine '{engineType}'?"))
+ return VerificationBaseline.Empty;
+
+ baselinePath = $"{engineType} (Default)";
+
+ try
+ {
+ return LoadEmbeddedBaseline(engineType);
+ }
+ catch (InvalidBaselineException)
+ {
+ throw new InvalidOperationException(
+ "Invalid baseline packed along ModVerify App. Please reach out to the creators. Thanks!");
+ }
+ }
+
+ internal VerificationBaseline LoadEmbeddedBaseline(GameEngineType engineType)
+ {
+ var baselineFileName = $"baseline-{engineType.ToString().ToLower()}.json";
+ var resourcePath = $"{typeof(BaselineResources).Namespace}.{baselineFileName}";
+
+ using var baselineStream = typeof(BaselineSelector).Assembly.GetManifestResourceStream(resourcePath)!;
+ return VerificationBaseline.FromJson(baselineStream);
+ }
+
+ private VerificationBaseline FindBaselineNonInteractive(string targetPath, out string? usedPath)
+ {
+ if (_baselineFactory.TryCreateBaseline(targetPath, 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);
+ usedPath = null;
+ return VerificationBaseline.Empty;
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs
index 69413c0..b994e97 100644
--- a/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs
+++ b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs
@@ -1,6 +1,6 @@
using System;
-namespace AET.ModVerifyTool.Reporting;
+namespace AET.ModVerify.App.Reporting;
internal sealed class EngineInitializeProgressReporter : IDisposable
{
diff --git a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs
index 37d3ca2..4700457 100644
--- a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs
+++ b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs
@@ -1,11 +1,11 @@
-using AnakinRaW.CommonUtilities;
-using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
-using ShellProgressBar;
-using System;
+using System;
using System.Threading;
using AET.ModVerify.Pipeline.Progress;
+using AnakinRaW.CommonUtilities;
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
+using ShellProgressBar;
-namespace AET.ModVerifyTool.Reporting;
+namespace AET.ModVerify.App.Reporting;
public sealed class VerifyConsoleProgressReporter(string toVerifyName) : DisposableObject, IVerifyProgressReporter
{
diff --git a/src/ModVerify.CliApp/Resources/Baselines/BaselineResources.cs b/src/ModVerify.CliApp/Resources/Baselines/BaselineResources.cs
new file mode 100644
index 0000000..bf49f9e
--- /dev/null
+++ b/src/ModVerify.CliApp/Resources/Baselines/BaselineResources.cs
@@ -0,0 +1,4 @@
+namespace AET.ModVerify.App.Resources.Baselines;
+
+// Marker class to provide static namespace information for resource lookup.
+internal static class BaselineResources;
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json b/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json
new file mode 100644
index 0000000..c94d121
--- /dev/null
+++ b/src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json
@@ -0,0 +1,2081 @@
+{
+ "version": "2.0",
+ "minSeverity": "Information",
+ "errors": [
+ {
+ "id": "XML04",
+ "verifiers": [
+ "XMLError"
+ ],
+ "message": "Expected double but got value \u002737\u0060\u0027. File=\u0027DATA\\XML\\COMMANDBARCOMPONENTS.XML #11571\u0027",
+ "severity": "Warning",
+ "context": [
+ "DATA\\XML\\COMMANDBARCOMPONENTS.XML",
+ "Size",
+ "parentName=\u0027bm_text_steal\u0027"
+ ],
+ "asset": "Size"
+ },
+ {
+ "id": "XML04",
+ "verifiers": [
+ "XMLError"
+ ],
+ "message": "Expected integer but got \u002780, 20\u0027. File=\u0027DATA\\XML\\SFXEVENTSWEAPONS.XML #90\u0027",
+ "severity": "Warning",
+ "context": [
+ "DATA\\XML\\SFXEVENTSWEAPONS.XML",
+ "Probability",
+ "parentName=\u0027Unit_TIE_Fighter_Fire\u0027"
+ ],
+ "asset": "Probability"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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.",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO"
+ ],
+ "asset": "Default.fx"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "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"
+ ],
+ "message": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_Kamino_Reflect.ALO"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "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\\UV_ECLIPSE_UC_DC.ALO"
+ ],
+ "asset": "p_ssd_debris"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_p_proton_torpedo.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_p_proton_torpedo.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_DStar_LeverPanel.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DStar_LeverPanel.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO"
+ ],
+ "asset": "lookat"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027Cin_DeathStar.tga\u0027 for context: [ALTTEST.ALO].",
+ "severity": "Error",
+ "context": [
+ "ALTTEST.ALO"
+ ],
+ "asset": "Cin_DeathStar.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\NB_PRISON.ALO"
+ ],
+ "asset": "p_prison_light"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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.",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO"
+ ],
+ "asset": "Default.fx"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027w_grenade.tga\u0027 for context: [W_GRENADE.ALO].",
+ "severity": "Error",
+ "context": [
+ "W_GRENADE.ALO"
+ ],
+ "asset": "w_grenade.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Rbel_NavyRow.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Rbel_NavyRow.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_Planet_Alderaan_High.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_Planet_Alderaan_High.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO"
+ ],
+ "asset": "lookat"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\EI_MARAJADE.ALO"
+ ],
+ "asset": "p_desert_ground_dust"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027p_splash_wake_lava.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "p_splash_wake_lava.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_rv_XWingProp.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_rv_XWingProp.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "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"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Probe_Droid.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Probe_Droid.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_05_STATION_D.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO"
+ ],
+ "asset": "p_uwstation_death"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO"
+ ],
+ "asset": "p_desert_ground_dust"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO"
+ ],
+ "asset": "p_steam_small"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027p_particle_master\u0027 for context: [P_DIRT_EMITTER_TEST1.ALO].",
+ "severity": "Error",
+ "context": [
+ "P_DIRT_EMITTER_TEST1.ALO"
+ ],
+ "asset": "p_particle_master"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\NB_PRISON.ALO"
+ ],
+ "asset": "p_smoke_small_thin4"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_EI_Vader.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EI_Vader.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO"
+ ],
+ "asset": "p_smoke_small_thin2"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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.",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO"
+ ],
+ "asset": "Default.fx"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_DeathStar_Wall.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DeathStar_Wall.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027W_droid_steam.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_droid_steam.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_DeathStar_High.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DeathStar_High.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027MODELS\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "MODELS"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027W_AllShaders.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_AllShaders.ALO"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO"
+ ],
+ "asset": "p_bomb_spin"
+ },
+ {
+ "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_03_STATION_D.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO"
+ ],
+ "asset": "p_uwstation_death"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Rbel_GreyGroup.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Rbel_GreyGroup.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\NB_SCH.ALO"
+ ],
+ "asset": "p_cold_tiny01"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CINE_EV_StarDestroyer.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CINE_EV_StarDestroyer.ALO"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO"
+ ],
+ "asset": "p_hp_archammer-damage"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Rbel_grey.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Rbel_grey.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Reb_CelebHall.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Reb_CelebHall.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Proxy particle \u0027P_mptl-2a_Die\u0027 not found for model \u0027DATA\\ART\\MODELS\\RV_MPTL-2A.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\RV_MPTL-2A.ALO"
+ ],
+ "asset": "P_mptl-2a_Die"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_ImperialCraft.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_ImperialCraft.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_DStar_Dish_close.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DStar_Dish_close.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\RV_BWING.ALO"
+ ],
+ "asset": "pe_bwing_yellow"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_bridge.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_bridge.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO"
+ ],
+ "asset": "p_desert_ground_dust"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Trooper_Row.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Trooper_Row.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_EV_TieAdvanced.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EV_TieAdvanced.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027w_sith_arch.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "w_sith_arch.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027NB_YsalamiriTree_B.tga\u0027 for context: [UV_MDU_CAGE.ALO].",
+ "severity": "Error",
+ "context": [
+ "UV_MDU_CAGE.ALO"
+ ],
+ "asset": "NB_YsalamiriTree_B.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027W_TE_Rock_f_02_b.tga\u0027 for context: [EV_TIE_PHANTOM.ALO].",
+ "severity": "Error",
+ "context": [
+ "EV_TIE_PHANTOM.ALO"
+ ],
+ "asset": "W_TE_Rock_f_02_b.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_EI_Palpatine.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EI_Palpatine.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Rbel_Soldier.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO"
+ ],
+ "asset": "p_smoke_small_thin2"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027W_Bush_Swmp00.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_Bush_Swmp00.ALO"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "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_HIGH.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO"
+ ],
+ "asset": "Lensflare0"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027Cin_Reb_CelebHall_Wall_B.tga\u0027 for context: [W_SITH_LEFTHALL.ALO].",
+ "severity": "Error",
+ "context": [
+ "W_SITH_LEFTHALL.ALO"
+ ],
+ "asset": "Cin_Reb_CelebHall_Wall_B.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_Coruscant.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_Coruscant.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO"
+ ],
+ "asset": "p_ewok_drag_dirt"
+ },
+ {
+ "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": [
+ "DATA\\ART\\MODELS\\NB_VCH.ALO"
+ ],
+ "asset": "P_heat_small01"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027W_Vol_Steam01.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_Vol_Steam01.ALO"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "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\\NB_NOGHRI_HUT.ALO"
+ ],
+ "asset": "p_explosion_smoke_small_thin5"
+ },
+ {
+ "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\\W_STARS_MEDIUM.ALO"
+ ],
+ "asset": "Lensflare0"
+ },
+ {
+ "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\\UB_02_STATION_D.ALO"
+ ],
+ "asset": "p_uwstation_death"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_Officer.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_Officer.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_04_STATION_D.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO"
+ ],
+ "asset": "p_uwstation_death"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Lambda_Head.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Lambda_Head.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Biker_Row.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Biker_Row.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_DStar_protons.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DStar_protons.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UI_IG88.ALO"
+ ],
+ "asset": "p_desert_ground_dust"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Lambda_Mouth.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Lambda_Mouth.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_Planet_Hoth_High.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_Planet_Hoth_High.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_LOW.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\W_STARS_LOW.ALO"
+ ],
+ "asset": "Lensflare0"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_NavyTrooper_Row.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_NavyTrooper_Row.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_DStar_TurretLasers.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DStar_TurretLasers.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": "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 \u0027Cin_EV_Stardestroyer_Warp.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EV_Stardestroyer_Warp.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_CINE_LUA.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO"
+ ],
+ "asset": "Lensflare0"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_DeathStar_Hangar.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_DeathStar_Hangar.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Fire_Medium.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_CINE.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\W_STARS_CINE.ALO"
+ ],
+ "asset": "Lensflare0"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier_Group.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Rbel_Soldier_Group.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "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": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_Volcano_Rock02.ALO"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027w_planet_volcanic.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "w_planet_volcanic.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027CIN_REb_CelebCharacters.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_REb_CelebCharacters.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "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.",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO"
+ ],
+ "asset": "Default.fx"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027Cin_Reb_CelebHall_Wall.tga\u0027 for context: [W_SITH_LEFTHALL.ALO].",
+ "severity": "Error",
+ "context": [
+ "W_SITH_LEFTHALL.ALO"
+ ],
+ "asset": "Cin_Reb_CelebHall_Wall.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "message": "Unable to find .ALO file \u0027Cin_EV_lambdaShuttle_150.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EV_lambdaShuttle_150.alo"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
+ "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\\EB_COMMANDCENTER.ALO"
+ ],
+ "asset": "p_explosion_small_delay00"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier",
+ "AET.ModVerify.Verifiers.Commons.TextureVeifier"
+ ],
+ "message": "Could not find texture \u0027UB_girder_B.tga\u0027 for context: [UV_MDU_CAGE.ALO].",
+ "severity": "Error",
+ "context": [
+ "UV_MDU_CAGE.ALO"
+ ],
+ "asset": "UB_girder_B.tga"
+ },
+ {
+ "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_01_STATION_D.ALO\u0027",
+ "severity": "Error",
+ "context": [
+ "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO"
+ ],
+ "asset": "p_uwstation_death"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0206_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0206_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0204_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0102_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0215_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0107_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Remove_Corruption_Leia"
+ ],
+ "asset": "U000_LEI0504_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Weather_Ambient_Clear_Sandstorm_Loop"
+ ],
+ "asset": "AMB_DES_CLEAR_LOOP_1.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0105_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0213_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0201_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0303_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0103_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0207_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Corrupt_Sabateur"
+ ],
+ "asset": "U000_DEF3006_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0309_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0209_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0209_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Weaken_Sabateur"
+ ],
+ "asset": "U000_DEF3106_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Remove_Corruption_Leia"
+ ],
+ "asset": "U000_LEI0503_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Remove_Corruption_Leia"
+ ],
+ "asset": "U000_LEI0502_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0212_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Weather_Ambient_Clear_Urban_Loop"
+ ],
+ "asset": "AMB_URB_CLEAR_LOOP_1.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0311_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0115_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0101_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Guard_Leia"
+ ],
+ "asset": "U000_LEI0401_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0315_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0106_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0106_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Increase_Production_Leia"
+ ],
+ "asset": "U000_LEI0603_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0104_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Remove_Corruption_Leia"
+ ],
+ "asset": "U000_LEI0501_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Gneneric_Test"
+ ],
+ "asset": "TESTUNITMOVE_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0108_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_StarDest_MC30_Frigate"
+ ],
+ "asset": "U000_MCF1601_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0111_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0111_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0211_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0110_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Guard_Leia"
+ ],
+ "asset": "U000_LEI0403_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0306_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0308_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0112_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0301_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Guard_Leia"
+ ],
+ "asset": "U000_LEI0404_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Tie_Mauler"
+ ],
+ "asset": "U000_TMC0212_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Star_Viper_Spinning_By"
+ ],
+ "asset": "EGL_STAR_VIPER_SPINNING_1.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0208_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Increase_Production_Leia"
+ ],
+ "asset": "U000_LEI0604_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "SFX_Anim_Beetle_Footsteps"
+ ],
+ "asset": "FS_BEETLE_3.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0109_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0202_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Increase_Production_Leia"
+ ],
+ "asset": "U000_LEI0602_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0305_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Assist_Move_Missile_Launcher"
+ ],
+ "asset": "U000_MAL0503_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Increase_Production_Leia"
+ ],
+ "asset": "U000_LEI0601_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Complete_Troops_Arc_Hammer"
+ ],
+ "asset": "U000_ARC3106_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "SFX_Anim_Beetle_Footsteps"
+ ],
+ "asset": "FS_BEETLE_4.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "SFX_Anim_Beetle_Footsteps"
+ ],
+ "asset": "FS_BEETLE_1.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0205_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0113_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0314_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0304_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0203_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "EHD_Death_Star_Activate"
+ ],
+ "asset": "C000_DST0102_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Select_Leia"
+ ],
+ "asset": "U000_LEI0114_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Produce_Troops_Arc_Hammer"
+ ],
+ "asset": "U000_ARC3104_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "SFX_Anim_Beetle_Footsteps"
+ ],
+ "asset": "FS_BEETLE_2.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Complete_Troops_Arc_Hammer"
+ ],
+ "asset": "U000_ARC3105_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0307_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0307_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Guard_Leia"
+ ],
+ "asset": "U000_LEI0402_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0312_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Move_Leia"
+ ],
+ "asset": "U000_LEI0210_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [
+ "Unit_Attack_Leia"
+ ],
+ "asset": "U000_LEI0313_ENG.WAV"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier"
+ ],
+ "message": "Could not find GUI texture \u0027underworld_logo_selected.tga\u0027 at location \u0027MegaTexture\u0027.",
+ "severity": "Error",
+ "context": [
+ "IDC_PLAY_FACTION_A_BUTTON_BIG",
+ "MegaTexture"
+ ],
+ "asset": "underworld_logo_selected.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier"
+ ],
+ "message": "Could not find GUI texture \u0027i_button_petro_sliver.tga\u0027 at location \u0027MegaTexture\u0027.",
+ "severity": "Error",
+ "context": [
+ "IDC_MENU_PETRO_LOGO",
+ "MegaTexture"
+ ],
+ "asset": "i_button_petro_sliver.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier"
+ ],
+ "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"
+ ],
+ "asset": "i_dialogue_button_large_middle_off.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier"
+ ],
+ "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_rollover.tga"
+ },
+ {
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier"
+ ],
+ "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"
+ ],
+ "asset": "underworld_logo_off.tga"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs b/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs
similarity index 83%
rename from src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs
rename to src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs
index d239112..0e5e203 100644
--- a/src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs
+++ b/src/ModVerify.CliApp/Settings/CommandLine/BaseModVerifyOptions.cs
@@ -3,57 +3,57 @@
using CommandLine;
using PG.StarWarsGame.Engine;
-namespace AET.ModVerifyTool.Options.CommandLine;
+namespace AET.ModVerify.App.Settings.CommandLine;
internal abstract class BaseModVerifyOptions
{
[Option('v', "verbose", Required = false, HelpText = "Sets output to verbose messages.")]
- public bool Verbose { get; set; }
+ public bool Verbose { get; init; }
[Option("offline", Default = false, HelpText = "When set, the application will work in offline mode and does not need an Internet connection.")]
- public bool OfflineMode { get; set; }
+ public bool OfflineMode { get; init; }
[Option("minSeverity", Required = false, Default = VerificationSeverity.Information,
HelpText = "When set, only findings with at least the specified severity value are processed.")]
- public VerificationSeverity MinimumSeverity { get; set; }
+ public VerificationSeverity MinimumSeverity { get; init; }
[Option("suppressions", Required = false, HelpText = "Path to a JSON suppression file.")]
- public string? Suppressions { get; set; }
+ 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 submods 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 or base games itself. " +
"The argument cannot be combined with any of --mods, --game or --fallbackGame")]
- public string? AutoPath { get; set; }
+ public string? AutoPath { 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. " +
"Leave empty, if you want to verify a game. If you want to use the interactive mode, leave this, --game and --fallbackGame empty.")]
- public IList? ModPaths { get; set; }
+ public IList? ModPaths { get; init; }
[Option("game", SetName = "manualPaths", Required = false, Default = null,
HelpText = "The path of the base game. For FoC mods this points to the FoC installation, for EaW mods this points to the EaW installation. " +
"Leave empty, if you want to auto-detect games. If you want to use the interactive mode, leave this, --mods and --fallbackGame empty. " +
"If this argument is set, you also need to set --mods (including sub mods) and --fallbackGame manually.")]
- public string? GamePath { get; set; }
+ public string? GamePath { get; init; }
[Option("fallbackGame", SetName = "manualPaths", Required = false, Default = null,
HelpText = "The path of the fallback game. Usually this points to the EaW installation. This argument only recognized if --game is set.")]
- public string? FallbackGamePath { get; set; }
+ 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'. " +
"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; set; }
+ public GameEngineType? GameType { get; init; }
[Option("additionalFallbackPaths", Required = false, Separator = ';',
HelpText = "Additional fallback paths, which may contain assets that shall be included when doing the verification. Do not add EaW here. " +
"Multiple paths can be separated using the ';' (semicolon) character.")]
- public IList? AdditionalFallbackPath { get; set; }
+ public IList? AdditionalFallbackPath { get; init; }
[Option("parallel", Default = false,
HelpText = "When set, game verifiers will run in parallel. " +
"While this may reduce analysis time, console output might be harder to read.")]
- public bool Parallel { get; set; }
+ public bool Parallel { get; init; }
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs
similarity index 59%
rename from src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs
rename to src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs
index 78132cc..dc60a73 100644
--- a/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs
+++ b/src/ModVerify.CliApp/Settings/CommandLine/CreateBaselineVerbOption.cs
@@ -1,10 +1,10 @@
using CommandLine;
-namespace AET.ModVerifyTool.Options.CommandLine;
+namespace AET.ModVerify.App.Settings.CommandLine;
[Verb("createBaseline", HelpText = "Verifies the specified game and creates a new baseline file at the specified location.")]
-internal class CreateBaselineVerbOption : BaseModVerifyOptions
+internal sealed class CreateBaselineVerbOption : BaseModVerifyOptions
{
[Option('o', "outFile", Required = true, HelpText = "The file path of the new baseline file.")]
- public string OutputFile { get; set; }
+ public required string OutputFile { get; init; }
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Settings/CommandLine/ModVerifyOptionsContainer.cs b/src/ModVerify.CliApp/Settings/CommandLine/ModVerifyOptionsContainer.cs
new file mode 100644
index 0000000..c9aab20
--- /dev/null
+++ b/src/ModVerify.CliApp/Settings/CommandLine/ModVerifyOptionsContainer.cs
@@ -0,0 +1,12 @@
+using AnakinRaW.ApplicationBase.Update.Options;
+
+namespace AET.ModVerify.App.Settings.CommandLine;
+
+internal sealed class ModVerifyOptionsContainer
+{
+ public BaseModVerifyOptions? ModVerifyOptions { get; init; }
+
+ public ApplicationUpdateOptions? UpdateOptions { get; init; }
+
+ public bool HasOptions => ModVerifyOptions is not null || UpdateOptions is not null;
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Settings/CommandLine/ModVerifyOptionsParser.cs b/src/ModVerify.CliApp/Settings/CommandLine/ModVerifyOptionsParser.cs
new file mode 100644
index 0000000..63a29d8
--- /dev/null
+++ b/src/ModVerify.CliApp/Settings/CommandLine/ModVerifyOptionsParser.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading;
+using AET.ModVerify.App.Utilities;
+using AnakinRaW.ApplicationBase.Environment;
+using AnakinRaW.ApplicationBase.Update.Options;
+using AnakinRaW.ExternalUpdater;
+using CommandLine;
+using CommandLine.Text;
+using Microsoft.Extensions.Logging;
+
+namespace AET.ModVerify.App.Settings.CommandLine;
+
+internal sealed class ModVerifyOptionsParser
+{
+ private readonly ApplicationEnvironment _applicationEnvironment;
+ private readonly ILogger? _logger;
+
+ [field: AllowNull, MaybeNull]
+ private Type[] AvailableVerbTypes => LazyInitializer.EnsureInitialized(ref field, GetAvailableVerbTypes)!;
+
+ public ModVerifyOptionsParser(ApplicationEnvironment applicationEnvironment, ILoggerFactory? loggerFactory)
+ {
+ _applicationEnvironment = applicationEnvironment;
+ _logger = loggerFactory?.CreateLogger(GetType());
+ }
+
+ public ModVerifyOptionsContainer Parse(IReadOnlyList args)
+ {
+ // If the application is updatable (.NET Framework) we need to remove potential arguments from the external updater
+ // in order to keep strict parsing rules enabled for better user error messages.
+ if (_applicationEnvironment.IsUpdatable())
+ args = StripExternalUpdateResults(args);
+
+ return ParseArguments(args);
+ }
+
+ private ModVerifyOptionsContainer ParseArguments(IReadOnlyList args)
+ {
+ // Empty arguments means that we are "interactive" mode (user simply double-clicked the executable)
+ if (args.Count == 0)
+ {
+ return new ModVerifyOptionsContainer
+ {
+ ModVerifyOptions = VerifyVerbOption.WithoutArguments,
+ UpdateOptions = null
+ };
+ }
+
+ var parseResult = Parser.Default.ParseArguments(args, AvailableVerbTypes);
+
+ BaseModVerifyOptions? modVerifyOptions = null;
+ ApplicationUpdateOptions? updateOptions = null;
+
+ parseResult.WithParsed(o => modVerifyOptions = o);
+ parseResult.WithParsed(o => updateOptions = o);
+
+ parseResult.WithNotParsed(_ =>
+ {
+ _logger?.LogError("Unable to parse command line");
+ Console.WriteLine(HelpText.AutoBuild(parseResult).ToString());
+ });
+
+ return new ModVerifyOptionsContainer
+ {
+ ModVerifyOptions = modVerifyOptions,
+ UpdateOptions = updateOptions,
+ };
+ }
+
+ public static IReadOnlyList StripExternalUpdateResults(IReadOnlyList args)
+ {
+ // Parser.Default.FormatCommandLine(ResultOption) as used in ProcessTool.cs either returns
+ // two argument segments or none (if Result == UpdaterNotRun)
+ if (args.Count < 2)
+ return args;
+
+ // The external updater promises to append the result to the arguments.
+ // Thus, it's sufficient to check the second last segment whether it matches.
+ var secondLast = args[^2];
+
+ return secondLast == ExternalUpdaterResultOptions.RawOptionString
+ ? [..args.Take(args.Count - 2)]
+ : args;
+ }
+
+ private Type[] GetAvailableVerbTypes()
+ {
+ return _applicationEnvironment.IsUpdatable()
+ ? [typeof(VerifyVerbOption), typeof(CreateBaselineVerbOption), typeof(ApplicationUpdateOptions)]
+ : [typeof(VerifyVerbOption), typeof(CreateBaselineVerbOption)];
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs
new file mode 100644
index 0000000..97f1536
--- /dev/null
+++ b/src/ModVerify.CliApp/Settings/CommandLine/VerifyVerbOption.cs
@@ -0,0 +1,39 @@
+using AET.ModVerify.Reporting;
+using CommandLine;
+
+namespace AET.ModVerify.App.Settings.CommandLine;
+
+[Verb("verify", HelpText = "Verifies the specified game and reports the findings.")]
+internal sealed class VerifyVerbOption : BaseModVerifyOptions
+{
+ internal static readonly VerifyVerbOption WithoutArguments = new()
+ {
+ IsRunningWithoutArguments = true,
+ SearchBaselineLocally = true,
+ };
+
+ [Option('o', "outDir", Required = false, HelpText = "Directory where result files shall be stored to.")]
+ 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.")]
+ 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.")]
+ public VerificationSeverity? MinimumFailureSeverity { get; set; }
+
+ [Option("ignoreAsserts", Required = false,
+ 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; }
+
+ [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; }
+
+ public bool IsRunningWithoutArguments { get; init; }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/GameInstallationsSettings.cs b/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs
similarity index 74%
rename from src/ModVerify.CliApp/Options/GameInstallationsSettings.cs
rename to src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs
index 667e2cf..00bc4f0 100644
--- a/src/ModVerify.CliApp/Options/GameInstallationsSettings.cs
+++ b/src/ModVerify.CliApp/Settings/GameInstallationsSettings.cs
@@ -1,11 +1,10 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using PG.StarWarsGame.Engine;
-namespace AET.ModVerifyTool.Options;
+namespace AET.ModVerify.App.Settings;
-internal record GameInstallationsSettings
+internal sealed record GameInstallationsSettings
{
public bool Interactive => string.IsNullOrEmpty(AutoPath) && ModPaths.Count == 0 && string.IsNullOrEmpty(GamePath);
@@ -17,13 +16,13 @@ internal record GameInstallationsSettings
public string? AutoPath { get; init; }
- public IList ModPaths { get; init; } = Array.Empty();
+ public IList ModPaths { get; init; } = [];
public string? GamePath { get; init; }
public string? FallbackGamePath { get; init; }
- public IList AdditionalFallbackPaths { get; init; } = Array.Empty();
+ public IList AdditionalFallbackPaths { get; init; } = [];
public GameEngineType? EngineType { get; init; }
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs
similarity index 72%
rename from src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs
rename to src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs
index 6a3b0bd..3376354 100644
--- a/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs
+++ b/src/ModVerify.CliApp/Settings/ModVerifyAppSettings.cs
@@ -1,9 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using AET.ModVerify.Reporting;
-using AET.ModVerify.Reporting.Settings;
using AET.ModVerify.Settings;
-namespace AET.ModVerifyTool.Options;
+namespace AET.ModVerify.App.Settings;
internal sealed class ModVerifyAppSettings
{
@@ -11,18 +10,14 @@ internal sealed class ModVerifyAppSettings
public required VerifyPipelineSettings VerifyPipelineSettings { get; init; }
- public required GlobalVerifyReportSettings GlobalReportSettings { get; init; }
+ public required ModVerifyReportSettings ReportSettings { get; init; }
public required GameInstallationsSettings GameInstallationsSettings { get; init; }
public VerificationSeverity? AppThrowsOnMinimumSeverity { get; init; }
- public string? ReportOutput { get; init; }
-
[MemberNotNullWhen(true, nameof(NewBaselinePath))]
public bool CreateNewBaseline => !string.IsNullOrEmpty(NewBaselinePath);
public string? NewBaselinePath { get; init; }
-
- public bool Offline { get; init; }
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs b/src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs
new file mode 100644
index 0000000..482b844
--- /dev/null
+++ b/src/ModVerify.CliApp/Settings/ModVerifyReportSettings.cs
@@ -0,0 +1,14 @@
+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/SettingsBuilder.cs b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs
similarity index 60%
rename from src/ModVerify.CliApp/SettingsBuilder.cs
rename to src/ModVerify.CliApp/Settings/SettingsBuilder.cs
index d2ba861..3c5535f 100644
--- a/src/ModVerify.CliApp/SettingsBuilder.cs
+++ b/src/ModVerify.CliApp/Settings/SettingsBuilder.cs
@@ -1,23 +1,20 @@
-using AET.ModVerify.Reporting;
-using AET.ModVerify.Reporting.Settings;
-using AET.ModVerify.Settings;
-using AET.ModVerifyTool.Options;
-using Microsoft.Extensions.DependencyInjection;
-using System;
+using System;
using System.Collections.Generic;
-using System.IO;
using System.IO.Abstractions;
+using AET.ModVerify.App.Settings.CommandLine;
+using AET.ModVerify.App.Utilities;
using AET.ModVerify.Pipeline;
-using AET.ModVerifyTool.Options.CommandLine;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Settings;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace AET.ModVerifyTool;
+namespace AET.ModVerify.App.Settings;
-internal sealed class SettingsBuilder(IServiceProvider services)
+internal sealed class SettingsBuilder(IServiceProvider serviceProvider)
{
- private readonly IFileSystem _fileSystem = services.GetRequiredService();
- private readonly ILogger? _logger =
- services.GetRequiredService()?.CreateLogger(typeof(SettingsBuilder));
+ private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(SettingsBuilder));
+ private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
public ModVerifyAppSettings BuildSettings(BaseModVerifyOptions options)
{
@@ -33,12 +30,6 @@ public ModVerifyAppSettings BuildSettings(BaseModVerifyOptions options)
private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions)
{
- var output = Environment.CurrentDirectory;
- var outDir = verifyOptions.OutputDirectory;
-
- if (!string.IsNullOrEmpty(outDir))
- output = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(Environment.CurrentDirectory, outDir!));
-
return new ModVerifyAppSettings
{
VerifyPipelineSettings = new VerifyPipelineSettings
@@ -54,9 +45,7 @@ private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions)
},
AppThrowsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity,
GameInstallationsSettings = BuildInstallationSettings(verifyOptions),
- GlobalReportSettings = BuilderGlobalReportSettings(verifyOptions),
- ReportOutput = output,
- Offline = verifyOptions.OfflineMode
+ ReportSettings = BuildReportSettings(verifyOptions),
};
VerificationSeverity? GetVerifierMinimumThrowSeverity()
@@ -66,8 +55,8 @@ private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions)
{
if (minFailSeverity == null)
{
- _logger?.LogWarning($"Verification is configured to fail fast but 'minFailSeverity' is not specified. " +
- $"Using severity '{VerificationSeverity.Information}'.");
+ _logger?.LogWarning(ModVerifyConstants.ConsoleEventId,
+ "Verification is configured to fail fast but 'minFailSeverity' is not specified. Using severity '{Info}'.", VerificationSeverity.Information);
minFailSeverity = VerificationSeverity.Information;
}
@@ -97,49 +86,28 @@ private ModVerifyAppSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOptio
},
AppThrowsOnMinimumSeverity = null,
GameInstallationsSettings = BuildInstallationSettings(baselineVerb),
- GlobalReportSettings = BuilderGlobalReportSettings(baselineVerb),
+ ReportSettings = BuildReportSettings(baselineVerb),
NewBaselinePath = baselineVerb.OutputFile,
- ReportOutput = null,
- Offline = baselineVerb.OfflineMode
};
}
- private GlobalVerifyReportSettings BuilderGlobalReportSettings(BaseModVerifyOptions options)
+ private static ModVerifyReportSettings BuildReportSettings(BaseModVerifyOptions options)
{
- return new GlobalVerifyReportSettings
+ var baselinePath = (options as VerifyVerbOption)?.Baseline;
+
+ return new ModVerifyReportSettings
{
- Baseline = CreateBaseline(),
- Suppressions = CreateSuppressions(),
+ BaselinePath = baselinePath,
MinimumReportSeverity = options.MinimumSeverity,
+ SearchBaselineLocally = SearchLocally(options),
+ SuppressionsPath = options.Suppressions
};
- VerificationBaseline CreateBaseline()
- {
- // It does not make sense to create a baseline on another baseline.
- if (options is not VerifyVerbOption verifyOptions || string.IsNullOrEmpty(verifyOptions.Baseline))
- return VerificationBaseline.Empty;
-
- using var fs = _fileSystem.FileStream.New(verifyOptions.Baseline!, FileMode.Open, FileAccess.Read);
-
- try
- {
- return VerificationBaseline.FromJson(fs);
- }
- catch (IncompatibleBaselineException)
- {
- Console.WriteLine($"The baseline '{verifyOptions.Baseline}' is not compatible with with version of ModVerify." +
- $"{Environment.NewLine}Please generate a new baseline file or download the latest version." +
- $"{Environment.NewLine}");
- throw;
- }
- }
-
- SuppressionList CreateSuppressions()
+ static bool SearchLocally(BaseModVerifyOptions o)
{
- if (options.Suppressions is null)
- return SuppressionList.Empty;
- using var fs = _fileSystem.FileStream.New(options.Suppressions, FileMode.Open, FileAccess.Read);
- return SuppressionList.FromJson(fs);
+ if (o is not VerifyVerbOption v)
+ return false;
+ return v.SearchBaselineLocally || v.LaunchedWithoutArguments();
}
}
@@ -167,16 +135,16 @@ private GameInstallationsSettings BuildInstallationSettings(BaseModVerifyOptions
var gamePath = options.GamePath;
if (!string.IsNullOrEmpty(gamePath))
- gamePath = _fileSystem.Path.GetFullPath(gamePath);
+ gamePath = _fileSystem.Path.GetFullPath(gamePath!);
string? fallbackGamePath = null;
if (!string.IsNullOrEmpty(gamePath) && !string.IsNullOrEmpty(options.FallbackGamePath))
- fallbackGamePath = _fileSystem.Path.GetFullPath(options.FallbackGamePath);
+ fallbackGamePath = _fileSystem.Path.GetFullPath(options.FallbackGamePath!);
var autoPath = options.AutoPath;
if (!string.IsNullOrEmpty(autoPath))
- autoPath = _fileSystem.Path.GetFullPath(autoPath);
+ autoPath = _fileSystem.Path.GetFullPath(autoPath!);
return new GameInstallationsSettings
{
diff --git a/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs b/src/ModVerify.CliApp/Updates/Github/GithubReleaseEntry.cs
similarity index 91%
rename from src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs
rename to src/ModVerify.CliApp/Updates/Github/GithubReleaseEntry.cs
index 968e6a9..4cd4585 100644
--- a/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs
+++ b/src/ModVerify.CliApp/Updates/Github/GithubReleaseEntry.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace AET.ModVerifyTool.Updates;
+namespace AET.ModVerify.App.Updates.Github;
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)]
[method: JsonConstructor]
diff --git a/src/ModVerify.CliApp/Updates/GithubReleaseList.cs b/src/ModVerify.CliApp/Updates/Github/GithubReleaseList.cs
similarity index 70%
rename from src/ModVerify.CliApp/Updates/GithubReleaseList.cs
rename to src/ModVerify.CliApp/Updates/Github/GithubReleaseList.cs
index a54edfb..92ef368 100644
--- a/src/ModVerify.CliApp/Updates/GithubReleaseList.cs
+++ b/src/ModVerify.CliApp/Updates/Github/GithubReleaseList.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-namespace AET.ModVerifyTool.Updates;
+namespace AET.ModVerify.App.Updates.Github;
internal sealed class GithubReleaseList : List;
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs b/src/ModVerify.CliApp/Updates/Github/GithubUpdateChecker.cs
similarity index 60%
rename from src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs
rename to src/ModVerify.CliApp/Updates/Github/GithubUpdateChecker.cs
index d0ab59b..df4d769 100644
--- a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs
+++ b/src/ModVerify.CliApp/Updates/Github/GithubUpdateChecker.cs
@@ -1,29 +1,31 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
+using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Semver;
-namespace AET.ModVerifyTool.Updates;
+namespace AET.ModVerify.App.Updates.Github;
-internal sealed class ModVerifyUpdaterChecker
+internal class GithubUpdateChecker
{
private readonly ILogger? _logger;
+ private readonly ModVerifyAppEnvironment _appEnvironment;
- public ModVerifyUpdaterChecker(IServiceProvider serviceProvider)
+ public GithubUpdateChecker(IServiceProvider serviceProvider)
{
_logger = serviceProvider.GetService()?.CreateLogger(GetType());
+ _appEnvironment = serviceProvider.GetRequiredService();
}
- public async Task CheckForUpdateAsync()
+ public async Task CheckForUpdateAsync()
{
var githubReleases = await DownloadReleaseList().ConfigureAwait(false);
- var branch = ModVerifyUpdaterInformation.BranchName;
+ var branch = GithubUpdateConstants.BranchName;
var latestRelease = githubReleases.FirstOrDefault(r => r.Branch == branch);
if (latestRelease == null)
@@ -32,20 +34,20 @@ public async Task CheckForUpdateAsync()
if (!SemVersion.TryParse(latestRelease.Tag, SemVersionStyles.Any, out var latestVersion))
throw new InvalidOperationException($"Cannot create a version from tag '{latestRelease.Tag}'.");
- var currentVersion = ModVerifyUpdaterInformation.CurrentVersion;
+ var currentVersion = _appEnvironment.AssemblyInfo.InformationalAsSemVer();
if (currentVersion is null)
throw new InvalidOperationException("Unable to get current version.");
if (SemVersion.ComparePrecedence(currentVersion, latestVersion) >= 0)
{
- _logger?.LogDebug($"No update available - [Current Version = {currentVersion}], [Available Version = {latestVersion}]");
+ _logger?.LogDebug("No update available - [Current Version = {CurrentVersion}], [Available Version = {LatestVersion}]", currentVersion, latestVersion);
return default;
}
- _logger?.LogDebug($"Update available - [Current Version = {currentVersion}], [Available Version = {latestVersion}]");
- return new UpdateInfo
+ _logger?.LogDebug("Update available - [Current Version = {CurrentVersion}], [Available Version = {LatestVersion}]", currentVersion, latestVersion);
+ return new GithubUpdateInfo
{
- DownloadLink = ModVerifyUpdaterInformation.ModVerifyReleasesDownloadLink,
+ DownloadLink = GithubUpdateConstants.ModVerifyReleasesDownloadLink,
IsUpdateAvailable = true,
NewVersion = latestVersion.ToString()
};
@@ -54,8 +56,8 @@ public async Task CheckForUpdateAsync()
private static async Task DownloadReleaseList()
{
using var httpClient = new HttpClient();
- httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ModVerifyUpdaterInformation.UserAgent);
- using var downloadStream = await httpClient.GetStreamAsync(ModVerifyUpdaterInformation.GithubReleasesApiLink).ConfigureAwait(false);
+ httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(GithubUpdateConstants.UserAgent);
+ using var downloadStream = await httpClient.GetStreamAsync(GithubUpdateConstants.GithubReleasesApiLink).ConfigureAwait(false);
using var jsonStream = new MemoryStream();
await downloadStream.CopyToAsync(jsonStream).ConfigureAwait(false);
diff --git a/src/ModVerify.CliApp/Updates/Github/GithubUpdateConstants.cs b/src/ModVerify.CliApp/Updates/Github/GithubUpdateConstants.cs
new file mode 100644
index 0000000..7695246
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/Github/GithubUpdateConstants.cs
@@ -0,0 +1,9 @@
+namespace AET.ModVerify.App.Updates.Github;
+
+internal static class GithubUpdateConstants
+{
+ public const string BranchName = "main";
+ public const string GithubReleasesApiLink = "https://api.github.com/repos/AlamoEngine-Tools/ModVerify/releases";
+ public const string ModVerifyReleasesDownloadLink = "https://github.com/AlamoEngine-Tools/ModVerify/releases/latest";
+ public const string UserAgent = "AET.Modifo";
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/UpdateInfo.cs b/src/ModVerify.CliApp/Updates/Github/GithubUpdateInfo.cs
similarity index 75%
rename from src/ModVerify.CliApp/Updates/UpdateInfo.cs
rename to src/ModVerify.CliApp/Updates/Github/GithubUpdateInfo.cs
index b6a6505..8d8f532 100644
--- a/src/ModVerify.CliApp/Updates/UpdateInfo.cs
+++ b/src/ModVerify.CliApp/Updates/Github/GithubUpdateInfo.cs
@@ -1,8 +1,8 @@
using System.Diagnostics.CodeAnalysis;
-namespace AET.ModVerifyTool.Updates;
+namespace AET.ModVerify.App.Updates.Github;
-internal readonly struct UpdateInfo
+internal readonly struct GithubUpdateInfo
{
public string DownloadLink { get; init; }
diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdateMode.cs b/src/ModVerify.CliApp/Updates/ModVerifyUpdateMode.cs
new file mode 100644
index 0000000..882a6a2
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/ModVerifyUpdateMode.cs
@@ -0,0 +1,8 @@
+namespace AET.ModVerify.App.Updates;
+
+public enum ModVerifyUpdateMode
+{
+ CheckOnly,
+ InteractiveUpdate,
+ AutoUpdate,
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdater.cs b/src/ModVerify.CliApp/Updates/ModVerifyUpdater.cs
new file mode 100644
index 0000000..af7d369
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/ModVerifyUpdater.cs
@@ -0,0 +1,149 @@
+using AET.ModVerify.App.Updates.Github;
+using AET.ModVerify.App.Updates.SelfUpdate;
+using AET.ModVerify.App.Utilities;
+using AnakinRaW.ApplicationBase;
+using AnakinRaW.ApplicationBase.Update.Options;
+using AnakinRaW.AppUpdaterFramework.Metadata.Update;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AET.ModVerify.App.Updates;
+
+internal sealed class ModVerifyUpdater
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ILogger? _logger;
+ private readonly ModVerifyAppEnvironment _appEnvironment;
+
+ public ModVerifyUpdater(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ _logger = serviceProvider.GetService()?.CreateLogger(GetType());
+ _appEnvironment = serviceProvider.GetRequiredService();
+ }
+
+ public async Task RunUpdateProcedure(ApplicationUpdateOptions updateOptions, ModVerifyUpdateMode mode)
+ {
+ _logger?.LogTrace("Running update procedure - '{mode}'", mode);
+
+ // If we are in the check-only mode, GitHub check is sufficient.
+ if (mode == ModVerifyUpdateMode.CheckOnly)
+ {
+ await CheckForUpdateAndReport().ConfigureAwait(false);
+ return;
+ }
+
+ await UpdateApplication(updateOptions, mode).ConfigureAwait(false);
+ }
+
+ private async Task UpdateApplication(ApplicationUpdateOptions updateOptions, ModVerifyUpdateMode mode)
+ {
+ if (!_appEnvironment.IsUpdatable(out var updatableEnvironment))
+ {
+ _logger?.LogWarning("Application is not updatable, yet we entered the update path. Checking only.");
+ await CheckForUpdateAndReport().ConfigureAwait(false);
+ return;
+ }
+
+ var updater = new ModVerifyApplicationUpdater(updatableEnvironment, _serviceProvider);
+
+ var actualBranchName = updater.GetBranchNameFromRegistry(updateOptions.BranchName, false);
+ var branch = updater.CreateBranch(actualBranchName, updateOptions.ManifestUrl);
+
+ using (ConsoleUtilities.CreateHorizontalFrame(length: 40, startWithNewLine: true, newLineAtEnd: true))
+ {
+ var currentAction = "checking for update";
+ try
+ {
+ var updateCheckSpinner = new ConsoleSpinnerOptions
+ {
+ CompletedMessage = "Update check completed.",
+ RunningMessage = "Checking for update...",
+ FailedMessage = "Update check failed",
+ HideCursor = true
+ };
+
+ var updateCatalog = await ConsoleSpinner.Run(async () =>
+ await updater.CheckForUpdateAsync(branch, CancellationToken.None),
+ updateCheckSpinner);
+
+
+ if (updateCatalog.Action != UpdateCatalogAction.Update)
+ {
+ Console.WriteLine("No update available.");
+ return;
+ }
+
+ Console.ForegroundColor = ConsoleColor.DarkGreen;
+ Console.WriteLine($"New update available: Version {updateCatalog.UpdateReference.Version}");
+ Console.ResetColor();
+
+ if (mode == ModVerifyUpdateMode.InteractiveUpdate)
+ {
+ var shallUpdate = ConsoleUtilities.UserYesNoQuestion("Do you want to update now?");
+ if (!shallUpdate)
+ return;
+ }
+
+ currentAction = "updating";
+
+
+ var updatingSpinner = new ConsoleSpinnerOptions
+ {
+ RunningMessage = $"Updating {ModVerifyConstants.AppNameString}...",
+ HideCursor = true
+ };
+ await ConsoleSpinner.Run(async () =>
+ await updater.UpdateAsync(updateCatalog, CancellationToken.None),
+ updatingSpinner);
+ }
+ catch (Exception e)
+ {
+ WriteError(e, $"Error while {currentAction}: {e.Message}");
+ }
+ }
+ }
+
+ private async Task CheckForUpdateAndReport()
+ {
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Checking for available update...");
+ try
+ {
+ var updateInfo = await new GithubUpdateChecker(_serviceProvider)
+ .CheckForUpdateAsync().ConfigureAwait(false);
+
+ if (updateInfo.IsUpdateAvailable)
+ {
+ using (ConsoleUtilities.HorizontalLineSeparatedBlock(startWithNewLine: true, newLineAtEnd: true))
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGreen;
+ Console.WriteLine("New Update Available!");
+ Console.ResetColor();
+ Console.WriteLine($"Version: {updateInfo.NewVersion}, Download here: {updateInfo.DownloadLink}");
+ }
+ }
+ else
+ {
+ _logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "No update available.");
+ }
+ }
+ catch (Exception e)
+ {
+ _logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Unable to check for updates due to an internal error: {message}", e.Message);
+ _logger?.LogTrace(e, "Checking for update failed: {message}", e.Message);
+ }
+ }
+
+ private void WriteError(Exception e, string? customMessage)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.DarkRed;
+ if (!string.IsNullOrEmpty(customMessage))
+ Console.WriteLine(customMessage);
+ Console.ResetColor();
+ _logger?.LogError(e, e.Message);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs
deleted file mode 100644
index 2fa1022..0000000
--- a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Diagnostics;
-using Semver;
-
-namespace AET.ModVerifyTool.Updates;
-
-internal static class ModVerifyUpdaterInformation
-{
- public const string BranchName = "main";
- public const string GithubReleasesApiLink = "https://api.github.com/repos/AlamoEngine-Tools/ModVerify/releases";
- public const string ModVerifyReleasesDownloadLink = "https://github.com/AlamoEngine-Tools/ModVerify/releases/latest";
- public const string UserAgent = "AET.Modifo";
-
- public static readonly SemVersion? CurrentVersion;
-
- static ModVerifyUpdaterInformation()
- {
- var currentAssembly = typeof(ModVerifyUpdaterInformation).Assembly;
- var fi = FileVersionInfo.GetVersionInfo(currentAssembly.Location);
- SemVersion.TryParse(fi.ProductVersion, SemVersionStyles.Any, out var currentVersion);
- CurrentVersion = currentVersion;
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/SelfUpdate/AssemblyInfo.cs b/src/ModVerify.CliApp/Updates/SelfUpdate/AssemblyInfo.cs
new file mode 100644
index 0000000..4b94b5e
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/SelfUpdate/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+#if NETFRAMEWORK
+using AnakinRaW.AppUpdaterFramework.Attributes;
+
+[assembly: UpdateProduct("AET ModVerify")]
+[assembly: UpdateComponent("AET.ModVerify.Exe", Name = "AET ModVerify")]
+#endif
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/SelfUpdate/ModVerifyApplicationUpdater.cs b/src/ModVerify.CliApp/Updates/SelfUpdate/ModVerifyApplicationUpdater.cs
new file mode 100644
index 0000000..9b97043
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/SelfUpdate/ModVerifyApplicationUpdater.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using AnakinRaW.ApplicationBase.Environment;
+using AnakinRaW.ApplicationBase.Update;
+using AnakinRaW.AppUpdaterFramework.Metadata.Product;
+using AnakinRaW.AppUpdaterFramework.Metadata.Update;
+
+namespace AET.ModVerify.App.Updates.SelfUpdate;
+
+
+internal class ModVerifyApplicationUpdater(
+ UpdatableApplicationEnvironment environment,
+ IServiceProvider serviceProvider)
+ : ApplicationUpdater(environment, serviceProvider)
+{
+ public override async Task CheckForUpdateAsync(ProductBranch branch, CancellationToken token = default)
+ {
+ var updateReference = ProductService.CreateProductReference(null, branch);
+
+ var updateCatalog = await UpdateService.CheckForUpdatesAsync(updateReference, token);
+
+ if (updateCatalog is null)
+ throw new InvalidOperationException("Update service was already doing something.");
+
+ return updateCatalog.Action is UpdateCatalogAction.Install or UpdateCatalogAction.Uninstall
+ ? throw new NotSupportedException("Install and Uninstall operations are not supported")
+ : updateCatalog;
+ }
+
+ public override async Task UpdateAsync(UpdateCatalog updateCatalog, CancellationToken token = default)
+ {
+ var updateResult = await UpdateService.UpdateAsync(updateCatalog, token).ConfigureAwait(false);
+ if (updateResult is null)
+ throw new InvalidOperationException("There is already an update running.");
+
+ var resultHandler = new ModVerifyUpdateResultHandler(Environment, ServiceProvider);
+ await resultHandler.Handle(updateResult).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/SelfUpdate/ModVerifyUpdateResultHandler.cs b/src/ModVerify.CliApp/Updates/SelfUpdate/ModVerifyUpdateResultHandler.cs
new file mode 100644
index 0000000..42ab413
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/SelfUpdate/ModVerifyUpdateResultHandler.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Threading.Tasks;
+using AnakinRaW.ApplicationBase.Environment;
+using AnakinRaW.ApplicationBase.Update;
+using AnakinRaW.AppUpdaterFramework.Handlers;
+using AnakinRaW.AppUpdaterFramework.Updater;
+
+namespace AET.ModVerify.App.Updates.SelfUpdate;
+
+internal sealed class ModVerifyUpdateResultHandler(
+ UpdatableApplicationEnvironment applicationEnvironment,
+ IServiceProvider serviceProvider)
+ : ApplicationUpdateResultHandler(applicationEnvironment, serviceProvider)
+{
+ protected override Task ShowError(UpdateResult updateResult)
+ {
+ Console.WriteLine();
+ Console.WriteLine($"Update failed with error: {updateResult.ErrorMessage}");
+ return base.ShowError(updateResult);
+ }
+
+ protected override void RestartApplication(RestartReason reason)
+ {
+ Console.WriteLine();
+ Console.WriteLine("Restarting application to complete update...");
+ base.RestartApplication(reason);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs
new file mode 100644
index 0000000..b2e7ab0
--- /dev/null
+++ b/src/ModVerify.CliApp/Utilities/ExtensionMethods.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics.CodeAnalysis;
+using AET.ModVerify.App.Settings.CommandLine;
+using AnakinRaW.ApplicationBase.Environment;
+using PG.StarWarsGame.Engine;
+using PG.StarWarsGame.Infrastructure.Games;
+
+namespace AET.ModVerify.App.Utilities;
+
+internal static class ExtensionMethods
+{
+ public static GameEngineType ToEngineType(this GameType type)
+ {
+ return type == GameType.Foc ? GameEngineType.Foc : GameEngineType.Eaw;
+ }
+
+ public static GameType FromEngineType(this GameEngineType type)
+ {
+ return type == GameEngineType.Foc ? GameType.Foc : GameType.Eaw;
+ }
+
+ extension(ApplicationEnvironment modVerifyEnvironment)
+ {
+ public bool IsUpdatable()
+ {
+ return modVerifyEnvironment.IsUpdatable(out _);
+ }
+
+ public bool IsUpdatable([NotNullWhen(true)] out UpdatableApplicationEnvironment? updatableEnvironment)
+ {
+ updatableEnvironment = modVerifyEnvironment as UpdatableApplicationEnvironment;
+ return updatableEnvironment is not null;
+ }
+ }
+
+ public static bool LaunchedWithoutArguments(this BaseModVerifyOptions options)
+ {
+ if (options is VerifyVerbOption verifyOptions)
+ return verifyOptions.IsRunningWithoutArguments;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs
new file mode 100644
index 0000000..6e6664a
--- /dev/null
+++ b/src/ModVerify.CliApp/Utilities/ModVerifyConsoleUtilities.cs
@@ -0,0 +1,30 @@
+using AnakinRaW.ApplicationBase;
+using Figgle;
+using System;
+
+namespace AET.ModVerify.App.Utilities;
+
+[GenerateFiggleText("HeaderText", "standard", ModVerifyConstants.AppNameString)]
+internal static partial class ModVerifyConsoleUtilities
+{
+ public static void WriteHeader(string? version = null)
+ {
+ const int lineLength = 73;
+ const string author = "by AnakinRaW";
+
+ ConsoleUtilities.WriteHorizontalLine('*', lineLength);
+ Console.WriteLine(HeaderText);
+ if (!string.IsNullOrEmpty(version))
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleUtilities.WriteLineRight($"Version: {version}", lineLength);
+ Console.ResetColor();
+ Console.WriteLine();
+ }
+ ConsoleUtilities.WriteHorizontalLine('*', lineLength);
+
+ ConsoleUtilities.WriteLineRight(author, lineLength);
+ Console.WriteLine();
+ Console.WriteLine();
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Utilities/Spinner.cs b/src/ModVerify.CliApp/Utilities/Spinner.cs
new file mode 100644
index 0000000..f25edda
--- /dev/null
+++ b/src/ModVerify.CliApp/Utilities/Spinner.cs
@@ -0,0 +1,172 @@
+using AnakinRaW.CommonUtilities;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AET.ModVerify.App.Utilities;
+
+///
+/// Options for configuring a .
+///
+public sealed class ConsoleSpinnerOptions
+{
+ public string? RunningMessage { get; init; }
+ public string? CompletedMessage { get; init; }
+ public string? FailedMessage { get; init; }
+ public bool HideCursor { get; init; }
+ public TextWriter Writer { get; init; } = Console.Out;
+ public int Interval { get; init; } = 200;
+ public string[] Animation { get; init; } = ["|", "/", "-", "\\"];
+
+ public static ConsoleSpinnerOptions Default { get; } = new();
+}
+
+
+
+internal sealed class ConsoleSpinner : IAsyncDisposable
+{
+ private readonly ConsoleSpinnerOptions _options;
+ private readonly CancellationTokenSource _cts = new();
+ private readonly Task _observedTask;
+ private readonly bool _origCursorVisibility;
+ private readonly string[] _animation;
+ private int _frame;
+ private int _lastTextLength;
+
+ private ConsoleSpinner(Task observedTask, ConsoleSpinnerOptions options)
+ {
+ _observedTask = observedTask;
+ _options = options;
+ _animation = options.Animation;
+ _origCursorVisibility = Console.CursorVisible;
+
+ if (_options.HideCursor)
+ Console.CursorVisible = false;
+
+ SpinnerLoop().Forget();
+ }
+
+ public static async Task Run(Task task, ConsoleSpinnerOptions? options = null)
+ {
+ options ??= ConsoleSpinnerOptions.Default;
+ await using var spinner = new ConsoleSpinner(task, options);
+ var result = await task.ConfigureAwait(false);
+ return result;
+ }
+
+ public static async Task Run(Task task, ConsoleSpinnerOptions? options = null)
+ {
+ options ??= ConsoleSpinnerOptions.Default;
+ await using var spinner = new ConsoleSpinner(task, options);
+ await task.ConfigureAwait(false);
+ }
+
+ public static Task Run(Func asyncAction, ConsoleSpinnerOptions? options = null)
+ {
+ if (asyncAction is null)
+ throw new ArgumentNullException(nameof(asyncAction));
+ return Run(asyncAction(), options);
+ }
+
+ public static Task Run(Func> asyncAction, ConsoleSpinnerOptions? options = null)
+ {
+ if (asyncAction is null)
+ throw new ArgumentNullException(nameof(asyncAction));
+ return Run(asyncAction(), options);
+ }
+
+ public static ConsoleSpinner Endless(ConsoleSpinnerOptions? options = null)
+ {
+ options ??= ConsoleSpinnerOptions.Default;
+ var tcs = new TaskCompletionSource
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
diff --git a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs
index a27ee9b..6405dbd 100644
--- a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs
+++ b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs
@@ -26,13 +26,13 @@ protected override void RunCore(CancellationToken token)
{
try
{
- Logger?.LogDebug($"Running verifier '{GameVerifier.FriendlyName}'...");
+ Logger?.LogDebug("Running verifier '{Name}'...", GameVerifier.FriendlyName);
ReportProgress(new ProgressEventArgs(0.0, "Started"));
GameVerifier.Progress += OnVerifyProgress;
GameVerifier.Verify(token);
- Logger?.LogDebug($"Finished verifier '{GameVerifier.FriendlyName}'");
+ Logger?.LogDebug("Finished verifier '{Name}'", GameVerifier.FriendlyName);
ReportProgress(new ProgressEventArgs(1.0, "Finished"));
}
finally
diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs
index b7ac00d..810651a 100644
--- a/src/ModVerify/Pipeline/GameVerifyPipeline.cs
+++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs
@@ -103,6 +103,7 @@ 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;
}
diff --git a/src/ModVerify/Reporting/IncompatibleBaselineException.cs b/src/ModVerify/Reporting/IncompatibleBaselineException.cs
deleted file mode 100644
index c9a9eb1..0000000
--- a/src/ModVerify/Reporting/IncompatibleBaselineException.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace AET.ModVerify.Reporting;
-
-public sealed class IncompatibleBaselineException : Exception
-{
- public override string Message => "The specified baseline is not compatible to this version of the application.";
-}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/InvalidBaselineException.cs b/src/ModVerify/Reporting/InvalidBaselineException.cs
new file mode 100644
index 0000000..37ab9c8
--- /dev/null
+++ b/src/ModVerify/Reporting/InvalidBaselineException.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace AET.ModVerify.Reporting;
+
+public sealed class InvalidBaselineException : Exception
+{
+ public InvalidBaselineException(string message) : base(message)
+ {
+ }
+
+ public InvalidBaselineException(string? message, Exception? inner) : base(message, inner)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Json/JsonBaselineParser.cs b/src/ModVerify/Reporting/Json/JsonBaselineParser.cs
new file mode 100644
index 0000000..ef2f5d1
--- /dev/null
+++ b/src/ModVerify/Reporting/Json/JsonBaselineParser.cs
@@ -0,0 +1,38 @@
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace AET.ModVerify.Reporting.Json;
+
+public static class JsonBaselineParser
+{
+ public static VerificationBaseline Parse(Stream dataStream)
+ {
+ if (dataStream == null)
+ throw new ArgumentNullException(nameof(dataStream));
+ try
+ {
+ var jsonNode = JsonNode.Parse(dataStream);
+ var jsonBaseline = ParseCore(jsonNode);
+
+ if (jsonBaseline is null)
+ throw new InvalidBaselineException($"Unable to parse input from stream to {nameof(VerificationBaseline)}. Unknown Error!");
+
+ return new VerificationBaseline(jsonBaseline);
+ }
+ catch (JsonException cause)
+ {
+ throw new InvalidBaselineException(cause.Message, cause);
+ }
+ }
+
+ private static JsonVerificationBaseline? ParseCore(JsonNode? jsonData)
+ {
+ if (jsonData is null)
+ return null;
+
+ JsonBaselineSchema.Evaluate(jsonData);
+ return jsonData.Deserialize();
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs b/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs
new file mode 100644
index 0000000..7c8b02a
--- /dev/null
+++ b/src/ModVerify/Reporting/Json/JsonBaselineSchema.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Nodes;
+using Json.Schema;
+
+namespace AET.ModVerify.Reporting.Json;
+
+public static class JsonBaselineSchema
+{
+ private static readonly JsonSchema Schema;
+ private static readonly EvaluationOptions EvaluationOptions;
+
+ static JsonBaselineSchema()
+ {
+ var evalvOptions = new EvaluationOptions
+ {
+ EvaluateAs = SpecVersion.Draft202012,
+ OutputFormat = OutputFormat.Hierarchical,
+ AllowReferencesIntoUnknownKeywords = false
+ };
+
+ Schema = GetCurrentSchema();
+ EvaluationOptions = evalvOptions;
+ }
+
+ ///
+ /// Evaluates a JSON node against the ModVerify Baseline JSON schema.
+ ///
+ /// The JSON node to evaluate.
+ /// is not valid against the baseline JSON schema.
+ /// is .
+ public static void Evaluate(JsonNode json)
+ {
+ if (json == null)
+ throw new ArgumentNullException(nameof(json));
+ var result = Schema.Evaluate(json, EvaluationOptions);
+ ThrowOnValidationError(result);
+ }
+
+ private static void ThrowOnValidationError(EvaluationResults result)
+ {
+ if (!result.IsValid)
+ {
+ var error = GetFirstError(result);
+ var errorMessage = "Baseline JSON not valid";
+
+ if (error is null)
+ errorMessage += ": Unknown Error";
+ else
+ errorMessage += $": {error}";
+
+ throw new InvalidBaselineException(errorMessage);
+ }
+ }
+
+ private static KeyValuePair? GetFirstError(EvaluationResults result)
+ {
+ if (result.HasErrors)
+ return result.Errors!.First();
+ foreach (var child in result.Details)
+ {
+ var error = GetFirstError(child);
+ if (error is not null)
+ return error;
+ }
+ return null;
+ }
+
+ private static JsonSchema GetCurrentSchema()
+ {
+ using var resourceStream = typeof(JsonBaselineSchema)
+ .Assembly.GetManifestResourceStream($"AET.ModVerify.Resources.Schemas.{GetVersionedPath()}.baseline.json");
+
+ Debug.Assert(resourceStream is not null);
+ var schema = JsonSchema.FromStream(resourceStream!).GetAwaiter().GetResult();
+
+ var id = schema.GetId();
+ if (id is null || !UriContainsVersion(id, VerificationBaseline.LatestVersionString))
+ throw new InvalidOperationException("Internal error: The embedded schema version does not match the expected baseline version!");
+
+ return schema;
+ }
+
+ private static bool UriContainsVersion(Uri id, string latestVersionString)
+ {
+ foreach (var segment in id.Segments)
+ {
+ var trimmed = segment.AsSpan().TrimEnd('/');
+ if (trimmed.Equals(latestVersionString, StringComparison.OrdinalIgnoreCase))
+ return true;
+ }
+ return false;
+ }
+
+ private static string GetVersionedPath()
+ {
+ var version = VerificationBaseline.LatestVersion;
+ var sb = new StringBuilder();
+
+ AddVersionSegment(version.Major, ref sb);
+ AddVersionSegment(version.Minor, ref sb);
+ AddVersionSegment(version.Build, ref sb);
+ AddVersionSegment(version.Revision, ref sb);
+
+ // Remove the trailing dot
+ sb.Length -= 1;
+
+ return sb.ToString();
+
+ static void AddVersionSegment(int segment, ref StringBuilder sb)
+ {
+ if (segment >= 0)
+ {
+ sb.Append('_');
+ sb.Append(segment);
+ sb.Append(".");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Json/JsonVerificationError.cs b/src/ModVerify/Reporting/Json/JsonVerificationError.cs
index ac80810..55f7b92 100644
--- a/src/ModVerify/Reporting/Json/JsonVerificationError.cs
+++ b/src/ModVerify/Reporting/Json/JsonVerificationError.cs
@@ -31,7 +31,7 @@ private JsonVerificationError(
string message,
VerificationSeverity severity,
IEnumerable? contextEntries,
- string asset)
+ string? asset)
{
Id = id;
VerifierChain = verifierChain ?? [];
diff --git a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs
index a2a00c5..cb6b258 100644
--- a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs
@@ -14,6 +14,7 @@ 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)
diff --git a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs
index 601e424..7de81eb 100644
--- a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs
+++ b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs
@@ -7,49 +7,46 @@ namespace AET.ModVerify.Reporting.Reporters;
public static class VerificationReportersExtensions
{
- public static IServiceCollection RegisterJsonReporter(this IServiceCollection serviceCollection)
+ extension(IServiceCollection serviceCollection)
{
- return RegisterJsonReporter(serviceCollection, new JsonReporterSettings
+ public IServiceCollection RegisterJsonReporter()
{
- OutputDirectory = "."
- });
- }
+ return RegisterJsonReporter(serviceCollection, new JsonReporterSettings
+ {
+ OutputDirectory = "."
+ });
+ }
- public static IServiceCollection RegisterTextFileReporter(this IServiceCollection serviceCollection)
- {
- return RegisterTextFileReporter(serviceCollection, new TextFileReporterSettings
+ public IServiceCollection RegisterTextFileReporter()
{
- OutputDirectory = "."
- });
- }
+ return RegisterTextFileReporter(serviceCollection, new TextFileReporterSettings
+ {
+ OutputDirectory = "."
+ });
+ }
- public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection, bool summaryOnly = false)
- {
- return RegisterConsoleReporter(serviceCollection, new VerifyReportSettings
+ public IServiceCollection RegisterConsoleReporter(bool summaryOnly = false)
{
- MinimumReportSeverity = VerificationSeverity.Error
- }, summaryOnly);
- }
+ return RegisterConsoleReporter(serviceCollection, new VerifyReportSettings
+ {
+ MinimumReportSeverity = VerificationSeverity.Error
+ }, summaryOnly);
+ }
- public static IServiceCollection RegisterJsonReporter(
- this IServiceCollection serviceCollection,
- JsonReporterSettings settings)
- {
- return serviceCollection.AddSingleton(sp => new JsonReporter(settings, sp));
- }
+ public IServiceCollection RegisterJsonReporter(JsonReporterSettings settings)
+ {
+ return serviceCollection.AddSingleton(sp => new JsonReporter(settings, sp));
+ }
- public static IServiceCollection RegisterTextFileReporter(
- this IServiceCollection serviceCollection,
- TextFileReporterSettings settings)
- {
- return serviceCollection.AddSingleton(sp => new TextFileReporter(settings, sp));
- }
+ public IServiceCollection RegisterTextFileReporter(TextFileReporterSettings settings)
+ {
+ return serviceCollection.AddSingleton(sp => new TextFileReporter(settings, sp));
+ }
- public static IServiceCollection RegisterConsoleReporter(
- this IServiceCollection serviceCollection,
- VerifyReportSettings settings,
- bool summaryOnly = false)
- {
- return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, summaryOnly, sp));
+ public IServiceCollection RegisterConsoleReporter(VerifyReportSettings settings,
+ bool summaryOnly = false)
+ {
+ return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, summaryOnly, sp));
+ }
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/VerificationBaseline.cs
index 55a8973..c37539b 100644
--- a/src/ModVerify/Reporting/VerificationBaseline.cs
+++ b/src/ModVerify/Reporting/VerificationBaseline.cs
@@ -11,7 +11,8 @@ namespace AET.ModVerify.Reporting;
public sealed class VerificationBaseline : IReadOnlyCollection
{
- private static readonly Version LatestVersion = new(2, 0);
+ public static readonly Version LatestVersion = new(2, 0);
+ public static readonly string LatestVersionString = LatestVersion.ToString(2);
public static readonly VerificationBaseline Empty = new(VerificationSeverity.Information, []);
@@ -60,14 +61,7 @@ public Task ToJsonAsync(Stream stream)
public static VerificationBaseline FromJson(Stream stream)
{
- var baselineJson = JsonSerializer.Deserialize(stream, JsonSerializerOptions.Default);
- if (baselineJson is null)
- throw new InvalidOperationException("Unable to deserialize baseline.");
-
- if (baselineJson.Version is null || baselineJson.Version != LatestVersion)
- throw new IncompatibleBaselineException();
-
- return new VerificationBaseline(baselineJson);
+ return JsonBaselineParser.Parse(stream);
}
///
diff --git a/src/ModVerify/Resources/Schemas/2.0/baseline.json b/src/ModVerify/Resources/Schemas/2.0/baseline.json
new file mode 100644
index 0000000..2520a58
--- /dev/null
+++ b/src/ModVerify/Resources/Schemas/2.0/baseline.json
@@ -0,0 +1,70 @@
+{
+ "$id": "https://AlamoEngine-Tools.github.io/schemas/mod-verify/2.0/baseline",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "description": "Represents a baseline for AET ModVerify",
+ "type": "object",
+ "$defs": {
+ "severity": {
+ "enum": [ "Information", "Warning", "Error", "Critical" ]
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "asset": {
+ "type": "string"
+ },
+ "severity": {
+ "$ref": "#/$defs/severity"
+ },
+ "verifiers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "context": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "message",
+ "asset",
+ "severity",
+ "verifiers",
+ "context"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "properties": {
+ "version": {
+ "const": "2.0"
+ },
+ "minSeverity": {
+ "$ref": "#/$defs/severity"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/error"
+ },
+ "additionalItems": false
+ }
+ },
+ "required": [
+ "version",
+ "minSeverity",
+ "errors"
+ ],
+ "additionalProperties": false
+}
\ No newline at end of file
diff --git a/src/ModVerify/Utilities/VerificationErrorExtensions.cs b/src/ModVerify/Utilities/VerificationErrorExtensions.cs
index 1244b45..e5aa9fe 100644
--- a/src/ModVerify/Utilities/VerificationErrorExtensions.cs
+++ b/src/ModVerify/Utilities/VerificationErrorExtensions.cs
@@ -6,23 +6,24 @@ namespace AET.ModVerify.Utilities;
public static class VerificationErrorExtensions
{
- public static IEnumerable ApplyBaseline(this IEnumerable errors,
- VerificationBaseline baseline)
+ extension(IEnumerable errors)
{
- if (errors == null)
- throw new ArgumentNullException(nameof(errors));
- if (baseline == null)
- throw new ArgumentNullException(nameof(baseline));
- return baseline.Apply(errors);
- }
+ public IEnumerable ApplyBaseline(VerificationBaseline baseline)
+ {
+ if (errors == null)
+ throw new ArgumentNullException(nameof(errors));
+ if (baseline == null)
+ throw new ArgumentNullException(nameof(baseline));
+ return baseline.Apply(errors);
+ }
- public static IEnumerable ApplySuppressions(this IEnumerable errors,
- SuppressionList suppressions)
- {
- if (errors == null)
- throw new ArgumentNullException(nameof(errors));
- if (suppressions == null)
- throw new ArgumentNullException(nameof(suppressions));
- return suppressions.Apply(errors);
+ public IEnumerable ApplySuppressions(SuppressionList suppressions)
+ {
+ if (errors == null)
+ throw new ArgumentNullException(nameof(errors));
+ if (suppressions == null)
+ throw new ArgumentNullException(nameof(suppressions));
+ return suppressions.Apply(errors);
+ }
}
}
\ 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 18539a7..3b77732 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameLocations.cs
@@ -7,6 +7,11 @@ namespace PG.StarWarsGame.Engine;
public sealed class GameLocations
{
+ ///
+ /// Gets the path that represents the topmost playable target. This is typically the actual mod selected by the user.
+ ///
+ public string TargetPath { get; }
+
public IReadOnlyList ModPaths { get; }
public string GamePath { get; }
@@ -38,5 +43,9 @@ public GameLocations(IList modPaths, string gamePath, IList fall
ModPaths = modPaths.ToList();
GamePath = gamePath;
FallbackPaths = fallbackPaths.ToList();
+
+ TargetPath = ModPaths.Count > 0
+ ? ModPaths[0]
+ : GamePath;
}
}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs
index 7fa4d02..130fb50 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs
@@ -62,7 +62,7 @@ public async Task InitializeAsync(CancellationToken token)
}
catch (Exception e)
{
- Logger?.LogError(e, $"Initialization of {this} failed: {e.Message}");
+ Logger?.LogError(e, "Initialization of {Class} failed: {Message}", this, e.Message);
throw;
}
OnInitialized();
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs
index 82ce825..879c16f 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs
@@ -79,7 +79,7 @@ public IReadOnlyDictionary GetTextureEn
{
if (!_perComponentTextures.TryGetValue(component, out var textures))
{
- Logger?.LogDebug($"The component '{component}' has no overrides. Using default textures.");
+ Logger?.LogDebug("The component '{Component}' has no overrides. Using default textures.", component);
componentExist = false;
return DefaultTextureEntries;
}
@@ -92,7 +92,7 @@ public bool TryGetTextureEntry(string component, GuiComponentType key, out Compo
{
if (!_perComponentTextures.TryGetValue(component, out var textures))
{
- Logger?.LogDebug($"The component '{component}' has no overrides. Using default textures.");
+ Logger?.LogDebug("The component '{Component}' has no overrides. Using default textures.", component);
textures = _defaultTextures;
}
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs
index 1304a89..e952db8 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs
@@ -84,7 +84,7 @@ protected FileFoundInfo GetFileInfoFromMasterMeg(ReadOnlySpan filePath)
if (filePath.Length > PGConstants.MaxMegEntryPathLength)
{
- Logger.LogWarning($"Trying to open a MEG entry which is longer than 259 characters: '{filePath.ToString()}'");
+ Logger.LogWarning("Trying to open a MEG entry which is longer than 259 characters: '{FilePath}'", filePath.ToString());
return default;
}
@@ -97,7 +97,7 @@ protected FileFoundInfo GetFileInfoFromMasterMeg(ReadOnlySpan filePath)
if (fileName.Length > PGConstants.MaxMegEntryPathLength)
{
- Logger.LogWarning($"Trying to open a MEG entry which is longer than 259 characters after normalization: '{fileName.ToString()}'");
+ Logger.LogWarning("Trying to open a MEG entry which is longer than 259 characters after normalization: '{FileName}'", fileName.ToString());
return default;
}
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs
index 42bfa54..d1451b0 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs
@@ -116,9 +116,9 @@ public void AddMegFile(string megFile)
if (megArchive is null)
{
if (IsSpeechMeg(megFile))
- Logger.LogDebug($"Unable to find Speech MEG file at '{megFile}'");
+ Logger.LogDebug("Unable to find Speech MEG file at '{MegFile}'", megFile);
else
- Logger.LogWarning($"Unable to find MEG file at '{megFile}'");
+ Logger.LogWarning("Unable to find MEG file at '{MegFile}'", megFile);
return;
}
@@ -217,7 +217,7 @@ protected IList LoadMegArchivesFromXml(string lookupPath)
if (xmlStream is null)
{
- Logger.LogWarning($"Unable to find MegaFiles.xml at '{lookupPath}'");
+ Logger.LogWarning("Unable to find MegaFiles.xml at '{LookupPath}'", lookupPath);
return Array.Empty();
}
@@ -251,12 +251,12 @@ internal void Seal()
if (megFileStream is not FileSystemStream fileSystemStream)
{
if (IsSpeechMeg(megPath))
- Logger.LogDebug($"Unable to find Speech MEG file '{megPath}'");
+ Logger.LogDebug("Unable to find Speech MEG file '{MegPath}'", megPath);
else
{
var message = $"Unable to find MEG file '{megPath}'";
_errorReporter.Assert(EngineAssert.Create(EngineAssertKind.FileNotFound, megPath, [], message));
- Logger.LogWarning($"Unable to find MEG file '{megPath}'");
+ Logger.LogWarning("Unable to find MEG file '{MegPath}'", megPath);
}
return null;
}
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj
index 6055649..e54e6f0 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;netstandard2.1;net9.0
+ netstandard2.0;netstandard2.1;net10.0
PG.StarWarsGame.Engine
PG.StarWarsGame.Engine
AlamoEngineTools.PG.StarWarsGame.Engine
@@ -23,22 +23,16 @@
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs
index d2acfba..8ce1a5a 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs
@@ -65,7 +65,7 @@ private async Task InitializeEngine(
{
try
{
- _logger?.LogInformation($"Initializing game engine for type '{engineType}'.");
+ _logger?.LogInformation("Initializing game engine for type '{GameEngineType}'.", engineType);
var repoFactory = _serviceProvider.GetRequiredService();
var repository = repoFactory.Create(engineType, gameLocations, errorReporter);
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs
index 5d5f98a..0c911e2 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs
@@ -37,13 +37,13 @@ public void ParseEntriesFromFileListXml(
ValueListDictionary entries,
Action? onFileParseAction = null) where T : notnull
{
- Logger.LogDebug($"Parsing container data '{xmlFile}'");
+ Logger.LogDebug("Parsing container data '{XmlFile}'", xmlFile);
using var containerStream = gameRepository.TryOpenFile(xmlFile);
if (containerStream == null)
{
_reporter?.Report(this, XmlParseErrorEventArgs.FromMissingFile(xmlFile));
- Logger.LogWarning($"Could not find XML file '{xmlFile}'");
+ Logger.LogWarning("Could not find XML file '{XmlFile}'", xmlFile);
var args = new XmlContainerParserErrorEventArgs(xmlFile, null, true)
{
@@ -89,7 +89,7 @@ public void ParseEntriesFromFileListXml(
if (fileStream is null)
{
_reporter?.Report(parser, XmlParseErrorEventArgs.FromMissingFile(file));
- Logger.LogWarning($"Could not find XML file '{file}'");
+ Logger.LogWarning("Could not find XML file '{File}'", file);
var args = new XmlContainerParserErrorEventArgs(file);
XmlParseError?.Invoke(this, args);
@@ -99,7 +99,7 @@ public void ParseEntriesFromFileListXml(
return;
}
- Logger.LogDebug($"Parsing File '{file}'");
+ Logger.LogDebug("Parsing File '{File}'", file);
try
{
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 4fd3dd4..c653074 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
@@ -16,11 +16,7 @@
snupkg
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
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 d6a7939..36221be 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
@@ -14,9 +14,10 @@
true
snupkg
+ preview
-
+
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 066e32d..fbb055c 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
@@ -15,15 +15,16 @@
true
snupkg
true
+ preview
-
-
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/CommonTestBase.cs b/test/ModVerify.CliApp.Test/CommonTestBase.cs
new file mode 100644
index 0000000..7d6dc18
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/CommonTestBase.cs
@@ -0,0 +1,29 @@
+using System;
+using System.IO.Abstractions;
+using AnakinRaW.CommonUtilities.Hashing;
+using Microsoft.Extensions.DependencyInjection;
+using PG.Commons;
+using Testably.Abstractions.Testing;
+
+namespace ModVerify.CliApp.Test;
+
+public abstract class CommonTestBase
+{
+ 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);
+ // ReSharper disable once VirtualMemberCallInConstructor
+ SetupServices(sc);
+ ServiceProvider = sc.BuildServiceProvider();
+ }
+
+ protected virtual void SetupServices(ServiceCollection serviceCollection)
+ {
+ }
+}
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs
new file mode 100644
index 0000000..cde1dad
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/EmbeddedBaselineTest.cs
@@ -0,0 +1,47 @@
+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;
+
+namespace ModVerify.CliApp.Test;
+
+public class BaselineSelectorTest
+{
+ private static readonly IFileSystem FileSystem = new RealFileSystem();
+ private static readonly ModVerifyAppSettings TestSettings = new()
+ {
+ ReportSettings = new(),
+ GameInstallationsSettings = new (),
+ VerifyPipelineSettings = new()
+ {
+ GameVerifySettings = new GameVerifySettings(),
+ VerifiersProvider = new NoVerifierProvider()
+ }
+ };
+
+ private readonly IServiceProvider _serviceProvider;
+
+ public BaselineSelectorTest()
+ {
+ var sc = new ServiceCollection();
+ sc.AddSingleton(FileSystem);
+ sc.AddSingleton(new ModVerifyAppEnvironment(typeof(ModVerifyAppEnvironment).Assembly, FileSystem));
+ _serviceProvider = sc.BuildServiceProvider();
+ }
+
+ [Theory]
+ // [InlineData(GameEngineType.Eaw)] TODO EaW is currently not supported
+ [InlineData(GameEngineType.Foc)]
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj
new file mode 100644
index 0000000..84d1bba
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net10.0
+ $(TargetFrameworks);net481
+ false
+ preview
+
+
+
+
+ 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
new file mode 100644
index 0000000..294baf7
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/ModVerifyOptionsParserTest.cs
@@ -0,0 +1,210 @@
+using AET.ModVerify.App.Settings.CommandLine;
+using AnakinRaW.ApplicationBase.Environment;
+using System;
+using System.IO.Abstractions;
+using ModVerify.CliApp.Test.TestData;
+using Testably.Abstractions;
+using ModVerify.CliApp.Test.Utilities;
+
+namespace ModVerify.CliApp.Test;
+
+public class ModVerifyOptionsParserTest_Updateable : ModVerifyOptionsParserTestBase
+{
+ protected override bool IsUpdatable => true;
+
+ protected override ApplicationEnvironment CreateEnvironment()
+ {
+ return new UpdatableEnv(GetType().Assembly, FileSystem);
+ }
+
+ [Fact]
+ public void Parse_UpdateAppArg()
+ {
+ const string argString = "updateApplication --updateBranch test --updateManifestUrl https://examlple.com";
+
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.True(settings.HasOptions);
+ Assert.Null(settings.ModVerifyOptions);
+ Assert.NotNull(settings.UpdateOptions);
+ Assert.Equal("test", settings.UpdateOptions.BranchName);
+ Assert.Equal("https://examlple.com", settings.UpdateOptions.ManifestUrl);
+ }
+
+ [Fact]
+ public void Parse_CombinedIsNotAllowed()
+ {
+ const string argString = "verify --updateBranch test --updateManifestUrl https://examlple.com";
+
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.False(settings.HasOptions);
+ Assert.Null(settings.ModVerifyOptions);
+ Assert.Null(settings.UpdateOptions);
+ }
+}
+
+public class ModVerifyOptionsParserTest_NotUpdateable : ModVerifyOptionsParserTestBase
+{
+ protected override bool IsUpdatable => false;
+
+ protected override ApplicationEnvironment CreateEnvironment()
+ {
+ return new TestEnv(GetType().Assembly, FileSystem);
+ }
+
+ [Theory]
+ [InlineData("verify --externalUpdaterResult UpdateSuccess")]
+ [InlineData("createBaseline --externalUpdaterResult UpdateSuccess")]
+ [InlineData("verify --junkOption")]
+ [InlineData("createBaseline --junkOption")]
+ [InlineData("updateApplication")]
+ [InlineData("updateApplication --updateBranch test --updateManifestUrl https://examlple.com")]
+ public void Parse_InvalidArgs_NotUpdateable(string argString)
+ {
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.False(settings.HasOptions);
+ Assert.Null(settings.ModVerifyOptions);
+ Assert.Null(settings.UpdateOptions);
+ }
+}
+
+
+public abstract class ModVerifyOptionsParserTestBase
+{
+ private protected readonly ModVerifyOptionsParser Parser;
+ protected readonly IFileSystem FileSystem = new RealFileSystem();
+
+ protected abstract ApplicationEnvironment CreateEnvironment();
+
+ protected abstract bool IsUpdatable { get; }
+
+ protected ModVerifyOptionsParserTestBase()
+ {
+ Parser = new ModVerifyOptionsParser(CreateEnvironment(), null);
+ }
+
+ [Fact]
+ public void Parse_NoArgs_IsVerify_IsInteractive()
+ {
+ var settings = Parser.Parse([]);
+
+ Assert.True(settings.HasOptions);
+ var verify = Assert.IsType(settings.ModVerifyOptions);
+ Assert.True(verify.IsRunningWithoutArguments);
+ Assert.Null(settings.UpdateOptions);
+ }
+
+ [Theory]
+ [InlineData("verify", false)]
+ [InlineData("verify -v", false)]
+ [InlineData("createBaseline -o out.json", true)]
+ [InlineData("createBaseline -v -o out.json", true)]
+ public void Parse_Interactive(string argString, bool createBaseLine)
+ {
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.True(settings.HasOptions);
+ if (createBaseLine)
+ {
+ Assert.IsType(settings.ModVerifyOptions);
+ }
+ else
+ {
+ var verify = Assert.IsType(settings.ModVerifyOptions);
+ Assert.False(verify.IsRunningWithoutArguments);
+ }
+ Assert.Null(settings.UpdateOptions);
+ }
+
+ [Theory]
+ [InlineData("verify --path myMod", false)]
+ [InlineData("verify -v --game myGame", false)]
+ [InlineData("createBaseline -o out.json --path myMod", true)]
+ [InlineData("createBaseline -v -o out.json --game myGame", true)]
+ public void Parse_NotInteractive(string argString, bool createBaseLine)
+ {
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.True(settings.HasOptions);
+ Assert.NotNull(settings.ModVerifyOptions);
+
+ if (createBaseLine)
+ Assert.IsType(settings.ModVerifyOptions);
+ else
+ {
+ var verify = Assert.IsType(settings.ModVerifyOptions);
+ Assert.False(verify.IsRunningWithoutArguments);
+ }
+
+ Assert.Null(settings.UpdateOptions);
+ }
+
+ [Theory]
+ [InlineData("verify --path myMod --game myGame")]
+ [InlineData("verify --game myMod --path myMod")]
+ [InlineData("verify --mod myMod --path myMod")]
+ [InlineData("verify --fallbackGame myGame --path myMod")]
+ public void Parse_InvalidPathConfig(string argString)
+ {
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.False(settings.HasOptions);
+ Assert.Null(settings.ModVerifyOptions);
+ Assert.Null(settings.UpdateOptions);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("junkVerb")]
+ [InlineData("junkVerb verify")]
+ [InlineData("junkVerb verify --v")]
+ [InlineData("junkVerb --v")]
+ [InlineData("verify --junkOption")]
+ [InlineData("verify -v --junkOption")]
+ [InlineData("updateApplication --junkOption")]
+ [InlineData("--junkOption")]
+ [InlineData("junkVerb --junkOption")]
+ [InlineData("junkVerb --externalUpdaterResult UpdateSuccess")]
+ [InlineData("-v")]
+ public void Parse_InvalidArgs(string argString)
+ {
+ var settings = Parser.Parse(argString.Split(' '));
+
+ Assert.False(settings.HasOptions);
+ Assert.Null(settings.ModVerifyOptions);
+ Assert.Null(settings.UpdateOptions);
+ }
+
+ [Fact]
+ public void Parse_UpdatePerformed_RestartedFromNoArgs()
+ {
+ // This only happens when we run without args, performed an auto-update and restarted the application automatically.
+ const string argString = "--externalUpdaterResult UpdateSuccess";
+
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ if (!IsUpdatable)
+ Assert.False(settings.HasOptions);
+ else
+ {
+ Assert.True(settings.HasOptions);
+ var verify = Assert.IsType(settings.ModVerifyOptions);
+ Assert.True(verify.IsRunningWithoutArguments);
+ Assert.Null(settings.UpdateOptions);
+ }
+ }
+
+ [Theory]
+ [InlineData("createBaseline")]
+ [InlineData("createBaseline -v")]
+ public void Parse_CreateBaseline_MissingRequired_Fails(string argString)
+ {
+ var settings = Parser.Parse(argString.Split(' ', StringSplitOptions.RemoveEmptyEntries));
+
+ Assert.False(settings.HasOptions);
+ Assert.Null(settings.ModVerifyOptions);
+ Assert.Null(settings.UpdateOptions);
+ }
+}
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs b/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs
new file mode 100644
index 0000000..ef7ad74
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/TestData/NoVerifierProvider.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using AET.ModVerify.Pipeline;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Verifiers;
+using PG.StarWarsGame.Engine;
+
+namespace ModVerify.CliApp.Test.TestData;
+
+internal sealed class NoVerifierProvider : IGameVerifiersProvider
+{
+ public IEnumerable GetVerifiers(IStarWarsGameEngine database, GameVerifySettings settings, IServiceProvider serviceProvider)
+ {
+ yield break;
+ }
+}
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/TestData/TestEnv.cs b/test/ModVerify.CliApp.Test/TestData/TestEnv.cs
new file mode 100644
index 0000000..f72f001
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/TestData/TestEnv.cs
@@ -0,0 +1,11 @@
+using System.IO.Abstractions;
+using System.Reflection;
+using AnakinRaW.ApplicationBase.Environment;
+
+namespace ModVerify.CliApp.Test.TestData;
+
+internal class TestEnv(Assembly assembly, IFileSystem fileSystem) : ApplicationEnvironment(assembly, fileSystem)
+{
+ public override string ApplicationName => "TestEnv";
+ protected override string ApplicationLocalDirectoryName => ApplicationName;
+}
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/TestData/UpdatableEnv.cs b/test/ModVerify.CliApp.Test/TestData/UpdatableEnv.cs
new file mode 100644
index 0000000..557f522
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/TestData/UpdatableEnv.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Abstractions;
+using System.Reflection;
+using AnakinRaW.ApplicationBase.Environment;
+using AnakinRaW.AppUpdaterFramework.Configuration;
+
+namespace ModVerify.CliApp.Test.TestData;
+
+internal class UpdatableEnv(Assembly assembly, IFileSystem fileSystem) : UpdatableApplicationEnvironment(assembly, fileSystem)
+{
+ public override string ApplicationName => "TestUpdateEnv";
+ protected override string ApplicationLocalDirectoryName => ApplicationName;
+ public override ICollection UpdateMirrors => [];
+ public override string UpdateRegistryPath => ApplicationName;
+ protected override UpdateConfiguration CreateUpdateConfiguration()
+ {
+ return UpdateConfiguration.Default;
+ }
+}
\ No newline at end of file
diff --git a/test/ModVerify.CliApp.Test/Utilities/StringExtensions.cs b/test/ModVerify.CliApp.Test/Utilities/StringExtensions.cs
new file mode 100644
index 0000000..052c322
--- /dev/null
+++ b/test/ModVerify.CliApp.Test/Utilities/StringExtensions.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace ModVerify.CliApp.Test.Utilities;
+
+internal static class StringExtensions
+{
+#if NETFRAMEWORK
+ public static string[] Split(this string str, char separator, StringSplitOptions options)
+ {
+ return str.Split([separator], options);
+ }
+#endif
+}
\ No newline at end of file
diff --git a/version.json b/version.json
index 251e6e2..3e87545 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "0.0-alpha",
+ "version": "0.1-beta",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],