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..26f6094 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,7 +3,7 @@
all
- 3.6.139
+ 3.6.143
\ 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/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs
index fa21e3b..2e5f5f4 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)
@@ -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..953b0fe 100644
--- a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs
@@ -1,8 +1,8 @@
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.Mods;
@@ -11,7 +11,7 @@ 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();
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..c620aae 100644
--- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
+++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs
@@ -1,21 +1,13 @@
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)
{
var gameLocations = new ModSelectorFactory(serviceProvider).CreateSelector(settings)
@@ -35,30 +27,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..82ddda9 100644
--- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj
+++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj
@@ -2,7 +2,7 @@
false
- net8.0;net48
+ net9.0;net48
Exe
AET.ModVerifyTool
ModVerify
@@ -20,23 +20,24 @@
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -50,7 +51,6 @@
-
@@ -58,6 +58,10 @@
+
+
+
+
diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs
index ea5fff9..a8da9eb 100644
--- a/src/ModVerify.CliApp/Program.cs
+++ b/src/ModVerify.CliApp/Program.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.IO.Abstractions;
-using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using AET.ModVerify;
using AET.ModVerify.Reporting.Reporters;
@@ -16,19 +14,19 @@
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.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.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 Testably.Abstractions;
namespace AET.ModVerifyTool;
@@ -90,7 +88,7 @@ await parseResult.WithNotParsedAsync(e =>
private static IServiceCollection CreateCoreServices(bool verboseLogging)
{
- var fileSystem = new FileSystem();
+ var fileSystem = new RealFileSystem();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new WindowsRegistry());
@@ -107,13 +105,12 @@ private static IServiceProvider CreateAppServices(IServiceCollection serviceColl
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);
+ serviceCollection.SupportMTD();
+ serviceCollection.SupportMEG();
AloServiceContribution.ContributeServices(serviceCollection);
- serviceCollection.CollectPgServiceContributions();
+ PetroglyphCommons.ContributeServices(serviceCollection);
XmlServiceContribution.ContributeServices(serviceCollection);
PetroglyphEngineServiceContribution.ContributeServices(serviceCollection);
@@ -122,15 +119,8 @@ private static IServiceProvider CreateAppServices(IServiceCollection serviceColl
SetupReporting(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));
+ serviceCollection.AddSingleton(sp => new OnlineModNameResolver(sp));
+ serviceCollection.AddSingleton(sp => new OnlineModGameTypeResolver(sp));
return serviceCollection.BuildServiceProvider();
}
diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json
index 51b4cdc..d84156a 100644
--- a/src/ModVerify.CliApp/Properties/launchSettings.json
+++ b/src/ModVerify.CliApp/Properties/launchSettings.json
@@ -2,7 +2,7 @@
"profiles": {
"Interactive": {
"commandName": "Project",
- "commandLineArgs": "-o verifyResults --minFailSeverity Information --baseline focBaseline.json"
+ "commandLineArgs": "-o verifyResults --minFailSeverity Information --baseline c:/test/focBaseline.json"
},
"FromModPath": {
diff --git a/src/ModVerify.CliApp/SettingsBuilder.cs b/src/ModVerify.CliApp/SettingsBuilder.cs
index eeba013..27c7857 100644
--- a/src/ModVerify.CliApp/SettingsBuilder.cs
+++ b/src/ModVerify.CliApp/SettingsBuilder.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.IO.Abstractions;
using AET.ModVerify.Reporting;
+using AET.ModVerify.Reporting.Settings;
using AET.ModVerify.Settings;
using AET.ModVerifyTool.Options;
using Microsoft.Extensions.DependencyInjection;
diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj
index 4a77b42..0649885 100644
--- a/src/ModVerify/ModVerify.csproj
+++ b/src/ModVerify/ModVerify.csproj
@@ -22,7 +22,8 @@
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -31,7 +32,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
@@ -39,4 +40,8 @@
+
+
+
+
diff --git a/src/ModVerify/ModVerify.csproj.DotSettings b/src/ModVerify/ModVerify.csproj.DotSettings
new file mode 100644
index 0000000..8af804c
--- /dev/null
+++ b/src/ModVerify/ModVerify.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/ConcurrentGameDatabaseErrorListener.cs b/src/ModVerify/Reporting/ConcurrentGameDatabaseErrorListener.cs
new file mode 100644
index 0000000..95461ad
--- /dev/null
+++ b/src/ModVerify/Reporting/ConcurrentGameDatabaseErrorListener.cs
@@ -0,0 +1,26 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+namespace AET.ModVerify.Reporting;
+
+internal class ConcurrentGameDatabaseErrorListener : DatabaseErrorListener, IDatabaseErrorCollection
+{
+ private readonly ConcurrentBag _xmlErrors = new();
+
+ private readonly ConcurrentBag _initializationErrors = new();
+
+ public IEnumerable XmlErrors => _xmlErrors.ToList();
+ public IEnumerable InitializationErrors => _initializationErrors.ToList();
+
+ public override void OnXmlError(XmlError error)
+ {
+ _xmlErrors.Add(error);
+ }
+
+ public override void OnInitializationError(InitializationError error)
+ {
+ _initializationErrors.Add(error);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/IDatabaseErrorCollection.cs b/src/ModVerify/Reporting/IDatabaseErrorCollection.cs
new file mode 100644
index 0000000..90c8203
--- /dev/null
+++ b/src/ModVerify/Reporting/IDatabaseErrorCollection.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+namespace AET.ModVerify.Reporting;
+
+internal interface IDatabaseErrorCollection
+{
+ IEnumerable XmlErrors { get; }
+ IEnumerable InitializationErrors { get; }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs
index bda277f..2e1cb6a 100644
--- a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using AET.ModVerify.Reporting.Settings;
namespace AET.ModVerify.Reporting.Reporters;
@@ -9,7 +10,7 @@ internal class ConsoleReporter(VerificationReportSettings settings, IServiceProv
{
public override Task ReportAsync(IReadOnlyCollection errors)
{
- var filteredErrors = FilteredErrors(errors).ToList();
+ var filteredErrors = FilteredErrors(errors).OrderByDescending(x => x.Severity).ToList();
Console.WriteLine();
Console.WriteLine("GAME VERIFICATION RESULT");
@@ -20,7 +21,7 @@ public override Task ReportAsync(IReadOnlyCollection errors)
Console.WriteLine("No errors!");
foreach (var error in filteredErrors)
- Console.WriteLine(error);
+ Console.WriteLine($"[{error.Severity}] [{error.Id}] Message={error.Message}");
Console.WriteLine();
diff --git a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs
index d0213d0..b66f29a 100644
--- a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs
+++ b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.IO.Abstractions;
+using AET.ModVerify.Reporting.Settings;
using Microsoft.Extensions.DependencyInjection;
namespace AET.ModVerify.Reporting.Reporters;
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..ff71507 100644
--- a/src/ModVerify/Reporting/Reporters/ReporterBase.cs
+++ b/src/ModVerify/Reporting/Reporters/ReporterBase.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using AET.ModVerify.Reporting.Settings;
namespace AET.ModVerify.Reporting.Reporters;
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..41c2938 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;
diff --git a/src/ModVerify/Reporting/FileBasedReporterSettings.cs b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs
similarity index 88%
rename from src/ModVerify/Reporting/FileBasedReporterSettings.cs
rename to src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs
index 6616258..fef047c 100644
--- a/src/ModVerify/Reporting/FileBasedReporterSettings.cs
+++ b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs
@@ -1,6 +1,6 @@
using System;
-namespace AET.ModVerify.Reporting;
+namespace AET.ModVerify.Reporting.Settings;
public record FileBasedReporterSettings : VerificationReportSettings
{
diff --git a/src/ModVerify/Reporting/GlobalVerificationReportSettings.cs b/src/ModVerify/Reporting/Settings/GlobalVerificationReportSettings.cs
similarity index 82%
rename from src/ModVerify/Reporting/GlobalVerificationReportSettings.cs
rename to src/ModVerify/Reporting/Settings/GlobalVerificationReportSettings.cs
index 3982d4d..fe19d68 100644
--- a/src/ModVerify/Reporting/GlobalVerificationReportSettings.cs
+++ b/src/ModVerify/Reporting/Settings/GlobalVerificationReportSettings.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace AET.ModVerify.Reporting;
+namespace AET.ModVerify.Reporting.Settings;
public record GlobalVerificationReportSettings : VerificationReportSettings
{
diff --git a/src/ModVerify/Reporting/VerificationReportSettings.cs b/src/ModVerify/Reporting/Settings/VerificationReportSettings.cs
similarity index 76%
rename from src/ModVerify/Reporting/VerificationReportSettings.cs
rename to src/ModVerify/Reporting/Settings/VerificationReportSettings.cs
index fc02024..6be2905 100644
--- a/src/ModVerify/Reporting/VerificationReportSettings.cs
+++ b/src/ModVerify/Reporting/Settings/VerificationReportSettings.cs
@@ -1,4 +1,4 @@
-namespace AET.ModVerify.Reporting;
+namespace AET.ModVerify.Reporting.Settings;
public record VerificationReportSettings
{
diff --git a/src/ModVerify/Reporting/VerificationReportBroker.cs b/src/ModVerify/Reporting/VerificationReportBroker.cs
index f9c3c68..ea4d127 100644
--- a/src/ModVerify/Reporting/VerificationReportBroker.cs
+++ b/src/ModVerify/Reporting/VerificationReportBroker.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using AET.ModVerify.Reporting.Settings;
using AET.ModVerify.Verifiers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
diff --git a/src/ModVerify/Settings/GameVerifySettings.cs b/src/ModVerify/Settings/GameVerifySettings.cs
index 488bfe4..5e67f46 100644
--- a/src/ModVerify/Settings/GameVerifySettings.cs
+++ b/src/ModVerify/Settings/GameVerifySettings.cs
@@ -1,4 +1,4 @@
-using AET.ModVerify.Reporting;
+using AET.ModVerify.Reporting.Settings;
namespace AET.ModVerify.Settings;
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/VerificationProvider.cs b/src/ModVerify/VerificationProvider.cs
index a1800dc..ca0f42d 100644
--- a/src/ModVerify/VerificationProvider.cs
+++ b/src/ModVerify/VerificationProvider.cs
@@ -13,5 +13,6 @@ public IEnumerable GetAllDefaultVerifiers(IGameDatabase databa
yield return new ReferencedModelsVerifier(database, settings, serviceProvider);
yield return new DuplicateNameFinder(database, settings, serviceProvider);
yield return new AudioFilesVerifier(database, settings, serviceProvider);
+ yield return new ReferencedTexturesVerifier(database, settings, serviceProvider);
}
}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/AudioFilesVerifier.cs b/src/ModVerify/Verifiers/AudioFilesVerifier.cs
index 1f4a3e9..e5ba70b 100644
--- a/src/ModVerify/Verifiers/AudioFilesVerifier.cs
+++ b/src/ModVerify/Verifiers/AudioFilesVerifier.cs
@@ -1,18 +1,22 @@
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.Audio.Sfx;
using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Engine.Language;
+using PG.StarWarsGame.Engine.Localization;
using PG.StarWarsGame.Files.MEG.Services.Builder.Normalization;
+
#if NETSTANDARD2_0
using AnakinRaW.CommonUtilities.FileSystem;
#endif
@@ -21,14 +25,21 @@ namespace AET.ModVerify.Verifiers;
public class AudioFilesVerifier : GameVerifierBase
{
- 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(IGameDatabase gameDatabase, GameVerifySettings settings, IServiceProvider serviceProvider)
+ : base(gameDatabase, settings, serviceProvider)
{
- _pathNormalizer = new(serviceProvider);
_hashingService = serviceProvider.GetRequiredService();
_fileSystem = serviceProvider.GetRequiredService();
_languageManager = serviceProvider.GetRequiredService()
@@ -41,67 +52,86 @@ protected override void RunVerification(CancellationToken token)
{
var visitedSamples = new HashSet();
var languagesToVerify = GetLanguagesToVerify().ToList();
- foreach (var sfxEvent in Database.SfxEvents.Entries)
+ foreach (var sfxEvent in Database.SfxGameManager.Entries)
{
foreach (var codedSample in sfxEvent.AllSamples)
{
- VerifySample(codedSample, sfxEvent, languagesToVerify, visitedSamples);
+ 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)
+ {
+ foreach (var language in languagesToVerify)
+ {
+ VerifySampleLocalized(sfxEvent, sampleNameBuffer, language, out var localized);
+ if (!localized)
+ return;
+ }
+ }
+ else
{
- var localizedSampleName = _languageManager.LocalizeFileName(normalizedSampleNameString, language, out var localized);
- VerifySample(localizedSampleName, sfxEvent);
-
- if (!localized)
- return;
+ VerifySample(sampleNameBuffer, sfxEvent);
}
}
- else
+ finally
{
- VerifySample(normalizedSampleNameString, sfxEvent);
+ if (pooledBuffer is not null)
+ ArrayPool.Shared.Return(pooledBuffer);
}
+
}
- private void VerifySample(string sample, SfxEvent sfxEvent)
+ 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);
+ }
+ finally
+ {
+ if (pooledBuffer is not null)
+ ArrayPool.Shared.Return(pooledBuffer);
+ }
+ }
+
+ 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.",
+ VerifierErrorCodes.SampleNotFound,
+ $"Audio file '{sampleString}' could not be found.",
VerificationSeverity.Error,
- sample));
+ sampleString));
return;
}
using var binaryReader = new BinaryReader(sampleStream);
@@ -121,42 +151,46 @@ private void VerifySample(string sample, SfxEvent sfxEvent)
if (format != WaveFormats.PCM)
{
+ var sampleString = sample.ToString();
AddError(VerificationError.Create(
this,
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,
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,
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,
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));
}
}
diff --git a/src/ModVerify/Verifiers/DatabaseError/GameDatabaseInitializationErrorCollector.cs b/src/ModVerify/Verifiers/DatabaseError/GameDatabaseInitializationErrorCollector.cs
new file mode 100644
index 0000000..e8543d6
--- /dev/null
+++ b/src/ModVerify/Verifiers/DatabaseError/GameDatabaseInitializationErrorCollector.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using AET.ModVerify.Reporting;
+using AET.ModVerify.Settings;
+using PG.StarWarsGame.Engine.Database;
+
+namespace AET.ModVerify.Verifiers;
+
+internal sealed class GameDatabaseInitializationErrorCollector(
+ IDatabaseErrorCollection errorCollection,
+ IGameDatabase gameDatabase,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider) : GameVerifierBase(gameDatabase, settings, serviceProvider)
+{
+ public override string FriendlyName => "Reporting Game Initialization Errors";
+
+ protected override void RunVerification(CancellationToken token)
+ {
+ AddErrors(new InitializationErrorReporter(Repository, Services).GetErrors(errorCollection.InitializationErrors));
+ AddErrors(new XmlParseErrorReporter(Repository, Services).GetErrors(errorCollection.XmlErrors));
+ }
+
+ private void AddErrors(IEnumerable errors)
+ {
+ foreach (var error in errors)
+ AddError(error);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/DatabaseError/InitializationErrorReporter.cs b/src/ModVerify/Verifiers/DatabaseError/InitializationErrorReporter.cs
new file mode 100644
index 0000000..e2e2e10
--- /dev/null
+++ b/src/ModVerify/Verifiers/DatabaseError/InitializationErrorReporter.cs
@@ -0,0 +1,16 @@
+using System;
+using AET.ModVerify.Reporting;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
+
+namespace AET.ModVerify.Verifiers;
+
+internal sealed class InitializationErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) : InitializationErrorReporterBase(gameRepository, serviceProvider)
+{
+ public override string Name => "InitializationErrors";
+
+ protected override void CreateError(InitializationError error, out ErrorData errorData)
+ {
+ errorData = new ErrorData("INIT00", error.Message, [error.GameManager], VerificationSeverity.Critical);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/DatabaseError/InitializationErrorReporterBase.cs b/src/ModVerify/Verifiers/DatabaseError/InitializationErrorReporterBase.cs
new file mode 100644
index 0000000..df3e22e
--- /dev/null
+++ b/src/ModVerify/Verifiers/DatabaseError/InitializationErrorReporterBase.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using AET.ModVerify.Reporting;
+using AnakinRaW.CommonUtilities;
+using PG.StarWarsGame.Engine.IO.Repositories;
+
+namespace AET.ModVerify.Verifiers;
+
+internal abstract class InitializationErrorReporterBase(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)
+ {
+ CreateError(error, out var data);
+ yield return new VerificationError(data.Identifier, data.Message, Name, data.Assets, data.Severity);
+ }
+ }
+
+ protected abstract void CreateError(T error, out ErrorData errorData);
+
+ protected readonly ref struct ErrorData
+ {
+ public string Identifier { get; }
+ public string Message { get; }
+ public IEnumerable Assets { get; }
+ public VerificationSeverity Severity { get; }
+
+ public ErrorData(string identifier, string message, IEnumerable assets, VerificationSeverity severity)
+ {
+ ThrowHelper.ThrowIfNullOrEmpty(identifier);
+ ThrowHelper.ThrowIfNullOrEmpty(message);
+ Identifier = identifier;
+ Message = message;
+ Assets = assets;
+ Severity = severity;
+ }
+
+ public ErrorData(string identifier, string message, VerificationSeverity severity) : this(identifier, message, [], severity)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/XmlParseErrorCollector.cs b/src/ModVerify/Verifiers/DatabaseError/XmlParseErrorReporter.cs
similarity index 54%
rename from src/ModVerify/Verifiers/XmlParseErrorCollector.cs
rename to src/ModVerify/Verifiers/DatabaseError/XmlParseErrorReporter.cs
index 0819eb8..9c0bdc9 100644
--- a/src/ModVerify/Verifiers/XmlParseErrorCollector.cs
+++ b/src/ModVerify/Verifiers/DatabaseError/XmlParseErrorReporter.cs
@@ -1,39 +1,36 @@
using System;
using System.Collections.Generic;
-using System.Threading;
+using System.IO.Abstractions;
using AET.ModVerify.Reporting;
-using AET.ModVerify.Settings;
-using PG.StarWarsGame.Engine.Database;
+using AET.ModVerify.Utilities;
+using Microsoft.Extensions.DependencyInjection;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
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)
+internal sealed class XmlParseErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) :
+ InitializationErrorReporterBase(gameRepository, serviceProvider)
{
- public override string FriendlyName => "XML Parsing Errors";
+ private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
- protected override void RunVerification(CancellationToken token)
+ public override string Name => "XMLError";
+
+ protected override void CreateError(XmlError error, out ErrorData errorData)
{
- foreach (var xmlError in xmlErrors)
- AddError(ConvertXmlToVerificationError(xmlError));
- }
+ var id = GetIdFromError(error.ErrorKind);
+ var severity = GetSeverityFromError(error.ErrorKind);
- private VerificationError ConvertXmlToVerificationError(XmlParseErrorEventArgs xmlError)
- {
- var id = GetIdFromError(xmlError.ErrorKind);
- var severity = GetSeverityFromError(xmlError.ErrorKind);
+ var strippedFileName = _fileSystem.Path
+ .GetGameStrippedPath(GameRepository.Path.AsSpan(), error.FileLocation.XmlFile.ToUpperInvariant().AsSpan()).ToString();
var assets = new List
{
- GetGameStrippedPath(xmlError.File.ToUpperInvariant())
+ strippedFileName
};
- var xmlElement = xmlError.Element;
+ var xmlElement = error.Element;
if (xmlElement is not null)
{
@@ -49,11 +46,19 @@ private VerificationError ConvertXmlToVerificationError(XmlParseErrorEventArgs x
}
- return VerificationError.Create(this, id, xmlError.Message, severity, assets);
+ var errorMessage = CreateErrorMessage(error, strippedFileName);
+ errorData = new ErrorData(id, errorMessage, assets, 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 VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErrorKind)
+ private static VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErrorKind)
{
return xmlErrorErrorKind switch
{
@@ -65,11 +70,13 @@ private VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErro
XmlParseErrorKind.MissingReference => VerificationSeverity.Error,
XmlParseErrorKind.TooLongData => VerificationSeverity.Warning,
XmlParseErrorKind.DataBeforeHeader => VerificationSeverity.Information,
+ XmlParseErrorKind.MissingNode => VerificationSeverity.Critical,
+ XmlParseErrorKind.UnknownNode => VerificationSeverity.Information,
_ => VerificationSeverity.Warning
};
}
- private string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind)
+ private static string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind)
{
return xmlErrorErrorKind switch
{
@@ -82,6 +89,8 @@ private string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind)
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)
};
}
diff --git a/src/ModVerify/Verifiers/DuplicateNameFinder.cs b/src/ModVerify/Verifiers/DuplicateNameFinder.cs
index 14d2e67..0a60b50 100644
--- a/src/ModVerify/Verifiers/DuplicateNameFinder.cs
+++ b/src/ModVerify/Verifiers/DuplicateNameFinder.cs
@@ -5,7 +5,7 @@
using AET.ModVerify.Settings;
using AnakinRaW.CommonUtilities.Collections;
using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Engine.DataTypes;
+using PG.StarWarsGame.Engine.Xml;
namespace AET.ModVerify.Verifiers;
@@ -19,15 +19,15 @@ public sealed class DuplicateNameFinder(
protected override void RunVerification(CancellationToken token)
{
- CheckDatabaseForDuplicates(Database.GameObjects, "GameObject");
- CheckDatabaseForDuplicates(Database.SfxEvents, "SFXEvent");
+ CheckDatabaseForDuplicates(Database.GameObjectTypeManager, "GameObject");
+ CheckDatabaseForDuplicates(Database.SfxGameManager, "SFXEvent");
}
- private void CheckDatabaseForDuplicates(IXmlDatabase database, string databaseName) where T : XmlObject
+ private void CheckDatabaseForDuplicates(IGameManager gameManager, string databaseName) where T : NamedXmlObject
{
- foreach (var key in database.EntryKeys)
+ foreach (var key in gameManager.EntryKeys)
{
- var entries = database.GetEntries(key);
+ var entries = gameManager.GetEntries(key);
if (entries.Count > 1)
{
var entryNames = entries.Select(x => x.Name);
@@ -41,7 +41,7 @@ private void CheckDatabaseForDuplicates(IXmlDatabase database, string data
}
}
- private string CreateDuplicateErrorMessage(string databaseName, ReadOnlyFrugalList entries) where T : XmlObject
+ private string CreateDuplicateErrorMessage(string databaseName, ReadOnlyFrugalList entries) where T : NamedXmlObject
{
var firstEntry = entries.First();
diff --git a/src/ModVerify/Verifiers/GameVerifierBase.cs b/src/ModVerify/Verifiers/GameVerifierBase.cs
index a1f10cd..768236c 100644
--- a/src/ModVerify/Verifiers/GameVerifierBase.cs
+++ b/src/ModVerify/Verifiers/GameVerifierBase.cs
@@ -1,16 +1,14 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO.Abstractions;
using System.Threading;
using AET.ModVerify.Reporting;
using AET.ModVerify.Settings;
-using AnakinRaW.CommonUtilities.FileSystem;
using AnakinRaW.CommonUtilities.SimplePipeline.Steps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PG.StarWarsGame.Engine.Database;
-using PG.StarWarsGame.Engine.Repositories;
+using PG.StarWarsGame.Engine.IO.Repositories;
namespace AET.ModVerify.Verifiers;
@@ -70,18 +68,4 @@ protected void GuardedVerify(Action action, Predicate exceptionFilter
exceptionHandler(e);
}
}
-
- protected string GetGameStrippedPath(string path)
- {
- if (!FileSystem.Path.IsPathFullyQualified(path))
- return path;
-
- if (path.Length <= Repository.Path.Length)
- return path;
-
- if (path.StartsWith(Repository.Path, StringComparison.OrdinalIgnoreCase))
- return path.Substring(Repository.Path.Length);
-
- return path;
- }
}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs
index 8a6e7ba..bfb9f7c 100644
--- a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs
+++ b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs
@@ -5,9 +5,8 @@
using System.Threading;
using AET.ModVerify.Reporting;
using AET.ModVerify.Settings;
+using AET.ModVerify.Utilities;
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;
@@ -16,7 +15,8 @@
using PG.StarWarsGame.Files.ALO.Services;
using PG.StarWarsGame.Files.ChunkFiles.Data;
using AnakinRaW.CommonUtilities.FileSystem;
-using System.Reflection;
+using PG.StarWarsGame.Files;
+using PG.StarWarsGame.Files.Binary;
namespace AET.ModVerify.Verifiers;
@@ -34,7 +34,7 @@ public sealed class ReferencedModelsVerifier(
protected override void RunVerification(CancellationToken token)
{
- var aloQueue = new Queue(Database.GameObjects.Entries
+ var aloQueue = new Queue(Database.GameObjectTypeManager.Entries
.SelectMany(x => x.Models)
.Concat(FocHardcodedConstants.HardcodedModels));
@@ -85,7 +85,7 @@ private void VerifyModelOrParticle(Stream modelStream, Queue workingQueu
}
catch (BinaryCorruptedException e)
{
- var aloFile = GetGameStrippedPath(modelStream.GetFilePath());
+ var aloFile = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), modelStream.GetFilePath().AsSpan()).ToString();
var message = $"{aloFile} is corrupted: {e.Message}";
AddError(VerificationError.Create(this, VerifierErrorCodes.ModelBroken, message, VerificationSeverity.Critical, aloFile));
}
@@ -99,7 +99,7 @@ private void VerifyParticle(IAloParticleFile file)
e => e is ArgumentException,
_ =>
{
- var modelFilePath = GetGameStrippedPath(file.FilePath);
+ var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
AddError(VerificationError.Create(
this,
VerifierErrorCodes.InvalidTexture,
@@ -116,7 +116,7 @@ private void VerifyParticle(IAloParticleFile file)
if (!fileName.Equals(name, StringComparison.OrdinalIgnoreCase))
{
- var modelFilePath = GetGameStrippedPath(file.FilePath);
+ var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
AddError(VerificationError.Create(
this,
VerifierErrorCodes.InvalidParticleName,
@@ -135,7 +135,8 @@ private void VerifyModel(IAloModelFile file, Queue workingQueue)
e => e is ArgumentException,
_ =>
{
- var modelFilePath = GetGameStrippedPath(file.FilePath);
+ var modelFilePath =
+ FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
AddError(VerificationError.Create(
this,
VerifierErrorCodes.InvalidTexture,
@@ -151,13 +152,14 @@ private void VerifyModel(IAloModelFile file, Queue workingQueue)
e => e is ArgumentException,
_ =>
{
- var modelFilePath = GetGameStrippedPath(file.FilePath);
+ var shaderPath =
+ FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
AddError(VerificationError.Create(
this,
VerifierErrorCodes.InvalidShader,
- $"Invalid texture file name '{shader}' in model '{modelFilePath}'",
+ $"Invalid texture file name '{shader}' in model '{shaderPath}'",
VerificationSeverity.Error,
- shader, modelFilePath));
+ shader, shaderPath));
});
}
@@ -168,13 +170,14 @@ private void VerifyModel(IAloModelFile file, Queue workingQueue)
e => e is ArgumentException,
_ =>
{
- var modelFilePath = GetGameStrippedPath(file.FilePath);
+ var proxyPath = FileSystem.Path
+ .GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString();
AddError(VerificationError.Create(
this,
VerifierErrorCodes.InvalidProxy,
- $"Invalid proxy file name '{proxy}' in model '{modelFilePath}'",
+ $"Invalid proxy file name '{proxy}' in model '{proxyPath}'",
VerificationSeverity.Error,
- proxy, modelFilePath));
+ proxy, proxyPath));
});
}
}
@@ -186,7 +189,8 @@ private void VerifyTextureExists(IPetroglyphFileHolder().ToArray();
+
+ private void VerifyGuiTextures(ISet visitedTextures)
+ {
+ VerifyMegaTexturesExist();
+
+ var components = new List
+ {
+ DefaultComponentIdentifier
+ };
+ components.AddRange(Database.GuiDialogManager.Components);
+
+ foreach (var component in components)
+ VerifyGuiComponentTexturesExist(component, visitedTextures);
+
+ }
+
+ private void VerifyMegaTexturesExist()
+ {
+ var megaTextureName = Database.GuiDialogManager.GuiDialogsXml?.TextureData.MegaTexture;
+ if (Database.GuiDialogManager.MtdFile is null)
+ {
+ var mtdFileName = megaTextureName ?? "<>";
+ VerificationError.Create(this, MtdNotFound, $"MtdFile '{mtdFileName}.mtd' could not be found",
+ VerificationSeverity.Critical, mtdFileName);
+ }
+
+
+ if (megaTextureName is not null)
+ {
+ var megaTextureFileName = $"{megaTextureName}.tga";
+
+ if (!Repository.TextureRepository.FileExists(megaTextureFileName))
+ {
+ VerificationError.Create(this, TexutreNotFound, $"Could not find texture '{megaTextureFileName}' could not be found",
+ VerificationSeverity.Error, megaTextureFileName);
+ }
+ }
+
+
+ var compressedMegaTextureName = Database.GuiDialogManager.GuiDialogsXml?.TextureData.CompressedMegaTexture;
+ if (compressedMegaTextureName is not null)
+ {
+ var compressedMegaTextureFieName = $"{compressedMegaTextureName}.dds";
+
+ if (!Repository.TextureRepository.FileExists(compressedMegaTextureFieName))
+ {
+ VerificationError.Create(this, TexutreNotFound, $"Could not find texture '{compressedMegaTextureFieName}' could not be found",
+ VerificationSeverity.Error, compressedMegaTextureFieName);
+ }
+ }
+ }
+
+ private void VerifyGuiComponentTexturesExist(string component, ISet visitedTextures)
+ {
+ 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 (!visitedTextures.Add(texture.Texture))
+ {
+ // 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 (!Database.GuiDialogManager.TextureExists(
+ texture,
+ out var origin,
+ out var isNone,
+ middleButtonInRepoMode)
+ && !isNone)
+ {
+
+ if (origin == GuiTextureOrigin.MegaTexture && texture.Texture.Length > MtdFileConstants.MaxFileNameSize)
+ {
+ AddError(VerificationError.Create(this, FileNameTooLong,
+ $"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(this, TexutreNotFound,
+ message, VerificationSeverity.Error,
+ texture.Texture, component, origin.ToString()));
+ }
+ }
+
+ 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 Database.GuiDialogManager.DefaultTextureEntries;
+ }
+ return Database.GuiDialogManager.GetTextureEntries(component, out defined);
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/ReferencedTexturesVerifier.cs b/src/ModVerify/Verifiers/ReferencedTexturesVerifier.cs
new file mode 100644
index 0000000..58e600c
--- /dev/null
+++ b/src/ModVerify/Verifiers/ReferencedTexturesVerifier.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using AET.ModVerify.Settings;
+using PG.StarWarsGame.Engine.Database;
+
+namespace AET.ModVerify.Verifiers;
+public sealed partial class ReferencedTexturesVerifier(
+ IGameDatabase gameDatabase,
+ GameVerifySettings settings,
+ IServiceProvider serviceProvider)
+ :
+ GameVerifierBase(gameDatabase, settings, serviceProvider)
+{
+ public const string MtdNotFound = "TEX00";
+ public const string TexutreNotFound = "TEX01";
+ public const string FileNameTooLong = "PAT00";
+
+ protected override void RunVerification(CancellationToken token)
+ {
+ var textures = new HashSet(StringComparer.OrdinalIgnoreCase);
+ try
+ {
+ VerifyGuiTextures(textures);
+ }
+ finally
+ {
+ textures.Clear();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/ModVerify/Verifiers/VerifierErrorCodes.cs b/src/ModVerify/Verifiers/VerifierErrorCodes.cs
index d4b3fa0..6a68e91 100644
--- a/src/ModVerify/Verifiers/VerifierErrorCodes.cs
+++ b/src/ModVerify/Verifiers/VerifierErrorCodes.cs
@@ -32,4 +32,6 @@ public static class VerifierErrorCodes
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/VerifyGamePipeline.cs b/src/ModVerify/VerifyGamePipeline.cs
index e546447..6eda635 100644
--- a/src/ModVerify/VerifyGamePipeline.cs
+++ b/src/ModVerify/VerifyGamePipeline.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -13,8 +12,6 @@
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;
@@ -23,14 +20,12 @@ public abstract class VerifyGamePipeline : Pipeline
private readonly List _verificationSteps = new();
private readonly GameEngineType _targetType;
private readonly GameLocations _gameLocations;
- private readonly ParallelRunner _verifyRunner;
+ private readonly ParallelStepRunner _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)
{
@@ -41,10 +36,9 @@ protected VerifyGamePipeline(GameEngineType targetType, GameLocations gameLocati
if (settings.ParallelVerifiers is < 0 or > 64)
throw new ArgumentException("Settings has invalid parallel worker number.", nameof(settings));
- _verifyRunner = new ParallelRunner(settings.ParallelVerifiers, serviceProvider);
+ _verifyRunner = new ParallelStepRunner(settings.ParallelVerifiers, serviceProvider);
}
-
protected sealed override Task PrepareCoreAsync()
{
_verificationSteps.Clear();
@@ -58,21 +52,18 @@ protected sealed override async Task RunCoreAsync(CancellationToken token)
{
var databaseService = ServiceProvider.GetRequiredService();
- IGameDatabase database;
- try
- {
- databaseService.XmlParseError += OnXmlParseError;
- database = await databaseService.CreateDatabaseAsync(_targetType, _gameLocations, token);
- }
- finally
+ var initializationErrorListener = new ConcurrentGameDatabaseErrorListener();
+ var initOptions = new GameInitializationOptions
{
- databaseService.XmlParseError -= OnXmlParseError;
- databaseService.Dispose();
- }
+ Locations = _gameLocations,
+ TargetEngineType = _targetType,
+ ErrorListener = initializationErrorListener
+
+ };
+ var database = await databaseService.InitializeGameAsync(initOptions, token);
+
+ AddStep(new GameDatabaseInitializationErrorCollector(initializationErrorListener, database, Settings, ServiceProvider));
-
- AddStep(new XmlParseErrorCollector(_xmlParseErrors, database, Settings, ServiceProvider));
-
foreach (var gameVerificationStep in CreateVerificationSteps(database))
AddStep(gameVerificationStep);
@@ -113,9 +104,4 @@ 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/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs
new file mode 100644
index 0000000..f054f1c
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs
@@ -0,0 +1,5 @@
+using PG.StarWarsGame.Engine.Database;
+
+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..0b4c407
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine.Database;
+using PG.StarWarsGame.Engine.Database.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, DatabaseErrorListenerWrapper errorListener, IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorListener, 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 = ServiceProvider.GetRequiredService();
+ contentParser.XmlParseError += OnParseError;
+ try
+ {
+ await Task.Run(() => contentParser.ParseEntriesFromContainerXml(
+ "DATA\\XML\\SFXEventFiles.XML",
+ ErrorListener,
+ GameRepository,
+ "DATA\\XML",
+ NamedEntries,
+ VerifyFilePathLength),
+ token);
+ }
+ finally
+ {
+ contentParser.XmlParseError -= OnParseError;
+ }
+ }
+
+ private void OnParseError(object sender, XmlContainerParserErrorEventArgs e)
+ {
+ if (e.IsContainer || e.IsError)
+ {
+ e.Continue = false;
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = GetMessage(e)
+ });
+ }
+ }
+
+ private static string GetMessage(XmlContainerParserErrorEventArgs errorEventArgs)
+ {
+ if (errorEventArgs.IsError)
+ 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)
+ {
+ ErrorListener.OnInitializationError(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/CommandBarComponentType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs
new file mode 100644
index 0000000..793eb44
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs
@@ -0,0 +1,15 @@
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+public enum CommandBarComponentType
+{
+ Shell,
+ Icon,
+ Button,
+ Text,
+ TextButton,
+ Model,
+ Bar,
+ Select,
+ Count,
+ None,
+}
\ 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..d53808e
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.Commons.Collections;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.CommandBar.Xml;
+using PG.StarWarsGame.Engine.Database;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Engine.Xml.Parsers;
+
+namespace PG.StarWarsGame.Engine.CommandBar;
+
+public interface ICommandBarGameManager : IGameManager
+{
+}
+
+internal class CommandBarGameManager(
+ GameRepository repository,
+ DatabaseErrorListenerWrapper errorListener,
+ IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorListener, serviceProvider), ICommandBarGameManager
+{
+ protected override async Task InitializeCoreAsync(CancellationToken token)
+ {
+ Logger?.LogInformation("PCreating command bar components...");
+
+ var contentParser = ServiceProvider.GetRequiredService();
+ contentParser.XmlParseError += OnParseError;
+
+ var parsedCommandBarComponents = new ValueListDictionary();
+
+ try
+ {
+ await Task.Run(() => contentParser.ParseEntriesFromContainerXml(
+ "DATA\\XML\\CommandBarComponentFiles.XML",
+ ErrorListener,
+ GameRepository,
+ ".\\DATA\\XML",
+ parsedCommandBarComponents,
+ VerifyFilePathLength),
+ token);
+ }
+ finally
+ {
+ contentParser.XmlParseError -= OnParseError;
+ }
+ }
+
+ private void OnParseError(object sender, XmlContainerParserErrorEventArgs e)
+ {
+ if (e.IsContainer || e.IsError)
+ {
+ e.Continue = false;
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = GetMessage(e)
+ });
+ }
+ }
+
+ private static string GetMessage(XmlContainerParserErrorEventArgs errorEventArgs)
+ {
+ if (errorEventArgs.IsError)
+ return $"Error while parsing CommandBar XML file '{errorEventArgs.File}': {errorEventArgs.Exception.Message}";
+ return "Could not find CommandBarComponentFiles.xml";
+ }
+
+ private void VerifyFilePathLength(string filePath)
+ {
+ if (filePath.Length > PGConstants.MaxCommandBarDatabaseFileName)
+ {
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = $"CommandBar file '{filePath}' is longer than {PGConstants.MaxCommandBarDatabaseFileName} characters."
+ });
+ }
+ }
+}
+
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Xml/CommandBarComponentData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Xml/CommandBarComponentData.cs
new file mode 100644
index 0000000..fb06793
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Xml/CommandBarComponentData.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Numerics;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.XML;
+
+namespace PG.StarWarsGame.Engine.CommandBar.Xml;
+
+public sealed class CommandBarComponentData(string name, Crc32 crc, XmlLocationInfo location) : NamedXmlObject(name, crc, location)
+{
+ public const float DefaultScale = 1.0f;
+ public const float DefaultBlinkRate = 0.2f;
+ public const int DefaultBaseLayer = 2;
+ public static readonly Vector2 DefaultOffsetWidescreenValue = new(9.9999998e17f, 9.9999998e17f);
+
+ public IReadOnlyList SelectedTextureNames { get; internal set; } = [];
+ public IReadOnlyList BlankTextureNames { get; internal set; } = [];
+ public IReadOnlyList IconAlternateTextureNames { get; internal set; } = [];
+ public IReadOnlyList MouseOverTextureNames { get; internal set; } = [];
+ public IReadOnlyList BarTextureNames { get; internal set; } = [];
+ public IReadOnlyList BarOverlayNames { get; internal set; } = [];
+ public IReadOnlyList AlternateFontNames { get; internal set; } = [];
+ public IReadOnlyList TooltipTexts { get; internal set; } = [];
+ public IReadOnlyList LowerEffectTextureNames { get; internal set; } = [];
+ public IReadOnlyList UpperEffectTextureNames { get; internal set; } = [];
+ public IReadOnlyList OverlayTextureNames { get; internal set; } = [];
+ public IReadOnlyList Overlay2TextureNames { get; internal set; } = [];
+
+ public string? IconTextureName { get; internal set; }
+ public string? DisabledTextureName { get; internal set; }
+ public string? FlashTextureName { get; internal set; }
+ public string? BuildTextureName { get; internal set; }
+ public string? ModelName { get; internal set; }
+ public string? BoneName { get; internal set; }
+ public string? CursorTextureName { get; internal set; }
+ public string? FontName { get; internal set; }
+ public string? ClickSfx { get; internal set; }
+ public string? MouseOverSfx { get; internal set; }
+ public string? RightClickSfx { get; internal set; }
+ public string? Type { get; internal set; }
+ public string? Group { get; internal set; }
+ public string? AssociatedText { get; internal set; }
+
+ public bool DragAndDrop { get; internal set; }
+ public bool DragSelect { get; internal set; }
+ public bool Receptor { get; internal set; }
+ public bool Toggle { get; internal set; }
+ public bool Tab { get; internal set; }
+ public bool Hidden { get; internal set; }
+ public bool ClearColor { get; internal set; }
+ public bool Disabled { get; internal set; }
+ public bool SwapTexture { get; internal set; }
+ public bool DrawAdditive { get; internal set; }
+ public bool Editable { get; internal set; }
+ public bool TextOutline { get; internal set; }
+ public bool Stackable { get; internal set; }
+ public bool ModelOffsetX { get; internal set; }
+ public bool ModelOffsetY { get; internal set; }
+ public bool ScaleModelX { get; internal set; }
+ public bool ScaleModelY { get; internal set; }
+ public bool Collideable { get; internal set; }
+ public bool TextEmboss { get; internal set; }
+ public bool ShouldGhost { get; internal set; }
+ public bool GhostBaseOnly { get; internal set; }
+ public bool CrossFade { get; internal set; }
+ public bool LeftJustified { get; internal set; }
+ public bool RightJustified { get; internal set; }
+ public bool NoShell { get; internal set; }
+ public bool SnapDrag { get; internal set; }
+ public bool SnapLocation { get; internal set; }
+ public bool OffsetRender { get; internal set; }
+ public bool BlinkFade { get; internal set; }
+ public bool NoHiddenCollision { get; internal set; }
+ public bool ManualOffset { get; internal set; }
+ public bool SelectedAlpha { get; internal set; }
+ public bool PixelAlign { get; internal set; }
+ public bool CanDragStack { get; internal set; }
+ public bool CanAnimate { get; internal set; }
+ public bool LoopAnim { get; internal set; }
+ public bool SmoothBar { get; internal set; }
+ public bool OutlinedBar { get; internal set; }
+ public bool DragBack { get; internal set; }
+ public bool LowerEffectAdditive { get; internal set; }
+ public bool UpperEffectAdditive { get; internal set; }
+ public bool ClickShift { get; internal set; }
+ public bool TutorialScene { get; internal set; }
+ public bool DialogScene { get; internal set; }
+ public bool ShouldRenderAtDragPos { get; internal set; }
+ public bool DisableDarken { get; internal set; }
+ public bool AnimateBack { get; internal set; }
+ public bool AnimateUpperEffect { get; internal set; }
+
+ public int BaseLayer { get; internal set; } = DefaultBaseLayer;
+ public int MaxBarLevel { get; internal set; }
+
+ public uint MaxTextLength { get; internal set; }
+ public int FontPointSize { get; internal set; }
+
+ public float ScaleDuration { get; internal set; }
+ public float BlinkDuration { get; internal set; }
+ public float MaxTextWidth { get; internal set; }
+ public float BlinkRate { get; internal set; } = DefaultBlinkRate;
+ public float Scale { get; internal set; } = DefaultScale;
+ public float AnimFps { get; internal set; }
+
+ public Vector2 Size { get; internal set; }
+ public Vector2 TextOffset { get; internal set; }
+ public Vector2 TextOffset2 { get; internal set; }
+ public Vector2 Offset { get; internal set; }
+ public Vector2 DefaultOffset { get; internal set; }
+ public Vector2 DefaultOffsetWidescreen { get; internal set; } = DefaultOffsetWidescreenValue;
+ public Vector2 IconOffset { get; internal set; }
+ public Vector2 MouseOverOffset { get; internal set; }
+ public Vector2 DisabledOffset { get; internal set; }
+ public Vector2 BuildDialOffset { get; internal set; }
+ public Vector2 BuildDial2Offset { get; internal set; }
+ public Vector2 LowerEffectOffset { get; internal set; }
+ public Vector2 UpperEffectOffset { get; internal set; }
+ public Vector2 OverlayOffset { get; internal set; }
+ public Vector2 Overlay2Offset { get; internal set; }
+
+ internal override void CoerceValues()
+ {
+ base.CoerceValues();
+ if (AlternateFontNames.Count == 0)
+ return;
+ var newFontNames = new string[AlternateFontNames.Count];
+ for (var i = 0; i < AlternateFontNames.Count; i++)
+ {
+ var current = AlternateFontNames[i];
+
+ if (current.AsSpan().IndexOf('_') != -1)
+ newFontNames[i] = current.Replace('_', ' ');
+ else
+ newFontNames[i] = current;
+ }
+ AlternateFontNames = new ReadOnlyCollection(newFontNames);
+ }
+}
+
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameConstants.cs
deleted file mode 100644
index a2a4498..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameConstants.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace PG.StarWarsGame.Engine.DataTypes;
-
-public class GameConstants
-{
-
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs
deleted file mode 100644
index 6cfd4c0..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using PG.Commons.Hashing;
-using PG.StarWarsGame.Files.XML;
-
-namespace PG.StarWarsGame.Engine.DataTypes;
-
-public sealed class GameObject : XmlObject
-{
- internal GameObject(
- string type,
- string name,
- Crc32 nameCrc,
- GameObjectType estimatedType,
- IReadOnlyValueListDictionary properties,
- XmlLocationInfo location)
- : base(name, nameCrc, properties, location)
- {
- Type = type ?? throw new ArgumentNullException(nameof(type));
- EstimatedType = estimatedType;
- }
-
- public string Type { get; }
-
-
- public GameObjectType EstimatedType { get; }
-
- ///
- /// Gets all model files (including particles) the game object references.
- ///
- public ISet Models
- {
- get
- {
- var models = XmlProperties.AggregateValues
- (new HashSet
- {
- "Galactic_Model_Name",
- "Destroyed_Galactic_Model_Name",
- "Land_Model_Name",
- "Space_Model_Name",
- "Model_Name",
- "Tactical_Model_Name",
- "Galactic_Fleet_Override_Model_Name",
- "GUI_Model_Name",
- "GUI_Model",
- // This can either be a model or a unit reference
- "Land_Model_Anim_Override_Name",
- "xxxSpace_Model_Name",
- "Damaged_Smoke_Asset_Name"
- }, v => v.EndsWith(".alo", StringComparison.OrdinalIgnoreCase),
- ValueListDictionaryExtensions.AggregateStrategy.LastValuePerKey);
-
- var terrainMappedModels = LandTerrainModelMapping?.Select(x => x.Model);
- if (terrainMappedModels is null)
- return new HashSet(models, StringComparer.OrdinalIgnoreCase);
-
- return new HashSet(models.Concat(terrainMappedModels), StringComparer.OrdinalIgnoreCase);
- }
- }
-
- public IList<(string Terrain, string Model)>? LandTerrainModelMapping =>
- GetLastPropertyOrDefault>("Land_Terrain_Model_Mapping");
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs
deleted file mode 100644
index 9920449..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs
+++ /dev/null
@@ -1,260 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using PG.Commons.Hashing;
-using PG.StarWarsGame.Engine.Xml.Tags;
-using PG.StarWarsGame.Files.XML;
-
-namespace PG.StarWarsGame.Engine.DataTypes;
-
-public sealed class SfxEvent : XmlObject
-{
- 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;
-
- private SfxEvent? _preset;
- private string? _presetName;
- private string? _overlapTestName;
- private string? _chainedSfxEvent;
- private IReadOnlyList? _textIds;
- private IReadOnlyList? _preSamples;
- private IReadOnlyList? _samples;
- private IReadOnlyList? _postSamples;
- private bool? _isPreset;
- private bool? _is2D;
- private bool? _is3D;
- private bool? _isGui;
- private bool? _isHudVo;
- private bool? _isUnitResponseVo;
- private bool? _isAmbientVo;
- private bool? _isLocalized;
- private bool? _playSequentially;
- private bool? _killsPreviousObjectsSfx;
- private byte? _priority;
- private byte? _probability;
- private sbyte? _playCount;
- private sbyte? _maxInstances;
- private uint? _minPredelay;
- private uint? _maxPredelay;
- private uint? _minPostdelay;
- private uint? _maxPostdelay;
- private float? _loopFadeInSeconds;
- private float? _loopFadeOutSeconds;
- private float? _volumeSaturationDistance;
-
- private static readonly Func PriorityCoercion = priority =>
- {
- if (!priority.HasValue)
- return DefaultPriority;
- if (priority < MinPriorityValue)
- return MinPriorityValue;
- if (priority > MaxPriorityValue)
- return MaxPriorityValue;
- return priority;
- };
-
- private static readonly Func MaxInstancesCoercion = maxInstances =>
- {
- if (!maxInstances.HasValue)
- return DefaultMaxInstances;
- if (maxInstances < MinMaxInstances)
- return MinMaxInstances;
- return maxInstances;
- };
-
- private static readonly Func ProbabilityCoercion = probability =>
- {
- if (!probability.HasValue)
- return DefaultProbability;
- if (probability > MaxProbability)
- return MaxProbability;
- return probability;
- };
-
- private static readonly Func PlayCountCoercion = playCount =>
- {
- if (!playCount.HasValue)
- return DefaultPlayCount;
- if (playCount < InfinitivePlayCount)
- return InfinitivePlayCount;
- return playCount;
- };
-
- private static readonly Func LoopAndSaturationCoercion = loopSeconds =>
- {
- if (!loopSeconds.HasValue)
- return DefaultVolumeSaturationDistance;
- if (loopSeconds < MinLoopSeconds)
- return MinLoopSeconds;
- return loopSeconds;
- };
-
- public bool IsPreset => LazyInitValue(ref _isPreset, SfxEventXmlTags.IsPreset, false)!.Value;
-
- public bool Is3D => LazyInitValue(ref _is3D, SfxEventXmlTags.Is3D, DefaultIs3d)!.Value;
-
- public bool Is2D => LazyInitValue(ref _is2D, SfxEventXmlTags.Is2D, false)!.Value;
-
- public bool IsGui => LazyInitValue(ref _isGui, SfxEventXmlTags.IsGui, false)!.Value;
-
- public bool IsHudVo => LazyInitValue(ref _isHudVo, SfxEventXmlTags.IsHudVo, false)!.Value;
-
- public bool IsUnitResponseVo => LazyInitValue(ref _isUnitResponseVo, SfxEventXmlTags.IsUnitResponseVo, false)!.Value;
-
- public bool IsAmbientVo => LazyInitValue(ref _isAmbientVo, SfxEventXmlTags.IsAmbientVo, false)!.Value;
-
- public bool IsLocalized => LazyInitValue(ref _isLocalized, SfxEventXmlTags.Localize, false)!.Value;
-
- public SfxEvent? Preset => LazyInitValue(ref _preset, SfxEventXmlTags.PresetXRef, null);
-
- public string? UsePresetName => LazyInitValue(ref _presetName, SfxEventXmlTags.UsePreset, null);
-
- public bool PlaySequentially => LazyInitValue(ref _playSequentially, SfxEventXmlTags.PlaySequentially, false)!.Value;
-
- public IEnumerable AllSamples => PreSamples.Concat(Samples).Concat(PostSamples);
-
- public IReadOnlyList PreSamples => LazyInitValue(ref _preSamples, SfxEventXmlTags.PreSamples, Array.Empty());
-
- public IReadOnlyList Samples => LazyInitValue(ref _samples, SfxEventXmlTags.Samples, Array.Empty());
-
- public IReadOnlyList PostSamples => LazyInitValue(ref _postSamples, SfxEventXmlTags.PostSamples, Array.Empty());
-
- public IReadOnlyList LocalizedTextIDs => LazyInitValue(ref _textIds, SfxEventXmlTags.TextID, Array.Empty());
-
- public byte Priority => LazyInitValue(ref _priority, SfxEventXmlTags.Priority, DefaultPriority, PriorityCoercion)!.Value;
-
- public byte Probability => LazyInitValue(ref _probability, SfxEventXmlTags.Probability, DefaultProbability, ProbabilityCoercion)!.Value;
-
- public sbyte PlayCount => LazyInitValue(ref _playCount, SfxEventXmlTags.PlayCount, DefaultPlayCount, PlayCountCoercion)!.Value;
-
- public float LoopFadeInSeconds => LazyInitValue(ref _loopFadeInSeconds, SfxEventXmlTags.LoopFadeInSeconds, 0f, LoopAndSaturationCoercion)!.Value;
-
- public float LoopFadeOutSeconds => LazyInitValue(ref _loopFadeOutSeconds, SfxEventXmlTags.LoopFadeOutSeconds, 0f, LoopAndSaturationCoercion)!.Value;
-
- public sbyte MaxInstances => LazyInitValue(ref _maxInstances, SfxEventXmlTags.MaxInstances, DefaultMaxInstances, MaxInstancesCoercion)!.Value;
-
- public uint MinPredelay => LazyInitValue(ref _minPredelay, SfxEventXmlTags.MinPredelay, 0u)!.Value;
-
- public uint MaxPredelay => LazyInitValue(ref _maxPredelay, SfxEventXmlTags.MaxPredelay, 0u)!.Value;
-
- public uint MinPostdelay => LazyInitValue(ref _minPostdelay, SfxEventXmlTags.MinPostdelay, 0u)!.Value;
-
- public uint MaxPostdelay => LazyInitValue(ref _maxPostdelay, SfxEventXmlTags.MaxPostdelay, 0u)!.Value;
-
- public float VolumeSaturationDistance => LazyInitValue(ref _volumeSaturationDistance,
- SfxEventXmlTags.VolumeSaturationDistance, DefaultVolumeSaturationDistance, LoopAndSaturationCoercion)!.Value;
-
- public bool KillsPreviousObjectsSfx => LazyInitValue(ref _killsPreviousObjectsSfx, SfxEventXmlTags.KillsPreviousObjectSFX, false)!.Value;
-
- public string? OverlapTestName => LazyInitValue(ref _overlapTestName, SfxEventXmlTags.OverlapTest, null);
-
- public string? ChainedSfxEventName => LazyInitValue(ref _chainedSfxEvent, SfxEventXmlTags.ChainedSfxEvent, null);
-
- public byte MinVolume { get; }
-
- public byte MaxVolume { get; }
-
- public byte MinPitch { get; }
-
- public byte MaxPitch { get; }
-
- public byte MinPan2D { get; }
-
- public byte MaxPan2D { get; }
-
- internal SfxEvent(string name, Crc32 nameCrc, IReadOnlyValueListDictionary properties,
- XmlLocationInfo location)
- : base(name, nameCrc, properties, location)
- {
- var minMaxVolume = GetMinMaxVolume(properties);
- MinVolume = minMaxVolume.min;
- MaxVolume = minMaxVolume.max;
-
- var minMaxPitch = GetMinMaxPitch(properties);
- MinPitch = minMaxPitch.min;
- MaxPitch = minMaxPitch.max;
-
- var minMaxPan = GetMinMaxPan2d(properties);
- MinPan2D = minMaxPan.min;
- MaxPan2D = minMaxPan.max;
- }
-
- private static (byte min, byte max) GetMinMaxVolume(IReadOnlyValueListDictionary properties)
- {
- return GetMinMaxValues(properties, SfxEventXmlTags.MinVolume, SfxEventXmlTags.MaxVolume, DefaultMinVolume,
- DefaultMaxVolume, null, MaxVolumeValue);
- }
-
- private static (byte min, byte max) GetMinMaxPitch(IReadOnlyValueListDictionary properties)
- {
- return GetMinMaxValues(properties, SfxEventXmlTags.MinPitch, SfxEventXmlTags.MaxPitch, DefaultMinPitch,
- DefaultMaxPitch, MinPitchValue, MaxPitchValue);
- }
-
- private static (byte min, byte max) GetMinMaxPan2d(IReadOnlyValueListDictionary properties)
- {
- return GetMinMaxValues(properties, SfxEventXmlTags.MinPan2D, SfxEventXmlTags.MaxPan2D, DefaultMinPan2d,
- DefaultMaxPan2d, null, MaxPan2dValue);
- }
-
-
- private static (byte min, byte max) GetMinMaxValues(
- IReadOnlyValueListDictionary properties,
- string minTag,
- string maxTag,
- byte defaultMin,
- byte defaultMax,
- byte? totalMinValue,
- byte? totalMaxValue)
- {
- var minValue = !properties.TryGetLastValue(minTag, out var minObj) ? defaultMin : Convert.ToByte(minObj);
- var maxValue = !properties.TryGetLastValue(maxTag, out var maxObj) ? defaultMax : Convert.ToByte(maxObj);
-
- if (totalMaxValue.HasValue)
- {
- if (minValue > totalMaxValue)
- minValue = totalMaxValue.Value;
- if (maxValue > totalMaxValue)
- maxValue = totalMaxValue.Value;
- }
-
- if (totalMinValue.HasValue)
- {
- if (minValue < totalMinValue)
- minValue = totalMinValue.Value;
- if (maxValue < totalMinValue)
- maxValue = totalMinValue.Value;
- }
-
- if (minValue > maxValue)
- minValue = maxValue;
-
- if (maxValue < minValue)
- maxValue = minValue;
-
- return (minValue, maxValue);
- }
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs
deleted file mode 100644
index b8161b1..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using PG.Commons.DataTypes;
-using PG.Commons.Hashing;
-using PG.StarWarsGame.Files.XML;
-
-namespace PG.StarWarsGame.Engine.DataTypes;
-
-public abstract class XmlObject(
- string name,
- Crc32 nameCrc,
- IReadOnlyValueListDictionary properties,
- XmlLocationInfo location)
- : IHasCrc32
-{
- public XmlLocationInfo Location { get; } = location;
-
- public Crc32 Crc32 { get; } = nameCrc;
-
- public IReadOnlyValueListDictionary XmlProperties { get; } = properties ?? throw new ArgumentNullException(nameof(properties));
-
- public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
-
- public T? GetLastPropertyOrDefault(string tagName, T? defaultValue = default)
- {
- if (!XmlProperties.TryGetLastValue(tagName, out var value))
- return defaultValue;
- return (T)value;
- }
-
- protected T LazyInitValue(ref T? field, string tag, T defaultValue, Func? coerceFunc = null)
- {
- if (field is null)
- {
- if (XmlProperties.TryGetLastValue(tag, out var value))
- {
- var tValue = (T)value;
- if (coerceFunc is not null)
- tValue = coerceFunc(tValue);
- field = tValue;
- }
- else
- field = defaultValue;
- }
-
- return field;
- }
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/DatabaseErrorListener.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/DatabaseErrorListener.cs
new file mode 100644
index 0000000..d123437
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/DatabaseErrorListener.cs
@@ -0,0 +1,12 @@
+namespace PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+public abstract class DatabaseErrorListener : IDatabaseErrorListener
+{
+ public virtual void OnXmlError(XmlError error)
+ {
+ }
+
+ public virtual void OnInitializationError(InitializationError error)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/DatabaseErrorListenerWrapper.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/DatabaseErrorListenerWrapper.cs
new file mode 100644
index 0000000..b68cfe8
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/DatabaseErrorListenerWrapper.cs
@@ -0,0 +1,61 @@
+using System;
+using AnakinRaW.CommonUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using PG.StarWarsGame.Files.XML.ErrorHandling;
+using PG.StarWarsGame.Files.XML.Parsers;
+
+namespace PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+internal class DatabaseErrorListenerWrapper : DisposableObject, IDatabaseErrorListener, IXmlParserErrorListener
+{
+ internal event EventHandler? InitializationError;
+
+ private readonly IDatabaseErrorListener? _errorListener;
+ private IPrimitiveXmlErrorParserProvider? _primitiveXmlParserErrorProvider;
+
+ public DatabaseErrorListenerWrapper(IDatabaseErrorListener? errorListener, IServiceProvider serviceProvider)
+ {
+ _errorListener = errorListener;
+ if (_errorListener is null)
+ return;
+ _primitiveXmlParserErrorProvider = serviceProvider.GetRequiredService();
+ _primitiveXmlParserErrorProvider.XmlParseError += ((IXmlParserErrorListener)this).OnXmlParseError;
+ }
+
+ public void OnXmlError(XmlError error)
+ {
+ _errorListener?.OnXmlError(error);
+ }
+
+ public void OnInitializationError(InitializationError error)
+ {
+ InitializationError?.Invoke(this, error);
+ if (_errorListener is null)
+ return;
+ _errorListener.OnInitializationError(error);
+ }
+
+ protected override void DisposeResources()
+ {
+ base.DisposeResources();
+ if (_primitiveXmlParserErrorProvider is null)
+ return;
+ _primitiveXmlParserErrorProvider.XmlParseError -= ((IXmlParserErrorListener)this).OnXmlParseError;
+ _primitiveXmlParserErrorProvider = null!;
+ }
+
+ void IXmlParserErrorListener.OnXmlParseError(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error)
+ {
+ if (_errorListener is null)
+ return;
+
+ OnXmlError(new XmlError
+ {
+ FileLocation = error.Location,
+ Parser = parser.ToString(),
+ Message = error.Message,
+ ErrorKind = error.ErrorKind,
+ Element = error.Element
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/IDatabaseErrorListener.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/IDatabaseErrorListener.cs
new file mode 100644
index 0000000..2df54c5
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/IDatabaseErrorListener.cs
@@ -0,0 +1,7 @@
+namespace PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+public interface IDatabaseErrorListener
+{
+ void OnXmlError(XmlError error);
+ void OnInitializationError(InitializationError error);
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/InitializationError.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/InitializationError.cs
new file mode 100644
index 0000000..55262ea
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/InitializationError.cs
@@ -0,0 +1,8 @@
+namespace PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+public sealed class InitializationError
+{
+ public required string GameManager { get; init; }
+
+ public required string Message { get; init; }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/XmlError.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/XmlError.cs
new file mode 100644
index 0000000..60b16bd
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/ErrorReporting/XmlError.cs
@@ -0,0 +1,18 @@
+using System.Xml.Linq;
+using PG.StarWarsGame.Files.XML;
+using PG.StarWarsGame.Files.XML.ErrorHandling;
+
+namespace PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+public sealed class XmlError
+{
+ public required XmlLocationInfo FileLocation { get; init; }
+
+ public required string Parser { get; init; }
+
+ public XElement? Element { get; init; }
+
+ public required XmlParseErrorKind ErrorKind { get; init; }
+
+ public required string Message { get; init; }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs
index f32f347..a72feb1 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs
@@ -1,19 +1,27 @@
using System.Collections.Generic;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Engine.Language;
-using PG.StarWarsGame.Engine.Repositories;
+using PG.StarWarsGame.Engine.Audio.Sfx;
+using PG.StarWarsGame.Engine.CommandBar;
+using PG.StarWarsGame.Engine.GameConstants;
+using PG.StarWarsGame.Engine.GameObjects;
+using PG.StarWarsGame.Engine.GuiDialog;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Engine.Localization;
namespace PG.StarWarsGame.Engine.Database;
internal class GameDatabase : IGameDatabase
{
+ public required ICommandBarGameManager CommandBarManager { get; init; }
+
public required IGameRepository GameRepository { get; init; }
- public required GameConstants GameConstants { get; init; }
+ public required IGameConstants GameConstants { get; init; }
+
+ public required IGuiDialogManager GuiDialogManager { get; init; }
- public required IXmlDatabase GameObjects { get; init; }
+ public required IGameObjectTypeGameManager GameObjectTypeManager { get; init; }
- public required IXmlDatabase SfxEvents { get; init; }
+ public required ISfxEventGameManager SfxGameManager { get; init; }
- public IEnumerable InstalledLanguages { get; init; }
+ public required IEnumerable InstalledLanguages { get; init; }
}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs
index fc1f5ca..e2122bf 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs
@@ -1,51 +1,24 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using AnakinRaW.CommonUtilities;
using Microsoft.Extensions.DependencyInjection;
-using PG.StarWarsGame.Engine.Database.Initialization;
-using PG.StarWarsGame.Engine.Repositories;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
-using PG.StarWarsGame.Files.XML.Parsers;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
namespace PG.StarWarsGame.Engine.Database;
-internal class GameDatabaseService : DisposableObject, IXmlParserErrorListener, IGameDatabaseService
+internal class GameDatabaseService(IServiceProvider serviceProvider) : IGameDatabaseService
{
- public event XmlErrorEventHandler? XmlParseError;
-
- private readonly IServiceProvider _serviceProvider;
- private IPrimitiveXmlErrorParserProvider _primitiveXmlParserErrorProvider;
-
- public GameDatabaseService(IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider;
- _primitiveXmlParserErrorProvider = serviceProvider.GetRequiredService();
- _primitiveXmlParserErrorProvider.XmlParseError += OnXmlParseError;
- }
-
- public async Task CreateDatabaseAsync(
- GameEngineType targetEngineType,
- GameLocations locations,
+ public Task InitializeGameAsync(
+ GameInitializationOptions gameInitializationOptions,
CancellationToken cancellationToken = default)
{
- var repoFactory = _serviceProvider.GetRequiredService();
- var repository = repoFactory.Create(targetEngineType, locations, this);
+ var repoFactory = serviceProvider.GetRequiredService();
- var pipeline = new GameDatabaseCreationPipeline(repository, this, _serviceProvider);
- await pipeline.RunAsync(cancellationToken);
- return pipeline.GameDatabase;
- }
+ using var errorListenerWrapper = new DatabaseErrorListenerWrapper(gameInitializationOptions.ErrorListener, serviceProvider);
+ var repository = repoFactory.Create(gameInitializationOptions.TargetEngineType, gameInitializationOptions.Locations, errorListenerWrapper);
- protected override void DisposeManagedResources()
- {
- base.DisposeManagedResources();
- _primitiveXmlParserErrorProvider.XmlParseError -= OnXmlParseError;
- _primitiveXmlParserErrorProvider = null!;
- }
-
- public void OnXmlParseError(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error)
- {
- XmlParseError?.Invoke(parser, error);
+ var gameInitializer = new GameInitializer(repository, gameInitializationOptions.CancelOnError, serviceProvider);
+ return gameInitializer.InitializeAsync(errorListenerWrapper, cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameInitializationOptions.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameInitializationOptions.cs
new file mode 100644
index 0000000..8c11841
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameInitializationOptions.cs
@@ -0,0 +1,14 @@
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+
+namespace PG.StarWarsGame.Engine.Database;
+
+public class GameInitializationOptions
+{
+ public required GameEngineType TargetEngineType { get; init; }
+
+ public required GameLocations Locations { get; init; }
+
+ public bool CancelOnError { get; init; }
+
+ public IDatabaseErrorListener? ErrorListener { get; init; }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameInitializer.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameInitializer.cs
new file mode 100644
index 0000000..7ad3ee9
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameInitializer.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine.Audio.Sfx;
+using PG.StarWarsGame.Engine.CommandBar;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.GameObjects;
+using PG.StarWarsGame.Engine.GuiDialog;
+using PG.StarWarsGame.Engine.IO.Repositories;
+
+namespace PG.StarWarsGame.Engine.Database;
+
+internal class GameInitializer(GameRepository repository, bool cancelOnError, IServiceProvider serviceProvider)
+{
+ private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(GameInitializer));
+
+ private CancellationTokenSource? _cancellationTokenSource;
+
+ public async Task InitializeAsync(DatabaseErrorListenerWrapper errorListener, CancellationToken token)
+ {
+ _logger?.LogInformation("Initializing Game Database...");
+ errorListener.InitializationError += OnInitializationError;
+
+ _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
+
+ try
+ {
+ // LensFlares.xml
+ // SurfaceFX.xml
+ // TerrainDecalFX.xml
+ // GraphicDetails.xml
+ // DynamicTrackFX.xml
+ // ShadowBlobMaterials.xml
+ // TacticalCameras.xml
+ // LightSources.xml
+ // StarWars3DTextCrawl.xml
+ // MusicEvents.xml
+ // SpeechEvents.xml
+ // GameConstantsXml.xml
+ // Audio.xml
+ // WeatherAudio.xml
+ // HeroClash.xml
+ // TradeRouteLines.xml
+ // RadarMap.xml
+ // WeatherModifiers.xml
+ // Movies.xml
+ // LightningEffectTypes.xml
+ // DifficultyAdjustments.xml
+ // WeatherScenarios.xml
+ // UnitAbilityTypes.xml
+ // BlackMarketItems.xml
+ // MovementClassTypeDefs.xml
+ // AITerrainEffectiveness.xml
+
+
+ // CONTAINER FILES:
+ // GameObjectFiles.xml
+ // TradeRouteFiles.xml
+ // HardPointDataFiles.xml
+ // CampaignFiles.xml
+ // FactionFiles.xml
+ // TargetingPrioritySetFiles.xml
+ // MousePointerFiles.xml
+
+ var gameConstants = new GameConstants.GameConstants(repository, errorListener, serviceProvider);
+ await gameConstants.InitializeAsync( _cancellationTokenSource.Token);
+
+ var guiDialogs = new GuiDialogGameManager(repository, errorListener, serviceProvider);
+ await guiDialogs.InitializeAsync(_cancellationTokenSource.Token);
+
+ var sfxGameManager = new SfxEventGameManager(repository, errorListener, serviceProvider);
+ await sfxGameManager.InitializeAsync( _cancellationTokenSource.Token);
+
+ var commandBarManager = new CommandBarGameManager(repository, errorListener, serviceProvider);
+ await commandBarManager.InitializeAsync( _cancellationTokenSource.Token);
+
+ var gameObjetTypeManager = new GameObjectTypeTypeGameManager(repository, errorListener, serviceProvider);
+ await gameObjetTypeManager.InitializeAsync( _cancellationTokenSource.Token);
+
+ repository.Seal();
+
+ return new GameDatabase
+ {
+ GameRepository = repository,
+ GameConstants = gameConstants,
+ GuiDialogManager = guiDialogs,
+ CommandBarManager = commandBarManager,
+ GameObjectTypeManager = gameObjetTypeManager,
+ SfxGameManager = sfxGameManager,
+ InstalledLanguages = sfxGameManager.InstalledLanguages
+ };
+ }
+ finally
+ {
+ errorListener.InitializationError -= OnInitializationError;
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+ _logger?.LogInformation("Finished initializing game database");
+ }
+ }
+
+ private void OnInitializationError(object sender, InitializationError e)
+ {
+ if(cancelOnError)
+ _cancellationTokenSource?.Cancel();
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameManagerBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameManagerBase.cs
new file mode 100644
index 0000000..3e1c18d
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameManagerBase.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Abstractions;
+using System.Threading;
+using System.Threading.Tasks;
+using AnakinRaW.CommonUtilities.Collections;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.Commons.Collections;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
+
+namespace PG.StarWarsGame.Engine.Database;
+
+internal abstract class GameManagerBase(GameRepository repository, DatabaseErrorListenerWrapper errorListener, IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorListener, serviceProvider), IGameManager
+{
+ protected readonly ValueListDictionary NamedEntries = new();
+
+ public ICollection Entries => NamedEntries.Values;
+
+ public ICollection EntryKeys => NamedEntries.Keys;
+
+ public ReadOnlyFrugalList GetEntries(Crc32 key)
+ {
+ return NamedEntries.GetValues(key);
+ }
+}
+
+internal abstract class GameManagerBase
+{
+ public event EventHandler? Initialized;
+
+ private bool _initialized;
+ private protected readonly GameRepository GameRepository;
+ protected readonly IServiceProvider ServiceProvider;
+ protected readonly IFileSystem FileSystem;
+ protected readonly ILogger? Logger;
+
+ protected readonly DatabaseErrorListenerWrapper ErrorListener;
+
+ public bool IsInitialized => _initialized;
+
+ protected GameManagerBase(GameRepository repository, DatabaseErrorListenerWrapper errorListener, IServiceProvider serviceProvider)
+ {
+ GameRepository = repository ?? throw new ArgumentNullException(nameof(repository));
+ ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+ Logger = serviceProvider.GetService()?.CreateLogger(GetType());
+ FileSystem = serviceProvider.GetRequiredService();
+ ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener));
+ }
+
+ public async Task InitializeAsync(CancellationToken token)
+ {
+ ThrowIfAlreadyInitialized();
+ token.ThrowIfCancellationRequested();
+ try
+ {
+ await InitializeCoreAsync(token);
+ _initialized = true;
+ }
+ catch (Exception e)
+ {
+ Logger?.LogError(e, $"Initialization of {this} failed: {e.Message}");
+ throw;
+ }
+ OnInitialized();
+ }
+
+ public override string ToString()
+ {
+ return GetType().Name;
+ }
+
+ protected abstract Task InitializeCoreAsync(CancellationToken token);
+
+ protected void ThrowIfAlreadyInitialized()
+ {
+ if (_initialized)
+ throw new InvalidOperationException("Game manager is already initialized.");
+ }
+
+ protected void ThrowIfNotInitialized()
+ {
+ if (!_initialized)
+ throw new InvalidOperationException("Game manager not initialized.");
+ }
+
+ private void OnInitialized()
+ {
+ Initialized?.Invoke(this, EventArgs.Empty);
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs
index 15dd8a1..087b7f8 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs
@@ -1,7 +1,10 @@
using System.Collections.Generic;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Engine.Language;
-using PG.StarWarsGame.Engine.Repositories;
+using PG.StarWarsGame.Engine.Audio.Sfx;
+using PG.StarWarsGame.Engine.GameConstants;
+using PG.StarWarsGame.Engine.GameObjects;
+using PG.StarWarsGame.Engine.GuiDialog;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Engine.Localization;
namespace PG.StarWarsGame.Engine.Database;
@@ -9,11 +12,13 @@ public interface IGameDatabase
{
IGameRepository GameRepository { get; }
- GameConstants GameConstants { get; }
+ IGameConstants GameConstants { get; }
- IXmlDatabase GameObjects { get; }
+ IGuiDialogManager GuiDialogManager { get; }
- IXmlDatabase SfxEvents { get; }
+ IGameObjectTypeGameManager GameObjectTypeManager { get; }
+
+ ISfxEventGameManager SfxGameManager { get; }
IEnumerable InstalledLanguages { get; }
}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs
index 575c521..46cfda2 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs
@@ -1,14 +1,9 @@
-using System;
-using System.Threading;
+using System.Threading;
using System.Threading.Tasks;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
namespace PG.StarWarsGame.Engine.Database;
-public interface IGameDatabaseService : IXmlParserErrorProvider, IDisposable
+public interface IGameDatabaseService
{
- Task CreateDatabaseAsync(
- GameEngineType targetEngineType,
- GameLocations locations,
- CancellationToken cancellationToken = default);
+ Task InitializeGameAsync(GameInitializationOptions gameInitializationOptions, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameManager.cs
similarity index 75%
rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs
rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameManager.cs
index c2edb8e..f331a24 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameManager.cs
@@ -1,11 +1,10 @@
using System.Collections.Generic;
using AnakinRaW.CommonUtilities.Collections;
using PG.Commons.Hashing;
-using PG.StarWarsGame.Engine.DataTypes;
namespace PG.StarWarsGame.Engine.Database;
-public interface IXmlDatabase where T : XmlObject
+public interface IGameManager
{
ICollection Entries { get; }
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs
deleted file mode 100644
index 2c1f20e..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Threading;
-using AnakinRaW.CommonUtilities.SimplePipeline.Steps;
-using Microsoft.Extensions.Logging;
-using PG.StarWarsGame.Engine.Repositories;
-
-namespace PG.StarWarsGame.Engine.Database.Initialization;
-
-internal abstract class CreateDatabaseStep(IGameRepository repository, IServiceProvider serviceProvider)
- : PipelineStep(serviceProvider) where T : class
-{
- public T Database { get; private set; } = null!;
-
- protected abstract string Name { get; }
-
- protected IGameRepository GameRepository { get; } = repository;
-
- protected sealed override void RunCore(CancellationToken token)
- {
- Logger?.LogDebug($"Creating {Name} database...");
- Database = CreateDatabase();
- }
-
- protected abstract T CreateDatabase();
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs
deleted file mode 100644
index 05f6e21..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using AnakinRaW.CommonUtilities.SimplePipeline;
-using AnakinRaW.CommonUtilities.SimplePipeline.Runners;
-using AnakinRaW.CommonUtilities.SimplePipeline.Steps;
-using Microsoft.Extensions.Logging;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Engine.Repositories;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
-
-namespace PG.StarWarsGame.Engine.Database.Initialization;
-
-internal class GameDatabaseCreationPipeline(GameRepository repository, IXmlParserErrorListener xmlParserErrorListener, IServiceProvider serviceProvider)
- : Pipeline(serviceProvider)
-{
- private ParseSingletonXmlStep _parseGameConstants = null!;
- private ParseXmlDatabaseFromContainerStep _parseGameObjects = null!;
- private ParseXmlDatabaseFromContainerStep _parseSfxEvents = null!;
-
- private StepRunner _parseXmlRunner = null!;
-
- public GameDatabase GameDatabase { get; private set; } = null!;
-
- protected override Task PrepareCoreAsync()
- {
- _parseXmlRunner = new ParallelRunner(4, ServiceProvider);
- foreach (var xmlParserStep in CreateXmlParserSteps())
- _parseXmlRunner.AddStep(xmlParserStep);
-
- return Task.FromResult(true);
- }
-
- private IEnumerable CreateXmlParserSteps()
- {
- // TODO: Use same load order as the engine!
-
- yield return _parseGameConstants = new ParseSingletonXmlStep("GameConstants",
- "DATA\\XML\\GAMECONSTANTS.XML", repository, xmlParserErrorListener, ServiceProvider);
-
- yield return _parseGameObjects = new ParseXmlDatabaseFromContainerStep("GameObjects",
- "DATA\\XML\\GAMEOBJECTFILES.XML", repository, xmlParserErrorListener, ServiceProvider);
-
- yield return _parseSfxEvents = new ParseXmlDatabaseFromContainerStep("SFXEvents",
- "DATA\\XML\\SFXEventFiles.XML", repository, xmlParserErrorListener, ServiceProvider);
-
- // GUIDialogs.xml
- // LensFlares.xml
- // SurfaceFX.xml
- // TerrainDecalFX.xml
- // GraphicDetails.xml
- // DynamicTrackFX.xml
- // ShadowBlobMaterials.xml
- // TacticalCameras.xml
- // LightSources.xml
- // StarWars3DTextCrawl.xml
- // MusicEvents.xml
- // SpeechEvents.xml
- // GameConstants.xml
- // Audio.xml
- // WeatherAudio.xml
- // HeroClash.xml
- // TradeRouteLines.xml
- // RadarMap.xml
- // WeatherModifiers.xml
- // Movies.xml
- // LightningEffectTypes.xml
- // DifficultyAdjustments.xml
- // WeatherScenarios.xml
- // UnitAbilityTypes.xml
- // BlackMarketItems.xml
- // MovementClassTypeDefs.xml
- // AITerrainEffectiveness.xml
-
-
- // CONTAINER FILES:
- // GameObjectFiles.xml
- // CommandBarComponentFiles.xml
- // TradeRouteFiles.xml
- // HardPointDataFiles.xml
- // CampaignFiles.xml
- // FactionFiles.xml
- // TargetingPrioritySetFiles.xml
- // MousePointerFiles.xml
- }
-
- protected override async Task RunCoreAsync(CancellationToken token)
- {
- Logger?.LogInformation("Creating Game Database...");
-
- try
- {
- try
- {
- Logger?.LogInformation("Parsing XML Files...");
- _parseXmlRunner.Error += OnError;
- await _parseXmlRunner.RunAsync(token);
- }
- finally
- {
- Logger?.LogInformation("Finished parsing XML Files");
- _parseXmlRunner.Error -= OnError;
- }
-
- ThrowIfAnyStepsFailed(_parseXmlRunner.Steps);
-
- token.ThrowIfCancellationRequested();
-
-
- Logger?.LogInformation("Initializing Language files...");
- var installedLanguages = repository.InitializeInstalledSfxMegFiles();
- Logger?.LogInformation("Finished initializing Language files");
-
-
- repository.Seal();
-
- GameDatabase = new GameDatabase
- {
- GameRepository = repository,
- GameConstants = _parseGameConstants.Database,
- GameObjects = _parseGameObjects.Database,
- SfxEvents = _parseSfxEvents.Database,
- InstalledLanguages = installedLanguages
- };
- }
- finally
- {
- Logger?.LogInformation("Finished creating game database");
- }
- }
-
- private sealed class ParseSingletonXmlStep(string name, string xmlFile, IGameRepository repository, IXmlParserErrorListener? listener, IServiceProvider serviceProvider)
- : ParseXmlDatabaseStep(xmlFile, repository, listener, serviceProvider) where T : class
- {
- protected override string Name => name;
-
- protected override T CreateDatabase(IList parsedDatabaseEntries)
- {
- if (parsedDatabaseEntries.Count != 1)
- throw new InvalidOperationException($"There can be only one {Name} model.");
-
- return parsedDatabaseEntries.First();
- }
- }
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs
deleted file mode 100644
index 5dcde76..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System;
-using System.IO.Abstractions;
-using System.Linq;
-using System.Xml;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using PG.Commons.Hashing;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Engine.Repositories;
-using PG.StarWarsGame.Engine.Xml;
-using PG.StarWarsGame.Files.XML;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
-
-namespace PG.StarWarsGame.Engine.Database.Initialization;
-
-internal class ParseXmlDatabaseFromContainerStep(
- string name,
- string xmlFile,
- IGameRepository repository,
- IXmlParserErrorListener? listener,
- IServiceProvider serviceProvider)
- : CreateDatabaseStep>(repository, serviceProvider)
- where T : XmlObject
-{
- private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService();
-
- protected readonly IPetroglyphXmlFileParserFactory FileParserFactory = serviceProvider.GetRequiredService();
-
- protected override string Name => name;
-
- protected sealed override IXmlDatabase CreateDatabase()
- {
- using var containerStream = GameRepository.OpenFile(xmlFile);
- var containerParser = FileParserFactory.GetFileParser(listener);
- Logger?.LogDebug($"Parsing container data '{xmlFile}'");
- var container = containerParser.ParseFile(containerStream);
-
- var xmlFiles = container.Files.Select(x => _fileSystem.Path.Combine("DATA\\XML", x)).ToList();
-
- var parsedEntries = new ValueListDictionary();
-
- foreach (var file in xmlFiles)
- {
- using var fileStream = GameRepository.TryOpenFile(file);
-
- var parser = FileParserFactory.GetFileParser(listener);
-
- if (fileStream is null)
- {
- listener?.OnXmlParseError(parser, XmlParseErrorEventArgs.FromMissingFile(file));
- Logger?.LogWarning($"Could not find XML file '{file}'");
- continue;
- }
-
- Logger?.LogDebug($"Parsing File '{file}'");
-
- try
- {
- parser.ParseFile(fileStream, parsedEntries);
- }
- catch (XmlException e)
- {
- listener?.OnXmlParseError(parser, new XmlParseErrorEventArgs(file, null, XmlParseErrorKind.Unknown, e.Message));
- }
- }
-
- return new XmlDatabase(parsedEntries, Services);
- }
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs
deleted file mode 100644
index 8050f02..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using PG.StarWarsGame.Engine.Repositories;
-using PG.StarWarsGame.Engine.Xml;
-using PG.StarWarsGame.Files.XML.ErrorHandling;
-
-namespace PG.StarWarsGame.Engine.Database.Initialization;
-
-internal abstract class ParseXmlDatabaseStep(
- IList xmlFiles,
- IGameRepository repository,
- IXmlParserErrorListener? listener,
- IServiceProvider serviceProvider)
- : CreateDatabaseStep(repository, serviceProvider)
- where T : class
-{
- protected readonly IPetroglyphXmlFileParserFactory FileParserFactory = serviceProvider.GetRequiredService();
-
- protected ParseXmlDatabaseStep(string xmlFile, IGameRepository repository, IXmlParserErrorListener? listener, IServiceProvider serviceProvider)
- : this([xmlFile], repository, listener, serviceProvider)
- {
- }
-
- protected sealed override T CreateDatabase()
- {
- var parsedDatabaseEntries = new List();
- foreach (var xmlFile in xmlFiles)
- {
- using var fileStream = GameRepository.OpenFile(xmlFile);
-
- var parser = FileParserFactory.GetFileParser(listener);
- Logger?.LogDebug($"Parsing File '{xmlFile}'");
- var parsedData = parser.ParseFile(fileStream)!;
- parsedDatabaseEntries.Add(parsedData);
- }
- return CreateDatabase(parsedDatabaseEntries);
- }
-
- protected abstract T CreateDatabase(IList parsedDatabaseEntries);
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs
deleted file mode 100644
index 38d0a78..0000000
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Collections.Generic;
-using AnakinRaW.CommonUtilities.Collections;
-using PG.Commons.Hashing;
-using PG.StarWarsGame.Engine.DataTypes;
-using PG.StarWarsGame.Files.XML;
-
-namespace PG.StarWarsGame.Engine.Database;
-
-internal class XmlDatabase(IReadOnlyValueListDictionary parsedObjects, IServiceProvider serviceProvider) : IXmlDatabase
- where T : XmlObject
-{
-
- private readonly IServiceProvider _serviceProvider =
- serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
-
- private readonly IReadOnlyValueListDictionary _parsedObjects = parsedObjects ?? throw new ArgumentNullException(nameof(parsedObjects));
-
- public ICollection Entries => _parsedObjects.Values;
-
- public ICollection EntryKeys => _parsedObjects.Keys;
-
- public ReadOnlyFrugalList GetEntries(Crc32 key)
- {
- return _parsedObjects.GetValues(key);
- }
-}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs
index 72996da..d7eb4af 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs
@@ -5,7 +5,7 @@ namespace PG.StarWarsGame.Engine;
public static class FocHardcodedConstants
{
///
- /// These models are hardcoded into StarWarsG.exe.
+ /// These models / particles are hardcoded into StarWarsG.exe.
///
public static IList HardcodedModels { get; } = new List
{
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstants.cs
new file mode 100644
index 0000000..508323c
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstants.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using PG.StarWarsGame.Engine.Database;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
+
+namespace PG.StarWarsGame.Engine.GameConstants;
+
+internal class GameConstants(GameRepository repository, DatabaseErrorListenerWrapper errorListener, IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorListener, serviceProvider), IGameConstants
+{
+ protected override Task InitializeCoreAsync(CancellationToken token)
+ {
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstantsXml.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstantsXml.cs
new file mode 100644
index 0000000..e98ed52
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstantsXml.cs
@@ -0,0 +1,3 @@
+namespace PG.StarWarsGame.Engine.GameConstants;
+
+public class GameConstantsXml;
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/IGameConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/IGameConstants.cs
new file mode 100644
index 0000000..552e406
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/IGameConstants.cs
@@ -0,0 +1,3 @@
+namespace PG.StarWarsGame.Engine.GameConstants;
+
+public interface IGameConstants;
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObject.cs
new file mode 100644
index 0000000..de7ed01
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObject.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.XML;
+
+namespace PG.StarWarsGame.Engine.GameObjects;
+
+public sealed class GameObject : NamedXmlObject
+{
+ internal GameObject(string type, string name, Crc32 nameCrc, GameObjectType estimatedType, XmlLocationInfo location)
+ : base(name, nameCrc, location)
+ {
+ Type = type ?? throw new ArgumentNullException(nameof(type));
+ EstimatedType = estimatedType;
+ LandTerrainModelMapping = new ReadOnlyDictionary(InternalLandTerrainModelMapping);
+ }
+
+ public string Type { get; }
+
+ public GameObjectType EstimatedType { get; }
+
+ public string? GalacticModel { get; internal set; }
+
+ public string? DestroyedGalacticModel { get; internal set; }
+
+ public string? LandModel { get; internal set; }
+
+ public string? SpaceModel { get; internal set; }
+
+ public string? TacticalModel { get; internal set; }
+
+ public string? GalacticFleetOverrideModel { get; internal set; }
+
+ public string? GuiModel { get; internal set; }
+
+ public string? ModelName { get; internal set; }
+
+ public string? LandAnimOverrideModel { get; internal set; }
+
+ public string? XxxSpaceModeModel { get; internal set; }
+
+ public string? DamagedSmokeAssetModel { get; internal set; }
+
+ public IReadOnlyDictionary LandTerrainModelMapping { get; }
+
+ internal Dictionary InternalLandTerrainModelMapping { get; } = new(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Gets all model files (including particles) the game object references.
+ ///
+ public IEnumerable Models
+ {
+ get
+ {
+ var models = new HashSet(StringComparer.OrdinalIgnoreCase);
+ AddNotEmpty(models, GalacticModel);
+ AddNotEmpty(models, DestroyedGalacticModel);
+ AddNotEmpty(models, LandModel);
+ AddNotEmpty(models, SpaceModel);
+ AddNotEmpty(models, TacticalModel);
+ AddNotEmpty(models, GalacticFleetOverrideModel);
+ AddNotEmpty(models, GuiModel);
+ AddNotEmpty(models, ModelName);
+ AddNotEmpty(models, LandAnimOverrideModel, s => s.EndsWith(".alo", StringComparison.OrdinalIgnoreCase));
+ AddNotEmpty(models, XxxSpaceModeModel);
+ AddNotEmpty(models, DamagedSmokeAssetModel);
+ foreach (var model in InternalLandTerrainModelMapping.Values)
+ models.Add(model);
+
+ return models;
+ }
+
+ }
+
+ private static void AddNotEmpty(ISet set, string? value, Predicate? predicate = null)
+ {
+ if (value is null)
+ return;
+ if (predicate is null || predicate(value))
+ set.Add(value);
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObjectType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectType.cs
similarity index 95%
rename from src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObjectType.cs
rename to src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectType.cs
index 116fa29..0b84517 100644
--- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObjectType.cs
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectType.cs
@@ -1,4 +1,4 @@
-namespace PG.StarWarsGame.Engine.DataTypes;
+namespace PG.StarWarsGame.Engine.GameObjects;
public enum GameObjectType
{
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectTypeTypeGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectTypeTypeGameManager.cs
new file mode 100644
index 0000000..8b0abb1
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectTypeTypeGameManager.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine.Database;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Engine.Xml.Parsers;
+
+namespace PG.StarWarsGame.Engine.GameObjects;
+
+internal class GameObjectTypeTypeGameManager(GameRepository repository, DatabaseErrorListenerWrapper errorListener, IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorListener, serviceProvider), IGameObjectTypeGameManager
+{
+ protected override async Task InitializeCoreAsync(CancellationToken token)
+ {
+ Logger?.LogInformation("Parsing GameObjects...");
+
+ var contentParser = ServiceProvider.GetRequiredService();
+
+ await Task.Run(() => contentParser.ParseEntriesFromContainerXml(
+ "DATA\\XML\\GAMEOBJECTFILES.XML",
+ ErrorListener,
+ GameRepository,
+ ".\\DATA\\XML",
+ NamedEntries,
+ VerifyFilePathLength), token);
+ }
+
+ private void VerifyFilePathLength(string filePath)
+ {
+ if (filePath.Length > PGConstants.MaxGameObjectDatabaseFileName)
+ {
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = $"Game object file '{filePath}' is longer than {PGConstants.MaxGameObjectDatabaseFileName} characters."
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/IGameObjectTypeGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/IGameObjectTypeGameManager.cs
new file mode 100644
index 0000000..ae22ca1
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/IGameObjectTypeGameManager.cs
@@ -0,0 +1,5 @@
+using PG.StarWarsGame.Engine.Database;
+
+namespace PG.StarWarsGame.Engine.GameObjects;
+
+public interface IGameObjectTypeGameManager : IGameManager;
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/ComponentTextureEntry.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/ComponentTextureEntry.cs
new file mode 100644
index 0000000..f1ffb04
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/ComponentTextureEntry.cs
@@ -0,0 +1,10 @@
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+public readonly struct ComponentTextureEntry(GuiComponentType componentType, string texture, bool isOverride)
+{
+ public string Texture { get; } = texture;
+
+ public GuiComponentType ComponentType { get; } = componentType;
+
+ public bool IsOverride { get; } = isOverride;
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiComponentType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiComponentType.cs
new file mode 100644
index 0000000..548cfee
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiComponentType.cs
@@ -0,0 +1,93 @@
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+public enum GuiComponentType
+{
+ ButtonLeft = 0x0,
+ ButtonMiddle = 0x1,
+ ButtonRight = 0x2,
+ ButtonLeftMouseOver = 0x3,
+ ButtonMiddleMouseOver = 0x4,
+ ButtonRightMouseOver = 0x5,
+ ButtonLeftPressed = 0x6,
+ ButtonMiddlePressed = 0x7,
+ ButtonRightPressed = 0x8,
+ ButtonLeftDisabled = 0x9,
+ ButtonMiddleDisabled = 0xA,
+ ButtonRightDisabled = 0xB,
+ CheckOff = 0xC,
+ CheckOn = 0xD,
+ DialLeft = 0xE,
+ DialMiddle = 0xF,
+ DialRight = 0x10,
+ DialPlus = 0x11,
+ DialPlusMouseOver = 0x12,
+ DialPlusPressed = 0x13,
+ DialMinus = 0x14,
+ DialMinusMouseOver = 0x15,
+ DialMinusPressed = 0x16,
+ DialTab = 0x17,
+ FrameBottom = 0x18,
+ FrameBottomLeft = 0x19,
+ FrameBottomRight = 0x1A,
+ FrameBackground = 0x1B,
+ FrameLeft = 0x1C,
+ FrameRight = 0x1D,
+ FrameTop = 0x1E,
+ FrameTopLeft = 0x1F,
+ FrameTopRight = 0x20,
+ FrameTopTransitionLeft = 0x21,
+ FrameTopTransitionRight = 0x22,
+ FrameBottomTransitionLeft = 0x23,
+ FrameBottomTransitionRight = 0x24,
+ FrameLeftTransitionTop = 0x25,
+ FrameLeftTransitionBottom = 0x26,
+ FrameRightTransitionTop = 0x27,
+ FrameRightTransitionBottom = 0x28,
+ RadioOff = 0x29,
+ RadioOn = 0x2A,
+ RadioDisabled = 0x2B,
+ RadioMouseOver = 0x2C,
+ ScrollDownButton = 0x2D,
+ ScrollDownButtonPressed = 0x2E,
+ ScrollDownButtonMouseOver = 0x2F,
+ ScrollMiddle = 0x30,
+ ScrollTab = 0x31,
+ ScrollUpButton = 0x32,
+ ScrollUpButtonPressed = 0x33,
+ ScrollUpButtonMouseOver = 0x34,
+ ScrollUpButtonDisabled = 0x35,
+ ScrollDownButtonDisabled = 0x36,
+ ScrollMiddleDisabled = 0x37,
+ ScrollTabDisabled = 0x38,
+ TrackbarScrollDownButton = 0x39,
+ TrackbarScrollDownButtonPressed = 0x3A,
+ TrackbarScrollDownButtonMouseOver = 0x3B,
+ TrackbarScrollMiddle = 0x3C,
+ TrackbarScrollTab = 0x3D,
+ TrackbarScrollUpButton = 0x3E,
+ TrackbarScrollUpButtonPressed = 0x3F,
+ TrackbarScrollUpButtonMouseOver = 0x40,
+ TrackbarScrollUpButtonDisabled = 0x41,
+ TrackbarScrollDownButtonDisabled = 0x42,
+ TrackbarScrollMiddleDisabled = 0x43,
+ TrackbarScrollTabDisabled = 0x44,
+ SmallFrameBottom = 0x45,
+ SmallFrameBottomLeft = 0x46,
+ SmallFrameBottomRight = 0x47,
+ SmallFrameMiddleLeft = 0x48,
+ SmallFrameMiddleRight = 0x49,
+ SmallFrameTop = 0x4A,
+ SmallFrameTopLeft = 0x4B,
+ SmallFrameTopRight = 0x4C,
+ SmallFrameBackground = 0x4D,
+ ComboboxPopdown = 0x4E,
+ ComboboxPopdownMouseOver = 0x4F,
+ ComboboxPopdownPressed = 0x50,
+ ComboboxTextBox = 0x51,
+ ComboboxLeftCap = 0x52,
+ ProgressLeft = 0x53,
+ ProgressMiddleOff = 0x54,
+ ProgressMiddleOn = 0x55,
+ ProgressRight = 0x56,
+ Scanlines = 0x57,
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs
new file mode 100644
index 0000000..5f20f0f
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.Commons.Hashing;
+using PG.StarWarsGame.Engine.Database;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.GuiDialog.Xml;
+using PG.StarWarsGame.Engine.IO.Repositories;
+using PG.StarWarsGame.Files.MTD.Binary;
+using PG.StarWarsGame.Files.MTD.Files;
+using PG.StarWarsGame.Files.MTD.Services;
+
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+internal partial class GuiDialogGameManager(GameRepository repository, DatabaseErrorListenerWrapper errorListener, IServiceProvider serviceProvider)
+ : GameManagerBase(repository, errorListener, serviceProvider), IGuiDialogManager
+{
+ private readonly IMtdFileService _mtdFileService = serviceProvider.GetRequiredService();
+ private readonly ICrc32HashingService _hashingService = serviceProvider.GetRequiredService();
+
+ // Unlike other strings for this game, the component's (aka gadget) name is case-sensitive.
+ private readonly Dictionary> _perComponentTextures = new(StringComparer.Ordinal);
+ private readonly Dictionary _defaultTextures = new();
+ private ReadOnlyDictionary _defaultTexturesRo = null!;
+
+ private IMtdFile? _megaTexture;
+ private GuiDialogsXml? _guiDialogsXml;
+ private string? _megaTextureFileName;
+ private bool _megaTextureExists;
+
+
+ public IMtdFile? MtdFile
+ {
+ get
+ {
+ ThrowIfNotInitialized();
+ return _megaTexture;
+ }
+ private set
+ {
+ ThrowIfAlreadyInitialized();
+ _megaTexture = value;
+ }
+ }
+
+ public GuiDialogsXml? GuiDialogsXml
+ {
+ get
+ {
+ ThrowIfNotInitialized();
+ return _guiDialogsXml;
+ }
+ private set
+ {
+ ThrowIfAlreadyInitialized();
+ _guiDialogsXml = value;
+ }
+ }
+
+ public IReadOnlyCollection Components
+ {
+ get
+ {
+ ThrowIfNotInitialized();
+ return _perComponentTextures.Keys;
+ }
+ }
+
+ public IReadOnlyDictionary DefaultTextureEntries
+ {
+ get
+ {
+ ThrowIfNotInitialized();
+ return _defaultTexturesRo;
+ }
+ }
+
+
+ public IReadOnlyDictionary GetTextureEntries(string component, out bool componentExist)
+ {
+ if (!_perComponentTextures.TryGetValue(component, out var textures))
+ {
+ Logger?.LogDebug($"The component '{component}' has no overrides. Using default textures.");
+ componentExist = false;
+ return DefaultTextureEntries;
+ }
+
+ componentExist = true;
+ return new ReadOnlyDictionary(textures);
+ }
+
+ public bool TryGetTextureEntry(string component, GuiComponentType key, out ComponentTextureEntry texture)
+ {
+ if (!_perComponentTextures.TryGetValue(component, out var textures))
+ {
+ Logger?.LogDebug($"The component '{component}' has no overrides. Using default textures.");
+ textures = _defaultTextures;
+ }
+
+ return textures.TryGetValue(key, out texture);
+ }
+
+ public bool TextureExists(
+ in ComponentTextureEntry textureInfo,
+ out GuiTextureOrigin textureOrigin,
+ out bool isNone,
+ bool buttonMiddleInRepoMode = false)
+ {
+ if (textureInfo.Texture == "none")
+ {
+ textureOrigin = default;
+ isNone = true;
+ return false;
+ }
+
+ isNone = false;
+
+ // Apparently, Scanlines only use the repository and not the MTD.
+ if (textureInfo.ComponentType == GuiComponentType.Scanlines)
+ {
+ textureOrigin = GuiTextureOrigin.Repository;
+ return GameRepository.TextureRepository.FileExists(textureInfo.Texture);
+ }
+
+ // The engine uses ButtonMiddle to switch to the special button mode.
+ // It searches first in the repo and then falls back to MTD
+ // (but only for this very type; the variants do not fallback to MTD).
+ if (textureInfo.ComponentType == GuiComponentType.ButtonMiddle)
+ {
+ if (GameRepository.TextureRepository.FileExists(textureInfo.Texture))
+ {
+ textureOrigin = GuiTextureOrigin.Repository;
+ return true;
+ }
+ }
+
+ // The engine does not fallback to MTD once it is in this special Button mode.
+ if (buttonMiddleInRepoMode && textureInfo.ComponentType is
+ GuiComponentType.ButtonMiddleDisabled or
+ GuiComponentType.ButtonMiddleMouseOver or
+ GuiComponentType.ButtonMiddlePressed)
+ {
+ textureOrigin = GuiTextureOrigin.Repository;
+ return GameRepository.TextureRepository.FileExists(textureInfo.Texture);
+ }
+
+ if (textureInfo.Texture.Length <= 63 && MtdFile is not null && _megaTextureExists)
+ {
+ var crc32 = _hashingService.GetCrc32Upper(textureInfo.Texture.AsSpan(), MtdFileConstants.NameEncoding);
+ if (MtdFile.Content.Contains(crc32))
+ {
+ textureOrigin = GuiTextureOrigin.MegaTexture;
+ return true;
+ }
+ }
+
+ // The background image for frames include a fallback the repository.
+ if (textureInfo.ComponentType == GuiComponentType.FrameBackground)
+ {
+ textureOrigin = GuiTextureOrigin.Repository;
+ return GameRepository.FileExists(textureInfo.Texture);
+ }
+
+ textureOrigin = default;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager_Initialization.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager_Initialization.cs
new file mode 100644
index 0000000..b96c4d7
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager_Initialization.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AnakinRaW.CommonUtilities.Collections;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using PG.StarWarsGame.Engine.Database.ErrorReporting;
+using PG.StarWarsGame.Engine.GuiDialog.Xml;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Engine.Xml.Tags;
+
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+partial class GuiDialogGameManager
+{
+ public const int MegaTextureMaxFilePathLength = 255;
+
+ protected override Task InitializeCoreAsync(CancellationToken token)
+ {
+ return Task.Run(() =>
+ {
+ var parserFactory = ServiceProvider.GetRequiredService();
+ var guiDialogParser = parserFactory.CreateFileParser();
+
+ _defaultTexturesRo = new ReadOnlyDictionary(_defaultTextures);
+
+ Logger?.LogInformation("Parsing GuiDialogs...");
+ using var fileStream = GameRepository.TryOpenFile("DATA\\XML\\GUIDIALOGS.XML");
+
+ if (fileStream is null)
+ {
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = "Unable to find GuiDialogs.xml"
+ });
+ return;
+ }
+
+ var guiDialogs = guiDialogParser.ParseFile(fileStream);
+ if (guiDialogs is null)
+ {
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = "Unable to parse GuiDialogs.xml"
+ });
+ return;
+ }
+
+ GuiDialogsXml = guiDialogs;
+
+ InitializeTextures(guiDialogs.TextureData, ErrorListener);
+
+ }, token);
+ }
+
+ private void InitializeTextures(GuiDialogsXmlTextureData textureData, DatabaseErrorListenerWrapper errorListener)
+ {
+ InitializeMegaTextures(textureData, ErrorListener);
+
+ var textures = textureData.Textures;
+
+ if (textures.Count == 0)
+ {
+ errorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = "No Textures defined in GuiDialogs.xml"
+ });
+ }
+ else
+ {
+ var defaultCandidate = textures.First();
+
+ // Regardless of its name, the game treats the first entry as default.
+ var defaultTextures = InitializeComponentTextures(defaultCandidate, true, out var invalidKeys);
+ foreach (var entry in defaultTextures)
+ _defaultTextures.Add(entry.Key, entry.Value);
+
+ ReportInvalidComponent(in invalidKeys);
+ }
+
+ foreach (var componentTextureData in textures.Skip(1))
+ {
+ // The game only uses the *first* entry.
+ if (_perComponentTextures.ContainsKey(componentTextureData.Component))
+ continue;
+
+ _perComponentTextures.Add(componentTextureData.Component, InitializeComponentTextures(componentTextureData, false, out var invalidKeys));
+ ReportInvalidComponent(in invalidKeys);
+ }
+ }
+
+ private Dictionary InitializeComponentTextures(XmlComponentTextureData textureData, bool isDefaultComponent, out FrugalList invalidKeys)
+ {
+ invalidKeys = new FrugalList();
+
+ var result = new Dictionary();
+
+ if (!isDefaultComponent)
+ {
+ // This assumes that _defaultTextures is already filled
+ foreach (var key in _defaultTextures.Keys)
+ result.Add(key, _defaultTextures[key]);
+ }
+
+
+ foreach (var keyText in textureData.Textures.Keys)
+ {
+ if (!ComponentTextureKeyExtensions.TryConvertToKey(keyText.AsSpan(), out var key))
+ {
+ invalidKeys.Add(keyText);
+ continue;
+ }
+
+ var textureValue = textureData.Textures.GetLastValue(keyText);
+ result[key] = new ComponentTextureEntry(key, textureValue, !isDefaultComponent);
+ }
+
+ return result;
+ }
+
+ private void InitializeMegaTextures(GuiDialogsXmlTextureData guiDialogs, DatabaseErrorListenerWrapper errorListener)
+ {
+ if (guiDialogs.MegaTexture is null)
+ {
+ errorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = "MtdFile is not defined in GuiDialogs.xml"
+ });
+ }
+ else
+ {
+ var mtdPath = FileSystem.Path.Combine("DATA\\ART\\TEXTURES", $"{guiDialogs.MegaTexture}.mtd");
+
+ if (mtdPath.Length > MegaTextureMaxFilePathLength)
+ {
+ errorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = $"Mtd file path is longer than {MegaTextureMaxFilePathLength}."
+ });
+ }
+
+ using var megaTexture = GameRepository.TryOpenFile(mtdPath);
+ MtdFile = megaTexture is null ? null : _mtdFileService.Load(megaTexture);
+ }
+
+ if (guiDialogs.CompressedMegaTexture is null)
+ {
+ errorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = "CompressedMegaTexture is not defined in GuiDialogs.xml"
+ });
+ }
+
+
+ // TODO: Support using the correct texture based on desired low-RAM flag
+ _megaTextureFileName = guiDialogs.MegaTexture;
+ var textureFileNameWithExtension = $"{guiDialogs.MegaTexture}.tga";
+ _megaTextureExists = GameRepository.TextureRepository.FileExists(textureFileNameWithExtension);
+
+ if (textureFileNameWithExtension.Length > MegaTextureMaxFilePathLength)
+ {
+ errorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = $"MegaTexture file path is longer than {MegaTextureMaxFilePathLength}."
+ });
+ }
+ }
+
+ private void ReportInvalidComponent(in FrugalList invalidKeys)
+ {
+ if (invalidKeys.Count == 0)
+ return;
+
+ ErrorListener.OnInitializationError(new InitializationError
+ {
+ GameManager = ToString(),
+ Message = $"The following XML keys are not valid to describe a GUI component: {string.Join(",", invalidKeys)}"
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiTextureOrigin.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiTextureOrigin.cs
new file mode 100644
index 0000000..751afea
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiTextureOrigin.cs
@@ -0,0 +1,7 @@
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+public enum GuiTextureOrigin
+{
+ MegaTexture,
+ Repository
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/IGuiDialogManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/IGuiDialogManager.cs
new file mode 100644
index 0000000..1ac7632
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/IGuiDialogManager.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using PG.StarWarsGame.Engine.GuiDialog.Xml;
+using PG.StarWarsGame.Files.MTD.Files;
+
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+public interface IGuiDialogManager
+{
+ IMtdFile? MtdFile { get; }
+
+ GuiDialogsXml? GuiDialogsXml { get; }
+
+ IReadOnlyCollection Components { get; }
+
+ IReadOnlyDictionary DefaultTextureEntries { get; }
+
+ IReadOnlyDictionary GetTextureEntries(string component, out bool componentExist);
+
+ bool TryGetTextureEntry(string component, GuiComponentType key, out ComponentTextureEntry texture);
+
+ bool TextureExists(
+ in ComponentTextureEntry textureInfo,
+ out GuiTextureOrigin textureOrigin,
+ out bool isNone,
+ bool buttonMiddleInRepoMode = false);
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/TypeBasedComponentTextureEntryComparer.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/TypeBasedComponentTextureEntryComparer.cs
new file mode 100644
index 0000000..eda572e
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/TypeBasedComponentTextureEntryComparer.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace PG.StarWarsGame.Engine.GuiDialog;
+
+public sealed class TypeBasedComponentTextureEntryComparer : IEqualityComparer
+{
+ public static readonly TypeBasedComponentTextureEntryComparer Instance = new();
+
+ private TypeBasedComponentTextureEntryComparer()
+ {
+ }
+
+ public bool Equals(ComponentTextureEntry x, ComponentTextureEntry y)
+ {
+ return x.ComponentType.Equals(y.ComponentType);
+ }
+
+ public int GetHashCode(ComponentTextureEntry obj)
+ {
+ return obj.ComponentType.GetHashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXml.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXml.cs
new file mode 100644
index 0000000..72cc5a0
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXml.cs
@@ -0,0 +1,9 @@
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.XML;
+
+namespace PG.StarWarsGame.Engine.GuiDialog.Xml;
+
+public class GuiDialogsXml(GuiDialogsXmlTextureData textureData, XmlLocationInfo location) : XmlObject(location)
+{
+ public GuiDialogsXmlTextureData TextureData { get; } = textureData;
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXmlTextureData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXmlTextureData.cs
new file mode 100644
index 0000000..6496417
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXmlTextureData.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.Linq;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.XML;
+
+namespace PG.StarWarsGame.Engine.GuiDialog.Xml;
+
+public class GuiDialogsXmlTextureData(IEnumerable textures, XmlLocationInfo location) : XmlObject(location)
+{
+ public string? MegaTexture { get; init; }
+
+ public string? CompressedMegaTexture { get; init; }
+
+ public IReadOnlyList Textures { get; } = textures.ToList();
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs
new file mode 100644
index 0000000..2ed95ce
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs
@@ -0,0 +1,14 @@
+using System;
+using PG.Commons.Collections;
+using PG.StarWarsGame.Engine.Xml;
+using PG.StarWarsGame.Files.XML;
+
+namespace PG.StarWarsGame.Engine.GuiDialog.Xml;
+
+public class XmlComponentTextureData(string componentId, IReadOnlyValueListDictionary textures, XmlLocationInfo location)
+ : XmlObject(location)
+{
+ public string Component { get; } = componentId ?? throw new ArgumentNullException(componentId);
+
+ public IReadOnlyValueListDictionary Textures { get; } = textures ?? throw new ArgumentNullException(nameof(textures));
+}
\ No newline at end of file
diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/EffectsRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/EffectsRepository.cs
new file mode 100644
index 0000000..32e08dd
--- /dev/null
+++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/EffectsRepository.cs
@@ -0,0 +1,103 @@
+using System;
+using PG.StarWarsGame.Engine.IO.Utilities;
+using PG.StarWarsGame.Engine.Utilities;
+
+namespace PG.StarWarsGame.Engine.IO.Repositories;
+
+internal class EffectsRepository(GameRepository baseRepository, IServiceProvider serviceProvider) : MultiPassRepository(baseRepository, serviceProvider)
+{
+ private static readonly string[] LookupPaths =
+ [
+ "DATA\\ART\\SHADERS",
+ "DATA\\ART\\SHADERS\\TERRAIN",
+ // This path is not coded to the engine
+ "DATA\\ART\\SHADERS\\ENGINE",
+ ];
+
+ // The engine does not support ".fxh" as a shader lookup, but as there might be some pre-compiling going on, this should be OK.
+ private static readonly string[] ShaderExtensions = [".fx", ".fxo", ".fxh"];
+
+ private protected override FileFoundInfo MultiPassAction(
+ ReadOnlySpan filePath,
+ ref ValueStringBuilder reusableStringBuilder,
+ ref ValueStringBuilder destination,
+ bool megFileOnly)
+ {
+ var strippedName = StripFileName(filePath);
+
+ if (strippedName.Length > PGConstants.MaxEffectFileName)
+ return default;
+
+ foreach (var ext in ShaderExtensions)
+ {
+ var extSpan = ext.AsSpan();
+
+
+ var fileFoundInfo = FindEffect(
+ strippedName,
+ extSpan,
+ ReadOnlySpan.Empty,
+ ref reusableStringBuilder,
+ ref destination);
+
+ if (fileFoundInfo.FileFound)
+ return fileFoundInfo;
+
+
+ foreach (var directory in LookupPaths)
+ {
+ fileFoundInfo = FindEffect(
+ strippedName,
+ extSpan,
+ directory.AsSpan(),
+ ref reusableStringBuilder,
+ ref destination);
+
+ if (fileFoundInfo.FileFound)
+ return fileFoundInfo;
+ }
+ }
+
+
+ return default;
+ }
+
+ private FileFoundInfo FindEffect(
+ ReadOnlySpan strippedName,
+ ReadOnlySpan extension,
+ ReadOnlySpan