Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
language: [ 'csharp' ]
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup .NET Core SDK
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup .NET
Expand All @@ -40,7 +40,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup .NET
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v5
Expand Down
58 changes: 58 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository purpose

PetroglyphTools is a set of .NET libraries (published as `AlamoEngineTools.*` NuGet packages) for reading and writing the proprietary file formats used by Petroglyph's Alamo / Glyphx engines — most notably Star Wars: Empire at War. Each format gets its own library; `PG.Commons` is the shared foundation.

## Solution layout

The solution file is **`PetroglyphTools.slnx`** (the new XML solution format — not `.sln`). Each format library lives in a top-level folder containing two sibling projects: `PG.<Name>` (production) and `PG.<Name>.Test` (xUnit v3 test runner). Notable cross-cutting projects:

- `PG.Commons` — CRC32 hashing, numerics, `ServiceBase`, file-name validation, stream/string utilities. Foundation for everything else.
- `PG.StarWarsGame.Files` — abstract `PetroglyphFileHolder<TModel, TFileInfo>` and `PetroglyphFileInformation` record types that all file holders extend.
- `PG.Testing` — `PGTestBase` (built on `AnakinRaW.CommonUtilities.Testing.TestBaseWithFileSystem` + Testably.Abstractions for an in-memory `IFileSystem`).
- `PG.StarWarsGame.Files.Testing` — shared file-builder test base.
- `PG.Benchmarks` — BenchmarkDotNet harness (not in the slnx test pipeline).

Format libraries: `PG.StarWarsGame.Files.{MEG,DAT,MTD,Xml,AnimationSfxMap}` and `PG.StarWarsGame.Localisation`. The `Xml` projects are present in the slnx but currently have `<Build Project="false" />` — they are not built or tested by the default solution build.

## Common commands

Run from the repo root. The repo uses `Microsoft.Testing.Platform` (declared in `global.json`), so test projects build as executables and `dotnet test` invokes MTP rather than VSTest.

```pwsh
dotnet build --configuration Release
dotnet test --configuration Release # full test suite (matches CI)
dotnet test PG.Commons/PG.Commons.Test # tests for one project
dotnet test PG.Commons/PG.Commons.Test --filter "FullyQualifiedName~PgFileNameUtilitiesTest"
dotnet pack --configuration Release --output ./packages # produces NuGet packages under bin/Packages/
```

Benchmarks are run by executing the `PG.Benchmarks` project directly (`dotnet run -c Release --project PG.Benchmarks`).

## Target frameworks

- Production libraries: `netstandard2.0;netstandard2.1;net10.0` (some are `netstandard2.0;netstandard2.1` only — e.g. `PG.StarWarsGame.Files`).
- Test projects: `net8.0;net10.0`, plus `net481` on Windows.
- `Directory.Build.props` enforces `Nullable=enable`, `ImplicitUsings=disable`, `LangVersion=latest`, and `EnableNETAnalyzers=true`. Most production projects additionally set `TreatWarningsAsErrors=true` and `GenerateDocumentationFile=true` — XML doc comments on public API are mandatory; missing-doc warnings will fail the build.
- Modern language features on `netstandard2.0` are enabled via the `Nullable`, `IsExternalInit`, and `Required` polyfill packages — do not remove these references when editing csproj files.

## Architectural conventions

**Service registration via `*ServiceContribution` classes.** Each library exposes a static class (e.g. `MegServiceContribution.SupportMEG(IServiceCollection)`, `DatServiceContribution.SupportDAT(...)`, `PetroglyphCommons.ContributeServices(...)`) that registers all of its services in DI. Consumers compose the libraries they need; tests call the relevant `Support*` extension from a `Common*TestBase` deriving from `PGTestBase`. **When adding a new service, register it in the corresponding `*ServiceContribution` method** — there is no auto-discovery.

**`ServiceBase` for services, `PetroglyphFileHolder<TModel, TFileInfo>` for file containers.** Services derive from `PG.Commons.Services.ServiceBase`, which captures `IServiceProvider`, `IFileSystem` (always resolved from DI — never use `System.IO` directly so the in-memory test FS works), and an `ILogger`. File holders derive from `PetroglyphFileHolder<TModel, TFileInfo>` and pair a parsed model with a `PetroglyphFileInformation` record (a `record` so it supports `with` expressions; the holder stores its own copy and disposes it).

