Skip to content

Commit c88191e

Browse files
AnakinRaWclaude
andauthored
Full Linux support (#42)
* fix debug disposal * Handle null `TextureData` during `GuiDialog` initialization. * Engine support for finding files on linux systems the way the game would do it wiht abstractions layers like Wine or Valve Proton * Add support for the `--useDefaultBaseline` option and improve baseline selection logic * Remove obsolete filesystem abstraction utilities for directory and file globbing. * Introduce `PetroglyphFileSystem` abstraction for cross-platform filesystem operations. * Refactor `PetroglyphXmlFileParserBase`: remove redundant `IServiceProvider` field, optimize path handling with OS-specific logic. * Add `PG.StarWarsGame.Engine.FileSystem` project to solution * Update dependencies and add reference to `PG.StarWarsGame.Engine.FileSystem` project * Refactor: Replace generic filesystem abstraction with `PetroglyphFileSystem` for consistent, cross-platform path handling across the engine. * Refactor: Simplify `NormalizePath` logic and remove unused return value * Refactor: Use `ValueStringBuilder` in `PathStartsWithDataDirectory` for performance and normalize input paths * Refactor: Replace direct filename handling with `NormalizeFileName` for consistency and re-enable particle name mismatch validation * Refactor path normalization to use PathNormalizer Replaces custom path normalization logic in PetroglyphFileSystem with PathNormalizer from AnakinRaW.CommonUtilities. Moves LinuxDirectorySeparatorNormalizeOptions to a static readonly field. Cleans up unused usings and retains legacy code as comments for reference. * Rename StringExtensions and add Enum.Parse<T> extension Renamed StringExtensions to Extensions. Added a generic Enum.Parse<T> extension method for NETFRAMEWORK builds to simplify enum parsing from strings. * Refactor AudioFileVerifier: move FriendlyName, remove Verify Moved FriendlyName override to class body and removed the Verify method implementation from AudioFileVerifier. * Add comprehensive unit tests for `PG.StarWarsGame.Engine.FileSystem` and include test project in solution * Remove unused test case for volume file names and update path handling in `FileExists` test * Optimize case-insensitive file existence check in `PetroglyphFileSystem` and add additional test cases * Update README with detailed mod verification examples for Windows and Linux * Add Linux-specific build target to release workflow * Fix mod paths in Linux instructions * revert to old impl * Add Linux-specific tests for case-insensitive file existence handling in `PetroglyphFileSystem` * Update README: include `--engine` parameter in examples and add Linux baseline creation example * update dependencies * Refactor file path handling in GetFileInfoFromMasterMeg Check filePath length before allocating ValueStringBuilder to improve efficiency. Update warning log to use filePath.ToString() for overlong paths. Reorganize normalization logic for clarity and ensure proper resource disposal. * rename method to express intent * formatting * Update path normalization for Windows-like behavior on Linux Replaced LinuxDirectorySeparatorNormalizeOptions with PGFileSystemDirectorySeparatorNormalizeOptions to ensure consistent Windows-like path handling on Linux. Updated class documentation to clarify behavior. Refactored IsDirectorySeparator to use defined separator constants for improved clarity. * documentation * Refactor path handling to use platform-specific separators ModPaths and AdditionalFallbackPath are now single string properties using the platform-specific path separator, instead of IList<string>. SettingsBuilder splits and normalizes these paths accordingly. Help texts are updated, and unit tests are added to verify correct path parsing and normalization using a mock file system. * fix reporting of missing texutres * add new algorithm to evaluate * Handle "none" GUI textures and unify repo lookups Refactored texture existence checks by introducing IsNone and GuiSpecialTextureExists helpers. Special cases for "none" textures now avoid false warnings and are consistently cached. Centralized repository lookup logic for Scanlines, ButtonMiddle, and FrameBackground types. Improved error messages by clarifying origin labeling and adjusting error context. * update sub * update verifiers * update sub * update deps * fix false positive for "none" textures * exceptions stack traces from a verifystep now are now shown instead of the unusefull pipeline trace * add an alternate wine-style file lookup algorithm * more consistent path handling * fix and test dot segements handling * update and fix wine file lookup * add linux fs path caching * spacing * search animations based on relative path instead of absolute path. * fix potential memory leaks because of undisposed VSB * implement file system strategies to find files in a case-insensitive manner * add design spec for live virtual file-exists strategy Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * window stategy is default engine for virtual stategy on windows * fix model corrupted findings are incorrectly reported if the the engine had crc collision and loaded a different file. * add implementation plan for live virtual file-exists strategy Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * remove design spec and implementation plan for live virtual file-exists strategy Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * rename to baseDirectory * conditional using * add some test gaps * update baseline --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5deed97 commit c88191e

85 files changed

Lines changed: 4628 additions & 1686 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ jobs:
4747
- name: Create Net Core Release
4848
# use publish for .NET Core
4949
run: dotnet publish ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net10.0 --output ./releases/net10.0 /p:DebugType=None /p:DebugSymbols=false
50+
- name: Create Linux Release
51+
run: dotnet publish ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net10.0 --runtime linux-x64 --self-contained true --output ./releases/linux-x64 /p:DebugType=None /p:DebugSymbols=false
5052
- name: Upload a Build Artifact
5153
uses: actions/upload-artifact@v7
5254
with:
@@ -117,4 +119,5 @@ jobs:
117119
generate_release_notes: true
118120
files: |
119121
./releases/net481/ModVerify.exe
120-
./releases/ModVerify-Net10.zip
122+
./releases/ModVerify-Net10.zip
123+
./releases/linux-x64/ModVerify

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
<ItemGroup>
3535
<PackageReference Update="SauceControl.InheritDoc" Version="2.0.2" />
36-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.201" PrivateAssets="All"/>
36+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.203" PrivateAssets="All"/>
3737
<PackageReference Include="Nerdbank.GitVersioning" Condition="!Exists('packages.config')">
3838
<PrivateAssets>all</PrivateAssets>
3939
<Version>3.9.50</Version>

ModVerify.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
<Project Path="modules/ModdingToolBase/src/Updater/ExternalUpdater.Core/ExternalUpdater.Core.csproj" />
1818
</Folder>
1919
<Folder Name="/PetroglyphTools/">
20+
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem.Test/PG.StarWarsGame.Engine.FileSystem.Test.csproj" />
21+
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/PG.StarWarsGame.Engine.FileSystem.csproj" />
2022
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj" />
2123
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj" />
2224
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj" />

README.md

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,42 @@ In general ModVerify has two operation mods.
8080
1. `verify` Verifying a game or mod
8181
2. `createBaseline` Creating a baseline for a game or mod, that can be used for further verifications in order to verify you did not add more errors to your mods.
8282

83-
### Example
84-
This is an example run configuration that analyzes a specific mod, uses a the FoC basline and writes the output into a dedicated directory:
83+
### Examples
8584

86-
```bash
85+
#### Example 1: Auto-detection with a custom baseline
86+
Analyzes a specific mod, uses the FoC baseline and writes the output into a dedicated directory:
87+
88+
**Windows:**
89+
```bat
8790
.\ModVerify.exe verify --path "C:\My Games\FoC\Mods\MyMod" --outDir "C:\My Games\FoC\Mods\MyMod\verifyResults" --baseline ./focBaseline.json
8891
```
8992

93+
**Linux:**
94+
```bash
95+
./ModVerify verify \
96+
--path "/home/user/games/FoC/Mods/MyMod" \
97+
--outDir "/home/user/games/FoC/Mods/MyMod/verifyResults" \
98+
--baseline ./focBaseline.json
99+
```
100+
101+
#### Example 2: Manual mod setup with sub-mods, EaW fallback and default baseline
102+
Uses manual mod setup, including sub-mods and the EaW fallback game, and uses the default embedded baseline:
103+
104+
**Windows:**
105+
```bat
106+
.\ModVerify.exe verify --mods "C:\My Games\FoC\Mods\MySubMod;C:\My Games\FoC\Mods\MyMod" --game "C:\My Games\FoC" --fallbackGame "C:\My Games\EaW" --engine FOC --useDefaultBaseline
107+
```
108+
109+
**Linux:**
110+
```bash
111+
./ModVerify verify \
112+
--mods "/home/user/games/FoC/Mods/MySubMod:/home/user/games/FoC/Mods/MyMod" \
113+
--game "/home/user/games/FoC" \
114+
--fallbackGame "/home/user/games/EaW" \
115+
--engine FOC \
116+
--useDefaultBaseline
117+
```
118+
90119
---
91120

92121
## Available Checks
@@ -116,6 +145,14 @@ The following verifiers are currently implemented:
116145
If you want to create your own baseline use the `createBaseline` option.
117146

118147
### Example
148+
149+
**Windows**
119150
```bash
120151
ModVerify.exe createBaseline --outFile myBaseline.json --path "C:\My Games\FoC\Mods\MyMod"
121152
```
153+
**Linux**
154+
```bash
155+
./ModVerify createBaseline \
156+
--outFile myBaseline.json \
157+
--path "C:\My Games\FoC\Mods\MyMod"
158+
```

src/ModVerify.CliApp/App/ModVerifyApplicationAction.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO.Abstractions;
3+
using System.Linq;
34
using System.Threading.Tasks;
45
using AET.ModVerify.App.GameFinder;
56
using AET.ModVerify.App.Reporting;
@@ -9,6 +10,7 @@
910
using AET.ModVerify.Reporting.Baseline;
1011
using AET.ModVerify.Reporting.Suppressions;
1112
using AnakinRaW.ApplicationBase;
13+
using AnakinRaW.CommonUtilities.SimplePipeline;
1214
using Microsoft.Extensions.DependencyInjection;
1315
using Microsoft.Extensions.Logging;
1416

@@ -74,9 +76,34 @@ public async Task<int> ExecuteAsync()
7476
var verificationResult = await VerifyTargetAsync(verificationTarget)
7577
.ConfigureAwait(false);
7678

79+
Console.WriteLine();
80+
81+
switch (verificationResult.Status)
82+
{
83+
case VerificationCompletionStatus.Cancelled:
84+
return ModVerifyConstants.VerifyCancelled;
85+
case VerificationCompletionStatus.Failed:
86+
return ReportVerificationFailure(verificationResult.Exception!);
87+
}
88+
7789
return await ProcessResult(verificationResult);
7890
}
7991

92+
private int ReportVerificationFailure(Exception verificationException)
93+
{
94+
var exceptionToReport = verificationException switch
95+
{
96+
AggregateException aggregate => aggregate.InnerExceptions.FirstOrDefault() ?? aggregate,
97+
StepFailureException stepFailure => stepFailure.FailedSteps.First().Error!,
98+
_ => verificationException
99+
};
100+
101+
ConsoleUtilities.WriteApplicationFatalError(_appEnvironment.ApplicationName, exceptionToReport);
102+
Logger?.LogError(exceptionToReport, exceptionToReport.Message);
103+
104+
return exceptionToReport.HResult;
105+
}
106+
80107
protected abstract Task<int> ProcessResult(VerificationResult result);
81108

82109
protected abstract VerificationBaseline GetBaseline(VerificationTarget verificationTarget);
@@ -112,6 +139,10 @@ private async Task<VerificationResult> VerifyTargetAsync(VerificationTarget veri
112139
case VerificationCompletionStatus.Cancelled:
113140
Logger?.LogWarning(ModVerifyConstants.ConsoleEventId, "Verification was cancelled.");
114141
break;
142+
case VerificationCompletionStatus.Failed:
143+
progressReporter.ReportError("Verification failed!",
144+
$"An unexpected error occurred while verifying '{verificationTarget.Name}'.");
145+
break;
115146
case VerificationCompletionStatus.Completed:
116147
default:
117148
Logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Verification completed successfully.");

src/ModVerify.CliApp/App/VerifyAction.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ protected override VerificationBaseline GetBaseline(VerificationTarget verificat
6161
{
6262
Console.WriteLine();
6363
ModVerifyConsoleUtilities.WriteBaselineInfo(baseline, baselinePath);
64-
Logger?.LogDebug("Using baseline {Baseline} from location '{Path}'", baseline.ToString(), baselinePath);
64+
Logger?.LogDebug("Using baseline {Baseline} from location '{Path}'",
65+
baseline.ToString(), baselinePath ?? "Embedded");
6566
Console.WriteLine();
6667
}
6768
return baseline;

src/ModVerify.CliApp/ModVerify.CliApp.csproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
3838
<PackageReference Include="AlamoEngineTools.SteamAbstraction" Version="5.0.7" />
3939
<PackageReference Include="CommandLineParser" Version="2.9.1" />
40-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
41-
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.5" />
42-
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
43-
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
44-
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.5" />
40+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
41+
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.7" />
42+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
43+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.7" />
44+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.7" />
4545
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
4646
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
4747
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
@@ -51,7 +51,7 @@
5151
</ItemGroup>
5252

5353
<ItemGroup>
54-
<PackageReference Include="Costura.Fody" Version="6.0.0">
54+
<PackageReference Include="Costura.Fody" Version="6.1.0">
5555
<PrivateAssets>all</PrivateAssets>
5656
</PackageReference>
5757
<PackageReference Include="PolySharp" Version="1.15.0">
@@ -62,15 +62,15 @@
6262

6363
<!-- Exclude System.Index from all dependencies, excluding Microsoft.Bcl.Memory -->
6464
<ItemGroup>
65-
<PackageReference Include="Vanara.Core" Version="5.0.1" Condition="'$(TargetFramework)' == 'net481'">
65+
<PackageReference Include="Vanara.Core" Version="5.0.4" Condition="'$(TargetFramework)' == 'net481'">
6666
<ExcludeAssets>compile</ExcludeAssets>
6767
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6868
</PackageReference>
69-
<PackageReference Include="IndexRange" Version="1.1.0" Condition="'$(TargetFramework)' == 'net481'">
69+
<PackageReference Include="IndexRange" Version="1.1.1" Condition="'$(TargetFramework)' == 'net481'">
7070
<ExcludeAssets>compile</ExcludeAssets>
7171
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
7272
</PackageReference>
73-
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.5" Condition="'$(TargetFramework)' == 'net481'" />
73+
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.7" Condition="'$(TargetFramework)' == 'net481'" />
7474
</ItemGroup>
7575

7676
<ItemGroup>

src/ModVerify.CliApp/ModVerifyConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal static class ModVerifyConstants
1212
public const int Success = 0;
1313
public const int CompletedWithFindings = 1;
1414
public const int ErrorBadArguments = 0xA0;
15+
public const int VerifyCancelled = -1;
1516

1617
public static readonly EventId ConsoleEventId = new(ConsoleEventIdValue, "LogToConsole");
1718
}

src/ModVerify.CliApp/Reporting/BaselineSelector.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public VerificationBaseline SelectBaseline(VerificationTarget verificationTarget
4242
}
4343
}
4444

45-
if (!settings.ReportSettings.SearchBaselineLocally)
45+
if (settings.ReportSettings is { SearchBaselineLocally: false, UseDefaultBaseline: false })
4646
{
4747
_logger?.LogDebug(ModVerifyConstants.ConsoleEventId,
4848
"No baseline path specified and local search is not enabled. Using empty baseline.");
@@ -134,7 +134,7 @@ internal static VerificationBaseline LoadEmbeddedBaseline(GameEngineType engineT
134134
private VerificationBaseline FindBaselineNonInteractive(VerificationTarget target, out string? usedPath)
135135
{
136136
if (_baselineFactory.TryFindBaselineInDirectory(
137-
target.Location.TargetPath,
137+
target.Location.TargetPath,
138138
b => IsBaselineCompatible(b, target),
139139
out var baseline,
140140
out usedPath))
@@ -144,6 +144,20 @@ private VerificationBaseline FindBaselineNonInteractive(VerificationTarget targe
144144
}
145145
_logger?.LogTrace("No baseline file found in taget path '{TargetPath}'.", target.Location.TargetPath);
146146
usedPath = null;
147+
if (settings.ReportSettings.UseDefaultBaseline)
148+
{
149+
try
150+
{
151+
var defaultBaseline = LoadEmbeddedBaseline(target.Engine);
152+
_logger?.LogInformation(ModVerifyConstants.ConsoleEventId, "Automatically applying default embedded baseline for engine '{Engine}'.", target.Engine);
153+
return defaultBaseline;
154+
}
155+
catch (InvalidBaselineException)
156+
{
157+
throw new InvalidOperationException(
158+
"Invalid baseline packed along ModVerify App. Please reach out to the creators. Thanks!");
159+
}
160+
}
147161
return VerificationBaseline.Empty;
148162
}
149163

0 commit comments

Comments
 (0)