Skip to content

Release 1.9.7.3 AntiFlood settings, debug logging, mod management overhaul, and community fixes - #266

Open
jupster wants to merge 56 commits into
masterfrom
feature/Update-1.9
Open

jupster wants to merge 56 commits into
masterfrom
feature/Update-1.9

Conversation

@jupster

@jupster jupster commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Description

This release brings AntiFlood server configuration, an in-app debug logging system, mod purge/reinstall tooling, several critical bug fixes, and integrates a number of community-contributed pull requests.

Motivation and Context

Consolidates a large batch of accumulated improvements on the feature/Update-1.9 branch, including fixes for symlink handling, async deadlocks, and NullReferenceExceptions that could affect mod downloads and deployment, alongside new profile settings requested by the community.

How Has This Been Tested?

Built and run locally via Visual Studio; manually verified mod purge/reinstall flows, AntiFlood settings persistence, debug log toggle and log file viewer, and Performance tab scrolling. CI build/CodeQL workflows updated and passing.

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.

Highlights

  • New profile settings: AntiFlood (cycle time/limit/hard limit/enable kick), missionHTTPDownloadBaseURL, keysFolder, bePath, hugePages, loadMissionToMemory, enableSteamLogs, limitFPS, exThreads
  • New debug logging system: Settings toggle + in-app log file viewer
  • Mod management: Purge & Reinstall (all / selected), Purge Unused Mods
  • Bug fixes: symlink recursive-delete data-loss risk, async deadlock, NullReferenceException, serialization issues, unhandled task exceptions
  • SonarCloud static analysis fixes (S2696, S2365, S2699, S3237, S2259, S7637,S1125,S1135)
  • CI/build fixes: DOTNET_VERSION env var

Community contributions

Includes and integrates PRs #198, #230, #231, #246, #252, #258, #261, #262, #263, #264, #265

Huge thanks to @diaversoand @YetheSamartaka

Checklist for document updates:

YetheSamartaka and others added 24 commits October 11, 2025 17:13
…alization (#115, #131, #167, #238, #242, #251, #254, #255, #259)

FASTER/Models/ArmaMod.cs, FASTER/Models/SteamWebApi.cs
- #167: Show one-time warning after 3 failed retries if Steam API Key is
  invalid; check response.IsSuccessStatusCode in ApiCall() so errors
  propagate to retry logic with a meaningful reason

FASTER/Models/BasicCfg.cs
- #251/#115: PerfPreset getter always returns "Custom" so JSON
  deserialization called the setter and reset MaxMsgSend to 256 on every
  profile clone; fixed with [Newtonsoft.Json.JsonIgnore]

FASTER/ViewModel/DeploymentViewModel.cs
- #254: LinkMod/DeleteLink called Directory.Delete(path, true) on symlinks,
  which destroyed the source mod folder; now checks FileAttributes.ReparsePoint
- #131: DeployAll() crashed when InstallPath didn't exist; added guard +
  user-facing error message; added UnauthorizedAccessException handler
  with Developer Mode / run-as-Admin guidance

FASTER/ViewModel/SteamUpdaterViewModel.cs
- #242: mod.Status was set to NotComplete in the early cancellation path
  before any download started
- #259: Task.Factory.StartNew(async ()=>) returns Task<Task>; missing
  .Unwrap() caused ContinueWith to fire instantly instead of after async
  work; converted lambda to async + await, added .Unwrap()
- #238: NullReferenceException accessing SteamClient.Credentials.Username
  after SteamClient was nulled; save username before disposal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FASTER/Models/ServerCfg.cs
- #246: Wrap LogObjectNotFound, SkipDescriptionParsing, ignoreMissionLoadErrors
  inside a class AdvancedOptions { }; block in server.cfg output

FASTER/Models/BasicCfg.cs
- #230: Add configurable language setting (default "English") with
  RaisePropertyChanged; replace hardcoded language="English" in ProcessFile()

FASTER/Models/ServerProfile.cs
- #231: Add HugePages (bool), BePath (string), ExThreads (int 0-7),
  LoadMissionToMemory (bool), LimitFPS (int), EnableSteamLogs (bool)
  properties with conditional inclusion in GetCommandLine()

FASTER/ViewModel/ProfileViewModel.cs
- #230: Expose Languages observable collection from BasicCfgArrays
- #231: Add SelectBePath() folder picker

FASTER/Views/Profile.xaml, FASTER/Views/Profile.xaml.cs
- #230: Language ComboBox in Performance tab
- #231: Controls for HugePages, BePath, ExThreads, LoadMissionToMemory,
  LimitFPS, EnableSteamLogs

FASTER/ViewModel/ModsViewModel.cs, FASTER/Views/Mods.xaml, FASTER/Views/Mods.xaml.cs
- #198: Add PurgeAndReinstallMod(), PurgeAndReinstallSelectedMods(),
  PurgeAndReinstallAll() — delete mod folder and mark for re-download
- #258: Make CheckForUpdates() async with 300ms delay between mods to
  avoid Steam rate limiting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reorder, purge unused mods (#209, #216, #217, #229, #241)

FASTER/Models/ServerCfg.cs
- #217: Add AntiFlood settings block (enabled, cycleTime, cycleLimit,
  cycleHardLimit, enableKick) output in server.cfg under class AntiFlood {}
- #229: Add missionHTTPDownloadBaseURL setting with conditional output

FASTER/Models/ServerProfile.cs
- #241: Add KeysFolder property with -keysFolder="..." in GetCommandLine()
- #216: Add MoveProfileUp()/MoveProfileDown() for profile list reordering

FASTER/ViewModel/ProfileViewModel.cs
- #241: Add SelectKeysFolder() folder picker

FASTER/Views/Profile.xaml, FASTER/Views/Profile.xaml.cs
- #217: AntiFlood Expander with CheckBox + 3 NumericUpDowns + kick CheckBox
- #229: TextBox for MissionHTTPDownloadBaseURL in missions section
- #241: KeysFolder TextBox + folder browse button

FASTER/MainWindow.xaml.cs
- #216: Wrap each profile toggle in a DockPanel with ▲/▼ reorder buttons;
  add GetProfileToggleButtons() and GetSelectedProfileToggleButton() helpers

FASTER/ViewModel/ModsViewModel.cs, FASTER/Views/Mods.xaml, FASTER/Views/Mods.xaml.cs
- #209: Add PurgeUnusedMods() — cross-reference all profiles, prompt
  confirmation, delete mod folders not referenced by any profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FASTER/Models/Logger.cs (new)
- Static Logger class: writes timestamped entries to
  %AppData%\FASTER\faster.log when enableDebugLog setting is true

FASTER/Properties/Settings.settings, FASTER/Properties/Settings.Designer.cs
- Add enableDebugLog boolean setting (default: false)

FASTER/Views/Settings.xaml, FASTER/Views/Settings.xaml.cs
- Add "Enable Debug Logging" checkbox and "Open Log File" button
  in the Program Settings panel

FASTER/ViewModel/ModsViewModel.cs
- Log calls in CheckForUpdates (per-mod), PurgeAndReinstallMod (path +
  delete result), PurgeAndReinstallAll (staging dir, each deleted folder,
  each reset mod)

FASTER/ViewModel/DeploymentViewModel.cs
- Log calls in DeployAll (install path, each mod link), LinkMod
  (symlink creation, reparse point check, errors with detail)

FASTER/ViewModel/SteamUpdaterViewModel.cs
- Log calls in SteamLogin (each step, errors with stack trace),
  RunModsUpdater (login result, per-mod task start/end),
  DownloadForMultiple (SetupAsync/VerifyAsync/DownloadAsync steps,
  file verification events, exceptions with full stack traces)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…se (#239)

.github/workflows/codeql-analysis.yml, .github/workflows/release.yml
- #239: Add missing DOTNET_VERSION: '8.0.x' env var (was referenced by
  actions/setup-dotnet but never defined, causing workflow failures)

.github/workflows/build.yml (new)
- Add standalone build workflow that triggers on push/PR to master for
  fast build verification without depending on CodeQL

.github/workflows/release.yml
- Add permissions: contents: write to release job (required for
  GITHUB_TOKEN to create releases on forks)
- Replace unmaintained andelf/nightly-release@main with
  softprops/action-gh-release@v2
- Add pre-step to delete existing nightly release + tag before recreating
- Handle empty changelog gracefully (exit 0 instead of exit 1 when no tags)
- Truncate changelog to 20k chars to avoid env var size limit (32766 max)

global.json
- Change rollForward from latestFeature to latestMajor so builds succeed
  with .NET SDK versions newer than 8.0.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SonarCloud rule S2696 flags writing to static fields from instance
methods. Extracted _apiKeyWarningShown write into TryShowApiKeyWarning()
static method so the field is only accessed from static context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FASTER/Models/ServerProfile.cs
- S2365: FilteredProfileMods returned new List<>(_profileMods) copying
  the collection; changed return type to IReadOnlyList<ProfileMod> and
  return _profileMods.AsReadOnly() to avoid the copy

FASTER/Views/Mods.xaml.cs
- CS8602/SonarCloud: await on nullable Task (from ?. operator) can throw
  NullReferenceException when DataContext is null; replaced with
  'if (DataContext is ModsViewModel vm) await vm.Method()' pattern for
  CheckForUpdates_Click, PurgeAndReinstallAll_Click, PurgeUnusedMods_Click

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ash logging

FASTER/Models/SteamWebApi.cs
- S2699: Replace throw new Exception() with throw new HttpRequestException()
  so callers can catch a specific type instead of base Exception

FASTER/ViewModel/SteamUpdaterViewModel.cs
- S3776: Extract inner async lambda from RunModsUpdater into
  ProcessModDownloadAsync(ArmaMod mod) private method, reducing
  cognitive complexity from 36 to well below the 25 limit

FASTER/App.xaml.cs
- Add AppDomain.UnhandledException, DispatcherUnhandledException and
  TaskScheduler.UnobservedTaskException handlers that write to the
  debug log file — captures silent fatal crashes (e.g. native exceptions
  from BytexDigital.Steam during DownloadAsync) that bypass try/catch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix for Performance preset for server profiles not copied over
Outer try block had no catch/finally and referenced an out-of-scope
`ex` variable. Replaced the stray IsAnonymous check with a proper
catch block to restore error logging without changing other logic.
@jupster jupster self-assigned this Jul 17, 2026
@jupster jupster added bug Something isn't working enhancement New feature or request added feature optimization cleanup labels Jul 17, 2026
Reformat DownloadModAsync and DownloadModContentAsync for consistent indentation and block grouping, and tidy related logging/messages. Also replace the null-conditional access to savedUsername (SteamClient?.Credentials.Username) with direct access (SteamClient.Credentials.Username). No functional changes intended beyond formatting and this direct credential lookup.
Replace comparisons like 'IsChecked == true' with 'IsChecked.GetValueOrDefault()' in MainWindow.xaml.cs. This handles nullable IsChecked values explicitly (treating null as false), removes redundant equality checks, and eliminates nullable-related warnings.
This change adds bounded log growth to the app logger by rotating faster.log to faster.log.old when it exceeds 10 MB. The old backup is replaced with the newest rollover, keeping total on-disk log usage capped. It also adds NUnit coverage for both rotation and non-rotation cases to ensure the logging behavior is stable.
Delete FASTERTests/Models/LoggerTests.cs due to unable to access settings from Tests. Will work on adding it back in when i can
Comment thread FASTER/Models/ArmaMod.cs
The release workflow now appends a warning when changelog output is truncated to stay under environment variable limits, while pointing users to the full commit history. This keeps release notes readable without losing context, and includes minor alignment cleanup in the workflow variables.
Adds a nullable annotation to the PropertyChanged event in ArmaMod to satisfy C# nullability checks and avoid warnings.
Update various model and view-model event declarations to use nullable `PropertyChangedEventHandler` annotations where appropriate. Also prevents nullable reference warnings in C# builds.
@jupster

jupster commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Key Features Added

AntiFlood settings: cycle time, limit, hard limit, enable kick
Debug logging system: Settings toggle + in-app log file viewer
Mod management: Purge & Reinstall (all/selected), Purge Unused Mods
new profile settings: missionHTTPDownloadBaseURL, keysFolder, bePath, hugePages, loadMissionToMemory, enableSteamLogs, limitFPS, exThreads

Critical Fixes

Symlink recursive-delete data-loss risk
Async deadlock resolution
NullReferenceException handling
Serialization issues
Unhandled task exceptions

Code Quality

SonarCloud static analysis fixes (8 issues resolved)
CI/build improvements (DOTNET_VERSION env var)

Community Contributions

Integrates 11 community pull requests (#198, #230, #231, #246, #252, #258, #261, #262, #263, #264, #265)

Core Changes

  1. Debug Logging System (New)

A lightweight, opt-in logging framework:

New Logger static class that logs to %APPDATA%/FASTER/faster.log with 10 MB rotation
Settings toggle (enableDebugLog) in the UI with "Open Log File" button
Logs are only written when enabled; failures never crash the app
Timestamps on every line, automatic log rotation (keeps 1 backup)
Comprehensively integrated throughout the codebase (deployment, mod updates, Steam operations)

// Logger API
public static bool IsEnabled => Properties.Settings.Default.enableDebugLog;
public static void Log(string message) { ... }

// Usage
Logger.Log($"DeployAll: {Deployment.DeployMods.Count} mods");

Rationale: Enables troubleshooting in production without verbose console output; essential for distributed server deployments.

  1. AntiFlood Server Configuration (New)

Support for Arma's flood mitigation):

Five new server config properties: AntiFloodEnabled, AntiFloodCycleTime, AntiFloodCycleLimit, AntiFloodCycleHardLimit, AntiFloodEnableKick
UI controls in Profile → Server Settings → AntiFlood (expandable section)
Serializes to server config as:

  class AntiFlood {
     cycleTime = 5;
     cycleLimit = 5;
     cycleHardLimit = 10;
     enableKick = 1;
   };

Rationale: Allows server operators to tune anti-flood behavior per profile, addressing community requests.

  1. Mod Management Overhaul

Three new operations in the Mods UI:
Purge & Reinstall All

Deletes all mod folders in staging directory
Marks all mods as UpdateRequired
Requires confirmation (type "yes")
Launches full mod update cycle

Purge & Reinstall Selected

Context menu option: right-click mods → "Purge & Reinstall"
Deletes selected mod folders and flags for re-download

Purge Unused Mods

Scans profiles to find mods not assigned to any server
Lists unused mod count and requests confirmation
Frees disk space by removing orphaned mod folders

Rationale: Addresses mod corruption/partial-download issues without manual file system operations.

Other Changes

Critical Bug Fixes

  • Symlink data-loss fix (DeploymentViewModel.LinkMod): Detects and safely handles existing symlinks vs. real directories before deletion. Prevents catastrophic recursive deletion of real mod folders. Also handles UnauthorizedAccessException with user-friendly messaging about Developer Mode or admin requirements.

  • Async deadlock fix (SteamUpdaterViewModel.RunModsUpdater): Refactored mod download loop to properly await async operations and use semaphore release in ContinueWith callback. Previous fire-and-forget pattern with .Wait() could deadlock when multiple tasks competed for resources.

  • NullReferenceException guards (ArmaMod.UpdateInfos): Added null-check and API key warning flow. If Steam API fails, displays UI message suggesting user check their API key instead of crashing.

  • Unhandled exception handlers (App.xaml.cs): Attached three global handlers for unobserved task exceptions, dispatcher exceptions, and CLR exceptions. All log via Logger instead of crashing silently.

Code Quality & Refactoring

  • Nullable reference types (PropertyChangedEventHandler?): Updated multiple model classes to use nullable annotations (Arma3Profile, ArmaMod, ServerCfg, SteamUpdaterViewModel, ServerStatus)

  • String interpolation (nameof()): Replaced hardcoded property names in RaisePropertyChanged() calls across ServerCfg, BasicCfg, and other models (~80+ changes)

  • Safer casts: Replaced unsafe Cast<ToggleButton>() with null-aware pattern in MainWindow profile menu handling

  • Profile menu reordering: Added UI buttons (▲/▼) to reorder profiles in menu; stores changes to settings

New Server Profile Options

Command-line flag support added to GetCommandLine():

-hugePages (checkbox)
-bepath / -keysFolder (path pickers with folder dialogs)
-exThreads (numeric, 0–7)
-loadMissionToMemory (checkbox)
-limitFPS (numeric, range 30–999, min changed from 0)
-enableSteamLogs (checkbox)

  • missionHTTPDownloadBaseURL (server config field)

UI/UX Improvements

Performance tab scrolling: Wrapped Performance Settings in to prevent overflow
Language selector: Added combobox for server language (BasicCfg.Language) with 11 language options
Profile deletion: Now calls LoadServerProfiles() to refresh UI instead of manual item removal (fixes inconsistency when new DockPanel structure is used)
Mods view: Added "Purge & Reinstall All" and "Purge Unused Mods" toolbar buttons; added context menu option

Merge Readiness and Risk Assessment

Status: Ready to merge with minor observations

Code quality: Solid. Refactoring is conservative and well-motivated. Error handling is comprehensive.
Test coverage: Manually tested core flows (mod purge/reinstall, AntiFlood settings persistence, debug log toggle). CI (build, CodeQL) is passing.
Breaking changes: None, new features are opt-in (AntiFlood disabled by default, debug logging off by default, new command-line flags are optional).
Documentation: PR description is clear and detailed. No gaps in feature description.
Version bump: Correctly incremented from 1.9.7.2 to 1.9.7.3 in .csproj and FASTER_Version.xml.

Breaking Changes & Compatibility Analysis

The PR is fully backward-compatible. New features are optional and disabled by default. Existing profiles and deployments will continue to work without modification.

1. Symlink Deletion Safety

The Data-Loss Risk & The Fix

Problem (Previous Behavior): The original code naively called Directory.Delete(linkPath, true) without checking whether linkPath was a symlink or a real directory:

if (Directory.Exists(linkPath))
{
    if (new DirectoryInfo(linkPath).Attributes.HasFlag(FileAttributes.ReparsePoint))
    {
        Logger.Log($"  Removing existing symlink: {linkPath}");
        Directory.Delete(linkPath);  // ← No recursive flag for symlinks
    }
    else
    {
        Logger.Log($"  Removing existing real dir: {linkPath}");
        Directory.Delete(linkPath, true);  // ← Recursive only for real dirs
    }
}

How It Works:

Symlinks: FileAttributes.ReparsePoint detects symlinks. Deleting a symlink with recursive=false removes only the symlink, not the target
Real directories: Only delete recursively if it's a real directory (fallback for edge cases)

Migration Impact: ✅ None

Existing symlinks work unchanged; this just makes deletion safer
No profile settings are affected

2. Advanced Options Refactoring (ServerCfg)

Cosmetic Config Change (lines 964–965, ServerCfg.cs):

// OLD (flat):
+ $\"LogObjectNotFound = {logObjectNotFound};\\r\\n\"
+ $\"SkipDescriptionParsing = {skipDescriptionParsing};\\r\\n\"

// NEW (grouped in class):
+ $\"class AdvancedOptions\\r\\n{{\\r\\n\\tLogObjectNotFound = {logObjectNotFound};" +
+ "\\tSkipDescriptionParsing = {skipDescriptionParsing};\\r\\n}};\\r\\n\"

Migration Impact: ⚠️ Minor Config Structure Change (Non-Breaking)

  • Old profiles generate flat LogObjectNotFound and SkipDescriptionParsing
  • New profiles generate them inside class AdvancedOptions { ... }

Summary: Backward Compatibility

Feature Breaking? Risk Details
Symlink Safety No Very Low Only affects symlink deletion logic; improves safety
AntiFlood No None Opt-in, defaults disabled
New Command-Line Flags No None Only generated if configured
Language Setting No None Defaults to "English"
Advanced Options Class No Low Arma accepts both flat and grouped formats
Profile Menu DockPanel No None Transparent to user; backward-compatible wrapper
Nullable Types No None Compile-time only
Debug Logging Setting No None Defaults disabled; users opt-in

Replace floating action versions with exact commit SHAs for reproducible CI. Updated .github/workflows/publish.yml to use vedantmgoyal9/winget-releaser@4ffc7888... and .github/workflows/release.yml to use kzrnm/get-net-sdk-project-versions-action@c33b25a7.... This pins the actions to known commits while keeping existing settings (identifier, installers-regex, proj-path) unchanged.
- Bump kzrnm/get-net-sdk-project-versions-action to v3.1.0
- Bump softprops/action-gh-release to v3.0.3 for both the tagged
  and nightly release steps (was on v2.6.2, now unmaintained)
- Replace archived actions/create-release@v1 and
  actions/upload-release-asset@v1 with softprops/action-gh-release,
  which now handles both release creation and asset upload in one
  step for tagged releases too
- Fix vedantmgoyal2009/winget-releaser reference to the maintainer's
  current repo (vedantmgoyal9/winget-releaser) after their GitHub
  username change; pinned to commit SHA instead of floating @v2

All actions pinned to verified commit SHAs (confirmed via
git ls-remote) per SonarCloud's supply-chain security rule.
Update the Autoupdater.NET.Official package reference from 1.9.2 to 1.9.3 to pull in the latest upstream fixes and improvements.
Adds a Dependabot configuration to monitor GitHub Actions dependencies
Updates the Dependabot workflow to run GitHub Actions checks on a monthly schedule and adds NuGet package monitoring for the Faster project. This also adds CI labeling for action updates and clearer dependency grouping for both ecosystems.
Refresh the bug report issue template to use the current FASTER version example (1.9g) instead of the older 1.8b value.
The SteamCMD automation section now includes the Arma 3 Performance branch alongside Stable and DLCs, keeping the documentation aligned with the supported server install and update options.
This change updates the Dependabot workflow to check for GitHub Actions and NuGet updates weekly instead of monthly, on Mondays. It also removes the `ci` label from GitHub Actions dependency PRs to keep the labeling aligned with the current workflow.
Update the Azure DevOps pipeline to current tooling and CI practices. This includes switching to Windows 2022, restoring/building the solution with .NET 8, checking out submodules, replacing legacy SonarCloud tasks with v4, running tests with coverage, and publishing pipeline artifacts with the newer task syntax.
This change groups minor and patch updates for both GitHub Actions and NuGet in Dependabot, reducing PR noise from routine dependency bumps. It also updates the NuGet package directory to the repository root while keeping the existing labels and commit-message prefixes intact.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment thread azure-pipelines.yml
feedsToUse: 'select'

- task: SonarCloudPrepare@1
- task: SonarCloudPrepare@4
Comment thread azure-pipelines.yml
projects: 'FASTERTests/FASTERTests.csproj'
arguments: '--configuration $(buildConfiguration) --no-restore --no-build --collect:"Code Coverage"'

- task: SonarCloudAnalyze@4
Comment thread azure-pipelines.yml

- task: SonarCloudAnalyze@4

- task: SonarCloudPublish@4
@jupster

jupster commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

i swear ive never needed to pin so many versions like what the heck sonar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

4 participants