**MEG packable files.** `PetroglyphMegPackableFileInformation` skips the on-disk existence check when `IsInsideMeg` is true. When writing code that constructs file information for content extracted from a MEG, use this subtype rather than `PetroglyphFileInformation` directly.

**MEG versions.** The MEG library handles three on-disk format versions (`MegFileVersion.V1/V2/V3`) — V1 is what EaW/FoC ship; V3 may be encrypted. Binary readers/writers are split into `Binary/Reader/V1/...` and `Binary/Construction/V1/...` subfolders; do not assume there is only one version when adding code under `Binary/`.

**CRC32 is foundational.** Many Petroglyph formats key entries by CRC32 of an ASCII (uppercased) name. `ICrc32HashingService` and `CrcUtilities` in `PG.Commons` are the canonical entry points; do not introduce a second CRC32 implementation.

## Branching & release

- Day-to-day work targets **`develop`** (CI: `test.yml` runs build+test on Windows and Ubuntu for PRs into `develop`).
- Merges into **`master`** trigger `release.yml`: it runs the test workflow, then `dotnet pack`, pushes packages to nuget.org, and creates a GitHub release tagged `v{SemVer2}`. Versioning is computed by **Nerdbank.GitVersioning** from `version.json` — do not hand-edit version numbers in csproj files.
- The `.vscode/settings.json` references `PetroglyphTools.sln`, which does not exist in the repo. Use the `.slnx` file (or open the folder) instead.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Condition="!Exists('packages.config')">
<PrivateAssets>all</PrivateAssets>
<Version>3.9.50</Version>
<Version>3.10.85</Version>
</PackageReference>
<PackageReference Include="SauceControl.InheritDoc" Version="2.0.2" PrivateAssets="all"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300">
Expand Down
8 changes: 4 additions & 4 deletions PG.Benchmarks/PG.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<TargetFrameworks>net10.0</TargetFrameworks>
Expand All @@ -19,10 +19,10 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
<PackageReference Include="System.Buffers" Version="4.6.1" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.4.0" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.6.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion PG.Commons/PG.Commons.Test/PG.Commons.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GitHubActionsTestLogger" Version="3.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.2.3" />
<PackageReference Include="IsExternalInit" Version="1.0.3">
Expand Down
6 changes: 3 additions & 3 deletions PG.Commons/PG.Commons/Data/CrcBasedEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace PG.Commons.Data;
public sealed class CrcBasedEqualityComparer<T> : IEqualityComparer<T> where T : IHasCrc32
{
/// <summary>
/// Returns a default equality comparer for the type specified by the generic argument.
/// Gets the default equality comparer for the type specified by the generic argument.
/// </summary>
public static readonly CrcBasedEqualityComparer<T> Instance = new();

Expand All @@ -45,8 +45,8 @@ public sealed class CrcBasedEqualityComparer<T> : IEqualityComparer<T> where T :
/// <item><description>The <see cref="IHasCrc32.Crc32"/> values of <paramref name="x"/> and <paramref name="y"/> are equal.</description></item>
/// </list>
/// </remarks>
/// <param name="x">The first object of type <typeparamref name="T"/> to compare.</param>
/// <param name="y">The second object of type <typeparamref name="T"/> to compare.</param>
/// <param name="x">The first object to compare.</param>
/// <param name="y">The second object to compare.</param>
/// <returns>
/// <see langword="true"/> if the specified objects are both <see langword="null"/>,
/// are the same reference, or have equal CRC32 checksums; otherwise, <see langword="false"/>.
Expand Down
2 changes: 1 addition & 1 deletion PG.Commons/PG.Commons/Data/IHasCrc32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace PG.Commons.Data;

/// <summary>
/// An interface representing data that has a CRC32 checksum.
/// Represents data that has a CRC32 checksum.
/// </summary>
/// <remarks>
/// It is up to the implementation to decide which properties are considered for the CRC32 checksum.
Expand Down
13 changes: 7 additions & 6 deletions PG.Commons/PG.Commons/Hashing/Crc32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public override string ToString()
}

