diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8f37748..b43ddf6 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,7 +5,24 @@
version: 2
updates:
- - package-ecosystem: "nuget" # See documentation for possible values
- directory: "/" # Location of package manifests
+ - package-ecosystem: "github-actions"
+ directory: "/"
schedule:
- interval: "daily"
\ No newline at end of file
+ interval: "daily"
+ groups:
+ actions-deps:
+ patterns:
+ - "*"
+ target-branch: "develop"
+
+
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ target-branch: "develop"
+ open-pull-requests-limit: 1
+ groups:
+ all-deps:
+ patterns:
+ - "*"
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 31c58a8..a0cd9f9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,7 +27,7 @@ jobs:
- name: Create NetFramework Release
run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net48 --output ./releases/net48
- name: Create Net Core Release
- run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net8.0 --output ./releases/net8.0
+ run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net9.0 --output ./releases/net9.0
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
@@ -53,8 +53,8 @@ jobs:
path: ./releases
- name: Create NET Core .zip
# Change into the artifacts directory to avoid including the directory itself in the zip archive
- working-directory: ./releases/net8.0
- run: zip -r ../ModVerify-Net8.zip .
+ working-directory: ./releases/net9.0
+ run: zip -r ../ModVerify-Net9.zip .
- uses: dotnet/nbgv@v0.4.2
id: nbgv
- name: Create GitHub release
@@ -66,4 +66,4 @@ jobs:
generate_release_notes: true
files: |
./releases/net48/ModVerify.exe
- ./releases/ModVerify-Net8.zip
\ No newline at end of file
+ ./releases/ModVerify-Net9.zip
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 20a36cc..f6c4d43 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -25,6 +25,6 @@ jobs:
submodules: recursive
- uses: actions/setup-dotnet@v4
with:
- dotnet-version: 8.0.x
+ dotnet-version: 9.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
deleted file mode 100644
index 25d6b29..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "PetroglyphTools"]
- path = PetroglyphTools
- url = https://github.com/AlamoEngine-Tools/PetroglyphTools
diff --git a/Directory.Build.props b/Directory.Build.props
index 1d41c28..49ca26f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,9 +1,52 @@
-
-
-
- all
- 3.6.139
-
-
+
+
+
+ $(MSBuildThisFileDirectory)
+ true
+ true
+ $(RepoRootPath)
+ $(RepoRootPath)bin\Packages\$(Configuration)\
+
+
+
+ ModVerify
+ Alamo Engine Tools and Contributors
+ Copyright © 2025 Alamo Engine Tools and contributors. All rights reserved.
+ https://github.com/AlamoEngine-Tools/ModVerify
+ $(RepoRootPath)LICENSE
+ MIT
+ https://github.com/AlamoEngine-Tools/ModVerify
+ git
+ Alamo Engine Tools
+ README.md
+ aet.png
+
+
+
+ latest
+ disable
+ enable
+ True
+ latest
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+ 3.7.115
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ModVerify.sln b/ModVerify.sln
index baee725..d09a64e 100644
--- a/ModVerify.sln
+++ b/ModVerify.sln
@@ -5,12 +5,6 @@ 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}") = "PG.Commons", "PetroglyphTools\PG.Commons\PG.Commons\PG.Commons.csproj", "{1A9E1B15-DD77-47E3-893E-AFADF982CEC6}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.StarWarsGame.Files.DAT", "PetroglyphTools\PG.StarWarsGame.Files.DAT\PG.StarWarsGame.Files.DAT\PG.StarWarsGame.Files.DAT.csproj", "{4630F85C-D1C4-4454-9126-BE13F6901E0B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.StarWarsGame.Files.MEG", "PetroglyphTools\PG.StarWarsGame.Files.MEG\PG.StarWarsGame.Files.MEG\PG.StarWarsGame.Files.MEG.csproj", "{885291F4-E5E8-45B2-B0B4-40B2910228A3}"
-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}"
@@ -29,18 +23,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Release|Any CPU.Build.0 = Release|Any CPU
- {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Release|Any CPU.Build.0 = Release|Any CPU
- {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Release|Any CPU.Build.0 = Release|Any CPU
{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
@@ -70,9 +52,6 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {1A9E1B15-DD77-47E3-893E-AFADF982CEC6} = {15F8B753-814A-406E-9147-EB048DADAC96}
- {4630F85C-D1C4-4454-9126-BE13F6901E0B} = {15F8B753-814A-406E-9147-EB048DADAC96}
- {885291F4-E5E8-45B2-B0B4-40B2910228A3} = {15F8B753-814A-406E-9147-EB048DADAC96}
{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}
diff --git a/PetroglyphTools b/PetroglyphTools
deleted file mode 160000
index 0347671..0000000
--- a/PetroglyphTools
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0347671ec7c89d2a79b167e2110df3cd495f71aa
diff --git a/README.md b/README.md
index 9ef3e13..70b4f91 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# ModVerify: A Mod Verification Tool
-ModVerify is a command-line tool designed to verify mods for the game Star Wars: Empire at War and its expansion Forces of Corruption.
+ModVerify is a command-line tool designed to analyze
+mods for the game Star Wars: Empire at War and its expansion Forces of Corruption
+for common errors in XML and other game files.
## Table of Contents
@@ -8,6 +10,9 @@ ModVerify is a command-line tool designed to verify mods for the game Star Wars:
- [Usage](#usage)
- [Options](#options)
- [Available Checks](#available-checks)
+- [Creating a new Baseline](#creating-a-new-baseline)
+
+---
## Installation
@@ -18,15 +23,18 @@ Download the latest release from the [releases page](https://github.com/AlamoEng
You can place the files anywhere on your system, eg. your Desktop. There is no need to place it inside a mod's directory.
-*Note: Both versions have the exact same feature set. They just target a different .NET runtime. Linux and CI/CD support is not fully tested yet. Current priority is on the Windows-only version.*
+***Note**: Both versions have the exact same feature set. They just target a different .NET runtime. Linux and CI/CD support is not fully tested yet. Current priority is on the Windows-only version.*
+
+---
## Usage
Simply run the executable file `ModVerify.exe`.
-When given no specific argument through the command line, the app will ask you which game or mod you want to verify. When the tool is done, it will write the verification results into new files next to the executable.
+When given no specific argument through the command line, ModVerify will ask you which game or mod you want to verify. When ModVerify is done analyzing, it will write the verification results into new files next to the executable.
-A `.JSON` file lists all found issues. Into seperate `.txt` files the same errors get grouped by a category of the finding. The text files may be easier to read, while the json file is more useful for 3rd party tool processing.
+A `.JSON` file contains all identified issues. The additional `.txt` files contain the same errors but are grouped by the verifier that reported the issue.
+The text files may be easier to read, while the JSON file is more useful for 3rd party tool processing.
## Options
@@ -49,8 +57,6 @@ Specified the output path where analysis result shall be written to.
### `--baseline`
Specifies a baseline file that shall be used to filter out known errors. You can download the [FoC baseline](focBaseline.json) which includes all errors produced by the vanilla game.
-### `--createBaseline`
-If you want to create your own baseline, add this option with a file path such as `myModBaseline.json`.
### Example
This is an example run configuration that analyzes a specific mod, uses a the FoC basline and writes the output into a dedicated directory:
@@ -59,19 +65,35 @@ This is an example run configuration that analyzes a specific mod, uses a the Fo
ModVerify.exe --path "C:\My Games\FoC\Mods\MyMod" --output "C:\My Games\FoC\Mods\MyMod\verifyResults" --baseline focBaseline.json
```
+---
## Available Checks
The following verifiers are currently implemented:
-### For SFX Events:
-- Checks whether coded preset exists
+### SFX Events:
+- Checks whether coded presets exist
- Checks the referenced samples for validity (bit rate, sample size, channels, etc.)
- Duplicates
+### GUIDialogs
+- Checks the referenced textures exist
-### For GameObjects
+### GameObjects
- Checks the referenced models for validity (textures, particles and shaders)
- Duplicates
+### Engine
+- Performs assertion checks the Debug builds of the game are also doing (not complete)
+- Sports XML errors and unexpected values
+
+---
+
+## Creating a new Baseline
+If you want to create your own baseline use the `createBaseline` option.
+
+### Example
+```bash
+ModVerify.exe createBaseline -o myBaseline.json --path "C:\My Games\FoC\Mods\MyMod"
+```
diff --git a/focBaseline.json b/focBaseline.json
index 2b429dc..ad63da6 100644
--- a/focBaseline.json
+++ b/focBaseline.json
@@ -1,1632 +1,1827 @@
{
+ "version": "2.0",
+ "minSeverity": "Information",
"errors": [
{
"id": "XML04",
- "verifier": "AET.ModVerify.Verifiers.XmlParseErrorCollector",
- "message": "Expected integer but got \u002780, 20\u0027 at DATA\\XML\\SFXEVENTSWEAPONS.XML at line: 90",
- "severity": 0,
- "assets": [
+ "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"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_VCH.ALO references missing proxy particle: P_heat_small01.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_VCH.ALO",
- "P_heat_small01.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_SCH.ALO references missing proxy particle: p_cold_tiny01.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_SCH.ALO",
- "p_cold_tiny01.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO references missing proxy particle: p_smoke_small_thin2.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO",
- "p_smoke_small_thin2.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO references missing proxy particle: p_explosion_smoke_small_thin5.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO",
- "p_explosion_smoke_small_thin5.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_rv_XWingProp.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_rv_XWingProp.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\ALTTEST.ALO references missing texture: Cin_DeathStar.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\ALTTEST.ALO",
- "Cin_DeathStar.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO references missing texture: UB_girder_B.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO",
- "UB_girder_B.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO references missing texture: NB_YsalamiriTree_B.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO",
- "NB_YsalamiriTree_B.tga"
- ]
- },
- {
- "id": "ALO04",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO references missing shader effect: Default.fx",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO",
- "Default.fx"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\RV_MPTL-2A.ALO references missing proxy particle: P_mptl-2a_Die.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\RV_MPTL-2A.ALO",
- "P_mptl-2a_Die.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027w_planet_volcanic.alo\u0027",
- "severity": 0,
- "assets": [
- "w_planet_volcanic.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027RV_nebulonb_D_death_00.ALO\u0027",
- "severity": 0,
- "assets": [
- "RV_nebulonb_D_death_00.ALO"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_STARS_LOW.ALO references missing proxy particle: Lensflare0.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_STARS_LOW.ALO",
- "Lensflare0.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO references missing proxy particle: Lensflare0.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO",
- "Lensflare0.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO references missing proxy particle: Lensflare0.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO",
- "Lensflare0.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_STARS_CINE.ALO references missing proxy particle: Lensflare0.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_STARS_CINE.ALO",
- "Lensflare0.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO references missing proxy particle: Lensflare0.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO",
- "Lensflare0.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\EI_MARAJADE.ALO references missing proxy particle: p_desert_ground_dust.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\EI_MARAJADE.ALO",
- "p_desert_ground_dust.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO references missing proxy particle: p_desert_ground_dust.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO",
- "p_desert_ground_dust.alo"
- ]
- },
- {
- "id": "ALO04",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO references missing shader effect: Default.fx",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO",
- "Default.fx"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO references missing proxy particle: p_smoke_small_thin2.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO",
- "p_smoke_small_thin2.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO references missing proxy particle: p_steam_small.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO",
- "p_steam_small.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_PRISON.ALO references missing proxy particle: p_prison_light.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_PRISON.ALO",
- "p_prison_light.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_PRISON.ALO references missing proxy particle: p_smoke_small_thin2.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_PRISON.ALO",
- "p_smoke_small_thin2.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_PRISON.ALO references missing proxy particle: p_smoke_small_thin4.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_PRISON.ALO",
- "p_smoke_small_thin4.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO references missing proxy particle: p_explosion_small_delay00.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO",
- "p_explosion_small_delay00.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO references missing proxy particle: p_bomb_spin.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO",
- "p_bomb_spin.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_GRENADE.ALO references missing texture: w_grenade.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_GRENADE.ALO",
- "w_grenade.tga"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027p_splash_wake_lava.alo\u0027",
- "severity": 0,
- "assets": [
- "p_splash_wake_lava.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\EV_TIE_PHANTOM.ALO references missing texture: W_TE_Rock_f_02_b.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\EV_TIE_PHANTOM.ALO",
- "W_TE_Rock_f_02_b.tga"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO references missing proxy particle: p_hp_archammer-damage.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO",
- "p_hp_archammer-damage.alo"
- ]
- },
- {
- "id": "ALO04",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO references missing shader effect: Default.fx",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO",
- "Default.fx"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\RV_BWING.ALO references missing proxy particle: pe_bwing_yellow.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\RV_BWING.ALO",
- "pe_bwing_yellow.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_F9TZTRANSPORT.ALO references missing texture: W_TE_Rock_f_02_b.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_F9TZTRANSPORT.ALO",
- "W_TE_Rock_f_02_b.tga"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO references missing proxy particle: p_ewok_drag_dirt.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO",
- "p_ewok_drag_dirt.alo"
- ]
- },
- {
- "id": "ALO04",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO references missing shader effect: Default.fx",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO",
- "Default.fx"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_VENGEANCE.ALO references missing texture: W_TE_Rock_f_02_b.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_VENGEANCE.ALO",
- "W_TE_Rock_f_02_b.tga"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO",
- "p_uwstation_death.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO",
- "p_uwstation_death.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO",
- "p_uwstation_death.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO",
- "p_uwstation_death.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO",
- "p_uwstation_death.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO references missing proxy particle: lookat.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO",
- "lookat.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO references missing proxy particle: p_ssd_debris.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO",
- "p_ssd_debris.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO references missing proxy particle: lookat.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO",
- "lookat.alo"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO references missing proxy particle: p_desert_ground_dust.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO",
- "p_desert_ground_dust.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\NB_YSALAMIRI_TREE.ALO references missing texture: NB_YsalamiriTree_B.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\NB_YSALAMIRI_TREE.ALO",
- "NB_YsalamiriTree_B.tga"
- ]
- },
- {
- "id": "ALO03",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UI_IG88.ALO references missing proxy particle: p_desert_ground_dust.alo",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UI_IG88.ALO",
- "p_desert_ground_dust.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\UV_KRAYTCLASSDESTROYER_TYBER.ALO references missing texture: W_TE_Rock_f_02_b.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\UV_KRAYTCLASSDESTROYER_TYBER.ALO",
- "W_TE_Rock_f_02_b.tga"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_SwampGasEmit.ALO\u0027",
- "severity": 0,
- "assets": [
- "W_SwampGasEmit.ALO"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_Bush_Swmp00.ALO\u0027",
- "severity": 0,
- "assets": [
- "W_Bush_Swmp00.ALO"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027",
- "severity": 0,
- "assets": [
- "W_Volcano_Rock02.ALO"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_Vol_Steam01.ALO\u0027",
- "severity": 0,
- "assets": [
- "W_Vol_Steam01.ALO"
- ]
+ ],
+ "asset": "Probability"
+ },
+ {
+ "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": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Fire_Medium.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027",
- "severity": 0,
- "assets": [
- "W_Kamino_Reflect.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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_AllShaders.ALO\u0027",
- "severity": 0,
- "assets": [
- "W_AllShaders.ALO"
- ]
+ "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_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_Biker_Row.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Biker_Row.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027W_droid_steam.alo\u0027",
- "severity": 0,
- "assets": [
- "W_droid_steam.alo"
- ]
+ "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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_bridge.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_bridge.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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_DeathStar_High.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_DeathStar_High.alo"
- ]
+ "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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CINE_EV_StarDestroyer.ALO\u0027",
- "severity": 0,
- "assets": [
- "CINE_EV_StarDestroyer.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 \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 \u0027Cin_EI_Vader.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EI_Vader.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_Coruscant.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_Coruscant.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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_Planet_Alderaan_High.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_Planet_Alderaan_High.alo"
- ]
+ "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 \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_droid_steam.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_droid_steam.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_Planet_Hoth_High.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_Planet_Hoth_High.alo"
- ]
+ "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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_EV_Stardestroyer_Warp.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_EV_Stardestroyer_Warp.alo"
- ]
+ "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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_Officer.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_Officer.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": "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": "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_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"
+ ],
+ "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": "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_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": "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"
+ ],
+ "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": "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 \u0027RV_nebulonb_D_death_00.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "RV_nebulonb_D_death_00.ALO"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "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": 0,
- "assets": [
- "Cin_DStar_Dish_close.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_DStar_LeverPanel.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_DStar_LeverPanel.alo"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DStar_Dish_close.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 \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_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 \u0027CIN_NavyTrooper_Row.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_NavyTrooper_Row.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
"message": "Unable to find .ALO file \u0027Cin_DeathStar_Wall.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_DeathStar_Wall.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_DStar_protons.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_DStar_protons.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_DStar_TurretLasers.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_DStar_TurretLasers.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_EV_TieAdvanced.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_EV_TieAdvanced.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_DeathStar_Wall.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_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.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
"message": "Unable to find .ALO file \u0027Cin_ImperialCraft.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_ImperialCraft.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_ImperialCraft.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_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": "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 \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_Shuttle_Tyderium.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_Shuttle_Tyderium.alo"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_Shuttle_Tyderium.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_p_proton_torpedo.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_p_proton_torpedo.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": "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 \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_EV_TieAdvanced.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EV_TieAdvanced.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_DeathStar_Hangar.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_DeathStar_Hangar.alo"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO references missing texture: Cin_Reb_CelebHall_Wall.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO",
- "Cin_Reb_CelebHall_Wall.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO references missing texture: Cin_Reb_CelebHall_Wall_B.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO",
- "Cin_Reb_CelebHall_Wall_B.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO references missing texture: Cin_Reb_CelebHall_Wall.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO",
- "Cin_Reb_CelebHall_Wall.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO references missing texture: Cin_Reb_CelebHall_Wall_B.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO",
- "Cin_Reb_CelebHall_Wall_B.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_TILE.ALO references missing texture: Cin_Reb_CelebHall_Wall_B.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_TILE.ALO",
- "Cin_Reb_CelebHall_Wall_B.tga"
- ]
- },
- {
- "id": "ALO02",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "DATA\\ART\\MODELS\\W_TILE.ALO references missing texture: Cin_Reb_CelebHall_Wall.tga",
- "severity": 0,
- "assets": [
- "DATA\\ART\\MODELS\\W_TILE.ALO",
- "Cin_Reb_CelebHall_Wall.tga"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "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_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": "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"
+ ],
+ "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 \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"
+ ],
"message": "Unable to find .ALO file \u0027w_sith_arch.alo\u0027",
- "severity": 0,
- "assets": [
- "w_sith_arch.alo"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "w_sith_arch.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Trooper_Row.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Trooper_Row.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": "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": "Unable to find .ALO file \u0027W_SwampGasEmit.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_SwampGasEmit.ALO"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Biker_Row.alo\u0027",
- "severity": 0,
- "assets": [
- "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": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "id": "FILE00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "AET.ModVerify.Verifiers.Commons.SingleModelVerifier"
+ ],
"message": "Unable to find .ALO file \u0027CIN_Officer_Row.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Officer_Row.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_NavyTrooper_Row.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_NavyTrooper_Row.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Lambda_Mouth.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Lambda_Mouth.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Lambda_Head.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Lambda_Head.alo"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Officer_Row.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Reb_CelebHall.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Reb_CelebHall.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_REb_CelebCharacters.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_REb_CelebCharacters.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Rbel_NavyRow.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Rbel_NavyRow.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Rbel_Soldier.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "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": 0,
- "assets": [
- "CIN_Rbel_Soldier_Group.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Rbel_grey.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Rbel_grey.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Rbel_GreyGroup.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Rbel_GreyGroup.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_EV_lambdaShuttle_150.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_EV_lambdaShuttle_150.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027Cin_EI_Vader.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_EI_Vader.alo"
- ]
- },
- {
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_Rbel_Soldier_Group.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\\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 \u0027Cin_EI_Palpatine.alo\u0027",
- "severity": 0,
- "assets": [
- "Cin_EI_Palpatine.alo"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "Cin_EI_Palpatine.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\\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_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 \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"
+ ],
+ "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": "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": "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 \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_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": "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": "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_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_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 \u0027CIN_p_proton_torpedo.alo\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "CIN_p_proton_torpedo.alo"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Fire_Huge.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Fire_Huge.alo"
- ]
+ "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_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": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_Kamino_Reflect.ALO"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Fire_Medium.alo"
- ]
+ "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_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": "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": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027",
+ "severity": "Error",
+ "context": [],
+ "asset": "W_Volcano_Rock02.ALO"
},
{
- "id": "ALO00",
- "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier",
- "message": "Unable to find .ALO file \u0027CIN_Probe_Droid.alo\u0027",
- "severity": 0,
- "assets": [
- "CIN_Probe_Droid.alo"
- ]
+ "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": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "C000_DST0102_ENG.WAV"
- ]
+ "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": "WAV03",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027WIND_GUST_1_STEREO.WAV\u0027 is not mono audio.",
- "severity": 0,
- "assets": [
- "WIND_GUST_1_STEREO.WAV"
- ]
+ "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": "WAV03",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027WIND_GUST_2_STEREO.WAV\u0027 is not mono audio.",
- "severity": 0,
- "assets": [
- "WIND_GUST_2_STEREO.WAV"
- ]
+ "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": "WAV03",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027WIND_GUST_3_STEREO.WAV\u0027 is not mono audio.",
- "severity": 0,
- "assets": [
- "WIND_GUST_3_STEREO.WAV"
- ]
+ "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": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "TESTUNITMOVE_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0106_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0106_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "AMB_DES_CLEAR_LOOP_1.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_MAL0503_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "AMB_URB_CLEAR_LOOP_1.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0315_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_TMC0212_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "FS_BEETLE_3.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_MAL0503_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0314_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_DEF3006_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0109_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_DEF3106_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "FS_BEETLE_2.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0101_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0202_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0102_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0204_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0103_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0304_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0104_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "EGL_STAR_VIPER_SPINNING_1.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0105_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0402_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0106_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0106_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0112_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0107_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_ARC3106_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0108_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0201_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0109_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_ARC3104_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0110_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0311_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
"message": "Audio file \u0027U000_LEI0111_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0111_ENG.WAV"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0111_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0112_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0114_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0113_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_DEF3106_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0114_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0110_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0115_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0503_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0201_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_ARC3105_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0202_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0305_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0203_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0208_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0204_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0313_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0205_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0308_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
"message": "Audio file \u0027U000_LEI0206_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0206_ENG.WAV"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0206_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0207_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0105_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0208_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "FS_BEETLE_1.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0209_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0209_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "FS_BEETLE_4.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0210_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0209_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0209_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0211_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0501_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0212_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "TESTUNITMOVE_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0213_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0312_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0215_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0404_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0301_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0603_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0303_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0108_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0304_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0306_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0305_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0303_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0306_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "AMB_DES_CLEAR_LOOP_1.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
"message": "Audio file \u0027U000_LEI0307_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0307_ENG.WAV"
- ]
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0307_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0308_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0102_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0309_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "C000_DST0102_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0311_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0205_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0312_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0504_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0313_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0210_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0314_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0403_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0315_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0211_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0401_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0604_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0402_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_TMC0212_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0403_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0309_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0404_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_DEF3006_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0501_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0101_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0502_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0203_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0503_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0401_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0504_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0602_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0601_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_MCF1601_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0602_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0115_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0603_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0207_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_LEI0604_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0502_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_ARC3104_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0113_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_ARC3105_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0213_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_ARC3106_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0104_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "U000_MCF1601_ENG.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0301_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "FS_BEETLE_1.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0601_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "FS_BEETLE_2.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0212_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "FS_BEETLE_3.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0103_ENG.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "FS_BEETLE_4.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "AMB_URB_CLEAR_LOOP_1.WAV"
},
{
"id": "WAV00",
- "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier",
- "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.",
- "severity": 0,
- "assets": [
- "EGL_STAR_VIPER_SPINNING_1.WAV"
- ]
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0107_ENG.WAV"
+ },
+ {
+ "id": "WAV00",
+ "verifiers": [
+ "AET.ModVerify.Verifiers.AudioFilesVerifier"
+ ],
+ "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.",
+ "severity": "Error",
+ "context": [],
+ "asset": "U000_LEI0215_ENG.WAV"
+ },
+ {
+ "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 \u0027underworld_logo_off.tga\u0027 at location \u0027MegaTexture\u0027.",
+ "severity": "Error",
+ "context": [
+ "IDC_PLAY_FACTION_A_BUTTON_BIG",
+ "MegaTexture"
+ ],
+ "asset": "underworld_logo_off.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_selected.tga\u0027 at location \u0027MegaTexture\u0027.",
+ "severity": "Error",
+ "context": [
+ "IDC_PLAY_FACTION_A_BUTTON_BIG",
+ "MegaTexture"
+ ],
+ "asset": "underworld_logo_selected.tga"
}
]
}
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
deleted file mode 100644
index 4bc8e0c..0000000
--- a/src/Directory.Build.props
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
- $(MSBuildThisFileDirectory)../
- true
- true
- $(RepoRootPath)
- $(RepoRootPath)bin\Packages\$(Configuration)\
-
-
-
- Alamo Engine Tools and Contributors
- Copyright © 2024 Alamo Engine Tools and contributors. All rights reserved.
- https://github.com/AlamoEngine-Tools/ModVerify
- $(RepoRootPath)LICENSE
- MIT
- https://github.com/AlamoEngine-Tools/ModVerify
- git
- Alamo Engine Tools
- README.md
- aet.png
-
-
-
- latest
- disable
- enable
- True
- latest
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ConsoleUtilities.cs b/src/ModVerify.CliApp/ConsoleUtilities.cs
new file mode 100644
index 0000000..db3e741
--- /dev/null
+++ b/src/ModVerify.CliApp/ConsoleUtilities.cs
@@ -0,0 +1,94 @@
+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/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
index fa21e3b..78fe408 100644
--- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
+++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
@@ -8,7 +8,6 @@
using PG.StarWarsGame.Infrastructure.Games;
using PG.StarWarsGame.Infrastructure.Mods;
using PG.StarWarsGame.Infrastructure.Services;
-using PG.StarWarsGame.Infrastructure.Services.Dependencies;
using PG.StarWarsGame.Infrastructure.Services.Detection;
namespace AET.ModVerifyTool.GameFinder;
@@ -69,17 +68,20 @@ public GameFinderResult FindGamesFromPathOrGlobal(string path)
private bool TryDetectGame(GameType gameType, IList detectors, out GameDetectionResult result)
{
var gd = new CompositeGameDetector(detectors, _serviceProvider);
- result = gd.Detect(new GameDetectorOptions(gameType));
- if (result.Error is not null)
+ try
{
- _logger?.LogTrace($"Unable to find game installation: {result.Error.Message}", result.Error);
- return false;
+ result = gd.Detect(gameType);
+ if (result.GameLocation is null)
+ return false;
+ return true;
}
- if (result.GameLocation is null)
+ catch (Exception e)
+ {
+ result = GameDetectionResult.NotInstalled(gameType);
+ _logger?.LogTrace($"Unable to find game installation: {e.Message}");
return false;
-
- return true;
+ }
}
private GameFinderResult FindGames(IList detectors)
@@ -95,7 +97,7 @@ private GameFinderResult FindGames(IList detectors)
if (result.GameLocation is null)
throw new GameNotFoundException("Unable to find game installation: Wrong install path?");
- _logger?.LogTrace($"Found game installation: {result.GameIdentity} at {result.GameLocation.FullName}");
+ _logger?.LogInformation($"Found game installation: {result.GameIdentity} at {result.GameLocation.FullName}");
var game = _gameFactory.CreateGame(result, CultureInfo.InvariantCulture);
@@ -116,7 +118,7 @@ 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?.LogTrace($"Found fallback game installation: {fallbackResult.GameIdentity} at {fallbackResult.GameLocation.FullName}");
+ _logger?.LogInformation($"Found fallback game installation: {fallbackResult.GameIdentity} at {fallbackResult.GameLocation.FullName}");
fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture);
@@ -128,15 +130,15 @@ private GameFinderResult FindGames(IList detectors)
private void SetupMods(IGame game)
{
- var modFinder = _serviceProvider.GetRequiredService();
+ var modFinder = _serviceProvider.GetRequiredService();
var modRefs = modFinder.FindMods(game);
var mods = new List();
foreach (var modReference in modRefs)
{
- var mod = _modFactory.FromReference(game, modReference, CultureInfo.InvariantCulture);
- mods.AddRange(mod);
+ var mod = _modFactory.CreatePhysicalMod(game, modReference, CultureInfo.InvariantCulture);
+ mods.Add(mod);
}
foreach (var mod in mods)
@@ -145,9 +147,7 @@ private void SetupMods(IGame game)
// Mods need to be added to the game first, before resolving their dependencies.
foreach (var mod in mods)
{
- var resolver = _serviceProvider.GetRequiredService();
- mod.ResolveDependencies(resolver,
- new DependencyResolverOptions { CheckForCycle = true, ResolveCompleteChain = true });
+ mod.ResolveDependencies();
}
}
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs
index 1d2a85f..00fc4ae 100644
--- a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs
@@ -1,11 +1,9 @@
using System;
-using System.Diagnostics;
using System.Globalization;
using System.IO.Abstractions;
+using System.Linq;
using AET.ModVerifyTool.GameFinder;
using AET.ModVerifyTool.Options;
-using EawModinfo.Model;
-using EawModinfo.Spec;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PG.StarWarsGame.Engine;
@@ -13,7 +11,7 @@
using PG.StarWarsGame.Infrastructure.Games;
using PG.StarWarsGame.Infrastructure.Mods;
using PG.StarWarsGame.Infrastructure.Services;
-using PG.StarWarsGame.Infrastructure.Services.Dependencies;
+using PG.StarWarsGame.Infrastructure.Services.Detection;
namespace AET.ModVerifyTool.ModSelectors;
@@ -21,10 +19,14 @@ internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelec
{
private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
- public override GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, out GameEngineType? actualEngineType)
+ public override GameLocations? Select(
+ GameInstallationsSettings settings,
+ out IPhysicalPlayableObject? targetObject,
+ out GameEngineType? actualEngineType)
{
var pathToVerify = settings.AutoPath;
- Debug.Assert(pathToVerify is not null);
+ if (pathToVerify is null)
+ throw new InvalidOperationException("path to verify cannot be null.");
actualEngineType = settings.EngineType;
@@ -100,14 +102,18 @@ private GameLocations GetDetachedModLocations(string modPath, GameFinderResult g
if (game is null)
throw new GameNotFoundException($"Unable to find game of type '{settings.EngineType}'");
+ var modFinder = ServiceProvider.GetRequiredService();
+ var modRef = modFinder.FindMods(game, _fileSystem.DirectoryInfo.New(modPath)).FirstOrDefault();
+
+ if (modRef is null)
+ throw new NotSupportedException($"The mod at '{modPath}' is not compatible to the found game '{game}'.");
+
var modFactory = ServiceProvider.GetRequiredService();
- mod = modFactory.FromReference(game, new ModReference(modPath, ModType.Default), true, CultureInfo.InvariantCulture);
+ mod = modFactory.CreatePhysicalMod(game, modRef, CultureInfo.InvariantCulture);
game.AddMod(mod);
- var resolver = ServiceProvider.GetRequiredService();
- mod.ResolveDependencies(resolver,
- new DependencyResolverOptions { CheckForCycle = true, ResolveCompleteChain = true });
+ mod.ResolveDependencies();
return GetLocations(mod, gameResult, settings.AdditionalFallbackPaths);
}
diff --git a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
index c218305..7a29368 100644
--- a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
@@ -1,17 +1,18 @@
using System;
using System.Collections.Generic;
+using AET.Modinfo.Spec;
using AET.ModVerifyTool.GameFinder;
using AET.ModVerifyTool.Options;
-using EawModinfo.Spec;
using PG.StarWarsGame.Engine;
using PG.StarWarsGame.Infrastructure;
+using PG.StarWarsGame.Infrastructure.Games;
using PG.StarWarsGame.Infrastructure.Mods;
namespace AET.ModVerifyTool.ModSelectors;
internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider)
{
- public override GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject,
+ public override GameLocations Select(GameInstallationsSettings settings, out IPhysicalPlayableObject targetObject,
out GameEngineType? actualEngineType)
{
var gameResult = GameFinderService.FindGames();
@@ -20,13 +21,15 @@ internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelecto
return GetLocations(targetObject, gameResult, settings.AdditionalFallbackPaths);
}
- private IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult)
+ private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult)
{
var list = new List();
var game = finderResult.Game;
list.Add(finderResult.Game);
+ Console.WriteLine();
+ ConsoleUtilities.WriteHorizontalLine();
Console.WriteLine($"0: {game.Name}");
var counter = 1;
@@ -52,7 +55,11 @@ private IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResu
{
var fallbackGame = finderResult.FallbackGame;
list.Add(fallbackGame);
- Console.WriteLine($"{counter++}: {fallbackGame.Name}");
+
+ ConsoleUtilities.WriteHorizontalLine('_');
+ Console.WriteLine(fallbackGame.Type == GameType.Eaw
+ ? $"{counter++}: {fallbackGame.Name} [Not yet supported]"
+ : $"{counter++}: {fallbackGame.Name}");
foreach (var mod in fallbackGame.Mods)
{
@@ -72,22 +79,34 @@ private IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResu
}
}
- Console.WriteLine("Workshop Items:");
- foreach (var mod in workshopMods)
+ if (workshopMods.Count > 0)
{
- Console.WriteLine($"{counter++}:\t{mod.Name}");
- list.Add(mod);
+ ConsoleUtilities.WriteHorizontalLine('_');
+ Console.WriteLine("Workshop Items:");
+ foreach (var mod in workshopMods)
+ {
+ Console.WriteLine($"{counter++}:\t{mod.Name}");
+ list.Add(mod);
+ }
}
- while (true)
- {
- Console.Write("Select a game or mod: ");
- var numberString = Console.ReadLine();
+ ConsoleUtilities.WriteHorizontalLine();
- if (!int.TryParse(numberString, out var number))
- continue;
- if (number < list.Count)
- return list[number];
+ try
+ {
+ var selected = ConsoleUtilities.UserQuestionOnSameLine("Select a game or mod: ",
+ (string input, out int value) =>
+ {
+ if (!int.TryParse(input, out value))
+ return false;
+
+ return value <= list.Count;
+ });
+ return list[selected];
+ }
+ finally
+ {
+ Console.WriteLine();
}
}
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs
index 00f4c9e..f0b30a0 100644
--- a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs
@@ -6,5 +6,8 @@ namespace AET.ModVerifyTool.ModSelectors;
internal interface IModSelector
{
- GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, out GameEngineType? actualEngineType);
+ GameLocations? Select(
+ GameInstallationsSettings settings,
+ out IPhysicalPlayableObject? targetObject,
+ out GameEngineType? actualEngineType);
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs
index 08f6537..d5913c6 100644
--- a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs
@@ -7,7 +7,9 @@ namespace AET.ModVerifyTool.ModSelectors;
internal class ManualModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider)
{
- public override GameLocations Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject,
+ public override GameLocations Select(
+ GameInstallationsSettings settings,
+ out IPhysicalPlayableObject? targetObject,
out GameEngineType? actualEngineType)
{
actualEngineType = settings.EngineType;
@@ -21,7 +23,7 @@ public override GameLocations Select(GameInstallationsSettings settings, out IPh
return new GameLocations(
settings.ModPaths,
- settings.GamePath,
+ settings.GamePath!,
GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths));
}
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs
index 861451a..0eec285 100644
--- a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs
@@ -25,7 +25,9 @@ protected ModSelectorBase(IServiceProvider serviceProvider)
GameFinderService = new GameFinderService(serviceProvider);
}
- public abstract GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject,
+ public abstract GameLocations? Select(
+ GameInstallationsSettings settings,
+ out IPhysicalPlayableObject? targetObject,
out GameEngineType? actualEngineType);
protected GameLocations GetLocations(IPhysicalPlayableObject playableObject, GameFinderResult finderResult, IList additionalFallbackPaths)
@@ -66,7 +68,6 @@ private IList GetModPaths(IPhysicalPlayableObject modOrGame)
var traverser = ServiceProvider.GetRequiredService();
return traverser.Traverse(mod)
- .Select(x => x.Mod)
.OfType().Select(x => x.Directory.FullName)
.ToList();
}
diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
index 356e6c2..12f7861 100644
--- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
@@ -1,24 +1,17 @@
using System;
-using System.Globalization;
-using System.IO.Abstractions;
using System.Linq;
using AET.ModVerifyTool.Options;
-using EawModinfo.Model;
-using EawModinfo.Spec;
-using Microsoft.Extensions.DependencyInjection;
using PG.StarWarsGame.Engine;
using PG.StarWarsGame.Infrastructure;
-using PG.StarWarsGame.Infrastructure.Games;
-using PG.StarWarsGame.Infrastructure.Services.Name;
namespace AET.ModVerifyTool.ModSelectors;
internal class SettingsBasedModSelector(IServiceProvider serviceProvider)
{
- private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
- public VerifyGameInstallationData CreateInstallationDataFromSettings(GameInstallationsSettings settings)
+ public VerifyInstallationInformation CreateInstallationDataFromSettings(GameInstallationsSettings settings)
{
- var gameLocations = new ModSelectorFactory(serviceProvider).CreateSelector(settings)
+ var gameLocations = new ModSelectorFactory(serviceProvider)
+ .CreateSelector(settings)
.Select(settings, out var targetObject, out var engineType);
if (gameLocations is null)
@@ -27,7 +20,7 @@ public VerifyGameInstallationData CreateInstallationDataFromSettings(GameInstall
if (engineType is null)
throw new InvalidOperationException("Engine type not specified.");
- return new VerifyGameInstallationData
+ return new VerifyInstallationInformation
{
EngineType = engineType.Value,
GameLocations = gameLocations,
@@ -35,30 +28,12 @@ public VerifyGameInstallationData CreateInstallationDataFromSettings(GameInstall
};
}
- private string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations, GameEngineType engineType)
+ private static string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations, GameEngineType engineType)
{
if (targetObject is not null)
return targetObject.Name;
var mod = gameLocations.ModPaths.FirstOrDefault();
-
- var name = mod is not null ? GetNameFromMod(mod) : GetNameFromGame(engineType);
-
- if (string.IsNullOrEmpty(name))
- throw new InvalidOperationException("Mod or game name cannot be null or empty.");
-
- return name;
- }
-
- private string? GetNameFromGame(GameEngineType type)
- {
- var nameResolver = serviceProvider.GetRequiredService();
- return nameResolver.ResolveName(new GameIdentity(type.FromEngineType(), GamePlatform.Undefined), CultureInfo.InvariantCulture);
- }
-
- private string? GetNameFromMod(string mod)
- {
- var nameResolver = serviceProvider.GetRequiredService();
- return nameResolver.ResolveName(new ModReference(_fileSystem.Path.GetFullPath(mod), ModType.Default), CultureInfo.InvariantCulture);
+ return mod ?? gameLocations.GamePath;
}
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj
index 1fbe04f..61c0a3b 100644
--- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj
+++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj
@@ -1,8 +1,7 @@
- false
- net8.0;net48
+ net9.0;net48
Exe
AET.ModVerifyTool
ModVerify
@@ -14,48 +13,48 @@
alamo,petroglyph,glyphx
-
- true
- true
-
-
+
+
+
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
+
+
-
-
-
-
+
+ true
+
-
+
+
@@ -66,6 +65,4 @@
-
-
diff --git a/src/ModVerify.CliApp/ModVerifyApp.cs b/src/ModVerify.CliApp/ModVerifyApp.cs
index 598aa09..1270725 100644
--- a/src/ModVerify.CliApp/ModVerifyApp.cs
+++ b/src/ModVerify.CliApp/ModVerifyApp.cs
@@ -1,14 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Abstractions;
-using System.Threading.Tasks;
-using AET.ModVerify;
+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;
@@ -19,42 +24,132 @@ internal class ModVerifyApp(ModVerifyAppSettings settings, IServiceProvider serv
public async Task RunApplication()
{
- var returnCode = 0;
+ 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);
- var installData = new SettingsBasedModSelector(services).CreateInstallationDataFromSettings(settings.GameInstallationsSettings);
+ 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.");
- _logger?.LogInformation($"Verify install data: {installData}");
+ return 0;
+ }
- var verifyPipeline = new ModVerifyPipeline(installData.EngineType, installData.GameLocations, settings.GameVerifySettings, services);
+ private async Task> Verify(VerifyInstallationInformation installInformation)
+ {
+ var gameEngineService = services.GetRequiredService();
+ var engineErrorReporter = new ConcurrentGameEngineErrorReporter();
+
+ IStarWarsGameEngine gameEngine;
try
{
- _logger?.LogInformation($"Verifying '{installData.Name}'...");
- await verifyPipeline.RunAsync().ConfigureAwait(false);
- _logger?.LogInformation("Finished Verifying");
+ 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 (GameVerificationException e)
+ catch (Exception e)
{
- returnCode = e.HResult;
+ _logger?.LogError(e, $"Creating game engine failed: {e.Message}");
+ throw;
}
+
+ var progressReporter = new VerifyConsoleProgressReporter(installInformation.Name);
- if (settings.CreateNewBaseline)
- await WriteBaseline(verifyPipeline.Errors, settings.NewBaselinePath).ConfigureAwait(false);
+ using var verifyPipeline = new GameVerifyPipeline(
+ gameEngine,
+ engineErrorReporter,
+ settings.VerifyPipelineSettings,
+ settings.GlobalReportSettings,
+ progressReporter,
+ services);
- return returnCode;
+ 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 WriteBaseline(IEnumerable errors, string baselineFile)
+ private async Task ReportErrors(IReadOnlyCollection errors)
{
- var currentBaseline = settings.GameVerifySettings.GlobalReportSettings.Baseline;
+ _logger?.LogInformation("Reporting Errors...");
+
+ var reportBroker = new VerificationReportBroker(services);
+
+ await reportBroker.ReportAsync(errors);
- var newBaseline = currentBaseline.MergeWith(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 newBaseline.ToJsonAsync(fs);
+ await baseline.ToJsonAsync(fs);
}
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/ModVerifyPipeline.cs b/src/ModVerify.CliApp/ModVerifyPipeline.cs
deleted file mode 100644
index a2a798c..0000000
--- a/src/ModVerify.CliApp/ModVerifyPipeline.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using AET.ModVerify;
-using AET.ModVerify.Settings;
-using AET.ModVerify.Verifiers;
-using Microsoft.Extensions.DependencyInjection;
-using PG.StarWarsGame.Engine;
-using PG.StarWarsGame.Engine.Database;
-
-namespace AET.ModVerifyTool;
-
-internal class ModVerifyPipeline(
- GameEngineType targetType,
- GameLocations gameLocations,
- GameVerifySettings settings,
- IServiceProvider serviceProvider)
- : VerifyGamePipeline(targetType, gameLocations, settings, serviceProvider)
-{
- protected override IEnumerable CreateVerificationSteps(IGameDatabase database)
- {
- var verifyProvider = ServiceProvider.GetRequiredService();
- return verifyProvider.GetAllDefaultVerifiers(database, Settings);
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/ModVerifyOptions.cs b/src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs
similarity index 66%
rename from src/ModVerify.CliApp/Options/ModVerifyOptions.cs
rename to src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs
index 65e3a9b..d239112 100644
--- a/src/ModVerify.CliApp/Options/ModVerifyOptions.cs
+++ b/src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs
@@ -3,48 +3,33 @@
using CommandLine;
using PG.StarWarsGame.Engine;
-namespace AET.ModVerifyTool.Options;
+namespace AET.ModVerifyTool.Options.CommandLine;
-internal class ModVerifyOptions
+internal abstract class BaseModVerifyOptions
{
- [Option('o', "output", Required = false, HelpText = "directory where result files shall be stored to.")]
- public string? Output { get; set; }
-
- [Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")]
+ [Option('v', "verbose", Required = false, HelpText = "Sets output to verbose messages.")]
public bool Verbose { get; set; }
- [Option("baseline", Required = false, HelpText = "Path to a JSON baseline file.")]
- public string? Baseline { get; set; }
+ [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; }
+
+ [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; }
[Option("suppressions", Required = false, HelpText = "Path to a JSON suppression file.")]
public string? Suppressions { 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("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("createBaseline", Required = false,
- HelpText = "When a path is specified, the tools creates a new baseline file. " +
- "An existing file will be overwritten. Previous baselines are merged into the new baseline.")]
- public string? NewBaselineFile { get; set; }
-
-
[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. " +
"The argument cannot be combined with any of --mods, --game or --fallbackGame")]
public string? AutoPath { get; set; }
-
[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; }
-
[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. " +
@@ -57,7 +42,7 @@ internal class ModVerifyOptions
[Option("type", Required = false, Default = null,
- HelpText = "The game type of the mod that shall be verified. Leave this value to auto-determine the type. Valid values are 'Eaw' and 'Foc'. " +
+ 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; }
@@ -66,4 +51,9 @@ internal class ModVerifyOptions
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; }
+
+ [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; }
}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs b/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs
new file mode 100644
index 0000000..78132cc
--- /dev/null
+++ b/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs
@@ -0,0 +1,10 @@
+using CommandLine;
+
+namespace AET.ModVerifyTool.Options.CommandLine;
+
+[Verb("createBaseline", HelpText = "Verifies the specified game and creates a new baseline file at the specified location.")]
+internal class CreateBaselineVerbOption : BaseModVerifyOptions
+{
+ [Option('o', "outFile", Required = true, HelpText = "The file path of the new baseline file.")]
+ public string OutputFile { get; set; }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs
new file mode 100644
index 0000000..517ceda
--- /dev/null
+++ b/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs
@@ -0,0 +1,26 @@
+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/Options/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs
index 6c99bf4..6a3b0bd 100644
--- a/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs
+++ b/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs
@@ -1,19 +1,28 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Reporting.Settings;
using AET.ModVerify.Settings;
namespace AET.ModVerifyTool.Options;
-internal record ModVerifyAppSettings
+internal sealed class ModVerifyAppSettings
{
- public required GameVerifySettings GameVerifySettings { get; init; }
+ public bool Interactive => GameInstallationsSettings.Interactive;
+
+ public required VerifyPipelineSettings VerifyPipelineSettings { get; init; }
+
+ public required GlobalVerifyReportSettings GlobalReportSettings { get; init; }
public required GameInstallationsSettings GameInstallationsSettings { get; init; }
- public string Output { get; init; } = Environment.CurrentDirectory;
+ 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/Program.cs b/src/ModVerify.CliApp/Program.cs
index ea5fff9..7c88edb 100644
--- a/src/ModVerify.CliApp/Program.cs
+++ b/src/ModVerify.CliApp/Program.cs
@@ -1,96 +1,154 @@
-using System;
-using System.Collections.Generic;
-using System.IO.Abstractions;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-using AET.ModVerify;
+using AET.ModVerify;
+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.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 Microsoft.Extensions.Logging.Console;
-using PG.Commons.Extensibility;
+using PG.Commons;
using PG.StarWarsGame.Engine;
+using PG.StarWarsGame.Engine.Xml.Parsers;
using PG.StarWarsGame.Files.ALO;
-using PG.StarWarsGame.Files.DAT.Services.Builder;
-using PG.StarWarsGame.Files.MEG.Data.Archives;
+using PG.StarWarsGame.Files.MEG;
+using PG.StarWarsGame.Files.MTD;
using PG.StarWarsGame.Files.XML;
+using PG.StarWarsGame.Files.XML.Parsers;
using PG.StarWarsGame.Infrastructure;
-using PG.StarWarsGame.Infrastructure.Clients;
using PG.StarWarsGame.Infrastructure.Services.Detection;
using PG.StarWarsGame.Infrastructure.Services.Name;
using Serilog;
using Serilog.Events;
using Serilog.Filters;
+using Serilog.Sinks.SystemConsole.Themes;
+using System;
+using System.IO.Abstractions;
+using System.Threading.Tasks;
+using Testably.Abstractions;
+using ILogger = Serilog.ILogger;
namespace AET.ModVerifyTool;
internal class Program
{
- private const string EngineParserNamespace = "PG.StarWarsGame.Engine.Xml.Parsers";
- private const string ParserNamespace = "PG.StarWarsGame.Files.XML.Parsers";
+ 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 async Task Main(string[] args)
{
+ ConsoleUtilities.WriteHeader();
+
var result = 0;
- var parseResult = Parser.Default.ParseArguments(args);
+ Type[] programVerbs =
+ [
+ typeof(VerifyVerbOption),
+ typeof(CreateBaselineVerbOption),
+ ];
- ModVerifyOptions? verifyOptions = null!;
- await parseResult.WithParsedAsync(o =>
- {
- verifyOptions = o;
- return Task.CompletedTask;
- }).ConfigureAwait(false);
+ 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;
- }).ConfigureAwait(false);
+ });
- if (verifyOptions is null)
- {
- if (result != 0)
- return result;
- throw new InvalidOperationException("Mod verify was executed with the wrong arguments.");
- }
+ return result;
+ }
- var coreServiceCollection = CreateCoreServices(verifyOptions.Verbose);
+ private static async Task Run(BaseModVerifyOptions options)
+ {
+ var coreServiceCollection = CreateCoreServices(options.Verbose);
var coreServices = coreServiceCollection.BuildServiceProvider();
var logger = coreServices.GetService()?.CreateLogger(typeof(Program));
logger?.LogDebug($"Raw command line: {Environment.CommandLine}");
+ var interactive = false;
try
{
- var settings = new SettingsBuilder(coreServices)
- .BuildSettings(verifyOptions);
-
+ 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);
}
catch (Exception e)
{
- Console.WriteLine($"Error: {e.Message}");
+ ConsoleUtilities.WriteApplicationFailure();
logger?.LogCritical(e, e.Message);
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 static async Task CheckForUpdate(IServiceProvider services, Microsoft.Extensions.Logging.ILogger? logger)
+ {
+ var updateChecker = new ModVerifyUpdaterChecker(services);
+
+ logger?.LogDebug("Checking for available update");
+
+ 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();
+
+ }
+ }
+ catch(Exception e)
+ {
+ logger?.LogWarning($"Unable to check for updates due to an internal error: {e.Message}");
+ logger?.LogTrace(e, "Checking for update failed: " + e.Message);
+ }
+ }
+
private static IServiceCollection CreateCoreServices(bool verboseLogging)
{
- var fileSystem = new FileSystem();
+ var fileSystem = new RealFileSystem();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new WindowsRegistry());
@@ -101,54 +159,59 @@ private static IServiceCollection CreateCoreServices(bool verboseLogging)
return serviceCollection;
}
-
private static IServiceProvider CreateAppServices(IServiceCollection serviceCollection, ModVerifyAppSettings settings)
{
serviceCollection.AddSingleton(sp => new HashingService(sp));
SteamAbstractionLayer.InitializeServices(serviceCollection);
- PetroglyphGameClients.InitializeServices(serviceCollection);
PetroglyphGameInfrastructure.InitializeServices(serviceCollection);
- RuntimeHelpers.RunClassConstructor(typeof(IDatBuilder).TypeHandle);
- RuntimeHelpers.RunClassConstructor(typeof(IMegArchive).TypeHandle);
- AloServiceContribution.ContributeServices(serviceCollection);
- serviceCollection.CollectPgServiceContributions();
- XmlServiceContribution.ContributeServices(serviceCollection);
+ serviceCollection.SupportMTD();
+ serviceCollection.SupportMEG();
+ serviceCollection.SupportALO();
+ serviceCollection.SupportXML();
+ PetroglyphCommons.ContributeServices(serviceCollection);
PetroglyphEngineServiceContribution.ContributeServices(serviceCollection);
+ serviceCollection.RegisterVerifierCache();
- ModVerifyServiceContribution.ContributeServices(serviceCollection);
-
- SetupReporting(serviceCollection, settings);
+ SetupVerifyReporting(serviceCollection, settings);
- serviceCollection.AddSingleton(sp => new CompositeModNameResolver(sp, s =>
- new List
- {
- new OfflineWorkshopNameResolver(s),
- new OnlineWorkshopNameResolver(s),
- new DirectoryModNameResolver(s)
- }));
-
- serviceCollection.AddSingleton(sp => new OfflineModGameTypeResolver(sp));
+ if (settings.Offline)
+ {
+ serviceCollection.AddSingleton(sp => new OfflineModNameResolver(sp));
+ serviceCollection.AddSingleton(sp => new OfflineModGameTypeResolver(sp));
+ }
+ else
+ {
+ serviceCollection.AddSingleton(sp => new OnlineModNameResolver(sp));
+ serviceCollection.AddSingleton(sp => new OnlineModGameTypeResolver(sp));
+ }
return serviceCollection.BuildServiceProvider();
}
- private static void SetupReporting(IServiceCollection serviceCollection, ModVerifyAppSettings settings)
+ private static void SetupVerifyReporting(IServiceCollection serviceCollection, ModVerifyAppSettings settings)
{
- serviceCollection.RegisterConsoleReporter();
+ var printOnlySummary = settings.CreateNewBaseline;
+ serviceCollection.RegisterConsoleReporter(new VerifyReportSettings
+ {
+ MinimumReportSeverity = VerificationSeverity.Error
+ }, printOnlySummary);
+
+ if (string.IsNullOrEmpty(settings.ReportOutput))
+ return;
serviceCollection.RegisterJsonReporter(new JsonReporterSettings
{
- OutputDirectory = settings.Output,
- MinimumReportSeverity = settings.GameVerifySettings.GlobalReportSettings.MinimumReportSeverity
+ OutputDirectory = settings.ReportOutput!,
+ MinimumReportSeverity = settings.GlobalReportSettings.MinimumReportSeverity
});
serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings
{
- OutputDirectory = settings.Output,
- MinimumReportSeverity = settings.GameVerifySettings.GlobalReportSettings.MinimumReportSeverity
+ OutputDirectory = settings.ReportOutput!,
+ MinimumReportSeverity = settings.GlobalReportSettings.MinimumReportSeverity
});
}
@@ -157,48 +220,59 @@ private static void ConfigureLogging(ILoggingBuilder loggingBuilder, IFileSystem
loggingBuilder.ClearProviders();
// ReSharper disable once RedundantAssignment
- var logLevel = LogLevel.Information;
+ var logLevel = LogEventLevel.Information;
#if DEBUG
- logLevel = LogLevel.Debug;
+ logLevel = LogEventLevel.Debug;
loggingBuilder.AddDebug();
-#else
+#endif
+
if (verbose)
{
- logLevel = LogLevel.Debug;
+ logLevel = LogEventLevel.Verbose;
loggingBuilder.AddDebug();
}
-#endif
- loggingBuilder.SetMinimumLevel(logLevel);
-
- SetupFileLogging(loggingBuilder, fileSystem);
- loggingBuilder.AddFilter((category, level) =>
- {
- if (level < logLevel)
- return false;
- if (string.IsNullOrEmpty(category))
- return false;
- if (category.StartsWith(EngineParserNamespace) || category.StartsWith(ParserNamespace))
+ var fileLogger = SetupFileLogging(fileSystem, logLevel);
+ 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('\"');
+
+ if (source.StartsWith(ModVerifyRootNameSpace.AsSpan()))
+ return true;
+
return false;
- return true;
- }).AddConsole();
+ })
+ .CreateLogger();
+ loggingBuilder.AddSerilog(cLogger);
}
-
- private static void SetupFileLogging(ILoggingBuilder loggingBuilder, IFileSystem fileSystem)
+
+ private static ILogger SetupFileLogging(IFileSystem fileSystem, LogEventLevel minLevel)
{
var logPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), "ModVerify_log.txt");
-
- var logger = new LoggerConfiguration()
+ return new LoggerConfiguration()
.Enrich.FromLogContext()
- .MinimumLevel.Verbose()
+ .MinimumLevel.Is(minLevel)
.Filter.ByExcluding(IsXmlParserLogging)
- .WriteTo.RollingFile(
- logPath,
- outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}")
+ .WriteTo.Async(c =>
+ {
+ c.RollingFile(
+ logPath,
+ outputTemplate:
+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}");
+ })
.CreateLogger();
-
- loggingBuilder.AddSerilog(logger);
}
private static bool IsXmlParserLogging(LogEvent logEvent)
diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json
index 51b4cdc..1e01ece 100644
--- a/src/ModVerify.CliApp/Properties/launchSettings.json
+++ b/src/ModVerify.CliApp/Properties/launchSettings.json
@@ -1,8 +1,12 @@
{
"profiles": {
- "Interactive": {
+ "Interactive Verify": {
"commandName": "Project",
- "commandLineArgs": "-o verifyResults --minFailSeverity Information --baseline focBaseline.json"
+ "commandLineArgs": "verify -o verifyResults --minFailSeverity Information -v --baseline focBaseline.json --offline"
+ },
+ "Interactive Baseline": {
+ "commandName": "Project",
+ "commandLineArgs": "createBaseline -o focBaseline.json --offline"
},
"FromModPath": {
diff --git a/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs
new file mode 100644
index 0000000..69413c0
--- /dev/null
+++ b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace AET.ModVerifyTool.Reporting;
+
+internal sealed class EngineInitializeProgressReporter : IDisposable
+{
+ private Progress? _progress;
+
+ public EngineInitializeProgressReporter(Progress? progress)
+ {
+ if (progress is null)
+ return;
+ progress.ProgressChanged += OnProgress;
+ }
+
+ private void OnProgress(object sender, string e)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine(e);
+ Console.ResetColor();
+ }
+
+ public void Dispose()
+ {
+ Console.WriteLine();
+ if (_progress is not null)
+ _progress.ProgressChanged -= OnProgress;
+ _progress = null;
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs
new file mode 100644
index 0000000..37d3ca2
--- /dev/null
+++ b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs
@@ -0,0 +1,84 @@
+using AnakinRaW.CommonUtilities;
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
+using ShellProgressBar;
+using System;
+using System.Threading;
+using AET.ModVerify.Pipeline.Progress;
+
+namespace AET.ModVerifyTool.Reporting;
+
+public sealed class VerifyConsoleProgressReporter(string toVerifyName) : DisposableObject, IVerifyProgressReporter
+{
+ private static readonly ProgressBarOptions ProgressBarOptions = new()
+ {
+ BackgroundColor = ConsoleColor.DarkGray,
+ ForegroundColorError = ConsoleColor.DarkRed,
+ ProgressCharacter = '─',
+ WriteQueuedMessage = WriteQueuedMessage,
+ };
+
+ private ProgressBar? _progressBar;
+
+ public void ReportError(string message, string? errorLine)
+ {
+ var progressBar = EnsureProgressBar();
+ progressBar.WriteErrorLine(errorLine);
+ progressBar.Message = message;
+ }
+
+ public void Report(string message, double progress)
+ {
+ Report(progress, message, VerifyProgress.ProgressType, default);
+ }
+
+ public void Report(double progress, string? progressText, ProgressType type, VerifyProgressInfo detailedProgress)
+ {
+ if (type != VerifyProgress.ProgressType)
+ return;
+
+ var progressBar = EnsureProgressBar();
+
+ // TODO: Only recognize detailed mode
+ progressBar.Message = progressText;
+
+ if (progress >= 1.0)
+ progressBar.Message = $"Verified '{toVerifyName}'";
+
+ var cpb = progressBar.AsProgress();
+ cpb.Report(progress);
+
+ // TODO: Only in verbose mode
+ //progressBar.WriteLine(progressText);
+ }
+
+ protected override void DisposeResources()
+ {
+ base.DisposeResources();
+ _progressBar?.Dispose();
+ Console.WriteLine();
+ }
+
+ private ProgressBar EnsureProgressBar()
+ {
+ return LazyInitializer.EnsureInitialized(ref _progressBar,
+ () => new ProgressBar(100, $"Verifying '{toVerifyName}'", ProgressBarOptions))!;
+ }
+
+ private static int WriteQueuedMessage(ConsoleOutLine arg)
+ {
+ if (string.IsNullOrEmpty(arg.Line))
+ return 0;
+
+ var writer = Console.Out;
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ try
+ {
+ writer.WriteLine(arg.Line);
+ return 1;
+ }
+ finally
+ {
+ Console.ResetColor();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/SettingsBuilder.cs b/src/ModVerify.CliApp/SettingsBuilder.cs
index eeba013..d2ba861 100644
--- a/src/ModVerify.CliApp/SettingsBuilder.cs
+++ b/src/ModVerify.CliApp/SettingsBuilder.cs
@@ -1,81 +1,149 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Abstractions;
-using AET.ModVerify.Reporting;
+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.Collections.Generic;
+using System.IO;
+using System.IO.Abstractions;
+using AET.ModVerify.Pipeline;
+using AET.ModVerifyTool.Options.CommandLine;
+using Microsoft.Extensions.Logging;
namespace AET.ModVerifyTool;
-internal class SettingsBuilder(IServiceProvider services)
+internal sealed class SettingsBuilder(IServiceProvider services)
{
private readonly IFileSystem _fileSystem = services.GetRequiredService();
+ private readonly ILogger? _logger =
+ services.GetRequiredService()?.CreateLogger(typeof(SettingsBuilder));
- public ModVerifyAppSettings BuildSettings(ModVerifyOptions options)
+ public ModVerifyAppSettings BuildSettings(BaseModVerifyOptions options)
{
- var output = Environment.CurrentDirectory;
- if (!string.IsNullOrEmpty(options.Output))
- output = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(Environment.CurrentDirectory, options.Output));
+ switch (options)
+ {
+ case VerifyVerbOption verifyVerb:
+ return BuildFromVerifyVerb(verifyVerb);
+ case CreateBaselineVerbOption baselineVerb:
+ return BuildFromCreateBaselineVerb(baselineVerb);
+ }
+ throw new NotSupportedException($"The option '{options.GetType().Name}' is not supported!");
+ }
+ 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
{
- GameVerifySettings = BuildGameVerifySettings(options),
- GameInstallationsSettings = BuildInstallationSettings(options),
- Output = output,
- NewBaselinePath = options.NewBaselineFile
+ VerifyPipelineSettings = new VerifyPipelineSettings
+ {
+ ParallelVerifiers = verifyOptions.Parallel ? 4 : 1,
+ VerifiersProvider = new DefaultGameVerifiersProvider(),
+ FailFast = verifyOptions.FailFast,
+ GameVerifySettings = new GameVerifySettings
+ {
+ IgnoreAsserts = verifyOptions.IgnoreAsserts,
+ ThrowsOnMinimumSeverity = GetVerifierMinimumThrowSeverity()
+ }
+ },
+ AppThrowsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity,
+ GameInstallationsSettings = BuildInstallationSettings(verifyOptions),
+ GlobalReportSettings = BuilderGlobalReportSettings(verifyOptions),
+ ReportOutput = output,
+ Offline = verifyOptions.OfflineMode
};
+
+ VerificationSeverity? GetVerifierMinimumThrowSeverity()
+ {
+ var minFailSeverity = verifyOptions.MinimumFailureSeverity;
+ if (verifyOptions.FailFast)
+ {
+ if (minFailSeverity == null)
+ {
+ _logger?.LogWarning($"Verification is configured to fail fast but 'minFailSeverity' is not specified. " +
+ $"Using severity '{VerificationSeverity.Information}'.");
+ minFailSeverity = VerificationSeverity.Information;
+ }
+
+ return minFailSeverity;
+ }
+
+ // Only in a failFast scenario we want the verifier to throw.
+ // In a normal run, the verifier should simply store the error.
+ return null;
+ }
}
- private GameVerifySettings BuildGameVerifySettings(ModVerifyOptions options)
+ private ModVerifyAppSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOption baselineVerb)
{
- var settings = GameVerifySettings.Default;
-
- return settings with
+ return new ModVerifyAppSettings
{
- GlobalReportSettings = BuilderGlobalReportSettings(options),
- AbortSettings = BuildAbortSettings(options)
+ VerifyPipelineSettings = new VerifyPipelineSettings
+ {
+ ParallelVerifiers = baselineVerb.Parallel ? 4 : 1,
+ GameVerifySettings = new GameVerifySettings
+ {
+ IgnoreAsserts = false,
+ ThrowsOnMinimumSeverity = null,
+ },
+ VerifiersProvider = new DefaultGameVerifiersProvider(),
+ FailFast = false,
+ },
+ AppThrowsOnMinimumSeverity = null,
+ GameInstallationsSettings = BuildInstallationSettings(baselineVerb),
+ GlobalReportSettings = BuilderGlobalReportSettings(baselineVerb),
+ NewBaselinePath = baselineVerb.OutputFile,
+ ReportOutput = null,
+ Offline = baselineVerb.OfflineMode
};
}
- private VerificationAbortSettings BuildAbortSettings(ModVerifyOptions options)
+ private GlobalVerifyReportSettings BuilderGlobalReportSettings(BaseModVerifyOptions options)
{
- return new VerificationAbortSettings
+ return new GlobalVerifyReportSettings
{
- FailFast = options.FailFast,
- MinimumAbortSeverity = options.MinimumFailureSeverity ?? VerificationSeverity.Information,
- ThrowsGameVerificationException = options.MinimumFailureSeverity.HasValue || options.FailFast
+ Baseline = CreateBaseline(),
+ Suppressions = CreateSuppressions(),
+ MinimumReportSeverity = options.MinimumSeverity,
};
- }
- private GlobalVerificationReportSettings BuilderGlobalReportSettings(ModVerifyOptions options)
- {
- var baseline = VerificationBaseline.Empty;
- var suppressions = SuppressionList.Empty;
-
- if (options.Baseline is not null)
+ VerificationBaseline CreateBaseline()
{
- using var fs = _fileSystem.FileStream.New(options.Baseline, FileMode.Open, FileAccess.Read);
- baseline = VerificationBaseline.FromJson(fs);
+ // 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;
+ }
}
- if (options.Suppressions is not null)
+ SuppressionList CreateSuppressions()
{
+ if (options.Suppressions is null)
+ return SuppressionList.Empty;
using var fs = _fileSystem.FileStream.New(options.Suppressions, FileMode.Open, FileAccess.Read);
- suppressions = SuppressionList.FromJson(fs);
+ return SuppressionList.FromJson(fs);
}
-
- return new GlobalVerificationReportSettings
- {
- Baseline = baseline,
- Suppressions = suppressions,
- MinimumReportSeverity = VerificationSeverity.Information,
- };
}
- private GameInstallationsSettings BuildInstallationSettings(ModVerifyOptions options)
+ private GameInstallationsSettings BuildInstallationSettings(BaseModVerifyOptions options)
{
var modPaths = new List();
if (options.ModPaths is not null)
diff --git a/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs b/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs
new file mode 100644
index 0000000..968e6a9
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+
+namespace AET.ModVerifyTool.Updates;
+
+[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)]
+[method: JsonConstructor]
+internal sealed class GithubReleaseEntry(string tag, string branch, bool isPrerelease)
+{
+ [JsonPropertyName("tag_name")]
+ public string Tag { get; } = tag;
+
+ [JsonPropertyName("target_commitish")]
+ public string Branch { get; } = branch;
+
+ [JsonPropertyName("prerelease")]
+ public bool IsPrerelease { get; } = isPrerelease;
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/GithubReleaseList.cs b/src/ModVerify.CliApp/Updates/GithubReleaseList.cs
new file mode 100644
index 0000000..a54edfb
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/GithubReleaseList.cs
@@ -0,0 +1,5 @@
+using System.Collections.Generic;
+
+namespace AET.ModVerifyTool.Updates;
+
+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/ModVerifyUpdaterChecker.cs
new file mode 100644
index 0000000..d0ab59b
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs
@@ -0,0 +1,67 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Semver;
+
+namespace AET.ModVerifyTool.Updates;
+
+internal sealed class ModVerifyUpdaterChecker
+{
+ private readonly ILogger? _logger;
+
+ public ModVerifyUpdaterChecker(IServiceProvider serviceProvider)
+ {
+ _logger = serviceProvider.GetService()?.CreateLogger(GetType());
+ }
+
+ public async Task CheckForUpdateAsync()
+ {
+ var githubReleases = await DownloadReleaseList().ConfigureAwait(false);
+
+ var branch = ModVerifyUpdaterInformation.BranchName;
+ var latestRelease = githubReleases.FirstOrDefault(r => r.Branch == branch);
+
+ if (latestRelease == null)
+ throw new InvalidOperationException($"Unable to find a release for branch '{branch}'.");
+
+ 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;
+ 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}]");
+ return default;
+ }
+
+ _logger?.LogDebug($"Update available - [Current Version = {currentVersion}], [Available Version = {latestVersion}]");
+ return new UpdateInfo
+ {
+ DownloadLink = ModVerifyUpdaterInformation.ModVerifyReleasesDownloadLink,
+ IsUpdateAvailable = true,
+ NewVersion = latestVersion.ToString()
+ };
+ }
+
+ 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);
+
+ using var jsonStream = new MemoryStream();
+ await downloadStream.CopyToAsync(jsonStream).ConfigureAwait(false);
+ jsonStream.Seek(0, SeekOrigin.Begin);
+
+ var releases = await JsonSerializer.DeserializeAsync(jsonStream).ConfigureAwait(false);
+ return releases ?? throw new InvalidOperationException("Unable to deserialize releases.");
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs
new file mode 100644
index 0000000..2fa1022
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs
@@ -0,0 +1,22 @@
+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/UpdateInfo.cs b/src/ModVerify.CliApp/Updates/UpdateInfo.cs
new file mode 100644
index 0000000..b6a6505
--- /dev/null
+++ b/src/ModVerify.CliApp/Updates/UpdateInfo.cs
@@ -0,0 +1,14 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace AET.ModVerifyTool.Updates;
+
+internal readonly struct UpdateInfo
+{
+ public string DownloadLink { get; init; }
+
+ public string? NewVersion { get; init; }
+
+ [MemberNotNullWhen(true, nameof(DownloadLink), nameof(NewVersion))]
+ public bool IsUpdateAvailable { get; init; }
+
+}
\ No newline at end of file
diff --git a/src/ModVerify.CliApp/VerifyGameInstallationData.cs b/src/ModVerify.CliApp/VerifyInstallationInformation.cs
similarity index 94%
rename from src/ModVerify.CliApp/VerifyGameInstallationData.cs
rename to src/ModVerify.CliApp/VerifyInstallationInformation.cs
index 69513ca..66816ab 100644
--- a/src/ModVerify.CliApp/VerifyGameInstallationData.cs
+++ b/src/ModVerify.CliApp/VerifyInstallationInformation.cs
@@ -3,7 +3,7 @@
namespace AET.ModVerifyTool;
-internal class VerifyGameInstallationData
+internal sealed class VerifyInstallationInformation
{
public required string Name { get; init; }
diff --git a/src/ModVerify/GameVerificationException.cs b/src/ModVerify/GameVerificationException.cs
index 64415d9..1096179 100644
--- a/src/ModVerify/GameVerificationException.cs
+++ b/src/ModVerify/GameVerificationException.cs
@@ -5,29 +5,37 @@
namespace AET.ModVerify;
-public sealed class GameVerificationException(IEnumerable errors) : Exception
+public sealed class GameVerificationException : Exception
{
- private readonly string? _error = null;
- private readonly IEnumerable _errors = errors ?? throw new ArgumentNullException(nameof(errors));
-
- public GameVerificationException(VerificationError error) : this([error])
- {
- }
-
- ///
- public override string Message => Error;
-
- private string Error
+ private readonly string? _errorMessage = null;
+
+ public IReadOnlyCollection Errors { get; }
+
+ private string ErrorMessage
{
get
{
- if (_error != null)
- return _error;
+ if (_errorMessage != null)
+ return _errorMessage;
var stringBuilder = new StringBuilder();
- foreach (var error in _errors)
- stringBuilder.AppendLine($"Verification error: {error.Id}:{error.Message};");
+ foreach (var error in Errors)
+ stringBuilder.AppendLine($"Verification error: {error.Id}: {error.Message};");
return stringBuilder.ToString().TrimEnd(';');
}
}
+
+ ///
+ public override string Message => ErrorMessage;
+
+ public GameVerificationException(VerificationError error) : this([error])
+ {
+ }
+
+ public GameVerificationException(IEnumerable errors)
+ {
+ if (errors is null)
+ throw new ArgumentNullException(nameof(errors));
+ Errors = [..errors];
+ }
}
\ No newline at end of file
diff --git a/src/ModVerify/IGameVerifier.cs b/src/ModVerify/IGameVerifier.cs
deleted file mode 100644
index 1184c86..0000000
--- a/src/ModVerify/IGameVerifier.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Collections.Generic;
-using AET.ModVerify.Reporting;
-
-namespace AET.ModVerify;
-
-public interface IGameVerifier
-{
- string Name { get; }
-
- string FriendlyName { get; }
-
- IReadOnlyCollection VerifyErrors { get; }
-}
\ No newline at end of file
diff --git a/src/ModVerify/IVerificationProvider.cs b/src/ModVerify/IVerificationProvider.cs
deleted file mode 100644
index 2d87ab5..0000000
--- a/src/ModVerify/IVerificationProvider.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Collections.Generic;
-using AET.ModVerify.Settings;
-using AET.ModVerify.Verifiers;
-using PG.StarWarsGame.Engine.Database;
-
-namespace AET.ModVerify;
-
-public interface IVerificationProvider
-{
- IEnumerable GetAllDefaultVerifiers(IGameDatabase database, GameVerifySettings settings);
-}
\ No newline at end of file
diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj
index 4a77b42..e5f5b7d 100644
--- a/src/ModVerify/ModVerify.csproj
+++ b/src/ModVerify/ModVerify.csproj
@@ -22,21 +22,27 @@
-
-
- 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
+
+
+
-
-
+
+
+
+
+
+
diff --git a/src/ModVerify/ModVerify.csproj.DotSettings b/src/ModVerify/ModVerify.csproj.DotSettings
new file mode 100644
index 0000000..fcd6f14
--- /dev/null
+++ b/src/ModVerify/ModVerify.csproj.DotSettings
@@ -0,0 +1,4 @@
+
+ True
+ True
+ True
\ No newline at end of file
diff --git a/src/ModVerify/ModVerifyServiceContribution.cs b/src/ModVerify/ModVerifyServiceContribution.cs
deleted file mode 100644
index d223d2c..0000000
--- a/src/ModVerify/ModVerifyServiceContribution.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace AET.ModVerify;
-
-public static class ModVerifyServiceContribution
-{
- public static void ContributeServices(IServiceCollection serviceCollection)
- {
- serviceCollection.AddSingleton(sp => new VerificationProvider(sp));
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify/ModVerifyServiceExtensions.cs b/src/ModVerify/ModVerifyServiceExtensions.cs
new file mode 100644
index 0000000..8b46960
--- /dev/null
+++ b/src/ModVerify/ModVerifyServiceExtensions.cs
@@ -0,0 +1,12 @@
+using AET.ModVerify.Verifiers;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AET.ModVerify;
+
+public static class ModVerifyServiceExtensions
+{
+ public static IServiceCollection RegisterVerifierCache(this IServiceCollection serviceCollection)
+ {
+ return serviceCollection.AddSingleton(sp => new AlreadyVerifiedCache(sp));
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs b/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs
new file mode 100644
index 0000000..26b3893
--- /dev/null
+++ b/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Verifiers;
+using AET.ModVerify.Verifiers.GuiDialogs;
+using PG.StarWarsGame.Engine;
+
+namespace AET.ModVerify.Pipeline;
+
+public sealed class DefaultGameVerifiersProvider : IGameVerifiersProvider
+{
+ public IEnumerable GetVerifiers(
+ IStarWarsGameEngine database,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider)
+ {
+ yield return new ReferencedModelsVerifier(database, settings, serviceProvider);
+ yield return new DuplicateNameFinder(database, settings, serviceProvider);
+ yield return new AudioFilesVerifier(database, settings, serviceProvider);
+ yield return new GuiDialogsVerifier(database, settings, serviceProvider);
+ //yield return new CommandBarVerifier(database, settings, serviceProvider);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs
new file mode 100644
index 0000000..a27ee9b
--- /dev/null
+++ b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs
@@ -0,0 +1,55 @@
+using AET.ModVerify.Verifiers;
+using AnakinRaW.CommonUtilities.SimplePipeline;
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
+using AnakinRaW.CommonUtilities.SimplePipeline.Steps;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading;
+using AET.ModVerify.Pipeline.Progress;
+
+namespace AET.ModVerify.Pipeline;
+
+public sealed class GameVerifierPipelineStep(
+ GameVerifier verifier,
+ IServiceProvider serviceProvider)
+ : PipelineStep(serviceProvider), IProgressStep
+{
+ public event EventHandler>? Progress;
+
+ internal GameVerifier GameVerifier { get; } = verifier ?? throw new ArgumentNullException(nameof(verifier));
+
+ public ProgressType Type => VerifyProgress.ProgressType;
+
+ public long Size => 1;
+
+ protected override void RunCore(CancellationToken token)
+ {
+ try
+ {
+ Logger?.LogDebug($"Running verifier '{GameVerifier.FriendlyName}'...");
+ ReportProgress(new ProgressEventArgs(0.0, "Started"));
+
+ GameVerifier.Progress += OnVerifyProgress;
+ GameVerifier.Verify(token);
+
+ Logger?.LogDebug($"Finished verifier '{GameVerifier.FriendlyName}'");
+ ReportProgress(new ProgressEventArgs(1.0, "Finished"));
+ }
+ finally
+ {
+ GameVerifier.Progress += OnVerifyProgress;
+ }
+ }
+
+ private void OnVerifyProgress(object _, ProgressEventArgs e)
+ {
+ if (e.Progress > 1.0)
+ e = new ProgressEventArgs(1.0, e.ProgressText, e.ProgressInfo);
+ ReportProgress(e);
+ }
+
+ private void ReportProgress(ProgressEventArgs e)
+ {
+ Progress?.Invoke(this, e);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs
new file mode 100644
index 0000000..b7ac00d
--- /dev/null
+++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs
@@ -0,0 +1,133 @@
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Reporting.Settings;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Utilities;
+using AET.ModVerify.Verifiers;
+using AnakinRaW.CommonUtilities.SimplePipeline;
+using AnakinRaW.CommonUtilities.SimplePipeline.Runners;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AET.ModVerify.Pipeline.Progress;
+
+namespace AET.ModVerify.Pipeline;
+
+public sealed class GameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline
+{
+ private readonly List _verifiers = new();
+ private readonly List _verificationSteps = new();
+ private readonly StepRunnerBase _verifyRunner;
+
+ private readonly IStarWarsGameEngine _gameEngine;
+ private readonly IGameEngineErrorCollection _engineErrors;
+
+ private readonly VerifyPipelineSettings _pipelineSettings;
+ private readonly GlobalVerifyReportSettings _reportSettings;
+
+ private readonly IVerifyProgressReporter _progressReporter;
+
+ protected override bool FailFast { get; }
+
+ public IReadOnlyCollection FilteredErrors { get; private set; } = [];
+
+ public GameVerifyPipeline(
+ IStarWarsGameEngine gameEngine,
+ IGameEngineErrorCollection engineErrors,
+ VerifyPipelineSettings pipelineSettings,
+ GlobalVerifyReportSettings reportSettings,
+ IVerifyProgressReporter progressReporter,
+ IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine));
+ _engineErrors = engineErrors ?? throw new ArgumentNullException(nameof(gameEngine));
+ _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings));
+ _reportSettings = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings));
+ _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter));
+
+ if (pipelineSettings.ParallelVerifiers is < 0 or > 64)
+ throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", nameof(pipelineSettings));
+
+ if (pipelineSettings.ParallelVerifiers == 1)
+ _verifyRunner = new SequentialStepRunner(serviceProvider);
+ else
+ _verifyRunner = new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider);
+
+ FailFast = pipelineSettings.FailFast;
+ }
+
+ protected override Task PrepareCoreAsync()
+ {
+ _verifiers.Clear();
+
+ AddStep(new GameEngineErrorCollector(_engineErrors, _gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider));
+
+ foreach (var gameVerificationStep in CreateVerificationSteps(_gameEngine))
+ AddStep(gameVerificationStep);
+
+ return Task.FromResult(true);
+ }
+
+ protected override async Task RunCoreAsync(CancellationToken token)
+ {
+ var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps);
+
+ try
+ {
+ Logger?.LogInformation("Running game verifiers...");
+ _verifyRunner.Error += OnError;
+ await _verifyRunner.RunAsync(token);
+ }
+ finally
+ {
+ aggregatedVerifyProgressReporter.Dispose();
+ _verifyRunner.Error -= OnError;
+ Logger?.LogDebug("Game verifiers finished.");
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ var failedSteps = _verifyRunner.ExecutedSteps.Where(p =>
+ p.Error != null && !p.Error.IsExceptionType()).ToList();
+
+ if (failedSteps.Count != 0)
+ throw new StepFailureException(failedSteps);
+
+ FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList();
+ }
+
+ protected override void OnError(object sender, StepRunnerErrorEventArgs e)
+ {
+ if (FailFast && e.Exception is GameVerificationException v)
+ {
+ if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error)))
+ return;
+ }
+ base.OnError(sender, e);
+ }
+
+ private IEnumerable CreateVerificationSteps(IStarWarsGameEngine database)
+ {
+ return _pipelineSettings.VerifiersProvider.GetVerifiers(database, _pipelineSettings.GameVerifySettings, ServiceProvider);
+ }
+
+ private void AddStep(GameVerifier verifier)
+ {
+ var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider);
+ _verifyRunner.AddStep(verificationStep);
+ _verificationSteps.Add(verificationStep);
+ _verifiers.Add(verifier);
+ }
+
+ private IEnumerable GetReportableErrors(IEnumerable errors)
+ {
+ Logger?.LogDebug("Applying baseline and suppressions.");
+ // NB: We don't filter for severity here, as the individual reporters handle that.
+ // This allows better control over what gets reported.
+ return errors.ApplyBaseline(_reportSettings.Baseline)
+ .ApplySuppressions(_reportSettings.Suppressions);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/IGameVerifiersProvider.cs b/src/ModVerify/Pipeline/IGameVerifiersProvider.cs
new file mode 100644
index 0000000..930afa6
--- /dev/null
+++ b/src/ModVerify/Pipeline/IGameVerifiersProvider.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Verifiers;
+using PG.StarWarsGame.Engine;
+
+namespace AET.ModVerify.Pipeline;
+
+public interface IGameVerifiersProvider
+{
+ IEnumerable GetVerifiers(
+ IStarWarsGameEngine database,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider);
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs b/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs
new file mode 100644
index 0000000..361febe
--- /dev/null
+++ b/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs
@@ -0,0 +1,72 @@
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
+using System;
+using System.Collections.Generic;
+
+namespace AET.ModVerify.Pipeline.Progress;
+
+internal class AggregatedVerifyProgressReporter(
+ IVerifyProgressReporter progressReporter,
+ IEnumerable steps)
+ : AggregatedProgressReporter(progressReporter, steps)
+{
+ private readonly object _syncLock = new();
+ private readonly IDictionary _progressTable = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ private double _completedSize;
+ private readonly HashSet _completedVerifiers = new();
+
+ protected override string GetProgressText(GameVerifierPipelineStep step, string? progressText)
+ {
+ return string.IsNullOrEmpty(progressText)
+ ? $"Verifier '{step.GameVerifier.FriendlyName}'"
+ : $"Verifier '{step.GameVerifier.FriendlyName}': {progressText}";
+ }
+
+ protected override ProgressEventArgs CalculateAggregatedProgress(
+ GameVerifierPipelineStep step,
+ ProgressEventArgs progress)
+ {
+ var key = step.GameVerifier.Name;
+
+ var progressValue = progress.Progress;
+ var stepProgress = progressValue * step.Size;
+
+ if (progressValue >= 1.0)
+ _completedVerifiers.Add(key);
+
+ double totalProgress;
+
+ lock (_syncLock)
+ {
+ if (!_progressTable.TryGetValue(key, out var storedProgress))
+ {
+ _progressTable.Add(key, stepProgress);
+ _completedSize += stepProgress;
+ }
+ else
+ {
+ var deltaSize = stepProgress - storedProgress;
+ _progressTable[key] = stepProgress;
+ _completedSize += deltaSize;
+ }
+
+ totalProgress = _completedSize / TotalSize;
+ totalProgress = Math.Min(totalProgress, 1.0);
+ }
+
+ if (_completedVerifiers.Count >= TotalStepCount && progressValue >= 1.0)
+ totalProgress = 1.0;
+
+ var progressInfo = new VerifyProgressInfo
+ {
+ TotalVerifiers = TotalStepCount,
+ };
+ return new ProgressEventArgs(totalProgress, progress.ProgressText, progressInfo);
+ }
+
+ protected override void DisposeResources()
+ {
+ base.DisposeResources();
+ _progressTable.Clear();
+ _completedVerifiers.Clear();
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs b/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs
new file mode 100644
index 0000000..e63e0f9
--- /dev/null
+++ b/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs
@@ -0,0 +1,5 @@
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
+
+namespace AET.ModVerify.Pipeline.Progress;
+
+public interface IVerifyProgressReporter : IProgressReporter;
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgress.cs b/src/ModVerify/Pipeline/Progress/VerifyProgress.cs
new file mode 100644
index 0000000..a18f3d3
--- /dev/null
+++ b/src/ModVerify/Pipeline/Progress/VerifyProgress.cs
@@ -0,0 +1,12 @@
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
+
+namespace AET.ModVerify.Pipeline.Progress;
+
+public static class VerifyProgress
+{
+ public static readonly ProgressType ProgressType = new()
+ {
+ Id = "Verify",
+ DisplayName = "Verify"
+ };
+}
\ No newline at end of file
diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs b/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs
new file mode 100644
index 0000000..1409239
--- /dev/null
+++ b/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs
@@ -0,0 +1,8 @@
+namespace AET.ModVerify.Pipeline.Progress;
+
+public struct VerifyProgressInfo
+{
+ public bool IsDetailed { get; init; }
+
+ public int TotalVerifiers { get; internal set; }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs b/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs
new file mode 100644
index 0000000..3130547
--- /dev/null
+++ b/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs
@@ -0,0 +1,34 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using PG.StarWarsGame.Engine.ErrorReporting;
+
+namespace AET.ModVerify.Reporting;
+
+public sealed class ConcurrentGameEngineErrorReporter : GameEngineErrorReporter, IGameEngineErrorCollection
+{
+ private readonly ConcurrentBag _xmlErrors = new();
+ private readonly ConcurrentBag _initializationErrors = new();
+ private readonly ConcurrentBag _asserts = new();
+
+ public IEnumerable XmlErrors => _xmlErrors.ToList();
+
+ public IEnumerable InitializationErrors => _initializationErrors.ToList();
+
+ public IEnumerable Asserts => _asserts.ToList();
+
+ public override void Report(XmlError error)
+ {
+ _xmlErrors.Add(error);
+ }
+
+ public override void Report(InitializationError error)
+ {
+ _initializationErrors.Add(error);
+ }
+
+ public override void Assert(EngineAssert assert)
+ {
+ _asserts.Add(assert);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/IGameEngineErrorCollection.cs b/src/ModVerify/Reporting/IGameEngineErrorCollection.cs
new file mode 100644
index 0000000..14d59d9
--- /dev/null
+++ b/src/ModVerify/Reporting/IGameEngineErrorCollection.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using PG.StarWarsGame.Engine.ErrorReporting;
+
+namespace AET.ModVerify.Reporting;
+
+public interface IGameEngineErrorCollection
+{
+ IEnumerable XmlErrors { get; }
+ IEnumerable InitializationErrors { get; }
+ IEnumerable Asserts { get; }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/IncompatibleBaselineException.cs b/src/ModVerify/Reporting/IncompatibleBaselineException.cs
new file mode 100644
index 0000000..c9a9eb1
--- /dev/null
+++ b/src/ModVerify/Reporting/IncompatibleBaselineException.cs
@@ -0,0 +1,8 @@
+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/Json/JsonSuppressionFilter.cs b/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs
index 92047d8..6d232c7 100644
--- a/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs
+++ b/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
namespace AET.ModVerify.Reporting.Json;
@@ -13,7 +12,7 @@ internal class JsonSuppressionFilter(SuppressionFilter filter)
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Verifier { get; } = filter.Verifier;
- [JsonPropertyName("assets")]
+ [JsonPropertyName("asset")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public IEnumerable? Assets { get; } = filter.Assets;
+ public string? Asset { get; } = filter.Asset;
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs b/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs
index 303e168..4688c98 100644
--- a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs
+++ b/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
@@ -6,17 +7,28 @@ namespace AET.ModVerify.Reporting.Json;
internal class JsonVerificationBaseline
{
+ [JsonPropertyName("version")]
+ public Version? Version { get; }
+
+ [JsonPropertyName("minSeverity")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public VerificationSeverity MinimumSeverity { get; }
+
[JsonPropertyName("errors")]
public IEnumerable Errors { get; }
public JsonVerificationBaseline(VerificationBaseline baseline)
{
Errors = baseline.Select(x => new JsonVerificationError(x));
+ Version = baseline.Version;
+ MinimumSeverity = baseline.MinimumSeverity;
}
[JsonConstructor]
- private JsonVerificationBaseline(IEnumerable errors)
+ private JsonVerificationBaseline(Version version, VerificationSeverity minimumSeverity, IEnumerable errors)
{
Errors = errors;
+ Version = version;
+ MinimumSeverity = minimumSeverity;
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Json/JsonVerificationError.cs b/src/ModVerify/Reporting/Json/JsonVerificationError.cs
index 9c69d8d..ac80810 100644
--- a/src/ModVerify/Reporting/Json/JsonVerificationError.cs
+++ b/src/ModVerify/Reporting/Json/JsonVerificationError.cs
@@ -8,34 +8,46 @@ internal class JsonVerificationError
[JsonPropertyName("id")]
public string Id { get; }
- [JsonPropertyName("verifier")]
- public string Verifier { get; }
+ [JsonPropertyName("verifiers")]
+ public IReadOnlyList VerifierChain { get; }
[JsonPropertyName("message")]
public string Message { get; }
[JsonPropertyName("severity")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
public VerificationSeverity Severity { get; }
- [JsonPropertyName("assets")]
- public IEnumerable Assets { get; }
+ [JsonPropertyName("context")]
+ public IEnumerable ContextEntries { get; }
+
+ [JsonPropertyName("asset")]
+ public string Asset { get; }
[JsonConstructor]
- private JsonVerificationError(string id, string verifier, string message, VerificationSeverity severity, IEnumerable assets)
+ private JsonVerificationError(
+ string id,
+ IReadOnlyList? verifierChain,
+ string message,
+ VerificationSeverity severity,
+ IEnumerable? contextEntries,
+ string asset)
{
Id = id;
- Verifier = verifier;
+ VerifierChain = verifierChain ?? [];
Message = message;
Severity = severity;
- Assets = assets;
+ ContextEntries = contextEntries ?? [];
+ Asset = asset ?? string.Empty;
}
public JsonVerificationError(VerificationError error)
{
Id = error.Id;
- Verifier = error.Verifier;
+ VerifierChain = error.VerifierChain;
Message = error.Message;
Severity = error.Severity;
- Assets = error.AffectedAssets;
+ ContextEntries = error.ContextEntries;
+ Asset = error.Asset;
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs
index bda277f..7ee435d 100644
--- a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs
@@ -2,28 +2,68 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using AET.ModVerify.Reporting.Settings;
namespace AET.ModVerify.Reporting.Reporters;
-internal class ConsoleReporter(VerificationReportSettings settings, IServiceProvider serviceProvider) : ReporterBase(settings, serviceProvider)
+internal class ConsoleReporter(
+ VerifyReportSettings settings,
+ bool summaryOnly,
+ IServiceProvider serviceProvider) :
+ ReporterBase(settings, serviceProvider)
{
public override Task ReportAsync(IReadOnlyCollection errors)
{
- var filteredErrors = FilteredErrors(errors).ToList();
+ var filteredErrors = FilteredErrors(errors).OrderByDescending(x => x.Severity).ToList();
+ PrintErrorStats(errors, filteredErrors);
+ Console.WriteLine();
+ return Task.CompletedTask;
+ }
+ private void PrintErrorStats(IReadOnlyCollection errors, List filteredErrors)
+ {
+ Console.WriteLine();
Console.WriteLine();
- Console.WriteLine("GAME VERIFICATION RESULT");
- Console.WriteLine($"Errors of severity {Settings.MinimumReportSeverity}: {filteredErrors.Count}");
+ Console.WriteLine("***********************");
+ Console.WriteLine(" Error Report ");
+ Console.WriteLine("***********************");
Console.WriteLine();
+ if (errors.Count == 0)
+ {
+ if (summaryOnly)
+ {
+ Console.WriteLine("No errors found.");
+ }
+ else
+ {
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("No errors! Well done :)");
+ }
- if (filteredErrors.Count == 0)
- Console.WriteLine("No errors!");
+ Console.ResetColor();
+ return;
+ }
- foreach (var error in filteredErrors)
- Console.WriteLine(error);
+ Console.WriteLine($"TOTAL Verification Errors: {errors.Count}");
+ var groupedBySeverity = errors.GroupBy(x => x.Severity);
+ foreach (var group in groupedBySeverity)
+ Console.WriteLine($" Severity {group.Key}: {group.Count()}");
Console.WriteLine();
- return Task.CompletedTask;
+ if (filteredErrors.Count == 0)
+ {
+ if (errors.Count != 0)
+ Console.WriteLine("Some errors are not displayed to the console. Please check the created output files.");
+ return;
+ }
+
+ if (summaryOnly)
+ return;
+
+ Console.WriteLine($"Below the list of errors with severity '{Settings.MinimumReportSeverity}' or higher:");
+
+ foreach (var error in filteredErrors)
+ Console.WriteLine($"[{error.Severity}] [{error.Id}] Message={error.Message}");
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs b/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs
new file mode 100644
index 0000000..5ecbb6f
--- /dev/null
+++ b/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using AnakinRaW.CommonUtilities;
+using PG.StarWarsGame.Engine.IO;
+
+namespace AET.ModVerify.Reporting.Reporters.Engine;
+
+internal abstract class EngineErrorReporterBase(IGameRepository gameRepository, IServiceProvider serviceProvider)
+{
+ protected readonly IGameRepository GameRepository = gameRepository ?? throw new ArgumentNullException(nameof(gameRepository));
+ protected readonly IServiceProvider ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+
+ public abstract string Name { get; }
+
+ public IEnumerable GetErrors(IEnumerable errors)
+ {
+ foreach (var error in errors)
+ {
+ var errorData = CreateError(error);
+ yield return new VerificationError(
+ errorData.Identifier, errorData.Message, [Name], errorData.Context, errorData.Asset, errorData.Severity);
+ }
+ }
+
+ protected abstract ErrorData CreateError(T error);
+
+ protected readonly ref struct ErrorData
+ {
+ public string Identifier { get; }
+ public string Message { get; }
+ public IEnumerable Context { get; }
+ public string Asset { get; }
+ public VerificationSeverity Severity { get; }
+
+ public ErrorData(string identifier, string message, IEnumerable context, string asset, VerificationSeverity severity)
+ {
+ ThrowHelper.ThrowIfNullOrEmpty(identifier);
+ ThrowHelper.ThrowIfNullOrEmpty(message);
+ Identifier = identifier;
+ Message = message;
+ Context = context;
+ Asset = asset;
+ Severity = severity;
+ }
+
+ public ErrorData(string identifier, string message, string asset, VerificationSeverity severity) : this(identifier, message, [], asset, severity)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs
new file mode 100644
index 0000000..a2a00c5
--- /dev/null
+++ b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using AET.ModVerify.Verifiers;
+using PG.StarWarsGame.Engine.ErrorReporting;
+using PG.StarWarsGame.Engine.IO;
+
+namespace AET.ModVerify.Reporting.Reporters.Engine;
+
+internal sealed class GameAssertErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider)
+ : EngineErrorReporterBase(gameRepository, serviceProvider)
+{
+ public override string Name => "GameAsserts";
+
+ protected override ErrorData CreateError(EngineAssert assert)
+ {
+ var context = new List();
+
+ if (assert.Value is not null)
+ context.Add($"value='{assert.Value}'");
+ if (assert.Context is not null)
+ context.Add($"context='{assert.Context}'");
+
+ // The location is the only identifiable thing of an assert. 'Value' might be null, thus we cannot use it.
+ var asset = GetLocation(assert);
+
+ return new ErrorData(GetIdFromError(assert.Kind), assert.Message, asset, VerificationSeverity.Warning);
+ }
+
+ private static string GetLocation(EngineAssert assert)
+ {
+ var sb = new StringBuilder("method='");
+ if (assert.TypeName is not null)
+ {
+ sb.Append(assert.TypeName);
+ sb.Append("::");
+ }
+ sb.Append(assert.Method);
+ sb.Append('\'');
+ return sb.ToString();
+ }
+
+ private static string GetIdFromError(EngineAssertKind assertKind)
+ {
+ return assertKind switch
+ {
+ EngineAssertKind.NullOrEmptyValue => VerifierErrorCodes.AssertValueNullOrEmpty,
+ EngineAssertKind.ValueOutOfRange => VerifierErrorCodes.AssertValueOutOfRange,
+ EngineAssertKind.InvalidValue => VerifierErrorCodes.AssertValueInvalid,
+ EngineAssertKind.FileNotFound => VerifierErrorCodes.FileNotFound,
+ _ => throw new ArgumentOutOfRangeException(nameof(assertKind), assertKind, null)
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs
new file mode 100644
index 0000000..fc44c66
--- /dev/null
+++ b/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs
@@ -0,0 +1,21 @@
+using System;
+using AET.ModVerify.Verifiers;
+using PG.StarWarsGame.Engine.ErrorReporting;
+using PG.StarWarsGame.Engine.IO;
+
+namespace AET.ModVerify.Reporting.Reporters.Engine;
+
+internal sealed class InitializationErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider)
+ : EngineErrorReporterBase(gameRepository, serviceProvider)
+{
+ public override string Name => "InitializationErrors";
+
+ protected override ErrorData CreateError(InitializationError error)
+ {
+ return new ErrorData(
+ VerifierErrorCodes.InitializationError,
+ error.Message,
+ error.GameManager,
+ VerificationSeverity.Critical);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs
new file mode 100644
index 0000000..094a701
--- /dev/null
+++ b/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Abstractions;
+using AET.ModVerify.Utilities;
+using AET.ModVerify.Verifiers;
+using Microsoft.Extensions.DependencyInjection;
+using PG.StarWarsGame.Engine.ErrorReporting;
+using PG.StarWarsGame.Engine.IO;
+using PG.StarWarsGame.Files.XML.ErrorHandling;
+
+namespace AET.ModVerify.Reporting.Reporters.Engine;
+
+internal sealed class XmlParseErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) :
+ EngineErrorReporterBase(gameRepository, serviceProvider)
+{
+ private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
+
+ public override string Name => "XMLError";
+
+ protected override ErrorData CreateError(XmlError error)
+ {
+ var id = GetIdFromError(error.ErrorKind);
+ var severity = GetSeverityFromError(error.ErrorKind);
+
+ var strippedFileName = _fileSystem.Path
+ .GetGameStrippedPath(GameRepository.Path.AsSpan(), error.FileLocation.XmlFile.ToUpperInvariant().AsSpan()).ToString();
+
+ var asset = strippedFileName;
+
+ var context = new List
+ {
+ strippedFileName
+ };
+
+ var xmlElement = error.Element;
+
+ if (xmlElement is not null)
+ {
+ var localName = xmlElement.Name.LocalName;
+ context.Add(localName);
+
+ asset = localName;
+
+ var parent = xmlElement.Parent;
+
+ if (parent != null)
+ {
+ var parentName = parent.Attribute("Name");
+ context.Add(parentName != null ? $"parentName='{parentName.Value}'" : $"parentTag='{parent.Name.LocalName}'");
+ }
+ }
+
+ var errorMessage = CreateErrorMessage(error, strippedFileName);
+ return new ErrorData(id, errorMessage, context, asset, severity);
+ }
+
+ private static string CreateErrorMessage(XmlError error, string strippedFileName)
+ {
+ if (error.FileLocation.Line.HasValue)
+ return $"{error.Message} File='{strippedFileName} #{error.FileLocation.Line.Value}'";
+ return $"{error.Message} File='{strippedFileName}'";
+ }
+
+ private static VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErrorKind)
+ {
+ return xmlErrorErrorKind switch
+ {
+ XmlParseErrorKind.EmptyRoot => VerificationSeverity.Critical,
+ XmlParseErrorKind.MissingFile => VerificationSeverity.Error,
+ XmlParseErrorKind.InvalidValue => VerificationSeverity.Information,
+ XmlParseErrorKind.MalformedValue => VerificationSeverity.Warning,
+ XmlParseErrorKind.MissingAttribute => VerificationSeverity.Error,
+ XmlParseErrorKind.MissingReference => VerificationSeverity.Error,
+ XmlParseErrorKind.TooLongData => VerificationSeverity.Warning,
+ XmlParseErrorKind.DataBeforeHeader => VerificationSeverity.Information,
+ XmlParseErrorKind.MissingNode => VerificationSeverity.Critical,
+ XmlParseErrorKind.UnknownNode => VerificationSeverity.Information,
+ _ => VerificationSeverity.Warning
+ };
+ }
+
+ private static string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind)
+ {
+ return xmlErrorErrorKind switch
+ {
+ XmlParseErrorKind.EmptyRoot => VerifierErrorCodes.EmptyXmlRoot,
+ XmlParseErrorKind.MissingFile => VerifierErrorCodes.FileNotFound,
+ XmlParseErrorKind.InvalidValue => VerifierErrorCodes.InvalidXmlValue,
+ XmlParseErrorKind.MalformedValue => VerifierErrorCodes.MalformedXmlValue,
+ XmlParseErrorKind.MissingAttribute => VerifierErrorCodes.MissingXmlAttribute,
+ XmlParseErrorKind.MissingReference => VerifierErrorCodes.MissingXmlReference,
+ XmlParseErrorKind.TooLongData => VerifierErrorCodes.XmlValueTooLong,
+ XmlParseErrorKind.Unknown => VerifierErrorCodes.GenericXmlError,
+ XmlParseErrorKind.DataBeforeHeader => VerifierErrorCodes.XmlDataBeforeHeader,
+ XmlParseErrorKind.MissingNode => VerifierErrorCodes.XmlMissingNode,
+ XmlParseErrorKind.UnknownNode => VerifierErrorCodes.XmlUnsupportedTag,
+ _ => throw new ArgumentOutOfRangeException(nameof(xmlErrorErrorKind), xmlErrorErrorKind, null)
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs
index d0213d0..1057bfa 100644
--- a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs
@@ -1,11 +1,13 @@
using System;
using System.IO;
using System.IO.Abstractions;
+using AET.ModVerify.Reporting.Settings;
using Microsoft.Extensions.DependencyInjection;
namespace AET.ModVerify.Reporting.Reporters;
-public abstract class FileBasedReporter(T settings, IServiceProvider serviceProvider) : ReporterBase(settings, serviceProvider) where T : FileBasedReporterSettings
+public abstract class FileBasedReporter(T settings, IServiceProvider serviceProvider)
+ : ReporterBase(settings, serviceProvider) where T : FileBasedReporterSettings
{
private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs
index 731482a..941a9d5 100644
--- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs
@@ -7,7 +7,8 @@
namespace AET.ModVerify.Reporting.Reporters.JSON;
-internal class JsonReporter(JsonReporterSettings settings, IServiceProvider serviceProvider) : FileBasedReporter(settings, serviceProvider)
+internal class JsonReporter(JsonReporterSettings settings, IServiceProvider serviceProvider)
+ : FileBasedReporter(settings, serviceProvider)
{
public const string FileName = "VerificationResult.json";
diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs
index fd5962d..4207b36 100644
--- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs
+++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs
@@ -1,3 +1,5 @@
-namespace AET.ModVerify.Reporting.Reporters.JSON;
+using AET.ModVerify.Reporting.Settings;
+
+namespace AET.ModVerify.Reporting.Reporters.JSON;
public record JsonReporterSettings : FileBasedReporterSettings;
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/ReporterBase.cs b/src/ModVerify/Reporting/Reporters/ReporterBase.cs
index 9360c0e..fd86119 100644
--- a/src/ModVerify/Reporting/Reporters/ReporterBase.cs
+++ b/src/ModVerify/Reporting/Reporters/ReporterBase.cs
@@ -2,16 +2,16 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using AET.ModVerify.Reporting.Settings;
namespace AET.ModVerify.Reporting.Reporters;
-public abstract class ReporterBase(T settings, IServiceProvider serviceProvider) : IVerificationReporter where T : VerificationReportSettings
+public abstract class ReporterBase(T settings, IServiceProvider serviceProvider) : IVerificationReporter where T : VerifyReportSettings
{
protected IServiceProvider ServiceProvider { get; } = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
protected T Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings));
-
public abstract Task ReportAsync(IReadOnlyCollection errors);
protected IEnumerable FilteredErrors(IReadOnlyCollection errors)
diff --git a/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs b/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs
index 9ff34dd..cfc5c91 100644
--- a/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs
@@ -32,7 +32,7 @@ private async Task ReportWhole(IReadOnlyCollection errors)
private async Task ReportByVerifier(IReadOnlyCollection errors)
{
- var grouped = errors.GroupBy(x => x.Verifier);
+ var grouped = errors.GroupBy(x => x.VerifierChain.Last());
foreach (var group in grouped)
await ReportToSingleFile(group);
}
diff --git a/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs b/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs
index e6847a3..8fb833b 100644
--- a/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs
+++ b/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs
@@ -1,4 +1,6 @@
-namespace AET.ModVerify.Reporting.Reporters.Text;
+using AET.ModVerify.Reporting.Settings;
+
+namespace AET.ModVerify.Reporting.Reporters.Text;
public record TextFileReporterSettings : FileBasedReporterSettings
{
diff --git a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs
index 04f7c7d..601e424 100644
--- a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs
+++ b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs
@@ -1,5 +1,6 @@
using AET.ModVerify.Reporting.Reporters.JSON;
using AET.ModVerify.Reporting.Reporters.Text;
+using AET.ModVerify.Reporting.Settings;
using Microsoft.Extensions.DependencyInjection;
namespace AET.ModVerify.Reporting.Reporters;
@@ -22,26 +23,33 @@ public static IServiceCollection RegisterTextFileReporter(this IServiceCollectio
});
}
- public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection)
+ public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection, bool summaryOnly = false)
{
- return RegisterConsoleReporter(serviceCollection, new VerificationReportSettings
+ return RegisterConsoleReporter(serviceCollection, new VerifyReportSettings
{
MinimumReportSeverity = VerificationSeverity.Error
- });
+ }, summaryOnly);
}
- public static IServiceCollection RegisterJsonReporter(this IServiceCollection serviceCollection, JsonReporterSettings settings)
+ public static IServiceCollection RegisterJsonReporter(
+ this IServiceCollection serviceCollection,
+ JsonReporterSettings settings)
{
return serviceCollection.AddSingleton(sp => new JsonReporter(settings, sp));
}
- public static IServiceCollection RegisterTextFileReporter(this IServiceCollection serviceCollection, TextFileReporterSettings settings)
+ public static IServiceCollection RegisterTextFileReporter(
+ this IServiceCollection serviceCollection,
+ TextFileReporterSettings settings)
{
return serviceCollection.AddSingleton(sp => new TextFileReporter(settings, sp));
}
- public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection, VerificationReportSettings settings)
+ public static IServiceCollection RegisterConsoleReporter(
+ this IServiceCollection serviceCollection,
+ VerifyReportSettings settings,
+ bool summaryOnly = false)
{
- return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, sp));
+ return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, summaryOnly, sp));
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/FileBasedReporterSettings.cs b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs
similarity index 72%
rename from src/ModVerify/Reporting/FileBasedReporterSettings.cs
rename to src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs
index 6616258..c6233a1 100644
--- a/src/ModVerify/Reporting/FileBasedReporterSettings.cs
+++ b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs
@@ -1,8 +1,8 @@
using System;
-namespace AET.ModVerify.Reporting;
+namespace AET.ModVerify.Reporting.Settings;
-public record FileBasedReporterSettings : VerificationReportSettings
+public record FileBasedReporterSettings : VerifyReportSettings
{
private readonly string _outputDirectory = Environment.CurrentDirectory;
diff --git a/src/ModVerify/Reporting/GlobalVerificationReportSettings.cs b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs
similarity index 57%
rename from src/ModVerify/Reporting/GlobalVerificationReportSettings.cs
rename to src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs
index 3982d4d..b376fe3 100644
--- a/src/ModVerify/Reporting/GlobalVerificationReportSettings.cs
+++ b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs
@@ -1,8 +1,6 @@
-using System;
+namespace AET.ModVerify.Reporting.Settings;
-namespace AET.ModVerify.Reporting;
-
-public record GlobalVerificationReportSettings : VerificationReportSettings
+public record GlobalVerifyReportSettings : VerifyReportSettings
{
public VerificationBaseline Baseline { get; init; } = VerificationBaseline.Empty;
diff --git a/src/ModVerify/Reporting/VerificationReportSettings.cs b/src/ModVerify/Reporting/Settings/VerifyReportSettings.cs
similarity index 57%
rename from src/ModVerify/Reporting/VerificationReportSettings.cs
rename to src/ModVerify/Reporting/Settings/VerifyReportSettings.cs
index fc02024..1289822 100644
--- a/src/ModVerify/Reporting/VerificationReportSettings.cs
+++ b/src/ModVerify/Reporting/Settings/VerifyReportSettings.cs
@@ -1,6 +1,6 @@
-namespace AET.ModVerify.Reporting;
+namespace AET.ModVerify.Reporting.Settings;
-public record VerificationReportSettings
+public record VerifyReportSettings
{
public VerificationSeverity MinimumReportSeverity { get; init; } = VerificationSeverity.Information;
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/SuppressionFilter.cs b/src/ModVerify/Reporting/SuppressionFilter.cs
index 7fb41de..efac588 100644
--- a/src/ModVerify/Reporting/SuppressionFilter.cs
+++ b/src/ModVerify/Reporting/SuppressionFilter.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using AET.ModVerify.Reporting.Json;
@@ -7,33 +6,26 @@ namespace AET.ModVerify.Reporting;
public sealed class SuppressionFilter : IEquatable
{
- private static readonly AssetsEqualityComparer AssetComparer = AssetsEqualityComparer.Instance;
-
- private readonly HashSet? _assets;
-
public string? Id { get; }
public string? Verifier { get; }
- public IReadOnlyCollection? Assets { get; }
+ public string? Asset { get; }
- public SuppressionFilter(string? id, string? verifier, ICollection? assets)
+ public bool IsDisabled => Id == null && Verifier == null && Asset == null;
+
+ public SuppressionFilter(string? id, string? verifier, string? asset)
{
Id = id;
Verifier = verifier;
- if (assets is not null)
- _assets = new HashSet(assets);
- Assets = _assets?.ToList() ?? null;
+ Asset = asset;
}
internal SuppressionFilter(JsonSuppressionFilter filter)
{
Id = filter.Id;
Verifier = filter.Verifier;
-
- if (filter.Assets is not null)
- _assets = new HashSet(filter.Assets);
- Assets = _assets?.ToList() ?? null;
+ Asset = filter.Asset;
}
public bool Suppresses(VerificationError error)
@@ -50,31 +42,23 @@ public bool Suppresses(VerificationError error)
if (Verifier is not null)
{
- if (Verifier.Equals(error.Verifier))
+ if (error.VerifierChain.Contains(Verifier))
suppresses = true;
else
return false;
}
- if (_assets is not null)
+ if (Asset is not null)
{
- if (_assets.Count != error.AffectedAssets.Count)
- return false;
-
- if (!_assets.SetEquals(error.AffectedAssets))
+ if (error.Asset.Equals(Asset))
+ suppresses = true;
+ else
return false;
-
- suppresses = true;
}
return suppresses;
}
- public bool IsDisabled()
- {
- return Id == null && Verifier == null && (Assets == null || !Assets.Any());
- }
-
public int Specificity()
{
var specificity = 0;
@@ -82,7 +66,7 @@ public int Specificity()
specificity++;
if (Verifier != null)
specificity++;
- if (Assets != null && Assets.Any())
+ if (Asset != null)
specificity++;
return specificity;
}
@@ -95,7 +79,7 @@ public bool IsSupersededBy(SuppressionFilter other)
if (Verifier != null && other.Verifier != null && other.Verifier != Verifier)
return false;
- if (Assets != null && other.Assets != null && !AssetComparer.Equals(_assets, other._assets))
+ if (Asset != null && other.Asset != null)
return false;
return other.Specificity() < Specificity();
@@ -112,8 +96,7 @@ public bool Equals(SuppressionFilter? other)
return false;
if (Verifier != other.Verifier)
return false;
-
- return AssetComparer.Equals(_assets, other._assets);
+ return Asset == other.Asset;
}
public override bool Equals(object? obj)
@@ -126,10 +109,7 @@ public override int GetHashCode()
var hashCode = new HashCode();
hashCode.Add(Id);
hashCode.Add(Verifier);
- if (_assets is not null)
- hashCode.Add(_assets, AssetComparer);
- else
- hashCode.Add((HashSet)null!);
+ hashCode.Add(Asset);
return hashCode.ToHashCode();
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/SuppressionList.cs b/src/ModVerify/Reporting/SuppressionList.cs
index 23d1563..12ebab4 100644
--- a/src/ModVerify/Reporting/SuppressionList.cs
+++ b/src/ModVerify/Reporting/SuppressionList.cs
@@ -22,7 +22,7 @@ public SuppressionList(IEnumerable suppressionFilters)
if (suppressionFilters == null)
throw new ArgumentNullException(nameof(suppressionFilters));
- _filters = new List(suppressionFilters);
+ _filters = [..suppressionFilters];
_minimizedFilters = MinimizeSuppressions(_filters);
}
@@ -48,6 +48,10 @@ public static SuppressionList FromJson(Stream stream)
return new SuppressionList(baselineJson);
}
+ public IEnumerable Apply(IEnumerable errors)
+ {
+ return Count == 0 ? errors : errors.Where(e => !Suppresses(e));
+ }
public bool Suppresses(VerificationError error)
{
@@ -64,7 +68,7 @@ public bool Suppresses(VerificationError error)
private static IReadOnlyCollection MinimizeSuppressions(IEnumerable filters)
{
- var sortedFilters = filters.Where(f => !f.IsDisabled())
+ var sortedFilters = filters.Where(f => !f.IsDisabled)
.OrderBy(x => x.Specificity());
var result = new List();
diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/VerificationBaseline.cs
index 81cb8b3..55a8973 100644
--- a/src/ModVerify/Reporting/VerificationBaseline.cs
+++ b/src/ModVerify/Reporting/VerificationBaseline.cs
@@ -11,21 +11,41 @@ namespace AET.ModVerify.Reporting;
public sealed class VerificationBaseline : IReadOnlyCollection
{
- public static readonly VerificationBaseline Empty = new([]);
+ private static readonly Version LatestVersion = new(2, 0);
+
+ public static readonly VerificationBaseline Empty = new(VerificationSeverity.Information, []);
private readonly HashSet _errors;
+ public Version? Version { get; }
+
+ public VerificationSeverity MinimumSeverity { get; }
+
///
public int Count => _errors.Count;
internal VerificationBaseline(JsonVerificationBaseline baseline)
{
- _errors = new HashSet(baseline.Errors.Select(x => new VerificationError(x)));
+ _errors = [..baseline.Errors.Select(x => new VerificationError(x))];
+ Version = baseline.Version;
+ MinimumSeverity = baseline.MinimumSeverity;
+ }
+
+ public VerificationBaseline(VerificationSeverity minimumSeverity, IEnumerable errors)
+ {
+ _errors = [..errors];
+ Version = LatestVersion;
+ MinimumSeverity = minimumSeverity;
+ }
+
+ public bool Contains(VerificationError error)
+ {
+ return _errors.Contains(error);
}
- public VerificationBaseline(IEnumerable errors)
+ public IEnumerable Apply(IEnumerable errors)
{
- _errors = new(errors);
+ return Count == 0 ? errors : errors.Where(e => !_errors.Contains(e));
}
public void ToJson(Stream stream)
@@ -42,13 +62,12 @@ public static VerificationBaseline FromJson(Stream stream)
{
var baselineJson = JsonSerializer.Deserialize(stream, JsonSerializerOptions.Default);
if (baselineJson is null)
- throw new InvalidOperationException("Unable to deserialize baseline");
- return new VerificationBaseline(baselineJson);
- }
+ throw new InvalidOperationException("Unable to deserialize baseline.");
- public VerificationBaseline MergeWith(IEnumerable errors)
- {
- return new VerificationBaseline(this.Concat(errors));
+ if (baselineJson.Version is null || baselineJson.Version != LatestVersion)
+ throw new IncompatibleBaselineException();
+
+ return new VerificationBaseline(baselineJson);
}
///
@@ -61,4 +80,9 @@ IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
+
+ public override string ToString()
+ {
+ return $"Baseline [Version={Version}, MinSeverity={MinimumSeverity}, NumErrors={Count}]";
+ }
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/VerificationError.cs b/src/ModVerify/Reporting/VerificationError.cs
index fa25cf3..4174f32 100644
--- a/src/ModVerify/Reporting/VerificationError.cs
+++ b/src/ModVerify/Reporting/VerificationError.cs
@@ -2,98 +2,89 @@
using System.Collections.Generic;
using System.Linq;
using AET.ModVerify.Reporting.Json;
+using AET.ModVerify.Verifiers;
using AnakinRaW.CommonUtilities;
namespace AET.ModVerify.Reporting;
public sealed class VerificationError : IEquatable
{
- private static readonly AssetsEqualityComparer AssetComparer = AssetsEqualityComparer.Instance;
+ private static readonly VerificationErrorContextEqualityComparer ContextComparer = VerificationErrorContextEqualityComparer.Instance;
- private readonly HashSet _assets;
+ private readonly HashSet _contextEntries;
public string Id { get; }
public string Message { get; }
- public string Verifier { get; }
+ public IReadOnlyList VerifierChain { get; }
- public IReadOnlyCollection AffectedAssets { get; }
+ public IReadOnlyCollection ContextEntries { get; }
public VerificationSeverity Severity { get; }
- public VerificationError(string id, string message, string verifier, IEnumerable affectedAssets, VerificationSeverity severity)
+ public string Asset { get; }
+
+ public VerificationError(
+ string id,
+ string message,
+ IReadOnlyList verifiers,
+ IEnumerable contextEntries,
+ string asset,
+ VerificationSeverity severity)
{
- if (affectedAssets == null)
- throw new ArgumentNullException(nameof(affectedAssets));
+ if (contextEntries == null)
+ throw new ArgumentNullException(nameof(contextEntries));
+ if (asset is null)
+ throw new ArgumentNullException(nameof(asset));
ThrowHelper.ThrowIfNullOrEmpty(id);
+
Id = id;
Message = message ?? throw new ArgumentNullException(nameof(message));
- Verifier = verifier;
+ VerifierChain = verifiers;
Severity = severity;
- _assets = new HashSet(affectedAssets);
- AffectedAssets = _assets.ToList();
+ _contextEntries = [.. contextEntries];
+ ContextEntries = _contextEntries.ToList();
+ Asset = asset;
}
internal VerificationError(JsonVerificationError error)
{
Id = error.Id;
Message = error.Message;
- Verifier = error.Verifier;
- _assets = new HashSet(error.Assets);
- AffectedAssets = _assets.ToList();
+ VerifierChain = error.VerifierChain;
+ _contextEntries = [..error.ContextEntries];
+ ContextEntries = _contextEntries.ToList();
+ Asset = error.Asset;
}
public static VerificationError Create(
- IGameVerifier verifier,
+ IReadOnlyList verifiers,
string id,
string message,
- VerificationSeverity severity,
- IEnumerable assets)
- {
- return new VerificationError(id, message, verifier.Name, assets, severity);
- }
-
-
- public static VerificationError Create(
- IGameVerifier verifier,
- string id,
- string message,
- VerificationSeverity severity,
- params string[] assets)
+ VerificationSeverity severity,
+ IEnumerable context,
+ string asset)
{
- return new VerificationError(id, message, verifier.Name, assets, severity);
+ return new VerificationError(id, message, verifiers.Select(x => x.Name).ToList(), context, asset, severity);
}
public static VerificationError Create(
- IGameVerifier verifier,
- string id,
+ IReadOnlyList verifiers,
+ string id,
string message,
- VerificationSeverity severity)
- {
- return Create(verifier, id, message, severity, []);
- }
-
- internal static VerificationError Create(
- IGameVerifier verifier,
- string id,
- Exception exception,
VerificationSeverity severity,
- params string[] assets)
+ string asset)
{
- return new VerificationError(id, exception.Message, verifier.Name, assets, severity);
+ return new VerificationError(
+ id,
+ message,
+ verifiers.Select(x => x.Name).ToList(),
+ [],
+ asset,
+ severity);
}
- internal static VerificationError Create(
- IGameVerifier verifier,
- string id,
- Exception exception,
- VerificationSeverity severity)
- {
- return Create(verifier, id, exception, severity, []);
- }
-
-
public bool Equals(VerificationError? other)
{
if (other is null)
@@ -103,10 +94,11 @@ public bool Equals(VerificationError? other)
if (!Id.Equals(other.Id))
return false;
- if (!Verifier.Equals(other.Verifier))
+
+ if (!Asset.Equals(other.Asset))
return false;
- return AssetComparer.Equals(_assets, other._assets);
+ return ContextComparer.Equals(_contextEntries, other._contextEntries);
}
public override bool Equals(object? obj)
@@ -118,13 +110,13 @@ public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(Id);
- hashCode.Add(Verifier);
- hashCode.Add(_assets, AssetComparer);
+ hashCode.Add(_contextEntries, ContextComparer);
+ hashCode.Add(Asset);
return hashCode.ToHashCode();
}
public override string ToString()
{
- return $"[{Severity}] [{Verifier}] {Id}: Message={Message}; Affected Assets=[{string.Join(",", AffectedAssets)}];";
+ return $"[{Severity}] [{string.Join(" --> ", VerifierChain)}] {Id}: Message={Message}; Asset='{Asset}'; Context=[{string.Join(",", ContextEntries)}];";
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/AssetsEqualityComparer.cs b/src/ModVerify/Reporting/VerificationErrorContextEqualityComparer.cs
similarity index 63%
rename from src/ModVerify/Reporting/AssetsEqualityComparer.cs
rename to src/ModVerify/Reporting/VerificationErrorContextEqualityComparer.cs
index 8f058bc..773b260 100644
--- a/src/ModVerify/Reporting/AssetsEqualityComparer.cs
+++ b/src/ModVerify/Reporting/VerificationErrorContextEqualityComparer.cs
@@ -2,13 +2,13 @@
namespace AET.ModVerify.Reporting;
-internal class AssetsEqualityComparer : IEqualityComparer>
+internal class VerificationErrorContextEqualityComparer : IEqualityComparer>
{
readonly IEqualityComparer> _setComparer = HashSet.CreateSetComparer();
- public static AssetsEqualityComparer Instance { get; } = new();
+ public static VerificationErrorContextEqualityComparer Instance { get; } = new();
- private AssetsEqualityComparer()
+ private VerificationErrorContextEqualityComparer()
{
}
diff --git a/src/ModVerify/Reporting/VerificationReportBroker.cs b/src/ModVerify/Reporting/VerificationReportBroker.cs
index f9c3c68..5c1e8a3 100644
--- a/src/ModVerify/Reporting/VerificationReportBroker.cs
+++ b/src/ModVerify/Reporting/VerificationReportBroker.cs
@@ -1,22 +1,17 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
-using AET.ModVerify.Verifiers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AET.ModVerify.Reporting;
-internal class VerificationReportBroker(GlobalVerificationReportSettings reportSettings, IServiceProvider serviceProvider)
+public sealed class VerificationReportBroker(IServiceProvider serviceProvider)
{
private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(VerificationReportBroker));
-
- public async Task> ReportAsync(IEnumerable steps)
+ public async Task ReportAsync(IReadOnlyCollection errors)
{
- var suppressions = new SuppressionList(reportSettings.Suppressions);
- var errors = GetReportableErrors(steps, suppressions);
var reporters = serviceProvider.GetServices();
foreach (var reporter in reporters)
@@ -30,28 +25,5 @@ public async Task> ReportAsync(IEnumerabl
_logger?.LogError(e, "Exception while reporting verification error");
}
}
-
- return errors;
- }
-
- private IReadOnlyCollection GetReportableErrors(IEnumerable steps, SuppressionList suppressions)
- {
- var allErrors = steps.SelectMany(s => s.VerifyErrors);
-
- var errorsToReport = new List();
- foreach (var error in allErrors)
- {
- if (reportSettings.Baseline.Contains(error))
- continue;
-
- if (suppressions.Suppresses(error))
- continue;
-
- errorsToReport.Add(error);
- }
-
- // NB: We don't filter for severity here, as that is something the individual reporters should handle.
- // This allows better control over what gets reported.
- return errorsToReport;
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Settings/GameVerifySettings.cs b/src/ModVerify/Settings/GameVerifySettings.cs
index 488bfe4..3f84670 100644
--- a/src/ModVerify/Settings/GameVerifySettings.cs
+++ b/src/ModVerify/Settings/GameVerifySettings.cs
@@ -6,16 +6,14 @@ public record GameVerifySettings
{
public static readonly GameVerifySettings Default = new()
{
- AbortSettings = new(),
- GlobalReportSettings = new(),
- LocalizationOption = VerifyLocalizationOption.English
+ LocalizationOption = VerifyLocalizationOption.English,
+ IgnoreAsserts = false,
+ ThrowsOnMinimumSeverity = null
};
- public int ParallelVerifiers { get; init; } = 4;
-
- public VerificationAbortSettings AbortSettings { get; init; }
-
- public GlobalVerificationReportSettings GlobalReportSettings { get; init; }
+ public VerificationSeverity? ThrowsOnMinimumSeverity { get; init; }
public VerifyLocalizationOption LocalizationOption { get; init; }
+
+ public bool IgnoreAsserts { get; init; }
}
\ No newline at end of file
diff --git a/src/ModVerify/Settings/VerificationAbortSettings.cs b/src/ModVerify/Settings/VerificationAbortSettings.cs
deleted file mode 100644
index 767cef4..0000000
--- a/src/ModVerify/Settings/VerificationAbortSettings.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using AET.ModVerify.Reporting;
-
-namespace AET.ModVerify.Settings;
-
-public record VerificationAbortSettings
-{
- public bool FailFast { get; init; } = false;
-
- public VerificationSeverity MinimumAbortSeverity { get; init; } = VerificationSeverity.Warning;
-
- public bool ThrowsGameVerificationException { get; init; } = false;
-}
\ No newline at end of file
diff --git a/src/ModVerify/Settings/VerifyPipelineSettings.cs b/src/ModVerify/Settings/VerifyPipelineSettings.cs
new file mode 100644
index 0000000..2e11c74
--- /dev/null
+++ b/src/ModVerify/Settings/VerifyPipelineSettings.cs
@@ -0,0 +1,14 @@
+using AET.ModVerify.Pipeline;
+
+namespace AET.ModVerify.Settings;
+
+public sealed class VerifyPipelineSettings
+{
+ public required GameVerifySettings GameVerifySettings { get; init; }
+
+ public required IGameVerifiersProvider VerifiersProvider { get; init; }
+
+ public bool FailFast { get; init; }
+
+ public int ParallelVerifiers { get; init; } = 4;
+}
\ No newline at end of file
diff --git a/src/ModVerify/Utilities/Extensions.cs b/src/ModVerify/Utilities/Extensions.cs
new file mode 100644
index 0000000..996f474
--- /dev/null
+++ b/src/ModVerify/Utilities/Extensions.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq;
+
+namespace AET.ModVerify.Utilities;
+
+internal static class Extensions
+{
+ public static bool IsExceptionType(this Exception error) where T : Exception
+ {
+ switch (error)
+ {
+ case T _:
+ return true;
+ case AggregateException aggregateException:
+ return aggregateException.InnerExceptions.Any(p => p.IsExceptionType());
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Utilities/PathExtensions.cs b/src/ModVerify/Utilities/PathExtensions.cs
new file mode 100644
index 0000000..6ddc47a
--- /dev/null
+++ b/src/ModVerify/Utilities/PathExtensions.cs
@@ -0,0 +1,22 @@
+using System;
+using System.IO.Abstractions;
+using AnakinRaW.CommonUtilities.FileSystem;
+
+namespace AET.ModVerify.Utilities;
+
+public static class PathExtensions
+{
+ public static ReadOnlySpan GetGameStrippedPath(this IPath path, ReadOnlySpan gamePath, ReadOnlySpan modPath)
+ {
+ if (!path.IsPathFullyQualified(modPath))
+ return modPath;
+
+ if (modPath.Length <= gamePath.Length)
+ return modPath;
+
+ if (path.IsChildOf(gamePath, modPath))
+ return modPath.Slice(gamePath.Length);
+
+ return modPath;
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Utilities/VerificationErrorExtensions.cs b/src/ModVerify/Utilities/VerificationErrorExtensions.cs
new file mode 100644
index 0000000..1244b45
--- /dev/null
+++ b/src/ModVerify/Utilities/VerificationErrorExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using AET.ModVerify.Reporting;
+
+namespace AET.ModVerify.Utilities;
+
+public static class VerificationErrorExtensions
+{
+ public static IEnumerable ApplyBaseline(this IEnumerable errors,
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/VerificationProvider.cs b/src/ModVerify/VerificationProvider.cs
deleted file mode 100644
index a1800dc..0000000
--- a/src/ModVerify/VerificationProvider.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using AET.ModVerify.Settings;
-using AET.ModVerify.Verifiers;
-using PG.StarWarsGame.Engine.Database;
-
-namespace AET.ModVerify;
-
-internal class VerificationProvider(IServiceProvider serviceProvider) : IVerificationProvider
-{
- public IEnumerable GetAllDefaultVerifiers(IGameDatabase database, GameVerifySettings settings)
- {
- yield return new ReferencedModelsVerifier(database, settings, serviceProvider);
- yield return new DuplicateNameFinder(database, settings, serviceProvider);
- yield return new AudioFilesVerifier(database, settings, serviceProvider);
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/AlreadyVerifiedCache.cs b/src/ModVerify/Verifiers/AlreadyVerifiedCache.cs
new file mode 100644
index 0000000..530ca4e
--- /dev/null
+++ b/src/ModVerify/Verifiers/AlreadyVerifiedCache.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Concurrent;
+using Microsoft.Extensions.DependencyInjection;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine;
+
+namespace AET.ModVerify.Verifiers;
+
+internal sealed class AlreadyVerifiedCache(IServiceProvider serviceProvider) : IAlreadyVerifiedCache
+{
+ private readonly ICrc32HashingService _crc32Hashing = serviceProvider.GetRequiredService();
+ private readonly ConcurrentDictionary _cachedChecksums = new();
+
+ public bool TryAddEntry(string entry)
+ {
+ return TryAddEntry(entry.AsSpan());
+ }
+
+ public bool TryAddEntry(ReadOnlySpan entry)
+ {
+ return TryAddEntry(_crc32Hashing.GetCrc32Upper(entry, PGConstants.DefaultPGEncoding));
+ }
+
+ public bool TryAddEntry(Crc32 checksum)
+ {
+ return _cachedChecksums.TryAdd(checksum, 0);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/AudioFilesVerifier.cs b/src/ModVerify/Verifiers/AudioFilesVerifier.cs
index 1f4a3e9..c5e2085 100644
--- a/src/ModVerify/Verifiers/AudioFilesVerifier.cs
+++ b/src/ModVerify/Verifiers/AudioFilesVerifier.cs
@@ -1,17 +1,19 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
+using System.Text;
using System.Threading;
using AET.ModVerify.Reporting;
using AET.ModVerify.Settings;
+using AnakinRaW.CommonUtilities.FileSystem.Normalization;
using Microsoft.Extensions.DependencyInjection;
using PG.Commons.Hashing;
using PG.StarWarsGame.Engine;
-using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Engine.Language;
+using PG.StarWarsGame.Engine.Audio.Sfx;
+using PG.StarWarsGame.Engine.Localization;
using PG.StarWarsGame.Files.MEG.Services.Builder.Normalization;
#if NETSTANDARD2_0
using AnakinRaW.CommonUtilities.FileSystem;
@@ -19,89 +21,121 @@
namespace AET.ModVerify.Verifiers;
-public class AudioFilesVerifier : GameVerifierBase
+public class AudioFilesVerifier : GameVerifier
{
- private readonly PetroglyphDataEntryPathNormalizer _pathNormalizer;
+ private static readonly PathNormalizeOptions SampleNormalizerOptions = new()
+ {
+ UnifyCase = UnifyCasingKind.UpperCaseForce,
+ UnifySeparatorKind = DirectorySeparatorKind.Windows,
+ UnifyDirectorySeparators = true
+ };
+
+ private readonly EmpireAtWarMegDataEntryPathNormalizer _pathNormalizer = EmpireAtWarMegDataEntryPathNormalizer.Instance;
private readonly ICrc32HashingService _hashingService;
private readonly IFileSystem _fileSystem;
private readonly IGameLanguageManager _languageManager;
- public AudioFilesVerifier(IGameDatabase gameDatabase, GameVerifySettings settings, IServiceProvider serviceProvider) : base(gameDatabase, settings, serviceProvider)
+ public AudioFilesVerifier(IStarWarsGameEngine gameEngine, GameVerifySettings settings, IServiceProvider serviceProvider)
+ : base(null, gameEngine, settings, serviceProvider)
{
- _pathNormalizer = new(serviceProvider);
_hashingService = serviceProvider.GetRequiredService();
_fileSystem = serviceProvider.GetRequiredService();
_languageManager = serviceProvider.GetRequiredService()
.GetLanguageManager(Repository.EngineType);
}
- public override string FriendlyName => "Verify Audio Files";
+ public override string FriendlyName => "Audio Files";
- protected override void RunVerification(CancellationToken token)
+ public override void Verify(CancellationToken token)
{
var visitedSamples = new HashSet();
var languagesToVerify = GetLanguagesToVerify().ToList();
- foreach (var sfxEvent in Database.SfxEvents.Entries)
+
+
+ var numSamples = GameEngine.SfxGameManager.Entries.Sum(x => x.AllSamples.Count());
+ double counter = 0;
+
+ foreach (var sfxEvent in GameEngine.SfxGameManager.Entries)
{
foreach (var codedSample in sfxEvent.AllSamples)
{
- VerifySample(codedSample, sfxEvent, languagesToVerify, visitedSamples);
+ OnProgress(++counter / numSamples, $"Audio File - '{codedSample}'");
+ VerifySample(codedSample.AsSpan(), sfxEvent, languagesToVerify, visitedSamples);
}
}
}
- private void VerifySample(string sample, SfxEvent sfxEvent, IEnumerable languagesToVerify, HashSet visitedSamples)
+ private void VerifySample(ReadOnlySpan sample, SfxEvent sfxEvent, IEnumerable languagesToVerify, HashSet visitedSamples)
{
- Span sampleNameBuffer = stackalloc char[PGConstants.MaxPathLength];
+ char[]? pooledBuffer = null;
- var i = _pathNormalizer.Normalize(sample.AsSpan(), sampleNameBuffer);
- var normalizedSampleName = sampleNameBuffer.Slice(0, i);
- var crc = _hashingService.GetCrc32(normalizedSampleName, PGConstants.PGCrc32Encoding);
- if (!visitedSamples.Add(crc))
- return;
+ var buffer = sample.Length < PGConstants.MaxMegEntryPathLength
+ ? stackalloc char[PGConstants.MaxMegEntryPathLength]
+ : pooledBuffer = ArrayPool.Shared.Rent(sample.Length);
-
- if (normalizedSampleName.Length > PGConstants.MaxPathLength)
+ try
{
- AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.FilePathTooLong,
- $"Sample name '{sample}' is too long.",
- VerificationSeverity.Error,
- sample));
- return;
- }
+ var length = PathNormalizer.Normalize(sample, buffer, SampleNormalizerOptions);
+ var sampleNameBuffer = buffer.Slice(0, length);
- var normalizedSampleNameString = normalizedSampleName.ToString();
+ var crc = _hashingService.GetCrc32(sampleNameBuffer, Encoding.ASCII);
+ if (!visitedSamples.Add(crc))
+ return;
- if (sfxEvent.IsLocalized)
- {
- foreach (var language in languagesToVerify)
+ if (sfxEvent.IsLocalized)
{
- var localizedSampleName = _languageManager.LocalizeFileName(normalizedSampleNameString, language, out var localized);
- VerifySample(localizedSampleName, sfxEvent);
-
- if (!localized)
- return;
+ foreach (var language in languagesToVerify)
+ {
+ VerifySampleLocalized(sfxEvent, sampleNameBuffer, language, out var localized);
+ if (!localized)
+ return;
+ }
}
+ else
+ {
+ VerifySample(sampleNameBuffer, sfxEvent);
+ }
+ }
+ finally
+ {
+ if (pooledBuffer is not null)
+ ArrayPool.Shared.Return(pooledBuffer);
+ }
+
+ }
+
+ private void VerifySampleLocalized(SfxEvent sfxEvent, ReadOnlySpan sample, LanguageType language, out bool localized)
+ {
+ char[]? pooledBuffer = null;
+
+ var buffer = sample.Length < PGConstants.MaxMegEntryPathLength
+ ? stackalloc char[PGConstants.MaxMegEntryPathLength]
+ : pooledBuffer = ArrayPool.Shared.Rent(sample.Length);
+ try
+ {
+ var l = _languageManager.LocalizeFileName(sample, language, buffer, out localized);
+ var localizedName = buffer.Slice(0, l);
+ VerifySample(localizedName, sfxEvent);
}
- else
+ finally
{
- VerifySample(normalizedSampleNameString, sfxEvent);
+ if (pooledBuffer is not null)
+ ArrayPool.Shared.Return(pooledBuffer);
}
}
- private void VerifySample(string sample, SfxEvent sfxEvent)
+ private void VerifySample(ReadOnlySpan sample, SfxEvent sfxEvent)
{
using var sampleStream = Repository.TryOpenFile(sample);
if (sampleStream is null)
{
+ var sampleString = sample.ToString();
AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.SampleNotFound,
- $"Audio file '{sample}' could not be found.",
+ VerifierChain,
+ VerifierErrorCodes.SampleNotFound,
+ $"Audio file '{sampleString}' could not be found.",
VerificationSeverity.Error,
- sample));
+ sampleString));
return;
}
using var binaryReader = new BinaryReader(sampleStream);
@@ -121,42 +155,46 @@ private void VerifySample(string sample, SfxEvent sfxEvent)
if (format != WaveFormats.PCM)
{
+ var sampleString = sample.ToString();
AddError(VerificationError.Create(
- this,
+ VerifierChain,
VerifierErrorCodes.SampleNotPCM,
- $"Audio file '{sample}' has an invalid format '{format}'. Supported is {WaveFormats.PCM}",
+ $"Audio file '{sampleString}' has an invalid format '{format}'. Supported is {WaveFormats.PCM}",
VerificationSeverity.Error,
- sample));
+ sampleString));
}
if (channels > 1 && !IsAmbient2D(sfxEvent))
{
+ var sampleString = sample.ToString();
AddError(VerificationError.Create(
- this,
+ VerifierChain,
VerifierErrorCodes.SampleNotMono,
- $"Audio file '{sample}' is not mono audio.",
- VerificationSeverity.Information,
- sample));
+ $"Audio file '{sampleString}' is not mono audio.",
+ VerificationSeverity.Information,
+ sampleString));
}
if (sampleRate > 48_000)
{
+ var sampleString = sample.ToString();
AddError(VerificationError.Create(
- this,
+ VerifierChain,
VerifierErrorCodes. InvalidSampleRate,
- $"Audio file '{sample}' has a too high sample rate of {sampleRate}. Maximum is 48.000Hz.",
+ $"Audio file '{sampleString}' has a too high sample rate of {sampleRate}. Maximum is 48.000Hz.",
VerificationSeverity.Error,
- sample));
+ sampleString));
}
if (bitPerSecondPerChannel > 16)
{
+ var sampleString = sample.ToString();
AddError(VerificationError.Create(
- this,
+ VerifierChain,
VerifierErrorCodes.InvalidBitsPerSeconds,
- $"Audio file '{sample}' has an invalid bit size of {bitPerSecondPerChannel}. Supported are 16bit.",
- VerificationSeverity.Error,
- sample));
+ $"Audio file '{sampleString}' has an invalid bit size of {bitPerSecondPerChannel}. Supported are 16bit.",
+ VerificationSeverity.Error,
+ sampleString));
}
}
@@ -192,7 +230,7 @@ private IEnumerable GetLanguagesToVerify()
case VerifyLocalizationOption.CurrentSystem:
return new List { _languageManager.GetLanguagesFromUser() };
case VerifyLocalizationOption.AllInstalled:
- return Database.InstalledLanguages;
+ return GameEngine.InstalledLanguages;
case VerifyLocalizationOption.All:
return _languageManager.SupportedLanguages;
default:
diff --git a/src/ModVerify/Verifiers/CommandBar/CommandBarVerifier.Base.cs b/src/ModVerify/Verifiers/CommandBar/CommandBarVerifier.Base.cs
new file mode 100644
index 0000000..3373f8e
--- /dev/null
+++ b/src/ModVerify/Verifiers/CommandBar/CommandBarVerifier.Base.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Linq;
+using System.Threading;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Settings;
+using AnakinRaW.CommonUtilities.Collections;
+using PG.StarWarsGame.Engine;
+using PG.StarWarsGame.Engine.CommandBar;
+using PG.StarWarsGame.Engine.CommandBar.Components;
+
+namespace AET.ModVerify.Verifiers;
+
+public partial class CommandBarVerifier(IStarWarsGameEngine gameEngine, GameVerifySettings settings, IServiceProvider serviceProvider)
+ : GameVerifier(null, gameEngine, settings, serviceProvider)
+{
+ public const string CommandBarNoShellsGroup = "CMDBAR00";
+ public const string CommandBarManyShellsGroup = "CMDBAR01";
+ public const string CommandBarNoShellsComponentInShellGroup = "CMDBAR02";
+ public const string CommandBarDuplicateComponent = "CMDBAR03";
+ public const string CommandBarUnsupportedComponent = "CMDBAR04";
+ public const string CommandBarShellNoModel = "CMDBAR05";
+
+ public override string FriendlyName => "CommandBar";
+
+ public override void Verify(CancellationToken token)
+ {
+ VerifyCommandBarShellsGroups();
+ VerifyCommandBarComponents();
+ }
+
+ private void VerifySingleComponent(CommandBarBaseComponent component)
+ {
+ VerifyCommandBarModel(component);
+ VerifyComponentBone(component);
+ }
+}
+
+partial class CommandBarVerifier
+{
+ private void VerifyCommandBarModel(CommandBarBaseComponent component)
+ {
+ if (component is not CommandBarShellComponent shellComponent)
+ return;
+
+ if (shellComponent.ModelPath is null)
+ {
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarShellNoModel, $"The CommandBarShellComponent '{component.Name}' has no model specified.",
+ VerificationSeverity.Error, shellComponent.Name));
+ return;
+ }
+
+ var model = GameEngine.PGRender.LoadModelAndAnimations(shellComponent.ModelPath.AsSpan(), null);
+ if (model is null)
+ {
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarShellNoModel, $"Could not find model '{shellComponent.ModelPath}' for CommandBarShellComponent '{component.Name}'.",
+ VerificationSeverity.Error, [shellComponent.Name], shellComponent.ModelPath));
+ return;
+ }
+ }
+
+ private void VerifyComponentBone(CommandBarBaseComponent component)
+ {
+ if (component is CommandBarShellComponent)
+ return;
+
+ if (component.Bone == -1)
+ {
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarShellNoModel, $"The CommandBar component '{component.Name}' is not connected to a shell component.",
+ VerificationSeverity.Warning, component.Name));
+ }
+ }
+}
+
+partial class CommandBarVerifier
+{
+ private void VerifyCommandBarComponents()
+ {
+ var occupiedComponentIds = SupportedCommandBarComponentData.GetComponentIdsForEngine(Repository.EngineType).Keys
+ .ToDictionary(value => value, _ => false);
+
+ foreach (var component in GameEngine.CommandBar.Components)
+ {
+ if (!occupiedComponentIds.TryGetValue(component.Id, out var alreadyOccupied))
+ {
+ AddError(VerificationError.Create(
+ VerifierChain,
+ CommandBarUnsupportedComponent,
+ $"The CommandBar component '{component.Name}' is not supported by the game.",
+ VerificationSeverity.Information,
+ component.Name));
+ }
+ else
+ {
+ occupiedComponentIds[component.Id] = true;
+ }
+
+ if (alreadyOccupied)
+ {
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarDuplicateComponent,
+ $"The CommandBar component '{component.Name}' with ID '{component.Id}' already exists.",
+ VerificationSeverity.Warning,
+ component.Name));
+ }
+
+ VerifySingleComponent(component);
+ }
+ }
+}
+
+partial class CommandBarVerifier
+{
+ private void VerifyCommandBarShellsGroups()
+ {
+ var shellGroups = new FrugalList();
+ foreach (var groupPair in GameEngine.CommandBar.Groups)
+ {
+ if (groupPair.Key == CommandBarConstants.ShellGroupName)
+ {
+ shellGroups.Add(groupPair.Key);
+ VerifyShellGroup(groupPair.Value);
+ }
+ else if (groupPair.Key.Equals(CommandBarConstants.ShellGroupName, StringComparison.OrdinalIgnoreCase))
+ {
+ shellGroups.Add(groupPair.Key);
+ }
+ }
+
+ if (shellGroups.Count == 0)
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarNoShellsGroup,
+ $"No CommandBarGroup '{CommandBarConstants.ShellGroupName}' found.",
+ VerificationSeverity.Error,
+ "GameCommandBar"));
+
+ if (shellGroups.Count > 1)
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarManyShellsGroup,
+ $"Found more than one Shells CommandBarGroup. Mind that group names are case-sensitive. Correct name is '{CommandBarConstants.ShellGroupName}'",
+ VerificationSeverity.Warning,
+ shellGroups, "GameCommandBar"));
+ }
+
+ private void VerifyShellGroup(CommandBarComponentGroup shellGroup)
+ {
+ foreach (var component in shellGroup.Components)
+ {
+ var shellComponent = component as CommandBarShellComponent;
+ if (shellComponent?.Type is not CommandBarComponentType.Shell)
+ {
+ AddError(VerificationError.Create(VerifierChain,
+ CommandBarNoShellsComponentInShellGroup,
+ $"The CommandBar component '{component.Name}' is not a shell component, but part of the '{CommandBarConstants.ShellGroupName}' group.",
+ VerificationSeverity.Warning, component.Name));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/Commons/ModelVerifier.cs b/src/ModVerify/Verifiers/Commons/ModelVerifier.cs
new file mode 100644
index 0000000..25704d1
--- /dev/null
+++ b/src/ModVerify/Verifiers/Commons/ModelVerifier.cs
@@ -0,0 +1,288 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Utilities;
+using Microsoft.Extensions.DependencyInjection;
+using PG.StarWarsGame.Engine;
+using PG.StarWarsGame.Files;
+using PG.StarWarsGame.Files.ALO.Data;
+using PG.StarWarsGame.Files.ALO.Files;
+using PG.StarWarsGame.Files.ALO.Files.Models;
+using PG.StarWarsGame.Files.ALO.Files.Particles;
+using PG.StarWarsGame.Files.Binary;
+#if NETSTANDARD2_0 || NETFRAMEWORK
+using AnakinRaW.CommonUtilities.FileSystem;
+#endif
+
+namespace AET.ModVerify.Verifiers.Commons;
+
+public sealed class SingleModelVerifier : GameVerifier
+{
+ private const string ProxyAltIdentifier = "_ALT";
+
+ private readonly TextureVeifier _textureVerifier;
+ private readonly IAlreadyVerifiedCache? _cache;
+
+ public SingleModelVerifier(IGameVerifierInfo? parent,
+ IStarWarsGameEngine engine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider) : base(parent, engine, settings, serviceProvider)
+ {
+ _textureVerifier = new TextureVeifier(this, engine, settings, serviceProvider);
+ _cache = serviceProvider.GetService();
+ }
+
+ public override void Verify(string modelName, IReadOnlyCollection contextInfo, CancellationToken token)
+ {
+ try
+ {
+ _textureVerifier.Error += OnTextureError;
+
+ var modelPath = BuildModelPath(modelName);
+ VerifyAlamoFile(modelPath, contextInfo, token);
+ }
+ finally
+ {
+ _textureVerifier.Error -= OnTextureError;
+ }
+ }
+
+ private void OnTextureError(object sender, VerificationErrorEventArgs e)
+ {
+ AddError(e.Error);
+ }
+
+ private void VerifyAlamoFile(string modelPath, IReadOnlyCollection contextInfo, CancellationToken token)
+ {
+ token.ThrowIfCancellationRequested();
+
+ var modelName = FileSystem.Path.GetFileName(modelPath.AsSpan());
+
+ if (_cache?.TryAddEntry(modelName) == false)
+ return;
+
+ IAloFile? aloFile = null;
+ try
+ {
+ try
+ {
+ aloFile = GameEngine.PGRender.Load3DAsset(modelPath, true, true);
+ }
+ catch (BinaryCorruptedException e)
+ {
+ var aloFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), modelPath.AsSpan()).ToString();
+ var message = $"'{aloFilePath}' is corrupted: {e.Message}";
+ AddError(VerificationError.Create(VerifierChain, VerifierErrorCodes.FileCorrupt, message,
+ VerificationSeverity.Critical, contextInfo, aloFilePath));
+ return;
+ }
+
+ if (aloFile is null)
+ {
+ var modelNameString = modelName.ToString();
+ var error = VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.FileNotFound,
+ $"Unable to find .ALO file '{modelNameString}'",
+ VerificationSeverity.Error,
+ contextInfo,
+ modelNameString);
+ AddError(error);
+ return;
+ }
+
+ VerifyModelOrParticle(aloFile, contextInfo, token);
+ }
+ finally
+ {
+ aloFile?.Dispose();
+ }
+ }
+
+ private void VerifyModelOrParticle(
+ IAloFile aloFile,
+ IReadOnlyCollection contextInfo,
+ CancellationToken token)
+ {
+ switch (aloFile)
+ {
+ case IAloModelFile model:
+ VerifyModel(model, contextInfo, token);
+ break;
+ case IAloParticleFile particle:
+ VerifyParticle(particle, contextInfo);
+ break;
+ default:
+ throw new InvalidOperationException("The data stream is neither a model nor particle.");
+ }
+ }
+
+ private void VerifyParticle(IAloParticleFile file, IReadOnlyCollection contextInfo)
+ {
+ foreach (var texture in file.Content.Textures)
+ {
+ GuardedVerify(() => VerifyTextureExists(file, texture, contextInfo),
+ e => e is ArgumentException,
+ _ =>
+ {
+ var particlePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
+ AddError(VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.InvalidFilePath,
+ $"Invalid texture file name '{texture}' in particle '{particlePath}'",
+ VerificationSeverity.Error,
+ [particlePath], texture));
+ });
+ }
+
+ var fileName = FileSystem.Path.GetFileNameWithoutExtension(file.FilePath.AsSpan());
+ var name = file.Content.Name.AsSpan();
+
+ if (!fileName.Equals(name, StringComparison.OrdinalIgnoreCase))
+ {
+ var particlePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
+ AddError(VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.InvalidParticleName,
+ $"The particle name '{file.Content.Name}' does not match file name '{particlePath}'",
+ VerificationSeverity.Error,
+ particlePath));
+ }
+
+ }
+
+ private void VerifyModel(IAloModelFile file, IReadOnlyCollection contextInfo, CancellationToken token)
+ {
+ foreach (var texture in file.Content.Textures)
+ {
+ GuardedVerify(() => VerifyTextureExists(file, texture, contextInfo),
+ e => e is ArgumentException,
+ _ =>
+ {
+ var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
+ AddError(VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.InvalidFilePath,
+ $"Invalid texture file name '{texture}' in model '{modelFilePath}'",
+ VerificationSeverity.Error,
+ [modelFilePath],
+ texture));
+ });
+ }
+
+ foreach (var shader in file.Content.Shaders)
+ {
+ GuardedVerify(() => VerifyShaderExists(file, shader, contextInfo),
+ e => e is ArgumentException,
+ _ =>
+ {
+ var modelFilePath =
+ FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
+ AddError(VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.InvalidFilePath,
+ $"Invalid shader file name '{shader}' in model '{modelFilePath}'",
+ VerificationSeverity.Error,
+ [modelFilePath],
+ shader));
+ });
+ }
+
+
+ foreach (var proxy in file.Content.Proxies)
+ {
+ GuardedVerify(() => VerifyProxyExists(file, proxy, contextInfo, token),
+ e => e is ArgumentException,
+ _ =>
+ {
+ var modelFilePath = FileSystem.Path
+ .GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
+ AddError(VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.InvalidFilePath,
+ $"Invalid proxy file name '{proxy}' for model '{modelFilePath}'",
+ VerificationSeverity.Error,
+ [..contextInfo, modelFilePath],
+ proxy));
+ });
+ }
+ }
+
+ private void VerifyTextureExists(IPetroglyphFileHolder model, string texture, IReadOnlyCollection contextInfo)
+ {
+ if (texture == "None")
+ return;
+ _textureVerifier.Verify(texture, [..contextInfo, model.FileName], CancellationToken.None);
+ }
+
+ private void VerifyProxyExists(IPetroglyphFileHolder model, string proxy, IReadOnlyCollection contextInfo, CancellationToken token)
+ {
+ var proxyName = ProxyNameWithoutAlt(proxy);
+ var proxyPath = BuildModelPath(proxyName);
+
+ var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), model.FilePath.AsSpan()).ToString();
+
+ if (!Repository.ModelRepository.FileExists(proxyPath))
+ {
+ var message = $"Proxy particle '{proxyName}' not found for model '{modelFilePath}'";
+ var error = VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.FileNotFound,
+ message,
+ VerificationSeverity.Error,
+ [..contextInfo, modelFilePath],
+ proxyName);
+ AddError(error);
+ return;
+ }
+
+ VerifyAlamoFile(proxyPath, [..contextInfo, modelFilePath], token);
+ }
+
+ private void VerifyShaderExists(IPetroglyphFileHolder model, string shader, IReadOnlyCollection contextInfo)
+ {
+ if (shader is "alDefault.fx" or "alDefault.fxo")
+ return;
+
+ if (!Repository.EffectsRepository.FileExists(shader))
+ {
+ var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), model.FilePath.AsSpan()).ToString();
+ var message = $"Shader effect '{shader}' not found for model '{modelFilePath}'.";
+ var error = VerificationError.Create(
+ VerifierChain,
+ VerifierErrorCodes.FileNotFound,
+ message,
+ VerificationSeverity.Error,
+ [..contextInfo, modelFilePath],
+ shader);
+ AddError(error);
+ }
+ }
+
+ private string BuildModelPath(string fileName)
+ {
+ return FileSystem.Path.Combine("DATA\\ART\\MODELS", fileName);
+ }
+
+ private static string ProxyNameWithoutAlt(string proxy)
+ {
+ var proxyName = proxy.AsSpan();
+
+ var altSpan = ProxyAltIdentifier.AsSpan();
+
+ var altIndex = proxyName.LastIndexOf(altSpan);
+
+ if (altIndex == -1)
+ return proxy;
+
+ while (altIndex != -1)
+ {
+ proxyName = proxyName.Slice(0, altIndex);
+ altIndex = proxyName.LastIndexOf(altSpan);
+ }
+
+ return proxyName.ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/Commons/TextureVeifier.cs b/src/ModVerify/Verifiers/Commons/TextureVeifier.cs
new file mode 100644
index 0000000..80bb269
--- /dev/null
+++ b/src/ModVerify/Verifiers/Commons/TextureVeifier.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Settings;
+using Microsoft.Extensions.DependencyInjection;
+using PG.StarWarsGame.Engine;
+
+namespace AET.ModVerify.Verifiers.Commons;
+
+public sealed class TextureVeifier(
+ IGameVerifierInfo? parent,
+ IStarWarsGameEngine gameEngine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider)
+ : GameVerifier(parent, gameEngine, settings, serviceProvider)
+{
+ private readonly IAlreadyVerifiedCache? _cache = serviceProvider.GetService();
+
+ public override void Verify(string texturePath, IReadOnlyCollection contextInfo, CancellationToken token)
+ {
+ Verify(texturePath.AsSpan(), contextInfo, token);
+ }
+
+ public void Verify(ReadOnlySpan textureName, IReadOnlyCollection contextInfo, CancellationToken token)
+ {
+ token.ThrowIfCancellationRequested();
+
+
+ if (_cache?.TryAddEntry(textureName) == false)
+ return;
+
+ if (Repository.TextureRepository.FileExists(textureName, false, out var tooLongPath))
+ return;
+
+ var pathString = textureName.ToString();
+
+ if (tooLongPath)
+ {
+ VerificationError.Create(VerifierChain, VerifierErrorCodes.FilePathTooLong,
+ $"Could not find texture '{pathString}' because the engine resolved a path that is too long.",
+ VerificationSeverity.Error, contextInfo, pathString);
+ return;
+ }
+
+
+ var messageBuilder = new StringBuilder($"Could not find texture '{pathString}'");
+ if (contextInfo.Count > 0)
+ {
+ messageBuilder.Append(" for context: [");
+ messageBuilder.Append(string.Join("-->", contextInfo));
+ messageBuilder.Append(']');
+ }
+
+ messageBuilder.Append('.');
+
+ VerificationError.Create(VerifierChain, VerifierErrorCodes.FileNotFound,
+ messageBuilder.ToString(),
+ VerificationSeverity.Error, contextInfo, pathString);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/DuplicateNameFinder.cs b/src/ModVerify/Verifiers/DuplicateNameFinder.cs
index 14d2e67..23ab1b8 100644
--- a/src/ModVerify/Verifiers/DuplicateNameFinder.cs
+++ b/src/ModVerify/Verifiers/DuplicateNameFinder.cs
@@ -1,55 +1,104 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using AET.ModVerify.Reporting;
using AET.ModVerify.Settings;
using AnakinRaW.CommonUtilities.Collections;
-using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Engine.DataTypes;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.MTD.Data;
+using PG.StarWarsGame.Files.MTD.Files;
namespace AET.ModVerify.Verifiers;
public sealed class DuplicateNameFinder(
- IGameDatabase gameDatabase,
+ IStarWarsGameEngine gameEngine,
GameVerifySettings settings,
IServiceProvider serviceProvider)
- : GameVerifierBase(gameDatabase, settings, serviceProvider)
+ : GameVerifier(null, gameEngine, settings, serviceProvider)
{
- public override string FriendlyName => "Duplicate Definitions";
+ public override string FriendlyName => "Duplicates";
- protected override void RunVerification(CancellationToken token)
+ public override void Verify(CancellationToken token)
{
- CheckDatabaseForDuplicates(Database.GameObjects, "GameObject");
- CheckDatabaseForDuplicates(Database.SfxEvents, "SFXEvent");
- }
+ CheckXmlObjectsForDuplicates("GameObject", GameEngine.GameObjectTypeManager);
+ CheckXmlObjectsForDuplicates("SFXEvent", GameEngine.SfxGameManager);
+
+ if (GameEngine.GuiDialogManager.MtdFile is not null)
+ CheckMtdForDuplicates(GameEngine.GuiDialogManager.MtdFile);
- private void CheckDatabaseForDuplicates(IXmlDatabase database, string databaseName) where T : XmlObject
+ if (GameEngine.CommandBar.MegaTextureFile is not null)
+ {
+ if (!GameEngine.CommandBar.MegaTextureFile.FilePath.Equals(GameEngine.GuiDialogManager.MtdFile?.FileName))
+ CheckMtdForDuplicates(GameEngine.CommandBar.MegaTextureFile);
+ }
+ }
+
+ private void CheckForDuplicateCrcEntries(
+ string sourceName,
+ TSource source,
+ Func> crcSelector,
+ Func> entrySelector,
+ Func entryToStringSelector,
+ Func, IEnumerable> contextSelector,
+ Func, string, string> errorMessageCreator)
{
- foreach (var key in database.EntryKeys)
+ foreach (var crc32 in crcSelector(source))
{
- var entries = database.GetEntries(key);
+ var entries = entrySelector(source, crc32);
if (entries.Count > 1)
{
- var entryNames = entries.Select(x => x.Name);
+ var entryNames = entryToStringSelector(entries.First());
+ var context = contextSelector(entries);
AddError(VerificationError.Create(
- this,
+ VerifierChain,
VerifierErrorCodes.DuplicateFound,
- CreateDuplicateErrorMessage(databaseName, entries),
- VerificationSeverity.Warning,
+ errorMessageCreator(entries, sourceName),
+ VerificationSeverity.Error,
+ context,
entryNames));
}
}
}
- private string CreateDuplicateErrorMessage(string databaseName, ReadOnlyFrugalList entries) where T : XmlObject
+ private void CheckMtdForDuplicates(IMtdFile mtdFile)
+ {
+ CheckForDuplicateCrcEntries(
+ mtdFile.FileName,
+ mtdFile,
+ mtd => mtd.Content.Select(x => x.Crc32),
+ (mtd, crc32) => mtd.Content.EntriesWithCrc(crc32),
+ entry => entry.FileName,
+ entries => entries.Select(x => $"'{x.FileName}' (CRC: {x.Crc32})"),
+ CreateDuplicateMtdErrorMessage);
+ }
+
+ private void CheckXmlObjectsForDuplicates(string databaseName, IGameManager gameManager) where T : NamedXmlObject
+ {
+ CheckForDuplicateCrcEntries(
+ databaseName,
+ gameManager,
+ manager => manager.EntryKeys,
+ (manager, crc32) => manager.GetEntries(crc32),
+ entry => entry.Name,
+ entries => entries.Select(x => $"'{x.Name}' - {x.Location}"),
+ CreateDuplicateXmlErrorMessage);
+ }
+
+ private static string CreateDuplicateMtdErrorMessage(ReadOnlyFrugalList entries, string fileName)
{
var firstEntry = entries.First();
+ return $"MTD File '{fileName}' has duplicate definitions for CRC ({firstEntry}): {string.Join(",", entries.Select(x => x.FileName))}";
+ }
+ private static string CreateDuplicateXmlErrorMessage(ReadOnlyFrugalList entries, string databaseName) where T : NamedXmlObject
+ {
+ var firstEntry = entries.First();
var message = $"{databaseName} '{firstEntry.Name}' ({firstEntry.Crc32}) has duplicate definitions: ";
-
foreach (var entry in entries)
message += $"['{entry.Name}' in {entry.Location.XmlFile}:{entry.Location.Line}] ";
-
return message.TrimEnd();
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/GameEngineErrorCollector.cs b/src/ModVerify/Verifiers/GameEngineErrorCollector.cs
new file mode 100644
index 0000000..6475ea7
--- /dev/null
+++ b/src/ModVerify/Verifiers/GameEngineErrorCollector.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Reporting.Reporters.Engine;
+using AET.ModVerify.Settings;
+using PG.StarWarsGame.Engine;
+
+namespace AET.ModVerify.Verifiers;
+
+public sealed class GameEngineErrorCollector(
+ IGameEngineErrorCollection errorCollection,
+ IStarWarsGameEngine gameEngine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider) : GameVerifier(null, gameEngine, settings, serviceProvider)
+{
+ public override string FriendlyName => "Game Engine Initialization";
+
+ public override void Verify(CancellationToken token)
+ {
+ AddErrors(new InitializationErrorReporter(Repository, Services).GetErrors(errorCollection.InitializationErrors));
+ AddErrors(new XmlParseErrorReporter(Repository, Services).GetErrors(errorCollection.XmlErrors));
+ if (!Settings.IgnoreAsserts)
+ AddErrors(new GameAssertErrorReporter(Repository, Services).GetErrors(errorCollection.Asserts));
+ }
+
+ private void AddErrors(IEnumerable errors)
+ {
+ foreach (var error in errors)
+ AddError(error);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/GameVerifier.cs b/src/ModVerify/Verifiers/GameVerifier.cs
new file mode 100644
index 0000000..aebfe94
--- /dev/null
+++ b/src/ModVerify/Verifiers/GameVerifier.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using AET.ModVerify.Settings;
+using PG.StarWarsGame.Engine;
+
+namespace AET.ModVerify.Verifiers;
+
+public abstract class GameVerifier(
+ IGameVerifierInfo? parent,
+ IStarWarsGameEngine gameEngine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider)
+ : GameVerifierBase(parent, gameEngine, settings, serviceProvider) where T : notnull
+{
+ public abstract void Verify(T toVerify, IReadOnlyCollection contextInfo, CancellationToken token);
+}
+
+public abstract class GameVerifier(
+ IGameVerifierInfo? parent,
+ IStarWarsGameEngine gameEngine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider)
+ : GameVerifierBase(parent, gameEngine, settings, serviceProvider)
+{
+ public abstract void Verify(CancellationToken token);
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/GameVerifierBase.cs b/src/ModVerify/Verifiers/GameVerifierBase.cs
index a1f10cd..8c9d67d 100644
--- a/src/ModVerify/Verifiers/GameVerifierBase.cs
+++ b/src/ModVerify/Verifiers/GameVerifierBase.cs
@@ -1,62 +1,70 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO.Abstractions;
-using System.Threading;
-using AET.ModVerify.Reporting;
+using AET.ModVerify.Reporting;
using AET.ModVerify.Settings;
-using AnakinRaW.CommonUtilities.FileSystem;
-using AnakinRaW.CommonUtilities.SimplePipeline.Steps;
+using AnakinRaW.CommonUtilities.SimplePipeline.Progress;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Engine.Repositories;
+using PG.StarWarsGame.Engine.IO;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO.Abstractions;
+using AET.ModVerify.Pipeline.Progress;
+using PG.StarWarsGame.Engine;
namespace AET.ModVerify.Verifiers;
-public abstract class GameVerifierBase(
- IGameDatabase gameDatabase,
- GameVerifySettings settings,
- IServiceProvider serviceProvider)
- : PipelineStep(serviceProvider), IGameVerifier
+public abstract class GameVerifierBase : IGameVerifierInfo
{
+ public event EventHandler? Error;
- protected readonly IFileSystem FileSystem = serviceProvider.GetRequiredService();
-
- private readonly HashSet _verifyErrors = new();
-
- public IReadOnlyCollection VerifyErrors => _verifyErrors;
+ public event EventHandler>? Progress;
- protected GameVerifySettings Settings { get; } = settings;
+ private readonly IStarWarsGameEngine _gameEngine;
+ private readonly ConcurrentDictionary _verifyErrors = new();
- protected IGameDatabase Database { get; } = gameDatabase ?? throw new ArgumentNullException(nameof(gameDatabase));
+ protected readonly IFileSystem FileSystem;
+ protected readonly IServiceProvider Services;
+ protected readonly GameVerifySettings Settings;
- protected IGameRepository Repository => gameDatabase.GameRepository;
+ public IReadOnlyCollection VerifyErrors => [.. _verifyErrors.Keys];
public virtual string FriendlyName => GetType().Name;
public string Name => GetType().FullName;
- protected sealed override void RunCore(CancellationToken token)
+ public IGameVerifierInfo? Parent { get; }
+
+ protected IStarWarsGameEngine GameEngine { get; }
+
+ protected IGameRepository Repository => _gameEngine.GameRepository;
+
+ protected IReadOnlyList VerifierChain { get; }
+
+ protected GameVerifierBase(
+ IGameVerifierInfo? parent,
+ IStarWarsGameEngine gameEngine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider)
{
- Logger?.LogInformation($"Running verifier '{FriendlyName}'...");
- try
- {
- RunVerification(token);
- }
- finally
- {
- Logger?.LogInformation($"Finished verifier '{FriendlyName}'");
- }
+ if (serviceProvider == null)
+ throw new ArgumentNullException(nameof(serviceProvider));
+ FileSystem = serviceProvider.GetRequiredService();
+ Services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+ _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine));
+ Parent = parent;
+ Settings = settings ?? throw new ArgumentNullException(nameof(settings));
+ GameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine));
+ VerifierChain = CreateVerifierChain();
}
- protected abstract void RunVerification(CancellationToken token);
-
protected void AddError(VerificationError error)
{
- _verifyErrors.Add(error);
- if (Settings.AbortSettings.FailFast && error.Severity >= Settings.AbortSettings.MinimumAbortSeverity)
- throw new GameVerificationException(error);
+ if (_verifyErrors.TryAdd(error, 0))
+ {
+ Error?.Invoke(this, new VerificationErrorEventArgs(error));
+
+ if (error.Severity >= Settings.ThrowsOnMinimumSeverity)
+ throw new GameVerificationException(error);
+ }
}
protected void GuardedVerify(Action action, Predicate exceptionFilter, Action exceptionHandler)
@@ -71,17 +79,22 @@ protected void GuardedVerify(Action action, Predicate exceptionFilter
}
}
- protected string GetGameStrippedPath(string path)
+ protected void OnProgress(double progress, string? message)
{
- if (!FileSystem.Path.IsPathFullyQualified(path))
- return path;
+ Progress?.Invoke(this, new(progress, message));
+ }
- if (path.Length <= Repository.Path.Length)
- return path;
+ private IReadOnlyList CreateVerifierChain()
+ {
+ var verifierChain = new List { this };
- if (path.StartsWith(Repository.Path, StringComparison.OrdinalIgnoreCase))
- return path.Substring(Repository.Path.Length);
+ var parent = Parent;
+ while (parent != null)
+ {
+ verifierChain.Insert(0, parent);
+ parent = parent.Parent;
+ }
- return path;
+ return verifierChain;
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/GuiDialogs/GuiDialogsVerifier.cs b/src/ModVerify/Verifiers/GuiDialogs/GuiDialogsVerifier.cs
new file mode 100644
index 0000000..435c5de
--- /dev/null
+++ b/src/ModVerify/Verifiers/GuiDialogs/GuiDialogsVerifier.cs
@@ -0,0 +1,164 @@
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Verifiers.Commons;
+using Microsoft.Extensions.DependencyInjection;
+using PG.StarWarsGame.Engine;
+using PG.StarWarsGame.Engine.GuiDialog;
+using PG.StarWarsGame.Files.MTD.Binary;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace AET.ModVerify.Verifiers.GuiDialogs;
+
+sealed class GuiDialogsVerifier : GameVerifier
+{
+ internal const string DefaultComponentIdentifier = "<>";
+
+ private static readonly GuiComponentType[] GuiComponentTypes =
+ Enum.GetValues(typeof(GuiComponentType)).OfType().ToArray();
+
+ private readonly IAlreadyVerifiedCache? _cache;
+ private readonly TextureVeifier _textureVerifier;
+
+ public GuiDialogsVerifier(IStarWarsGameEngine gameEngine,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider) : base(null, gameEngine, settings, serviceProvider)
+ {
+ _cache = serviceProvider.GetService();
+ _textureVerifier = new TextureVeifier(this, gameEngine, settings, serviceProvider);
+ }
+
+ public override void Verify(CancellationToken token)
+ {
+ try
+ {
+ _textureVerifier.Error += OnTextureError;
+ VerifyMegaTexturesExist(token);
+ VerifyGuiTextures();
+ }
+ finally
+ {
+ _textureVerifier.Error -= OnTextureError;
+ }
+ }
+
+ private void VerifyGuiTextures()
+ {
+ var components = new List
+ {
+ DefaultComponentIdentifier
+ };
+ components.AddRange(GameEngine.GuiDialogManager.Components);
+
+ foreach (var component in components)
+ VerifyGuiComponentTexturesExist(component);
+
+ }
+
+ private void VerifyMegaTexturesExist(CancellationToken token)
+ {
+ var megaTextureName = GameEngine.GuiDialogManager.GuiDialogsXml?.TextureData.MegaTexture;
+ if (GameEngine.GuiDialogManager.MtdFile is null)
+ {
+ var mtdFileName = megaTextureName ?? "<>";
+ VerificationError.Create(VerifierChain, VerifierErrorCodes.FileNotFound, $"MtdFile '{mtdFileName}.mtd' could not be found",
+ VerificationSeverity.Critical, mtdFileName);
+ }
+
+ if (megaTextureName is not null)
+ {
+ var megaTextureFileName = $"{megaTextureName}.tga";
+ _textureVerifier.Verify(megaTextureFileName, ["GUIDIALOGS.XML"], token);
+ }
+
+
+ var compressedMegaTextureName = GameEngine.GuiDialogManager.GuiDialogsXml?.TextureData.CompressedMegaTexture;
+ if (compressedMegaTextureName is not null)
+ {
+ var compressedMegaTextureFieName = $"{compressedMegaTextureName}.dds";
+ _textureVerifier.Verify(compressedMegaTextureFieName, ["GUIDIALOGS.XML"], token);
+ }
+ }
+
+ private void VerifyGuiComponentTexturesExist(string component)
+ {
+ var middleButtonInRepoMode = false;
+
+
+ var entriesForComponent = GetTextureEntriesForComponents(component, out var defined);
+ if (!defined)
+ return;
+
+ foreach (var componentType in GuiComponentTypes)
+ {
+ try
+ {
+ if (!entriesForComponent.TryGetValue(componentType, out var texture))
+ continue;
+
+ if (_cache?.TryAddEntry(texture.Texture) == false)
+ {
+ // If we are in a special case we don't want to skip
+ if (!middleButtonInRepoMode &&
+ componentType is not GuiComponentType.ButtonMiddle &&
+ componentType is not GuiComponentType.Scanlines &&
+ componentType is not GuiComponentType.FrameBackground)
+ continue;
+ }
+
+ if (!GameEngine.GuiDialogManager.TextureExists(
+ texture,
+ out var origin,
+ out var isNone,
+ middleButtonInRepoMode)
+ && !isNone)
+ {
+
+ if (origin == GuiTextureOrigin.MegaTexture && texture.Texture.Length > MtdFileConstants.MaxFileNameSize)
+ {
+ AddError(VerificationError.Create(VerifierChain, VerifierErrorCodes.FilePathTooLong,
+ $"The filename is too long. Max length is {MtdFileConstants.MaxFileNameSize} characters.",
+ VerificationSeverity.Error, texture.Texture));
+ }
+ else
+ {
+ var message = $"Could not find GUI texture '{texture.Texture}' at location '{origin}'.";
+
+ if (texture.Texture.Length > PGConstants.MaxMegEntryPathLength)
+ message += " The file name is too long.";
+
+ AddError(VerificationError.Create(VerifierChain, VerifierErrorCodes.FileNotFound,
+ message, VerificationSeverity.Error,
+ [component, origin.ToString()], texture.Texture));
+ }
+ }
+
+ if (componentType is GuiComponentType.ButtonMiddle && origin is GuiTextureOrigin.Repository)
+ middleButtonInRepoMode = true;
+ }
+ finally
+ {
+
+ if (componentType >= GuiComponentType.ButtonRightDisabled)
+ middleButtonInRepoMode = false;
+ }
+ }
+ }
+
+ private IReadOnlyDictionary GetTextureEntriesForComponents(string component, out bool defined)
+ {
+ if (component == DefaultComponentIdentifier)
+ {
+ defined = true;
+ return GameEngine.GuiDialogManager.DefaultTextureEntries;
+ }
+ return GameEngine.GuiDialogManager.GetTextureEntries(component, out defined);
+ }
+
+ private void OnTextureError(object sender, VerificationErrorEventArgs e)
+ {
+ AddError(e.Error);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/IAlreadyVerifiedCache.cs b/src/ModVerify/Verifiers/IAlreadyVerifiedCache.cs
new file mode 100644
index 0000000..1fe8ad5
--- /dev/null
+++ b/src/ModVerify/Verifiers/IAlreadyVerifiedCache.cs
@@ -0,0 +1,11 @@
+using System;
+using PG.Commons.Hashing;
+
+namespace AET.ModVerify.Verifiers;
+
+public interface IAlreadyVerifiedCache
+{
+ public bool TryAddEntry(string entry);
+ public bool TryAddEntry(ReadOnlySpan entry);
+ public bool TryAddEntry(Crc32 checksum);
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/IGameVerifierInfo.cs b/src/ModVerify/Verifiers/IGameVerifierInfo.cs
new file mode 100644
index 0000000..cba43d1
--- /dev/null
+++ b/src/ModVerify/Verifiers/IGameVerifierInfo.cs
@@ -0,0 +1,10 @@
+namespace AET.ModVerify.Verifiers;
+
+public interface IGameVerifierInfo
+{
+ IGameVerifierInfo? Parent { get; }
+
+ string Name { get; }
+
+ string FriendlyName { get; }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs
index 8a6e7ba..a36648b 100644
--- a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs
+++ b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs
@@ -1,249 +1,50 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
+using AET.ModVerify.Settings;
+using AET.ModVerify.Verifiers.Commons;
+using PG.StarWarsGame.Engine;
+using System;
using System.Linq;
using System.Threading;
-using AET.ModVerify.Reporting;
-using AET.ModVerify.Settings;
-using Microsoft.Extensions.DependencyInjection;
-using PG.Commons.Binary;
-using PG.Commons.Files;
-using PG.Commons.Utilities;
-using PG.StarWarsGame.Engine;
-using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Files.ALO.Files.Models;
-using PG.StarWarsGame.Files.ALO.Files.Particles;
-using PG.StarWarsGame.Files.ALO.Services;
-using PG.StarWarsGame.Files.ChunkFiles.Data;
-using AnakinRaW.CommonUtilities.FileSystem;
-using System.Reflection;
namespace AET.ModVerify.Verifiers;
public sealed class ReferencedModelsVerifier(
- IGameDatabase database,
+ IStarWarsGameEngine engine,
GameVerifySettings settings,
IServiceProvider serviceProvider)
- : GameVerifierBase(database, settings, serviceProvider)
+ : GameVerifier(null, engine, settings, serviceProvider)
{
- private const string ProxyAltIdentifier = "_ALT";
-
- private readonly IAloFileService _modelFileService = serviceProvider.GetRequiredService();
-
public override string FriendlyName => "Referenced Models";
- protected override void RunVerification(CancellationToken token)
+ public override void Verify(CancellationToken token)
{
- var aloQueue = new Queue(Database.GameObjects.Entries
+ var models = GameEngine.GameObjectTypeManager.Entries
.SelectMany(x => x.Models)
- .Concat(FocHardcodedConstants.HardcodedModels));
-
- var visitedAloFiles = new HashSet(StringComparer.OrdinalIgnoreCase);
-
- while (aloQueue.Count != 0)
- {
- var model = aloQueue.Dequeue();
- if (!visitedAloFiles.Add(model))
- continue;
-
- token.ThrowIfCancellationRequested();
-
- using var modelStream = Repository.TryOpenFile(BuildModelPath(model));
+ .Concat(FocHardcodedConstants.HardcodedModels).ToList();
- if (modelStream is null)
- {
- var error = VerificationError.Create(
- this,
- VerifierErrorCodes.ModelNotFound,
- $"Unable to find .ALO file '{model}'",
- VerificationSeverity.Error,
- model);
+ if (models.Count == 0)
+ return;
- AddError(error);
- }
- else
- VerifyModelOrParticle(modelStream, aloQueue);
- }
- }
+ var numModels = models.Count;
+ var counter = 0;
- private void VerifyModelOrParticle(Stream modelStream, Queue workingQueue)
- {
+ var inner = new SingleModelVerifier(this, GameEngine, Settings, Services);
try
{
- using var aloData = _modelFileService.Load(modelStream, AloLoadOptions.Assets);
- switch (aloData)
+ inner.Error += OnModelError;
+ foreach (var model in models)
{
- case IAloModelFile model:
- VerifyModel(model, workingQueue);
- break;
- case IAloParticleFile particle:
- VerifyParticle(particle);
- break;
- default:
- throw new InvalidOperationException("The data stream is neither a model nor particle.");
+ OnProgress((double)++counter / numModels, $"Model - '{model}'");
+ inner.Verify(model, [], token);
}
}
- catch (BinaryCorruptedException e)
- {
- var aloFile = GetGameStrippedPath(modelStream.GetFilePath());
- var message = $"{aloFile} is corrupted: {e.Message}";
- AddError(VerificationError.Create(this, VerifierErrorCodes.ModelBroken, message, VerificationSeverity.Critical, aloFile));
- }
- }
-
- private void VerifyParticle(IAloParticleFile file)
- {
- foreach (var texture in file.Content.Textures)
- {
- GuardedVerify(() => VerifyTextureExists(file, texture),
- e => e is ArgumentException,
- _ =>
- {
- var modelFilePath = GetGameStrippedPath(file.FilePath);
- AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.InvalidTexture,
- $"Invalid texture file name" +
- $" '{texture}' in particle 'modelFilePath'",
- VerificationSeverity.Error,
- texture,
- modelFilePath));
- });
- }
-
- var fileName = FileSystem.Path.GetFileNameWithoutExtension(file.FilePath.AsSpan());
- var name = file.Content.Name.AsSpan();
-
- if (!fileName.Equals(name, StringComparison.OrdinalIgnoreCase))
- {
- var modelFilePath = GetGameStrippedPath(file.FilePath);
- AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.InvalidParticleName,
- $"The particle name '{file.Content.Name}' does not match file name '{modelFilePath}'",
- VerificationSeverity.Error,
- modelFilePath));
- }
-
- }
-
- private void VerifyModel(IAloModelFile file, Queue workingQueue)
- {
- foreach (var texture in file.Content.Textures)
- {
- GuardedVerify(() => VerifyTextureExists(file, texture),
- e => e is ArgumentException,
- _ =>
- {
- var modelFilePath = GetGameStrippedPath(file.FilePath);
- AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.InvalidTexture,
- $"Invalid texture file name '{texture}' in model '{modelFilePath}'",
- VerificationSeverity.Error,
- texture, modelFilePath));
- });
- }
-
- foreach (var shader in file.Content.Shaders)
- {
- GuardedVerify(() => VerifyShaderExists(file, shader),
- e => e is ArgumentException,
- _ =>
- {
- var modelFilePath = GetGameStrippedPath(file.FilePath);
- AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.InvalidShader,
- $"Invalid texture file name '{shader}' in model '{modelFilePath}'",
- VerificationSeverity.Error,
- shader, modelFilePath));
- });
- }
-
-
- foreach (var proxy in file.Content.Proxies)
- {
- GuardedVerify(() => VerifyProxyExists(file, proxy, workingQueue),
- e => e is ArgumentException,
- _ =>
- {
- var modelFilePath = GetGameStrippedPath(file.FilePath);
- AddError(VerificationError.Create(
- this,
- VerifierErrorCodes.InvalidProxy,
- $"Invalid proxy file name '{proxy}' in model '{modelFilePath}'",
- VerificationSeverity.Error,
- proxy, modelFilePath));
- });
- }
- }
-
- private void VerifyTextureExists(IPetroglyphFileHolder model, string texture)
- {
- if (texture == "None")
- return;
-
- if (!Repository.TextureRepository.FileExists(texture))
- {
- var modelFilePath = GetGameStrippedPath(model.FilePath);
- var message = $"{modelFilePath} references missing texture: {texture}";
- var error = VerificationError.Create(this, VerifierErrorCodes.ModelMissingTexture, message, VerificationSeverity.Error, modelFilePath, texture);
- AddError(error);
- }
- }
-
- private void VerifyProxyExists(IPetroglyphFileHolder model, string proxy, Queue workingQueue)
- {
- var proxyName = ProxyNameWithoutAlt(proxy);
- var particle = FileSystem.Path.ChangeExtension(proxyName, "alo");
- if (!Repository.FileExists(BuildModelPath(particle)))
- {
- var modelFilePath = GetGameStrippedPath(model.FilePath);
- var message = $"{modelFilePath} references missing proxy particle: {particle}";
- var error = VerificationError.Create(this, VerifierErrorCodes.ModelMissingProxy, message, VerificationSeverity.Error, modelFilePath, particle);
- AddError(error);
- }
- else
- workingQueue.Enqueue(particle);
- }
-
- private string BuildModelPath(string fileName)
- {
- return FileSystem.Path.Combine("DATA/ART/MODELS", fileName);
- }
-
- private void VerifyShaderExists(IPetroglyphFileHolder data, string shader)
- {
- if (shader is "alDefault.fx" or "alDefault.fxo")
- return;
-
- if (!Repository.EffectsRepository.FileExists(shader))
+ finally
{
- var modelFilePath = GetGameStrippedPath(data.FilePath);
- var message = $"{modelFilePath} references missing shader effect: {shader}";
- var error = VerificationError.Create(this, VerifierErrorCodes.ModelMissingShader, message, VerificationSeverity.Error, modelFilePath, shader);
- AddError(error);
+ inner.Error -= OnModelError;
}
}
- private static string ProxyNameWithoutAlt(string proxy)
+ private void OnModelError(object sender, VerificationErrorEventArgs e)
{
- var proxyName = proxy.AsSpan();
-
- var altSpan = ProxyAltIdentifier.AsSpan();
-
- var altIndex = proxyName.LastIndexOf(altSpan);
-
- if (altIndex == -1)
- return proxy;
-
- while (altIndex != -1)
- {
- proxyName = proxyName.Slice(0, altIndex);
- altIndex = proxyName.LastIndexOf(altSpan);
- }
-
- return proxyName.ToString();
+ AddError(e.Error);
}
}
diff --git a/src/ModVerify/Verifiers/VerificationErrorEventArgs.cs b/src/ModVerify/Verifiers/VerificationErrorEventArgs.cs
new file mode 100644
index 0000000..dcb8566
--- /dev/null
+++ b/src/ModVerify/Verifiers/VerificationErrorEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+using AET.ModVerify.Reporting;
+
+namespace AET.ModVerify.Verifiers;
+
+public sealed class VerificationErrorEventArgs(VerificationError error) : EventArgs
+{
+ public VerificationError Error { get; } = error ?? throw new ArgumentNullException(nameof(error));
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/VerifierErrorCodes.cs b/src/ModVerify/Verifiers/VerifierErrorCodes.cs
index d4b3fa0..986cbe6 100644
--- a/src/ModVerify/Verifiers/VerifierErrorCodes.cs
+++ b/src/ModVerify/Verifiers/VerifierErrorCodes.cs
@@ -2,34 +2,39 @@
public static class VerifierErrorCodes
{
+ public const string InitializationError = "INIT00";
+
+ public const string AssertValueNullOrEmpty = "ASRT00";
+ public const string AssertValueOutOfRange = "ASRT01";
+ public const string AssertValueInvalid = "ASRT02";
+
public const string GenericExceptionErrorCode = "MV00";
+ public const string FileCorrupt = "ENG00";
+
+
+ public const string FileNotFound = "FILE00";
+ public const string FilePathTooLong = "FILE01";
+ public const string InvalidFilePath = "FILE02";
+
public const string DuplicateFound = "DUP00";
public const string SampleNotFound = "WAV00";
- public const string FilePathTooLong = "WAV01";
public const string SampleNotPCM = "WAV02";
public const string SampleNotMono = "WAV03";
public const string InvalidSampleRate = "WAV04";
public const string InvalidBitsPerSeconds = "WAV05";
- public const string ModelNotFound = "ALO00";
- public const string ModelBroken = "ALO01";
- public const string ModelMissingTexture = "ALO02";
- public const string ModelMissingProxy = "ALO03";
- public const string ModelMissingShader = "ALO04";
- public const string InvalidTexture = "ALO05";
- public const string InvalidShader = "ALO06";
- public const string InvalidProxy = "ALO07";
- public const string InvalidParticleName = "ALO08";
+ public const string InvalidParticleName = "ALO01";
public const string GenericXmlError = "XML00";
public const string EmptyXmlRoot = "XML01";
- public const string MissingXmlFile = "XML02";
public const string InvalidXmlValue = "XML03";
public const string MalformedXmlValue = "XML04";
public const string MissingXmlAttribute = "XML05";
public const string MissingXmlReference = "XML06";
public const string XmlValueTooLong = "XML07";
public const string XmlDataBeforeHeader = "XML08";
+ public const string XmlMissingNode = "XML09";
+ public const string XmlUnsupportedTag = "XML10";
}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/XmlParseErrorCollector.cs b/src/ModVerify/Verifiers/XmlParseErrorCollector.cs
deleted file mode 100644
index 0819eb8..0000000
--- a/src/ModVerify/Verifiers/XmlParseErrorCollector.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using AET.ModVerify.Reporting;
-using AET.ModVerify.Settings;
-using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
-
-namespace AET.ModVerify.Verifiers;
-
-public sealed class XmlParseErrorCollector(
- IEnumerable xmlErrors,
- IGameDatabase gameDatabase,
- GameVerifySettings settings,
- IServiceProvider serviceProvider) :
- GameVerifierBase(gameDatabase, settings, serviceProvider)
-{
- public override string FriendlyName => "XML Parsing Errors";
-
- protected override void RunVerification(CancellationToken token)
- {
- foreach (var xmlError in xmlErrors)
- AddError(ConvertXmlToVerificationError(xmlError));
- }
-
- private VerificationError ConvertXmlToVerificationError(XmlParseErrorEventArgs xmlError)
- {
- var id = GetIdFromError(xmlError.ErrorKind);
- var severity = GetSeverityFromError(xmlError.ErrorKind);
-
- var assets = new List
- {
- GetGameStrippedPath(xmlError.File.ToUpperInvariant())
- };
-
- var xmlElement = xmlError.Element;
-
- if (xmlElement is not null)
- {
- assets.Add(xmlElement.Name.LocalName);
-
- var parent = xmlElement.Parent;
-
- if (parent != null)
- {
- var parentName = parent.Attribute("Name");
- assets.Add(parentName != null ? $"parentName='{parentName.Value}'" : $"parentTag='{parent.Name.LocalName}'");
- }
-
- }
-
- return VerificationError.Create(this, id, xmlError.Message, severity, assets);
-
- }
-
- private VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErrorKind)
- {
- return xmlErrorErrorKind switch
- {
- XmlParseErrorKind.EmptyRoot => VerificationSeverity.Critical,
- XmlParseErrorKind.MissingFile => VerificationSeverity.Error,
- XmlParseErrorKind.InvalidValue => VerificationSeverity.Information,
- XmlParseErrorKind.MalformedValue => VerificationSeverity.Warning,
- XmlParseErrorKind.MissingAttribute => VerificationSeverity.Error,
- XmlParseErrorKind.MissingReference => VerificationSeverity.Error,
- XmlParseErrorKind.TooLongData => VerificationSeverity.Warning,
- XmlParseErrorKind.DataBeforeHeader => VerificationSeverity.Information,
- _ => VerificationSeverity.Warning
- };
- }
-
- private string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind)
- {
- return xmlErrorErrorKind switch
- {
- XmlParseErrorKind.EmptyRoot => VerifierErrorCodes.EmptyXmlRoot,
- XmlParseErrorKind.MissingFile => VerifierErrorCodes.MissingXmlFile,
- XmlParseErrorKind.InvalidValue => VerifierErrorCodes.InvalidXmlValue,
- XmlParseErrorKind.MalformedValue => VerifierErrorCodes.MalformedXmlValue,
- XmlParseErrorKind.MissingAttribute => VerifierErrorCodes.MissingXmlAttribute,
- XmlParseErrorKind.MissingReference => VerifierErrorCodes.MissingXmlReference,
- XmlParseErrorKind.TooLongData => VerifierErrorCodes.XmlValueTooLong,
- XmlParseErrorKind.Unknown => VerifierErrorCodes.GenericXmlError,
- XmlParseErrorKind.DataBeforeHeader => VerifierErrorCodes.XmlDataBeforeHeader,
- _ => throw new ArgumentOutOfRangeException(nameof(xmlErrorErrorKind), xmlErrorErrorKind, null)
- };
- }
-}
\ No newline at end of file
diff --git a/src/ModVerify/VerifyGamePipeline.cs b/src/ModVerify/VerifyGamePipeline.cs
deleted file mode 100644
index e546447..0000000
--- a/src/ModVerify/VerifyGamePipeline.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using AET.ModVerify.Reporting;
-using AET.ModVerify.Settings;
-using AET.ModVerify.Verifiers;
-using AnakinRaW.CommonUtilities.SimplePipeline;
-using AnakinRaW.CommonUtilities.SimplePipeline.Runners;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using PG.StarWarsGame.Engine;
-using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
-using PG.StarWarsGame.Files.XML.Parsers;
-
-namespace AET.ModVerify;
-
-public abstract class VerifyGamePipeline : Pipeline
-{
- private readonly List _verificationSteps = new();
- private readonly GameEngineType _targetType;
- private readonly GameLocations _gameLocations;
- private readonly ParallelRunner _verifyRunner;
-
- protected GameVerifySettings Settings { get; }
-
- public IReadOnlyCollection Errors { get; private set; } = Array.Empty();
-
- private readonly ConcurrentBag _xmlParseErrors = new();
-
- protected VerifyGamePipeline(GameEngineType targetType, GameLocations gameLocations, GameVerifySettings settings, IServiceProvider serviceProvider)
- : base(serviceProvider)
- {
- _targetType = targetType;
- _gameLocations = gameLocations ?? throw new ArgumentNullException(nameof(gameLocations));
- Settings = settings ?? throw new ArgumentNullException(nameof(settings));
-
- if (settings.ParallelVerifiers is < 0 or > 64)
- throw new ArgumentException("Settings has invalid parallel worker number.", nameof(settings));
-
- _verifyRunner = new ParallelRunner(settings.ParallelVerifiers, serviceProvider);
- }
-
-
- protected sealed override Task PrepareCoreAsync()
- {
- _verificationSteps.Clear();
- return Task.FromResult(true);
- }
-
- protected sealed override async Task RunCoreAsync(CancellationToken token)
- {
- Logger?.LogInformation("Verifying...");
- try
- {
- var databaseService = ServiceProvider.GetRequiredService();
-
- IGameDatabase database;
- try
- {
- databaseService.XmlParseError += OnXmlParseError;
- database = await databaseService.CreateDatabaseAsync(_targetType, _gameLocations, token);
- }
- finally
- {
- databaseService.XmlParseError -= OnXmlParseError;
- databaseService.Dispose();
- }
-
-
- AddStep(new XmlParseErrorCollector(_xmlParseErrors, database, Settings, ServiceProvider));
-
- foreach (var gameVerificationStep in CreateVerificationSteps(database))
- AddStep(gameVerificationStep);
-
- try
- {
- Logger?.LogInformation("Verifying...");
- _verifyRunner.Error += OnError;
- await _verifyRunner.RunAsync(token);
- }
- finally
- {
- _verifyRunner.Error -= OnError;
- Logger?.LogInformation("Finished Verifying");
- }
-
-
- Logger?.LogInformation("Reporting Errors");
-
- var reportBroker = new VerificationReportBroker(Settings.GlobalReportSettings, ServiceProvider);
- var errors = await reportBroker.ReportAsync(_verificationSteps);
-
- Errors = errors;
-
- if (Settings.AbortSettings.ThrowsGameVerificationException &&
- errors.Any(x => x.Severity >= Settings.AbortSettings.MinimumAbortSeverity))
- throw new GameVerificationException(errors);
- }
- finally
- {
- Logger?.LogInformation("Finished game verification");
- }
- }
-
- protected abstract IEnumerable CreateVerificationSteps(IGameDatabase database);
-
- private void AddStep(GameVerifierBase verifier)
- {
- _verifyRunner.AddStep(verifier);
- _verificationSteps.Add(verifier);
- }
-
- private void OnXmlParseError(IPetroglyphXmlParser sender, XmlParseErrorEventArgs e)
- {
- _xmlParseErrors.Add(e);
- }
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/Directory.Build.props b/src/PetroglyphTools/Directory.Build.props
index c8ba572..4b4cdb9 100644
--- a/src/PetroglyphTools/Directory.Build.props
+++ b/src/PetroglyphTools/Directory.Build.props
@@ -1,12 +1,11 @@
+
-
-
+
- Alamo Engine Tools and Contributors
https://github.com/AlamoEngine-Tools/PetroglyphTools
https://github.com/AlamoEngine-Tools/PetroglyphTools
-
+
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs
new file mode 100644
index 0000000..41c4bb8
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs
@@ -0,0 +1,3 @@
+namespace PG.StarWarsGame.Engine.Audio.Sfx;
+
+public interface ISfxEventGameManager : IGameManager;
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEvent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEvent.cs
new file mode 100644
index 0000000..aed53f2
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEvent.cs
@@ -0,0 +1,239 @@
+using System.Collections.Generic;
+using System.Linq;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.XML;
+
+namespace PG.StarWarsGame.Engine.Audio.Sfx;
+
+public sealed class SfxEvent : NamedXmlObject
+{
+ private byte _minVolume = DefaultMinVolume;
+ private byte _maxVolume = DefaultMaxVolume;
+ private byte _minPitch = DefaultMinPitch;
+ private byte _maxPitch = DefaultMaxPitch;
+ private byte _minPan2D = DefaultMinPan2d;
+ private byte _maxPan2D = DefaultMaxPan2d;
+ private uint _minPredelay;
+ private uint _maxPredelay;
+ private uint _minPostdelay;
+ private uint _maxPostdelay;
+
+ public const byte MaxVolumeValue = 100;
+ public const byte MaxPitchValue = 200;
+ public const byte MinPitchValue = 50;
+ public const byte MaxPan2dValue = 100;
+ public const byte MinPriorityValue = 1;
+ public const byte MaxPriorityValue = 5;
+ public const byte MaxProbability = 100;
+ public const sbyte MinMaxInstances = 0;
+ public const sbyte InfinitivePlayCount = -1;
+ public const float MinLoopSeconds = 0.0f;
+ public const float MinVolumeSaturation = 0.0f;
+
+ // Default values which are not the default value of the type
+ public const byte DefaultPriority = 3;
+ public const bool DefaultIs3d = true;
+ public const byte DefaultProbability = 100;
+ public const sbyte DefaultPlayCount = 1;
+ public const sbyte DefaultMaxInstances = 1;
+ public const byte DefaultMinVolume = 100;
+ public const byte DefaultMaxVolume = 100;
+ public const byte DefaultMinPitch = 100;
+ public const byte DefaultMaxPitch = 100;
+ public const byte DefaultMinPan2d = 50;
+ public const byte DefaultMaxPan2d = 50;
+ public const float DefaultVolumeSaturationDistance = 300.0f;
+
+ public bool IsPreset { get; internal set; }
+
+ public bool Is3D { get; internal set; } = DefaultIs3d;
+
+ public bool Is2D { get; internal set; }
+
+ public bool IsGui { get; internal set; }
+
+ public bool IsHudVo { get; internal set; }
+
+ public bool IsUnitResponseVo { get; internal set; }
+
+ public bool IsAmbientVo { get; internal set; }
+
+ public bool IsLocalized { get; internal set; }
+
+ public SfxEvent? Preset { get; internal set; }
+
+ public string? UsePresetName { get; internal set; }
+
+ public bool PlaySequentially { get; internal set; }
+
+ public IEnumerable AllSamples => PreSamples.Concat(Samples).Concat(PostSamples);
+
+ public IReadOnlyList PreSamples { get; internal set; } = [];
+
+ public IReadOnlyList Samples { get; internal set; } = [];
+
+ public IReadOnlyList PostSamples { get; internal set; } = [];
+
+ public IReadOnlyList LocalizedTextIDs { get; internal set; } = [];
+
+ public byte Priority { get; internal set; } = DefaultPriority;
+
+ public byte Probability { get; internal set; } = DefaultProbability;
+
+ public sbyte PlayCount { get; internal set; } = DefaultPlayCount;
+
+ public float LoopFadeInSeconds { get; internal set; }
+
+ public float LoopFadeOutSeconds { get; internal set; }
+
+ public sbyte MaxInstances { get; internal set; } = DefaultMaxInstances;
+
+ public float VolumeSaturationDistance { get; internal set; } = DefaultVolumeSaturationDistance;
+
+ public bool KillsPreviousObjectsSfx { get; internal set; }
+
+ public string? OverlapTestName { get; internal set; }
+
+ public string? ChainedSfxEventName { get; internal set; }
+
+ public byte MinVolume
+ {
+ get => _minVolume;
+ internal set => _minVolume = value;
+ }
+
+ public byte MaxVolume
+ {
+ get => _maxVolume;
+ internal set => _maxVolume = value;
+ }
+
+ public byte MinPitch
+ {
+ get => _minPitch;
+ internal set => _minPitch = value;
+ }
+
+ public byte MaxPitch
+ {
+ get => _maxPitch;
+ internal set => _maxPitch = value;
+ }
+
+ public byte MinPan2D
+ {
+ get => _minPan2D;
+ internal set => _minPan2D = value;
+ }
+
+ public byte MaxPan2D
+ {
+ get => _maxPan2D;
+ internal set => _maxPan2D = value;
+ }
+
+ public uint MinPredelay
+ {
+ get => _minPredelay;
+ internal set => _minPredelay = value;
+ }
+
+ public uint MaxPredelay
+ {
+ get => _maxPredelay;
+ internal set => _maxPredelay = value;
+ }
+
+ public uint MinPostdelay
+ {
+ get => _minPostdelay;
+ internal set => _minPostdelay = value;
+ }
+
+ public uint MaxPostdelay
+ {
+ get => _maxPostdelay;
+ internal set => _maxPostdelay = value;
+ }
+
+
+ internal SfxEvent(string name, Crc32 nameCrc, XmlLocationInfo location)
+ : base(name, nameCrc, location)
+ {
+ }
+
+ internal override void CoerceValues()
+ {
+ AdjustMinMaxValues(ref _minVolume, ref _maxVolume);
+ AdjustMinMaxValues(ref _minPitch, ref _maxPitch);
+ AdjustMinMaxValues(ref _minPan2D, ref _maxPan2D);
+ AdjustMinMaxValues(ref _minPredelay, ref _maxPredelay);
+ AdjustMinMaxValues(ref _minPostdelay, ref _maxPostdelay);
+ }
+
+ /*
+ * The engine also copies the of the preset (which is usually null).
+ * As this would cause this SFXEvent loose the information of the coded preset, we do not copy the preset's preset value.
+ * Example:
+ *
+ *
+ * Preset Yes
+ * 90
+ *
+ *
+ * Engine Behavior: SFXEvent instance(Name: A, Use_Preset: null, Min_Volume: 90)
+ * PG.StarWarsGame.Engine Behavior: SFXEvent instance(Name: A, Use_Preset: Preset, Min_Volume: 90)
+ */
+ public void ApplyPreset(SfxEvent preset)
+ {
+ Is3D = preset.Is3D;
+ Is2D = preset.Is2D;
+ IsGui = preset.IsGui;
+ IsHudVo = preset.IsHudVo;
+ IsUnitResponseVo = preset.IsUnitResponseVo;
+ IsAmbientVo = preset.IsAmbientVo;
+ IsLocalized = preset.IsLocalized;
+ Preset = preset;
+ UsePresetName = preset.Name;
+ PlaySequentially = preset.PlaySequentially;
+ PreSamples = preset.PreSamples;
+ Samples = preset.Samples;
+ PostSamples = preset.PostSamples;
+ LocalizedTextIDs = preset.LocalizedTextIDs;
+ Priority = preset.Priority;
+ Probability = preset.Probability;
+ PlayCount = preset.PlayCount;
+ LoopFadeInSeconds = preset.LoopFadeInSeconds;
+ LoopFadeOutSeconds = preset.LoopFadeOutSeconds;
+ MaxInstances = preset.MaxInstances;
+ MinPredelay = preset.MinPredelay;
+ MaxPredelay = preset.MaxPredelay;
+ MinPostdelay = preset.MinPostdelay;
+ MaxPostdelay = preset.MaxPostdelay;
+ OverlapTestName = preset.OverlapTestName;
+ ChainedSfxEventName = preset.ChainedSfxEventName;
+ MinVolume = preset.MinVolume;
+ MaxVolume = preset.MaxVolume;
+ MinPitch = preset.MinPitch;
+ MaxPitch = preset.MaxPitch;
+ MinPan2D = preset.MinPan2D;
+ MaxPan2D = preset.MaxPan2D;
+ }
+
+ private static void AdjustMinMaxValues(ref byte minValue, ref byte maxValue)
+ {
+ if (minValue > maxValue)
+ minValue = maxValue;
+ if (maxValue < minValue)
+ maxValue = minValue;
+ }
+
+ private static void AdjustMinMaxValues(ref uint minValue, ref uint maxValue)
+ {
+ if (minValue > maxValue)
+ minValue = maxValue;
+ if (maxValue < minValue)
+ maxValue = minValue;
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs
new file mode 100644
index 0000000..b037939
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Engine.Localization;
+using PG.StarWarsGame.Engine.Xml.Parsers;
+
+namespace PG.StarWarsGame.Engine.Audio.Sfx;
+
+internal class SfxEventGameManager(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorReporter, serviceProvider), ISfxEventGameManager
+{
+ public IEnumerable InstalledLanguages { get; private set; } = [];
+
+ protected override async Task InitializeCoreAsync(CancellationToken token)
+ {
+ Logger?.LogInformation("Initializing Language files...");
+ InstalledLanguages = GameRepository.InitializeInstalledSfxMegFiles().ToList();
+ Logger?.LogInformation("Finished initializing Language files");
+
+ Logger?.LogInformation("Parsing SFXEvents...");
+
+ var contentParser = new XmlContainerContentParser(ServiceProvider, ErrorReporter);
+ contentParser.XmlParseError += OnParseError;
+ try
+ {
+ await Task.Run(() => contentParser.ParseEntriesFromFileListXml(
+ "DATA\\XML\\SFXEventFiles.XML",
+ GameRepository,
+ "DATA\\XML",
+ NamedEntries,
+ VerifyFilePathLength),
+ token);
+ }
+ finally
+ {
+ contentParser.XmlParseError -= OnParseError;
+ }
+ }
+
+ private void OnParseError(object sender, XmlContainerParserErrorEventArgs e)
+ {
+ if (e.ErrorInXmlFileList || e.HasException)
+ {
+ e.Continue = false;
+ ErrorReporter.Report(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = GetMessage(e)
+ });
+ }
+ }
+
+ private static string GetMessage(XmlContainerParserErrorEventArgs errorEventArgs)
+ {
+ if (errorEventArgs.HasException)
+ return $"Error while parsing SFXEvent XML file '{errorEventArgs.File}': {errorEventArgs.Exception.Message}";
+ return "Could not find SFXEventFiles.xml";
+ }
+
+ private void VerifyFilePathLength(string filePath)
+ {
+ if (filePath.Length > PGConstants.MaxSFXEventDatabaseFileName)
+ {
+ ErrorReporter.Report(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = $"SFXEvent file '{filePath}' is longer than {PGConstants.MaxSFXEventDatabaseFileName} characters."
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentGroup.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentGroup.cs
new file mode 100644
index 0000000..1799264
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentGroup.cs
@@ -0,0 +1,21 @@
+using PG.StarWarsGame.Engine.CommandBar.Components;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+public sealed class CommandBarComponentGroup
+{
+ public string Name { get; }
+
+ public IReadOnlyList Components { get; }
+
+ internal CommandBarComponentGroup(string name, IEnumerable components)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ if (components == null)
+ throw new ArgumentNullException(nameof(components));
+ Components = components.ToList();
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentId.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentId.cs
new file mode 100644
index 0000000..2ace3a0
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentId.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+public readonly struct CommandBarComponentId : IEquatable
+{
+ public static readonly CommandBarComponentId None = new(GameEngineType.Eaw, -1);
+
+ public GameEngineType TargetEngine { get; }
+
+ public int Value { get; }
+
+ internal CommandBarComponentId(GameEngineType engine, int value)
+ {
+ TargetEngine = engine;
+ Value = value;
+ }
+
+ public override string ToString()
+ {
+ if (Value == -1)
+ return "NONE (-1)";
+
+ var nameLookup = SupportedCommandBarComponentData.GetComponentIdsForEngine(TargetEngine);
+ return $"'{nameLookup[this]}' ({Value})";
+ }
+
+ public bool Equals(CommandBarComponentId other)
+ {
+ return TargetEngine == other.TargetEngine && Value == other.Value;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is CommandBarComponentId other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine((int)TargetEngine, Value);
+ }
+
+ public static bool operator ==(CommandBarComponentId left, CommandBarComponentId right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(CommandBarComponentId left, CommandBarComponentId right)
+ {
+ return !(left == right);
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs
new file mode 100644
index 0000000..9a4c6ee
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs
@@ -0,0 +1,17 @@
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+public enum CommandBarComponentType
+{
+ // Used for XML lookup
+ Shell = 0,
+ Icon = 1,
+ Button = 2,
+ Text = 3,
+ TextButton = 4,
+ Model = 5,
+ Bar = 6,
+ // Used internally by the engine only
+ Select = 7,
+ Count = 8,
+ None = 9,
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarConstants.cs
new file mode 100644
index 0000000..bec58ce
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarConstants.cs
@@ -0,0 +1,7 @@
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+public static class CommandBarConstants
+{
+ public const string MegaTextureBaseName = "MT_COMMANDBAR";
+ public const string ShellGroupName = "Shells";
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs
new file mode 100644
index 0000000..311c212
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs
@@ -0,0 +1,293 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.Commons.Collections;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.CommandBar.Components;
+using PG.StarWarsGame.Engine.CommandBar.Xml;
+using PG.StarWarsGame.Engine.ErrorReporting;
+using PG.StarWarsGame.Engine.GameConstants;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Engine.Rendering;
+using PG.StarWarsGame.Engine.Rendering.Font;
+using PG.StarWarsGame.Engine.Xml.Parsers;
+using PG.StarWarsGame.Files.Binary;
+using PG.StarWarsGame.Files.MTD.Files;
+using PG.StarWarsGame.Files.MTD.Services;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+internal class CommandBarGameManager(
+ GameRepository repository,
+ PGRender pgRender,
+ IGameConstants gameConstants,
+ IFontManager fontManager,
+ GameEngineErrorReporterWrapper errorReporter,
+ IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorReporter, serviceProvider), ICommandBarGameManager
+{
+ private readonly ICrc32HashingService _hashingService = serviceProvider.GetRequiredService();
+ private readonly IMtdFileService _mtdFileService = serviceProvider.GetRequiredService();
+ private readonly Dictionary