Skip to content

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

Merged
jupster merged 93 commits into
masterfrom
feature/Update-1.9
Sep 29, 2026
Merged

jupster merged 93 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 Adding a feature optimization making something better cleanup Cleaning up code labels Jul 17, 2026
Clone()'s auto-increment renamer used the wrong substring
length when rebuilding the name before the "(n)" suffix,
cutting off extra characters each time a profile was cloned
(e.g. "Server (2)" -> "Serv (3)"). Use Substring(0, i) to
keep everything up to the opening paren.
Update the Steam updater flow to check the returned UpdateState and show accurate user-facing messages for success, login failures, and cancellations. This prevents false 'All Done' output when the update did not actually complete. The workshop progress calculation was also corrected to report the percentage properly instead of using an incorrect formula.
Corrected the ArmaMod collection retrieval to read from the armaMods settings instead of the steamMods value. (which was left as legacy code)
SaveProfile created the Servers/<id> directory and wrote
all three config files before checking whether
Profile.Executable was set. With an empty executable,
ArmaPath resolves to an empty string, so Path.Combine
produced a relative path and left stray config files in
FASTER's own working directory. Move the executable check
to the top of the method, before any directory/file work.
ApiCall caught WebException, but HttpClient throws
HttpRequestException/TaskCanceledException, and calling
.Result on the async call wrapped whatever was thrown in
an AggregateException anyway, so the catch block could
never actually fire. Switch to .GetAwaiter().GetResult()
and catch the exception types HttpClient actually throws.
Also make HttpClient a shared static instance instead of
creating a new one per call, avoiding socket exhaustion
under repeated mod-info lookups.
WorkshopId (ArmaMod, DeploymentMod) raised change
notifications as "WorkshopID" and SteamLastUpdated
(ArmaMod) as "SteamLasttUpdated" — both mismatched from
the actual property name, so any future data binding to
them would get the initial value but never refresh. Fix
the notification strings to match the property names.
Updates view event handlers to await async methods on their ViewModels instead of firing-and-forgetting them. This ensures local mod imports, mod key operations, bulk updates, and updater clicks complete correctly and can report state asynchronously.
The BasicCfg.Custom setter was overwriting configured values even when the preset was 'Custom' or not found. This change keeps existing custom settings intact and only applies built-in preset defaults for recognized performance profiles.
Comment thread FASTER/ViewModel/ProfileViewModel.cs Outdated
Comment thread FASTER/Models/ServerProfile.cs
@jupster jupster added the dependencies Pull requests that update a dependency file label Sep 29, 2026
@jupster jupster added this to FASTER Sep 29, 2026
@github-project-automation github-project-automation Bot moved this to In progress in FASTER Sep 29, 2026
@jupster jupster added this to the 1.9 milestone Sep 29, 2026
Introduce Logger.LogCritical which always writes regardless of IsEnabled and refactor common write logic into a private WriteLine method. Update App.xaml.cs to call LogCritical for AppDomain, Dispatcher and TaskScheduler unhandled-exception handlers so fatal/unhandled exceptions are always recorded.
Adds SteamDiagLogger attachment to both single-file and multi-file download flows
Update the app startup flow to run asynchronously and await AppCenter.GetInstallIdAsync before continuing. This ensures the install ID is retrieved before the app configuration proceeds and avoids handling an incomplete task result during startup.
CONTRIBUTING.md linked to CODE-OF-CONDUCT.md (hyphens),
but the actual file is CODE_OF_CONDUCT.md (underscores).
Fix the link to match.
Replace BasicCfg smoke tests with real assertions
Add full ServerCfg test coverage
This change stops profile export from continuing after a write failure, showing the existing warning and returning early instead of attempting invalid follow-up work. It also makes the NUnit tests independent by switching setup to a per-test lifecycle and updates the Steam profile fixture URLs from HTTP to HTTPS to match current launcher output.

Swap http:// for https:// in the preset strings to clear the insecure-protocol hotspot.
Change [OneTimeSetUp] to [SetUp] in BasicCfgTests.cs. It is also test-only.
Return after the error instead of reporting the profile as saved.
Replace the literal password used in ServerCfgTests with a generated GUID-like join key and assert the processed config contains that value instead of a fixed string. This keeps the test focused on the generated output while avoiding a hardcoded password value in the test itself.
@jupster

jupster commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Tests are not worth it 😭

never touching this file again
@sonarqubecloud

Copy link
Copy Markdown

@jupster
jupster requested a review from Foxlider September 29, 2026 13:03

@jupster jupster left a comment •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self Review:

If i touch this anymore ill just keep changing it.

  • 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... plus more)
    CI/build fixes: DOTNET_VERSION env var... plus more

Future updates:

  • .Net10

  • Stop cancel leaving updater stuck

  • Only purge tracked Steam mod folders

  • Never delete real folders in DeleteLink

  • Guard empty selection in Settings

  • Null-check drive in mod conversion

  • Compare mission list by content

  • Order headless client mods by priority

  • Remove stray FASTER - Backup.csproj

    • and maybe maintenance folder ?
  • Add FASTERTests for servercfg

    • fix up basiccfg tests
    • fix and add loggertest
  • Figure out changes for Steam API key in StaticData.cs

  • Steam password move to DPAPI

  • SafeName strips all non-ASCII... possibly change

  • Azure pipeline have dotnet test run --filter "TestCategory!=Network"

  • Per-process CPU graph:

    • Counters are keyed by process name, so multiple arma3server processes read the first instance.
    • The value isn't divided by core count, so it can exceed 100%, which is the axis maximum.
    • "Read Outputs" never has data because BeginOutputReadLine is never called.
  • FASTER.csproj uses Newtonsoft.Json without referencing it, so it only builds through the submodule.

    • FontAwesome.WPF is unused.
    • LiveCharts.Wpf is unmaintained.
  • Fix workflow so it builds master

  • Tests
    Many assertions do nothing, like Is.Not.Null on ushort, long and double. Nothing covers ServerCfg, ServerProfile.Clone, or the deployment code. The parsing tests share one file name, so a failure in one can break the next.

    • add tests for the requiredBuild line, Clone naming and the BasicCfg defaults alongside their fixes.
    • The Microsoft.DotNet.UpgradeAssistant analyzer package in the test project is obsolete. i think might need to check that

@jupster
jupster merged commit d2f885c into master Sep 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

added feature Adding a feature bug Something isn't working change Changing something cleanup Cleaning up code dependencies Pull requests that update a dependency file enhancement New feature or request optimization making something better

Projects

Status: Done

5 participants