Skip to content

Commit d8e2bb8

Browse files
authored
Merge pull request #51 from AlamoEngine-Tools/develop
Quality Update
2 parents e2b3095 + b6788f4 commit d8e2bb8

159 files changed

Lines changed: 7045 additions & 4078 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: 72 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,8 @@ on:
1212
required: true
1313

1414
env:
15-
TOOL_PROJ_PATH: ./src/ModVerify.CliApp/ModVerify.CliApp.csproj
16-
CREATOR_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj
17-
UPLOADER_PROJ_PATH: ./modules/ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj
18-
TOOL_EXE: ModVerify.exe
19-
UPDATER_EXE: AnakinRaW.ExternalUpdater.exe
20-
MANIFEST_CREATOR: AnakinRaW.ApplicationManifestCreator.dll
21-
SFTP_UPLOADER: AnakinRaW.FtpUploader.dll
22-
ORIGIN_BASE: https://republicatwar.com/downloads/ModVerify
23-
ORIGIN_BASE_PART: downloads/ModVerify/
15+
# Per-run release channel. Every other per-app value lives in update-tooling.jsonc and is loaded
16+
# into the environment by the "Load app config" step in each job below.
2417
BRANCH_NAME: ${{ github.event.inputs.branch || 'stable' }}
2518

2619
jobs:
@@ -29,6 +22,25 @@ jobs:
2922
test:
3023
uses: ./.github/workflows/test.yml
3124

25+
# End-to-end self-update test.
26+
integration-test:
27+
name: Integration test
28+
needs: [test]
29+
runs-on: windows-latest
30+
steps:
31+
- uses: actions/checkout@v6
32+
with:
33+
fetch-depth: 0
34+
submodules: recursive
35+
- uses: actions/setup-dotnet@v5
36+
with:
37+
dotnet-version: 10.0.x
38+
- name: Run self-update integration suite
39+
shell: pwsh
40+
run: |
41+
$c = Get-Content -Raw ./update-tooling.jsonc | ConvertFrom-Json
42+
& "$($c.scriptsDir)/Test-LocalUpdateSuite.ps1" -ConfigPath ./update-tooling.jsonc
43+
3244
pack:
3345
name: Pack
3446
needs: [test]
@@ -39,6 +51,11 @@ jobs:
3951
with:
4052
fetch-depth: 0
4153
submodules: recursive
54+
- name: Load app config
55+
shell: pwsh
56+
run: |
57+
$c = Get-Content -Raw ./update-tooling.jsonc | ConvertFrom-Json
58+
"TOOL_PROJ_PATH=$($c.toolProject)" >> $env:GITHUB_ENV
4259
- name: Setup .NET
4360
uses: actions/setup-dotnet@v5
4461
- name: Create NetFramework Release
@@ -47,8 +64,6 @@ jobs:
4764
- name: Create Net Core Release
4865
# use publish for .NET Core
4966
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
5267
- name: Upload a Build Artifact
5368
uses: actions/upload-artifact@v7
5469
with:
@@ -59,10 +74,13 @@ jobs:
5974

6075
deploy:
6176
name: Deploy
62-
# Deploy on push to main or manual trigger
77+
# Stable deploys are gated to 'main'. Non-stable channels (beta, canary, etc.) can be
78+
# workflow_dispatched from any branch.
6379
if: |
64-
(github.ref == 'refs/heads/main' && github.event_name == 'push') || github.event_name == 'workflow_dispatch'
65-
needs: [pack]
80+
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
81+
(github.event_name == 'workflow_dispatch' &&
82+
(github.event.inputs.branch != 'stable' || github.ref == 'refs/heads/main'))
83+
needs: [pack, integration-test]
6684
runs-on: ubuntu-latest
6785
steps:
6886
- name: Checkout sources
@@ -75,37 +93,54 @@ jobs:
7593
name: Binary Releases
7694
path: ./releases
7795

