Files
ClassicAssist.Avalonia/.qoder/repowiki/knowledge/en/JSON-based Profile and Settings System with ISettingProvider Plugin Architecture/JSON-based Profile and Settings System with ISettingProvider Plugin Architecture.md
T
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

6.0 KiB

kind, name, category, scope, source_files
kind name category scope source_files
configuration_system JSON-based Profile and Settings System with ISettingProvider Plugin Architecture configuration_system
**
ClassicAssist.Shared/Data/Options.cs
ClassicAssist.Shared/Data/AssistantOptions.cs
ClassicAssist.Shared/Misc/ISettingProvider.cs
ClassicAssist/Engine.cs
ClassicAssist.Avalonia/Engine.cs
ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs
ClassicAssist.Launcher/ClassicOptions.cs
ClassicAssist.Launcher/ClassicOptionAttribute.cs

ClassicAssist uses a multi-layered, JSON-backed configuration system centered on per-user profiles, global assistant settings, and a pluggable serialization contract. There is no centralized config framework — instead, the codebase defines explicit model classes for each concern and wires them together via events and reflection.

What system/approach is used

  • File format: Plain JSON files serialized/deserialized with Newtonsoft.Json (JObject, JArray). No YAML/TOML/env-file support.
  • Profile model: Options (in ClassicAssist.Shared/Data/Options.cs) represents user-editable runtime options; it exposes an INotifyPropertyChanged property bag plus static Load(string, Options) / Save(Options) that read/write a file named by Name under a Profiles/ directory next to the executable. A default profile name constant is "settings.json".
  • Global assistant settings: AssistantOptions (in ClassicAssist.Shared/Data/AssistantOptions.cs) is a static class holding cross-cutting state such as last profile, language override, window size, auto-backup policy, saved passwords, and plugin assembly list. It persists to Assistant.json in the startup path.
  • Launcher CLI flags: The WPF launcher (ClassicAssist.Launcher) parses command-line arguments into a ClassicOptions POCO using a custom [ClassicOption] attribute and emits a Launcher.json file. This is separate from the main client's settings.
  • Pluggable section serializers: Each UI tab ViewModel implements ISettingProvider (Serialize(JObject), Deserialize(JObject, Options)). During load/save, the WPF/Avalonia Engine walks all registered BaseViewModel instances, casts to ISettingProvider, and delegates per-section persistence. This lets each feature own its JSON keys without a central schema registry.

Key files and packages

  • ClassicAssist.Shared/Data/Options.cs — main profile model, CurrentOptions singleton, Load/Save, profile enumeration, SmartTargetOption flag enum.
  • ClassicAssist.Shared/Data/AssistantOptions.cs — global app-level settings, password vault, profile linking by player serial, language/culture switching, auto-backup of the Profiles/ folder.
  • ClassicAssist.Shared/Misc/ISettingProvider.cs — contract every feature ViewModel must implement to participate in profile save/load.
  • ClassicAssist/Engine.cs (WPF host) and ClassicAssist.Avalonia/Engine.cs (Avalonia host) — subscribe to Options.SaveEvent / Options.LoadEvent, iterate BaseViewModel.Instances, call Serialize/Deserialize on any ISettingProvider.
  • ClassicAssist.Shared/UI/ViewModels/*TabViewModel.cs — concrete ISettingProvider implementations (General, Hotkeys, Macros, Agents tabs, etc.) that add their own JSON sections keyed by category names like "General".
  • ClassicAssist.Launcher/ClassicOptions.cs + ClassicAssist.Launcher/ClassicOptionAttribute.cs — attribute-driven CLI-to-POCO mapping for the launcher process.

Architecture and conventions

  • Per-profile JSON: User data lives in /Profiles/.json. Profiles are plain JSON objects whose shape is defined by the union of all ISettingProvider implementations plus the top-level Name field.
  • Global vs profile split: AssistantOptions holds application-wide, non-gameplay settings (language, window geometry, backup policy, saved passwords, plugin assemblies). Options.CurrentOptions holds gameplay/profile-specific toggles. They are persisted to different files (Assistant.json vs Profiles/*.json).
  • Event-driven composition: Options raises SaveEvent / LoadEvent carrying a shared JObject. Each ISettingProvider adds or reads its own sub-object (e.g., obj["General"] = ...). The Engine orchestrates discovery via BaseViewModel.Instances; there is no DI container.
  • Feature-owned sections: New features register themselves by implementing ISettingProvider and adding a new key under the root JSON object. There is no central schema validator — forward/backward compatibility relies on defensive null checks when reading unknown keys.
  • Password handling: Saved credentials are stored in Assistant.json under a "SavedPasswords" array; values are encrypted via Crypter.Encrypt/Decrypt before write/read.
  • Launcher separation: The launcher is a distinct process with its own ClassicOptions POCO and Launcher.json. It does not share the Options/AssistantOptions models.

Rules developers should follow

  1. Add a new setting → add a property to Options (with a backing field and SetProperty helper), wire it into one or more ISettingProvider implementations' Serialize/Deserialize, and ensure defaults are handled defensively in Deserialize.
  2. Persisting a new feature → implement ISettingProvider on the owning ViewModel and add a unique top-level JSON key (e.g., "MyFeature") inside Serialize; read it back in Deserialize with null-safe accessors.
  3. Do not mutate Options.CurrentOptions directly from background threads — use the dispatcher/UI thread where required, mirroring how existing properties trigger side effects (e.g., LightLevel sends a packet).
  4. Keep sensitive data out of Options — use AssistantOptions.SavedPasswords and rely on Crypter for encryption; never store plaintext secrets in profile JSON.
  5. Profile migration — because loading is JSON-passthrough, introduce new fields with sensible fallbacks in Deserialize so older profiles remain valid.
  6. Launcher flags — define new CLI switches by adding a property to ClassicOptions decorated with [ClassicOption(...)]; do not mix these with the client's Options model.