/// <inheritdoc cref="object.ToString()"/>
/// <param name="asSignedInteger">When <see langword="true"/>, the checksum is represented by a signed integer; unsigned otherwise.</param>
/// <param name="asSignedInteger"><see langword="true"/> to represent the checksum as a signed integer; otherwise, <see langword="false"/>.</param>
public string ToString(bool asSignedInteger)
{
var sb = new StringBuilder("CRC: ");
Expand Down Expand Up @@ -95,7 +95,7 @@ public override int GetHashCode()
/// <summary>
/// Returns the CRC32 checksum as a byte array.
/// </summary>
/// <returns>The CRC32 checksum as a byte array.</returns>
/// <returns>The CRC32 checksum in little endian byte order.</returns>
public unsafe byte[] GetBytes()
{
Span<byte> data = stackalloc byte[sizeof(Crc32)];
Expand All @@ -106,6 +106,7 @@ public unsafe byte[] GetBytes()
/// <summary>
/// Writes the CRC32 checksum into a span of bytes in little endian.
/// </summary>
/// <param name="destination">The span to write the checksum into.</param>
public void GetBytes(Span<byte> destination)
{
BinaryPrimitives.WriteUInt32LittleEndian(destination, _checksum);
Expand Down Expand Up @@ -178,15 +179,15 @@ public void GetBytes(Span<byte> destination)
}

/// <summary>
/// Defines an implicit conversion of an CRC32 checksum to an <see cref="uint"/>.
/// Defines an explicit conversion of a CRC32 checksum to a <see cref="uint"/>.
/// </summary>
/// <param name="crc">The checksum data.</param>
/// <param name="crc">The checksum to convert.</param>
public static explicit operator uint(Crc32 crc) => crc._checksum;

/// <summary>
/// Defines an implicit conversion of an CRC32 checksum to an <see cref="int"/>, which might be negative.
/// Defines an explicit conversion of a CRC32 checksum to an <see cref="int"/>, which might be negative.
/// </summary>
/// <param name="crc">The checksum data.</param>
/// <param name="crc">The checksum to convert.</param>
public static explicit operator int(Crc32 crc)
{
unchecked
Expand Down
8 changes: 4 additions & 4 deletions PG.Commons/PG.Commons/Numerics/Vector2Int.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ namespace PG.Commons.Numerics;
public int Second { get; }

/// <summary>
/// Constructs a vector from the given <see cref="ReadOnlySpan{T}"/>. If the span does not contain enough elements,
/// the default integer value 0 is used to initialize the respecting component.
/// Initializes a new instance of the <see cref="Vector2Int"/> struct from the given <see cref="ReadOnlySpan{T}"/>.
/// If the span does not contain enough elements, the default integer value 0 is used to initialize the respective component.
/// </summary>
/// <param name="values">The span of elements to assign to the vector.</param>
public Vector2Int(ReadOnlySpan<int> values)
Expand All @@ -32,7 +32,7 @@ public Vector2Int(ReadOnlySpan<int> values)
}

/// <summary>
/// Creates a vector whose elements have the specified values.
/// Initializes a new instance of the <see cref="Vector2Int"/> struct whose elements have the specified values.
/// </summary>
/// <param name="first">The value to assign to the <see cref="First"/> field.</param>
/// <param name="second">The value to assign to the <see cref="Second"/> field.</param>
Expand All @@ -56,7 +56,7 @@ public bool Equals(Vector2Int other)
/// Returns a value that indicates whether this instance and a specified object are equal.
/// </summary>
/// <param name="obj">The object to compare with the current instance.</param>
/// <returns><see langword="true"/> if the current instance and <paramref name="obj"/> are equal; otherwise, <see langword="false"/>. If obj is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
/// <returns><see langword="true"/> if the current instance and <paramref name="obj"/> are equal; otherwise, <see langword="false"/>. If <paramref name="obj"/> is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
public override bool Equals(object? obj)
{
return obj is Vector2Int other && Equals(other);
Expand Down
8 changes: 4 additions & 4 deletions PG.Commons/PG.Commons/Numerics/Vector3Int.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ namespace PG.Commons.Numerics;
public int Third { get; }

/// <summary>
/// Constructs a vector from the given <see cref="ReadOnlySpan{T}"/>. If the span does not contain enough elements,
/// the default integer value 0 is used to initialize the respecting component.
/// Initializes a new instance of the <see cref="Vector3Int"/> struct from the given <see cref="ReadOnlySpan{T}"/>.
/// If the span does not contain enough elements, the default integer value 0 is used to initialize the respective component.
/// </summary>
/// <param name="values">The span of elements to assign to the vector.</param>
public Vector3Int(ReadOnlySpan<int> values)
Expand All @@ -39,7 +39,7 @@ public Vector3Int(ReadOnlySpan<int> values)
}

/// <summary>
/// Creates a vector whose elements have the specified values.
/// Initializes a new instance of the <see cref="Vector3Int"/> struct whose elements have the specified values.
/// </summary>
/// <param name="first">The value to assign to the <see cref="First"/> field.</param>
/// <param name="second">The value to assign to the <see cref="Second"/> field.</param>
Expand All @@ -65,7 +65,7 @@ public bool Equals(Vector3Int other)
/// Returns a value that indicates whether this instance and a specified object are equal.
/// </summary>
/// <param name="obj">The object to compare with the current instance.</param>
/// <returns><see langword="true"/> if the current instance and <paramref name="obj"/> are equal; otherwise, <see langword="false"/>. If obj is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
/// <returns><see langword="true"/> if the current instance and <paramref name="obj"/> are equal; otherwise, <see langword="false"/>. If <paramref name="obj"/> is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
public override bool Equals(object? obj)
{
return obj is Vector3Int other && Equals(other);
Expand Down
8 changes: 4 additions & 4 deletions PG.Commons/PG.Commons/Numerics/Vector4Int.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ namespace PG.Commons.Numerics;
public int Fourth { get; }

/// <summary>
/// Constructs a vector from the given <see cref="ReadOnlySpan{T}"/>. If the span does not contain enough elements,
/// the default integer value 0 is used to initialize the respecting component.
/// Initializes a new instance of the <see cref="Vector4Int"/> struct from the given <see cref="ReadOnlySpan{T}"/>.
/// If the span does not contain enough elements, the default integer value 0 is used to initialize the respective component.
/// </summary>
/// <param name="values">The span of elements to assign to the vector.</param>
public Vector4Int(ReadOnlySpan<int> values)
Expand All @@ -46,7 +46,7 @@ public Vector4Int(ReadOnlySpan<int> values)
}

/// <summary>
/// Creates a vector whose elements have the specified values.
/// Initializes a new instance of the <see cref="Vector4Int"/> struct whose elements have the specified values.
/// </summary>
/// <param name="first">The value to assign to the <see cref="First"/> field.</param>
/// <param name="second">The value to assign to the <see cref="Second"/> field.</param>
Expand Down Expand Up @@ -74,7 +74,7 @@ public bool Equals(Vector4Int other)
/// Returns a value that indicates whether this instance and a specified object are equal.
/// </summary>
/// <param name="obj">The object to compare with the current instance.</param>
/// <returns><see langword="true"/> if the current instance and <paramref name="obj"/> are equal; otherwise, <see langword="false"/>. If obj is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
/// <returns><see langword="true"/> if the current instance and <paramref name="obj"/> are equal; otherwise, <see langword="false"/>. If <paramref name="obj"/> is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
public override bool Equals(object? obj)
{
return obj is Vector4Int other && Equals(other);
Expand Down
12 changes: 6 additions & 6 deletions PG.Commons/PG.Commons/PG.Commons.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AnakinRaW.CommonUtilities" Version="13.0.23" />
<PackageReference Include="AnakinRaW.CommonUtilities.FileSystem" Version="13.0.23" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
<PackageReference Include="System.IO.Hashing" Version="10.0.8" />
<PackageReference Include="AnakinRaW.CommonUtilities" Version="13.1.2" />
<PackageReference Include="AnakinRaW.CommonUtilities.FileSystem" Version="13.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
<PackageReference Include="System.IO.Hashing" Version="10.0.9" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" Condition="$(TargetFramework) == 'netstandard2.0'" />
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.8" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.9" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="Nullable" Version="1.3.1" Condition="$(TargetFramework) == 'netstandard2.0'">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
2 changes: 1 addition & 1 deletion PG.Commons/PG.Commons/Services/ServiceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace PG.Commons.Services;

/// <summary>
/// Base class for services.
/// Provides a base class for services.
/// </summary>
public abstract class ServiceBase : DisposableObject
{
Expand Down
Loading
Loading