Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
7a1c200
Implement custom mission support and game mode updates (#922)
SadPencil Feb 13, 2026
f1c2136
Replace pipe-delimited custom mission supplement config with numbered…
Copilot Feb 13, 2026
5189944
Broadcast and filter game options (#874)
11EJDE11 Feb 16, 2026
26b0b23
Replace BitConverter with BinaryPrimitives for explicit endianness (#…
Copilot Feb 17, 2026
1e65cea
Upgrade Polyfill to v9.9.0 (#931)
SadPencil Feb 17, 2026
371a742
Post update for "Broadcast and filter game options" (#932)
11EJDE11 Feb 17, 2026
7241760
Document GameSessionCheckBox and GameSessionDropDown INI settings (#929)
Copilot Feb 17, 2026
57b50ce
Document optional client enhancements (#933)
SadPencil Feb 17, 2026
fd437b5
Revert "Fix debug assertion messages for map file extension"
SadPencil Feb 17, 2026
7df7f27
Add more log messages on client startup (#936)
SadPencil Feb 18, 2026
bad593d
Greatly reduce time cost on loading custom map cache (#937)
SadPencil Feb 18, 2026
1c099fa
Add MapPreviewCacheManager for concurrent-safe image caching with LRU…
Copilot Feb 21, 2026
6041f0c
Fix AI removal when lobby options change (#938)
11EJDE11 Feb 28, 2026
dd080c7
Change PlayerExtraOptionsPanel base class from XNAWindow to XNAPanel …
Starkku Mar 4, 2026
3e52e8b
Show migration tip for unmigrated CampaignSelector configuration
SadPencil Mar 4, 2026
7f123d5
Fix a null reference exception when PlayerExtraOptionsPanel is null (…
BlackgamerzVN Mar 4, 2026
8484ec4
Show detailed error message on missing constants
SadPencil Mar 4, 2026
60612bc
Revert migrating CampaignSelector to INItializableWindow (#941)
SadPencil Mar 4, 2026
ea89046
Fix channel name not updating when lobby name changed (#944)
11EJDE11 Mar 8, 2026
1bd18f2
Various LAN Lobby fixes (#945)
11EJDE11 Mar 10, 2026
d79ddb1
Add mission preview image panel to CampaignSelector (#939)
BlackgamerzVN Mar 12, 2026
698c711
Select and scroll to locked game when joining (#913)
11EJDE11 Mar 14, 2026
d841cc5
Add ResetToDefaultOnGameExit option to CampaignCheckBox (#947)
SadPencil Mar 19, 2026
c5efb78
Add coding agent instructions (#951)
SadPencil Mar 23, 2026
1ea5d46
Clarify no localization needed in Funky's game identifier replacement
SadPencil Mar 23, 2026
aa62518
Show game version mismatch warning when joining a lobby
Copilot Mar 23, 2026
bc6cc69
Fix update check failure when mirror URL lacks trailing slash (#953)
Copilot Mar 25, 2026
ec5fa62
Fix translation game files not applied on first run (#952)
Copilot Mar 25, 2026
5a69029
Fix crash if there are no maps for all game modes (#955)
SadPencil Mar 25, 2026
b83ea05
Revise the contributing guidelines and copilot instructions (#956)
SadPencil Mar 26, 2026
2c61648
Improve the performance accessing `GameModeMapCollection.GameModes` (…
Copilot Mar 26, 2026
b544049
Fix translation game files not refreshed on asset updates (#960)
Copilot Mar 26, 2026
d56011f
Adjust RA1 settings keys and support embedded hotkey config in settin…
CO2-code Apr 1, 2026
ca0029b
Accelerate startup time via lazy-loading and multi-threading (#968)
SadPencil Apr 5, 2026
3abe037
Throw on unexpected registry hive when reading GameCollectionConfig.ini
SadPencil Apr 5, 2026
6255ed4
Fix GitVersion build issue in Copilot coding agent setup (#970)
Copilot Apr 5, 2026
3f5fa54
Accelerate startup time by optimizing official map loading (#971)
SadPencil Apr 5, 2026
bff1f3d
Fix the null-coalescing operator pitfall introduced in "Fix crash if …
SadPencil Apr 5, 2026
3e0bb14
Update Rampastring.XNAUI to improve asset loading performance (#973)
SadPencil Apr 6, 2026
740a39c
Implement smart default hotkeys and fix incorrect NumPad5 key handlin…
SadPencil Apr 6, 2026
abd89f1
Update Rampastring.XNAUI to suppress insignificant warnings
SadPencil Apr 7, 2026
a88b449
Fix `Get-CommonAssemblyList.ps1` and update common assembly lists
SadPencil Apr 8, 2026
1b60455
Update Rampastring.XNAUI to reduce image loading overhead
SadPencil Apr 9, 2026
9d2c1a6
Remove translation files to prevent Copilot checks for missing keys
SadPencil Apr 9, 2026
c10ab4b
Throw a friendly error message when game room capacity is exceeded
SadPencil Apr 11, 2026
2794632
Replace the hard-coded magic number 9 with MAX_PLAYER_COUNT constant
SadPencil Apr 11, 2026
99e967d
Fix GetCnCNetPlayerCount() blocking beyond timeout by migrating obsol…
Copilot Apr 11, 2026
78aa6d5
Update Rampastring.XNAUI to disable ComboBox scrolling inside scrolla…
SadPencil Apr 11, 2026
ff70be2
Port startup performance optimizations from DTA Client (#978)
Copilot Apr 12, 2026
b6547a2
Update UI strings to differentiate from DTA client
SadPencil Apr 12, 2026
7510ab0
Highlight important building and debugging notes in README
SadPencil Apr 12, 2026
915492b
Fix intermittent crash on exit (#979)
11EJDE11 Apr 12, 2026
efffa9d
Fix crash when Discord message is greater than 128 characters (#982)
11EJDE11 Apr 17, 2026
1f60251
Add CustomDragDistance setting which overrides DragDistance (#980)
11EJDE11 Apr 17, 2026
1614f89
Delay initializing map file watcher until custom map has been loaded …
SadPencil Apr 18, 2026
05d6760
Fix spelling of "Metallic"
11EJDE11 Apr 18, 2026
614ea9d
Fix undefined hotkey configurations being lost on startup and save (#…
Flactine Apr 25, 2026
6164ced
Add ClearObjDirs script and update README
SadPencil Apr 25, 2026
6a41500
Add DisableMainMenuHotkeys setting with default true (#988)
TheBirdGang Apr 26, 2026
8a6adc6
Throw on loading screen task failures instead of wait indefinitely (#…
SadPencil Apr 26, 2026
1de302c
Improve client startup time measurement
SadPencil Apr 26, 2026
96c328d
Make ClientCore/I18N concurrent safe (#993)
Copilot Apr 26, 2026
0965df6
Remove the outdated PowerShell Core link in build script
SadPencil Apr 26, 2026
231b9b5
Improve performance of map thumbnail loading (#985)
11EJDE11 Apr 26, 2026
c38c497
Add CustomIngameResolutions to ClientDefinitions.ini (#989)
CO2-code Apr 26, 2026
3bd86c8
Allow in-game resolutions to exceed XNA Reach profile limits
SadPencil Apr 26, 2026
6367513
Support "IniName" as key with fallback to "ININame"
SadPencil May 2, 2026
2243c83
Allow overriding CustomIniPath when loading game mode map code (#986)
11EJDE11 May 2, 2026
603d45b
Fix typos
SadPencil May 2, 2026
c07b01e
Fix map re-upload on every new player join in CnCNet lobbies (#950)
Copilot May 2, 2026
7ebdef0
Fix "Unable to load library 'libHarfBuzzSharp'" on .NET Framework cli…
11EJDE11 May 4, 2026
90efa47
Throw if map preview texture fails to load
SadPencil May 4, 2026
abb9a03
Add TTF/OTF support (#948)
11EJDE11 May 4, 2026
24c6d2e
Move Ude.NetStandard.dll as a common assembly
SadPencil May 4, 2026
360e062
Move steam_api64.dll to x64 folder
SadPencil May 4, 2026
0344ff1
Update Rampastring.XNAUI to further reduce image loading overhead
SadPencil May 4, 2026
b0fdf1b
Update TTF/OTF documentation for advanced settings
SadPencil May 4, 2026
8bb0930
Update Rampastring.XNAUI to bypass a render target error in XNA
SadPencil May 5, 2026
18a9617
Trim whitespace from mission tags (#1001)
Copilot May 5, 2026
2347731
Update documentation for new features and changes (#999)
SadPencil May 5, 2026
537c858
Upgrade Polyfill to v10.4.0
SadPencil May 5, 2026
1344ce0
Remove UGC sites such as Discord and YouTube from trusted domains
SadPencil May 5, 2026
5f0137e
Add "Use legacy fonts" option to allow users to revert to SpriteFonts…
11EJDE11 May 7, 2026
3c694a5
Revert "Add "Use legacy fonts" option to allow users to revert to Spr…
SadPencil May 7, 2026
6c71c6d
Fix dropdown border not expanding for preferred item label text (#981)
11EJDE11 May 7, 2026
133da7a
Fix surrogate pair splitting in string truncation (#1005)
Copilot May 8, 2026
a4582e1
Update Rampastring.XNAUI to fix text vertical alignment
SadPencil May 8, 2026
2bb9a83
Fix typo in dice error message
qyjoy May 8, 2026
182d278
Fix OpenGL client stutter in windowed mode (#1006)
11EJDE11 May 10, 2026
f5102ba
Update Rampastring.XNAUI to fix crash when rendering unpaired surrogates
SadPencil May 10, 2026
c450046
Use wall-clock time for tunnel refresh scheduling (#1009)
Copilot May 12, 2026
4c93d20
Fix dropdown positioning on filter panel (#1010)
11EJDE11 May 13, 2026
fcf0707
Add example horizontal scrollbar images (#1011)
11EJDE11 May 13, 2026
3e2c5bb
Prevent crash against unexpected skill levels (#1007)
11EJDE11 May 13, 2026
c3594ff
Revert "Allow overriding CustomIniPath when loading game mode map cod…
11EJDE11 May 14, 2026
0f32c65
Update Rampastring.XNAUI for a better visual experience
SadPencil May 15, 2026
b5d670d
Ignore .lscache files generated by C# Dev Kit
SadPencil May 15, 2026
dda17a6
Upgrade OpenMcdf to v3.1.4 (#1013)
Copilot May 16, 2026
3db5b45
Fix crash when Discord message is less than 128 characters
SadPencil May 16, 2026
a659b95
Fix Image memory leak in map preview cache via ref-counted leases (#1…
Copilot May 16, 2026
7bc09dd
Update common assembly lists
SadPencil May 16, 2026
2ee185b
Ignore `desktop.ini` in hash computation and INI preprocessing (#1019)
Copilot May 17, 2026
cfd50ab
Fix build failure in Copilot coding agent (#1020)
Copilot May 17, 2026
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
54 changes: 54 additions & 0 deletions .github/copilot-coding-agent-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# GitHub Copilot coding agent setup instructions

This section only applies to the GitHub Copilot coding agent, running in a Linux runner from the GitHub Action environment. It does not apply to other environments, such as local development.

The GitHub Actions workflow `.github/workflows/copilot-setup-steps.yml` runs the setup steps mentioned in this file automatically. The commands below are the manual equivalent and **should only be run if you encounter a build failure** — for example, if GitVersion cannot determine the version, if submodules are missing, or if NuGet restore fails.

## Step 1 — Initialize git submodules

`Rampastring.XNAUI` (and its nested submodule `Rampastring.Tools`) may not be pre-initialized. Missing them causes compile errors about unknown `Rampastring.*` types.

```shell
git submodule update --init --recursive
```

## Step 2 — Unshallow the clone and fetch `develop`

The build system uses **GitVersion.MsBuild** to compute version numbers at compile time. It requires two things:

- A full (non-shallow) commit history.
- The `develop` branch reachable as a remote-tracking ref (it is the mainline branch in `GitVersion.yml`). Without it, any branch that is not `develop` or `master` fails with `Gitversion could not determine which branch to treat as the development branch`.

Run all three commands unconditionally:

- `--unshallow` is a no-op on an already-full clone (`|| true` prevents it from aborting).
- `set-branches` resets the remote's fetch refspec to the standard glob `+refs/heads/*:refs/remotes/origin/*`, removing any single-branch refspec that a shallow clone may have injected. Without this, LibGit2Sharp (used by GitVersion 5.12.0) crashes with `ref 'refs/remotes/origin/develop' doesn't match the destination` because it iterates refspecs in order and fails on the first non-matching one instead of falling through to the glob.
- The final fetch brings `refs/remotes/origin/develop` into the local ref store through that glob refspec so GitVersion can find it.

The same fix must be applied to every submodule recursively: `Rampastring.XNAUI` and its nested `Rampastring.Tools` submodule also carry `GitVersion.MsBuild` and are subject to the same crash when checked out with a narrow single-branch refspec.

```shell
git fetch --unshallow origin || true
git remote set-branches origin '*'
git fetch origin develop
git submodule foreach --recursive \
'git fetch --unshallow origin || true; git remote set-branches origin "*"; git fetch origin'
```

## Step 3 — Restore NuGet packages

Run restore from the **solution file** so that ALL projects — including `SecondStageUpdater` — are restored. `SecondStageUpdater` is not a `<ProjectReference>` of `DXMainClient`, but it is always built via the custom `BuildUpdater` MSBuild target. If it is not restored before the build, NETSDK1127 or NETSDK1004 errors occur. Always pass the `Configuration` property; omitting it picks the wrong target frameworks.

```shell
dotnet restore DXClient.slnx -p:Configuration=UniversalGLRelease
```

## Step 4 — Build

`--no-restore` is **required**. When `dotnet build` runs without it, the implicit restore only traverses DXMainClient's `<ProjectReference>` graph, which excludes `SecondStageUpdater`. This leaves SecondStageUpdater's restore assets stale after any code change, causing build failures. Always run Step 3 first, then build with `--no-restore`.

```shell
dotnet build DXMainClient/DXMainClient.csproj -p:Configuration=UniversalGLRelease -f net8.0 --no-restore
```

A successful build ends with `0 Error(s)`.
40 changes: 40 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Agent Instructions


## General information

### Project structure

| Path | Description |
|------|-------------|
| `DXMainClient/` | Main entry-point project — always the build target |
| `ClientCore/` | Core game-client logic |
| `ClientGUI/` | UI layer |
| `ClientUpdater/` | Auto-updater logic |
| `SecondStageUpdater/` | Secondary updater executable |
| `Rampastring.XNAUI/` | UI framework (git submodule) |
| `GitVersion.yml` | GitVersion branch and versioning strategy |
| `global.json` | Pins the required .NET SDK version (10.0, any feature band) |
| `Directory.Build.props` | MSBuild properties shared across all projects |
| `Directory.Packages.props` | Central NuGet package version management |
| `Docs/Build.md` | Human-oriented build documentation |

### Build the project

Always run restore before building. `SecondStageUpdater` is built via a custom MSBuild target (`BuildUpdater`) that fires for every DXMainClient build, but it is not in DXMainClient's project reference graph. This means the implicit restore triggered by `dotnet build` (without `--no-restore`) will not restore it, causing a build failure after any code change that invalidates the NuGet cache.

```shell
dotnet restore DXClient.slnx -p:Configuration=UniversalGLRelease
dotnet build DXMainClient/DXMainClient.csproj -p:Configuration=UniversalGLRelease -f net8.0 --no-restore
```

A successful build ends with `0 Error(s)`.

### Contributing guidelines
See [Contributing.md](../Contributing.md) for coding style, formatting, and other contribution guidelines. Be aware, Copilot, you MUST read and follow this file, even if the user did not explicitly ask you to.

## GitHub Copilot coding agent setup instructions

This section only applies to the GitHub Copilot coding agent, running in a Linux runner from the GitHub Action environment. It does not apply to other environments, such as local development.

The steps in the [copilot-coding-agent-setup.md](./copilot-coding-agent-setup.md) file are automatically executed via a GitHub Action workflow before the agent starts. **Only read and run them manually if you encounter a build failure**.
57 changes: 57 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Copilot setup steps

# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout code with full history and submodules
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive

# the set-branches call is required to collapse the shallow-clone's specific-branch refspec back to the glob, preventing a LibGit2Sharp crash in GitVersion 5.12.0
- name: Unshallow clone and fetch develop branch
run: |
git fetch --unshallow origin || true
git remote set-branches origin '*'
git fetch origin develop
# Apply the same fix to every submodule (including nested ones), because GitVersion.MsBuild
# runs against each submodule directory that carries it, and the same LibGit2Sharp refspec
# crash occurs there when the submodule was checked out with a narrow single-branch refspec.
git submodule foreach --recursive \
'git fetch --unshallow origin || true; git remote set-branches origin "*"; git fetch origin'

- name: Set up .NET SDK
uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json

- name: Restore NuGet packages
# Restore from the solution file so that SecondStageUpdater is included.
# SecondStageUpdater is not a ProjectReference of DXMainClient, but it is
# always built via the BuildUpdater MSBuild target; omitting it from the
# restore causes NETSDK1127/NETSDK1004 on subsequent agent builds.
run: dotnet restore DXClient.slnx -p:Configuration=UniversalGLRelease

- name: Build
# --no-restore is required: the implicit restore triggered by 'dotnet build'
# only walks DXMainClient's ProjectReference graph and therefore skips
# SecondStageUpdater, leaving its restore assets stale.
run: dotnet build DXMainClient/DXMainClient.csproj -p:Configuration=UniversalGLRelease -f net8.0 --no-restore
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ FodyWeavers.xsd
!.vscode/extensions.json
*.code-workspace

# Official VS Code C# Dev Kit Extension exclusion
*.lscache

# Local History for Visual Studio Code
.history/

Expand Down
Binary file not shown.
Binary file added AdditionalFiles/PreprocessFont/BitsNPicas.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions AdditionalFiles/PreprocessFont/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This directory serves as a backup for the font preprocessing utilities referenced in [Fonts.md](/Docs/Fonts.md).

## Bits'N'Picas
https://github.com/kreativekorp/bitsnpicas

## BdfToolSP
https://github.com/SadPencil/BdfToolSP
62 changes: 62 additions & 0 deletions ClientCore/Caching/CacheLease.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#nullable enable
using System;
using System.Threading;

namespace ClientCore.Caching;

/// <summary>
/// A disposable lease on a cached value. The caller must dispose this lease when done
/// with the value to release the reference. If the underlying value is <see cref="IDisposable"/>,
/// it is disposed only when all leases and the cache itself have released their references.
/// </summary>
public sealed class CacheLease<T> : IDisposable
{
private readonly T value;
private readonly Action? onRelease;
private int disposeFlag = 0;

/// <summary>
/// Creates a lease that directly owns the value.
/// Disposing this lease invokes <paramref name="onRelease"/> if provided.
/// </summary>
/// <param name="value">The directly owned value.</param>
/// <param name="onRelease">Action to invoke when the lease is disposed, or <c>null</c>.</param>
public static CacheLease<T> CreateOwned(T value, Action? onRelease) => new CacheLease<T>(value, onRelease);

/// <summary>
/// Creates a lease that directly owns the value.
/// Disposing this lease invokes <paramref name="onRelease"/> if provided.
/// </summary>
internal CacheLease(T value, Action? onRelease)
{
this.value = value;
this.onRelease = onRelease;
}

/// <summary>
/// Creates a lease backed by a ref-counted value.
/// The ref count was already incremented by <see cref="RefCountedValue{T}.AcquireLease"/>;
/// disposing this lease calls <see cref="RefCountedValue{T}.Release"/>.
/// </summary>
internal CacheLease(RefCountedValue<T> refCounted)
{
value = refCounted.Value;
onRelease = refCounted.Release;
}

/// <summary>
/// Gets the leased value.
/// </summary>
public T Value => value;

/// <summary>
/// Releases this lease. If this was the last reference to a ref-counted value,
/// the underlying value is disposed.
/// Safe to call multiple times; only the first call takes effect.
/// </summary>
public void Dispose()
{
if (Interlocked.Exchange(ref disposeFlag, 1) == 0)
onRelease?.Invoke();
}
}
Loading
Loading