78-
# Deploy .NET Framework self-update release
7996
- name: Setup .NET
8097
uses: actions/setup-dotnet@v5
8198
with:
8299
dotnet-version: 10.0.x
83-
- name: Build Creator
84-
run: dotnet build ${{env.CREATOR_PROJ_PATH}} --configuration Release --output ./dev
85-
- name: Build Uploader
86-
run: dotnet build ${{env.UPLOADER_PROJ_PATH}} --configuration Release --output ./dev
87-
- name: Create binaries directory
88-
run: mkdir -p ./deploy
89-
- name: Copy self-update files
100+
101+
- name: Load app config
102+
shell: pwsh
103+
# Per-app release values come from update-tooling.jsonc.
104+
run: |
105+
$c = Get-Content -Raw ./update-tooling.jsonc | ConvertFrom-Json
106+
"PUBLISH_SCRIPT=$($c.scriptsDir)/Publish-Release.ps1" >> $env:GITHUB_ENV
107+
"TOOL_EXE=$($c.appExe)" >> $env:GITHUB_ENV
108+
"UPDATER_EXE=$($c.updaterExe)" >> $env:GITHUB_ENV
109+
"EMBEDDED_TRUST_CERT=$($c.embeddedTrustCert)" >> $env:GITHUB_ENV
110+
"ORIGIN_BASE=$($c.origin)" >> $env:GITHUB_ENV
111+
"SFTP_BASE_PATH=$($c.sftpBasePath)" >> $env:GITHUB_ENV
112+
"SFTP_HOST=$($c.sftpHost)" >> $env:GITHUB_ENV
113+
"SFTP_PORT=$($c.sftpPort)" >> $env:GITHUB_ENV
114+
"NEXT_ORIGIN_BASE=$($c.nextOrigin)" >> $env:GITHUB_ENV
115+
"NEXT_SFTP_BASE_PATH=$($c.nextSftpBasePath)" >> $env:GITHUB_ENV
116+
"COMPAT_UPDATER=$($c.compatUpdater)" >> $env:GITHUB_ENV
117+
118+
- name: Publish self-update release
119+
shell: pwsh
90120
run: |
91-
cp ./releases/net481/${{env.TOOL_EXE}} ./deploy/
92-
cp ./releases/net481/${{env.UPDATER_EXE}} ./deploy/
93-
- name: Create Manifest
94-
run: dotnet ./dev/${{env.MANIFEST_CREATOR}} -a deploy/${{env.TOOL_EXE}} --appDataFiles deploy/${{env.UPDATER_EXE}} --origin ${{env.ORIGIN_BASE}} -o ./deploy -b ${{env.BRANCH_NAME}}
95-
- name: Upload Build
96-
run: dotnet ./dev/${{env.SFTP_UPLOADER}} ftp --host $host --port $port -u ${{secrets.SFTP_USER}} -p ${{secrets.SFTP_PASSWORD}} --base $base_path -s $source
97-
env:
98-
host: republicatwar.com
99-
port: 1579
100-
base_path: ${{env.ORIGIN_BASE_PART}}
101-
source: ./deploy
121+
& $env:PUBLISH_SCRIPT `
122+
-AppExePath "./releases/net481/$env:TOOL_EXE" `
123+
-UpdaterExePath "./releases/net481/$env:UPDATER_EXE" `
124+
-EmbeddedTrustCertPath "$env:EMBEDDED_TRUST_CERT" `
125+
-Origin "$env:ORIGIN_BASE" `
126+
-SftpBasePath "$env:SFTP_BASE_PATH" `
127+
-Branch "$env:BRANCH_NAME" `
128+
-SigningPfxBase64 "${{ secrets.UPDATER_SIGNING_PFX_B64 }}" `
129+
-SigningPfxPassword "${{ secrets.UPDATER_SIGNING_PFX_PASSWORD }}" `
130+
-SftpHost "$env:SFTP_HOST" `
131+
-SftpPort $env:SFTP_PORT `
132+
-SftpUser "${{ secrets.SFTP_USER }}" `
133+
-SftpPassword "${{ secrets.SFTP_PASSWORD }}" `
134+
-NextOrigin "$env:NEXT_ORIGIN_BASE" `
135+
-NextSftpBasePath "$env:NEXT_SFTP_BASE_PATH" `
136+
-CompatibilityUpdaterExePath "$env:COMPAT_UPDATER"
102137
103138
# Deploy .NET Core and .NET Framework apps to Github
104139
- name: Create NET Core .zip
105140
# Change into the artifacts directory to avoid including the directory itself in the zip archive
106141
working-directory: ./releases/net10.0
107142
run: zip -r ../ModVerify-Net10.zip .
108-
- uses: dotnet/nbgv@v0.5.1
143+
- uses: dotnet/nbgv@v0.5.2
109144
id: nbgv
110145
- name: Create GitHub release
111146
# Create a GitHub release on push to main only
@@ -117,7 +152,6 @@ jobs:
117152
tag_name: v${{ steps.nbgv.outputs.SemVer2 }}
118153
token: ${{ secrets.GITHUB_TOKEN }}
119154
generate_release_notes: true
120-
files: |
121-
./releases/net481/ModVerify.exe
122-
./releases/ModVerify-Net10.zip
123-
./releases/linux-x64/ModVerify
155+
files: |
156+
./releases/net481/${{ env.TOOL_EXE }}
157+
./releases/ModVerify-Net10.zip

.github/workflows/test.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,21 @@ jobs:
2727
with:
2828
dotnet-version: 10.0.x
2929
- name: Build & Test in Release Mode
30-
run: dotnet test --configuration Release --report-github
30+
run: dotnet test --configuration Release --report-github
31+
32+
test-moddingtoolbase:
33+
name: Test ModdingToolBase
34+
runs-on: windows-latest
35+
steps:
36+
- uses: actions/checkout@v6
37+
with:
38+
fetch-depth: 0
39+
submodules: recursive
40+
- uses: actions/setup-dotnet@v5
41+
with:
42+
dotnet-version: 10.0.x
43+
- name: Test ModdingToolBase (submodule)
44+
shell: pwsh
45+
run: |
46+
$c = Get-Content -Raw ./update-tooling.jsonc | ConvertFrom-Json
47+
& "$($c.scriptsDir)/Test-ModdingToolBase.ps1"

CLAUDE.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## What this is
6+
7+
ModVerify is a .NET CLI tool that statically analyzes mods for *Star Wars: Empire at War* and *Forces of Corruption* by reimplementing parts of the Petroglyph Alamo engine in managed code and running verifiers over the loaded game data. It produces JSON + text reports of XML errors, missing assets, broken model references, malformed SFX samples, etc.
8+
9+
Vanilla EaW is currently not supported; FoC is the primary target.
10+
11+
## Build / test / run
12+
13+
The repo uses the Microsoft Testing Platform runner (configured via `global.json`), not VSTest. `dotnet test` works, but the test project is also an `Exe` and can be invoked directly.
14+
15+
```bash
16+
# Restore submodules (required — ModdingToolBase is a git submodule)
17+
git submodule update --init --recursive
18+
19+
# Build everything
20+
dotnet build ModVerify.slnx
21+
22+
# Run all tests (Release matches CI)
23+
dotnet test --configuration Release
24+
25+
# Run the CLI from source
26+
dotnet run --project src/ModVerify.CliApp -- verify --path "<mod path>" --useDefaultBaseline
27+
28+
# Build the Windows self-updating release (.NET Framework 4.8.1, Costura-packed single exe)
29+
dotnet build src/ModVerify.CliApp/ModVerify.CliApp.csproj -c Release -f net481
30+
31+
# Build the cross-platform release (.NET 10)
32+
dotnet publish src/ModVerify.CliApp/ModVerify.CliApp.csproj -c Release -f net10.0
33+
```
34+
35+
The CLI has two verbs: `verify` and `createBaseline`. Running with no arguments enters interactive mode and prompts for a target. See `README.md` for full option examples.
36+
37+
`deploy-local.ps1` builds the net481 exe, generates an update manifest, and stages it under `.local_deploy/` so the self-update flow can be exercised end-to-end without hitting the real CDN.
38+
39+
## Target frameworks and why
40+
41+
- `src/ModVerify` (the library): `netstandard2.0;netstandard2.1;net10.0` — keeps the core consumable from older tooling.
42+
- `src/ModVerify.CliApp`: `net10.0;net481`. The **net481** build is the user-facing Windows executable (Costura-Fody packs all dependencies into a single `ModVerify.exe`, and the AnakinRaW self-updater is wired in only on this TFM). The **net10.0** build is the cross-platform / CI variant, has no self-updater, and is shipped as a framework-dependent zip (Linux/macOS users run it via `dotnet`).
43+
- Test project: net10.0 always; net481 only on Windows.
44+
45+
When editing the CLI app, be aware that `IsUpdatable()` is conditional on the net481 build — code paths guarded by `IsUpdateableApplication` or `_applicationEnvironment.IsUpdatable()` only run there. PolySharp + `Microsoft.Bcl.Memory` + `IndexRange` are pulled in to make modern C# language features compile against net481.
46+
47+
## Architecture
48+
49+
### Two-layer split: `ModVerify` (library) vs `ModVerify.CliApp` (executable)
50+
51+
The library knows nothing about command-line parsing, console output, file I/O for results, or the updater. The CLI app composes the library with logging (Serilog), DI (`Microsoft.Extensions.DependencyInjection`), the AnakinRaW `ApplicationBase` lifecycle, and reporters. When adding functionality, decide which layer it belongs in: anything reusable by another host (CI integration, GUI, etc.) goes in `ModVerify`; anything tied to console UX, command-line verbs, or the Windows updater goes in `ModVerify.CliApp`.
52+
53+
### Verification pipeline
54+
55+
`GameVerifierService.VerifyAsync` is the entry point from `IGameVerifierService`. It builds a `GameVerifyPipeline` (a `StepRunnerPipelineBase<AsyncStepRunner>` from `AnakinRaW.CommonUtilities.SimplePipeline`) which:
56+
57+
1. Initializes a re-implementation of the game engine (`IPetroglyphStarWarsGameEngine` from the `PG.StarWarsGame.Engine` project under `src/PetroglyphTools/`) against the target's location/engine type. Engine init errors are collected by `GameEngineErrorCollector`, which becomes the first pipeline step.
58+
2. Asks `IGameVerifiersProvider` (default: `DefaultGameVerifiersProvider`) for the set of `GameVerifier` instances to run.
59+
3. Wraps each verifier in a `GameVerifierPipelineStep` and runs them via `AsyncStepRunner` (parallelism controlled by `VerifierServiceSettings.ParallelVerifiers`; 1 falls back to a `SequentialStepRunner`).
60+
4. Aggregates all `VerificationError`s, then applies `VerificationBaseline` and `SuppressionList` filters before returning a `VerificationResult`.
61+
62+
`FailFast` mode is non-trivial: the pipeline checks each thrown `GameVerificationException` against the baseline + suppressions and *swallows* it if every error is already accounted for, so fail-fast does not abort on known issues. Don't simplify this away.
63+
64+
When adding a new verifier: subclass `GameVerifier` (or `GameVerifier<T>` / `NamedGameEntityVerifier`), register it in `DefaultGameVerifiersProvider`, and pick a unique error code prefix (see `Verifiers/VerifierErrorCodes.cs`). `IAlreadyVerifiedCache` (registered via `RegisterVerifierCache()`) is shared across verifiers — use it to skip work that's already been done in the same run (e.g. the same texture being referenced by multiple GameObjects).
65+
66+
### Baselines
67+
68+
A baseline is a frozen JSON snapshot of "errors we already knew about, ignore them." `VerificationBaseline.LatestVersion` is **2.2** and the schema lives at `src/ModVerify/Resources/Schemas/2.2/baseline.json` (embedded). When bumping the baseline format, add a new versioned schema folder rather than mutating the existing one — old baselines on user disks must keep parsing.
69+
70+
`src/ModVerify.CliApp/Resources/Baselines/baseline-foc.json` is the embedded default baseline shipped with the CLI (selected via `--useDefaultBaseline`). The legacy top-level `focBaseline.json` is *not* the embedded one and exists separately; don't conflate them.
71+
72+
### Settings flow (CLI)
73+
74+
```
75+
argv → ModVerifyOptionsParser → ModVerifyOptionsContainer
76+
→ SettingsBuilder.BuildSettings → AppVerifySettings | AppBaselineSettings
77+
→ ModVerifyApplication → VerifyAction | CreateBaselineAction
78+
```
79+
80+
`SelfUpdateableAppLifecycle` (from ModdingToolBase) drives `InitializeAppAsync``CreateAppServices``RunAppAsync`. The app argument parser strips trailing arguments injected by the external updater (see `ModVerifyOptionsParser.StripExternalUpdateResults`) before handing off to CommandLineParser — needed so unknown-argument errors stay strict.
81+
82+
`VerifyVerbOption.WithoutArguments` is the sentinel returned when the user just double-clicks the exe; this triggers interactive target selection in `ConsoleSelector` and enables the interactive update prompt.
83+
84+
### Logging
85+
86+
Two Serilog sinks, configured in `Program.ConfigureLogging`:
87+
88+
- **Console sink** filters by an `EventId.Id == ModVerifyConstants.ConsoleEventIdValue` expression in normal mode (so verifier output stays clean), shows everything from the `AET.ModVerify` namespace at Debug level, and shows everything at Verbose. Fatals are excluded — they're handled by the global exception handler instead.
89+
- **File sink** writes to `ApplicationLocalPath/ModVerify_log.txt` and excludes XML-parser noise (the engine and `XmlFileParser<>` namespaces are reported by the verification pipeline).
90+
91+
When emitting log lines that the user *should* see in normal operation, attach `ModVerifyConstants.ConsoleEventId`. Plain `Logger.LogInformation(...)` without that EventId will be silently dropped at the console.
92+
93+
### Petroglyph engine reimplementation (`src/PetroglyphTools/`)
94+
95+
These projects (`PG.StarWarsGame.Engine`, `PG.StarWarsGame.Engine.FileSystem`, `PG.StarWarsGame.Files.ALO`, `PG.StarWarsGame.Files.ChunkFiles`, `PG.StarWarsGame.Files.XML`, etc.) are vendored copies that will eventually move to the standalone `AlamoEngine-Tools/PetroglyphTools` repo (per `src/PetroglyphTools/README.md`). Treat them as a separate library boundary — changes here may need to land upstream.
96+
97+
### Submodule
98+
99+
`modules/ModdingToolBase` is a git submodule (https://github.com/AnakinRaW/ModdingToolBase). It supplies the shared `ApplicationBase`, the AppUpdaterFramework, the manifest creator, and the FTP uploader used by the deploy pipeline. If you're touching the app lifecycle, updater, or release packaging, the relevant code likely lives there, not in this repo.
100+
101+
## CI
102+
103+
`.github/workflows/test.yml` builds + tests on Windows and Linux against .NET 10. `release.yml` (triggered on push to `main`) builds the two release artifacts (net481 self-updating exe, net10.0 portable zip), generates an update manifest via `ApplicationManifestCreator`, SFTPs the self-update bits to republicatwar.com, and creates a GitHub release tagged with the Nerdbank.GitVersioning SemVer.
104+
105+
Versioning is fully driven by `version.json` + `Nerdbank.GitVersioning` — do not hand-edit assembly versions.

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.203" PrivateAssets="All"/>
36+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300" 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<Solution>
2+
<Folder Name="/.github/">
3+
<File Path=".github/workflows/release.yml" />
4+
<File Path=".github/workflows/test.yml" />
5+
</Folder>
26
<Folder Name="/ModdingToolBase/" />
37
<Folder Name="/ModdingToolBase/AnakinApps/">
48
<Project Path="modules/ModdingToolBase/src/AnakinApps/ApplicationBase.CommandLine/ApplicationBase.CommandLine.csproj" />
@@ -7,6 +11,7 @@
711
</Folder>
812
<Folder Name="/ModdingToolBase/DeployTools/">
913
<Project Path="modules/ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj" />
14+
<Project Path="modules/ModdingToolBase/src/AnakinApps/ApplicationManifestSigner/ApplicationManifestSigner.csproj" />
1015
<Project Path="modules/ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj" />
1116
</Folder>
1217
<Folder Name="/ModdingToolBase/UpdateFrameworks/">
@@ -19,6 +24,8 @@
1924
<Folder Name="/PetroglyphTools/">
2025
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem.Test/PG.StarWarsGame.Engine.FileSystem.Test.csproj" />
2126
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/PG.StarWarsGame.Engine.FileSystem.csproj" />
27+
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.Test/PG.StarWarsGame.Engine.Test.csproj" />
28+
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine.Testing/PG.StarWarsGame.Engine.Testing.csproj" />
2229
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj" />
2330
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj" />
2431
<Project Path="src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj" />
@@ -27,4 +34,5 @@
2734
<Project Path="src/ModVerify.CliApp/ModVerify.CliApp.csproj" />
2835
<Project Path="src/ModVerify/ModVerify.csproj" />
2936
<Project Path="test/ModVerify.CliApp.Test/ModVerify.CliApp.Test.csproj" />
37+
<Project Path="test/ModVerify.Test/ModVerify.Test.csproj" />
3038
</Solution>

0 commit comments

Comments
 (0)