Configuration — ConfigSchema and IniFile
Burnsie.Common.Configuration gives you a declarative INI schema: every key is
defined once — with its section, default, clamping range and comment — and that single
table drives three things:
- Reading values (typed, clamped, warn-and-default on bad input),
- Writing the default file on first run (fully commented),
- Generating user documentation as a Markdown table.
Because file, values and docs come from one source, config/README drift is structurally impossible.
Quick start
var schema = new ConfigSchema("plugins/LSPDFR/MyPlugin.ini", log);
schema.HeaderComments = new[] { "MyPlugin configuration", "Edit and save; reload in game." };
var cooldown = schema.Int ("Settings", "CooldownSeconds", 30, 5, 300, "Seconds between uses.");
var volume = schema.Float("Settings", "Volume", 0.8f, 0f, 1f, "Playback volume.");
var enabled = schema.Bool ("Settings", "Enabled", true, "Master switch.");
var name = schema.Text ("Settings", "UnitName", "1-Adam-12", "Your callsign.");
var hotkey = schema.Enum ("Keybindings","Key", Keys.F11, "Activation key.");
schema.EnsureFileExists(); // writes the fully-commented default INI if none exists
schema.Load(); // reads every declared entry from disk
int seconds = cooldown.Value; // typed access after Load()
Keys key = hotkey.Value;
Entry types
| Method | Entry type | Validation |
|---|---|---|
Int(section, key, default, min, max, comment) | IntEntry | Clamped to [min, max]; out-of-range values are clamped with a warning naming the key |
Float(...) | FloatEntry | Same clamping behavior |
Bool(section, key, default, comment) | BoolEntry | Standard INI boolean parsing |
Text(section, key, default, comment) | TextEntry | Never null; missing key returns the default |
Enum<TEnum>(section, key, default, comment) | EnumEntry<TEnum> | Parsed by name, case-insensitive (e.g. Keys.F11 from f11); invalid names warn and use the default |
All entries expose .Value, which holds the default until Load() runs. Defining the
same section+key twice throws immediately (InvalidOperationException) — duplicates are a
programming error, caught at startup rather than shipped.
File generation
EnsureFileExists():
- Does nothing when the file already exists (never overwrites user edits).
- Otherwise creates the directory and writes a complete default file: header comments,
every section, each key preceded by its comment (with the permitted range appended,
e.g.
; Seconds between activations. (5–300)). - Never throws — on failure it warns and the plugin continues with built-in defaults.
BuildDefaultFileText() returns the same text as a string; the packaging script uses this
to ship a pristine default INI in the release zip.
Documentation generation
string markdown = schema.ToMarkdown();
Produces a per-section reference table (| Key | Default | Range | Description |) ready
to paste into your README. Regenerate it whenever you touch the schema and your docs can
never lag the code.
IniFile — the raw layer
ConfigSchema.File exposes the underlying IniFile, a thin exception-safe wrapper over
RPH's InitializationFile. Use it directly for things outside the schema, such as
migration reads from an old config:
if (schema.File.HasKey("LegacySection", "OldKey"))
{
int old = schema.File.ReadInt("LegacySection", "OldKey", 0);
// migrate...
}
schema.File.WriteString("State", "LastRunVersion", myVersion.ToString());
Every read method (ReadString, ReadInt, ReadFloat, ReadBool, ReadEnum) returns
the supplied default on absence or error, with a warning — a corrupt or hand-mangled
INI yields defaults, never an exception. A plugin must not die on a user's typo.
Rules
- Paths are relative to the GTA V root (that's how RPH's
InitializationFileresolves them); the conventional location isplugins/LSPDFR/YourPlugin.ini. - Call
EnsureFileExists()beforeLoad()on your startup path. - Re-run
Load()to pick up user edits (e.g. on duty start) — entries update in place.