> ⚠️ **Rollback hazard — one-way door once logins are taken.** Serialization is unchanged, so a save > written by this build still *loads* on the previous one. Its contents do not survive the trip: on > its first successful login each account is rehashed to `$argon2id$`, and the previous build ships > Argon2.Bindings 1.19.0, whose `Verify` is gated by the verifier's own configured type and answers > `false` for an `$argon2id$` hash. **After a shard running this build has accepted logins, do not > roll back past this commit** — every account that logged in is locked out on the older binary, and > the only recovery is rolling forward again or resetting passwords by hand. Roll back only from a > save taken before the first post-deploy login. Requires [Argon2.Bindings 1.20.0](https://github.com/modernuo/Argon2.Bindings/pull/14), now published. ## What - Consume `Argon2.Bindings` 1.20.0, which resolves the Argon2 type from the stored PHC string rather than from the verifier's own configuration. - Default to **Argon2id, m=16384, t=1, p=1** — 8.51 ms against the old Argon2i 8 MiB t=3 at 10.11 ms. Cheaper *and* stronger. - Rehash on a successful login whenever the stored parameters are stale, not only when the algorithm changes. - Fix `SetPassword`, which derived the password phrase from the outgoing algorithm while storing it under the incoming one. ## Why **Verification was gated by the verifier's configured type.** `Verify` passed the instance's own `ArgonType` to native `argon2_verify`, whose `decode_string` rejects a disagreeing `$argon2i$`/`$argon2id$` prefix and returns `DECODING_FAIL` — folded into `false`, the same answer as a wrong password. Switching the default type would have locked out every existing account, and `VerifyAndUpdate` could not have migrated them either: it delegates to the same type-fixed `Verify` and never compared `ArgonType`. Fixed upstream in 1.20.0. The pinned legacy-`$argon2i$` test here fails on 1.19.0 for exactly that reason, which is what makes the package bump load-bearing rather than incidental. **Changing the defaults would otherwise have reached nobody.** Argon2's PHC string embeds `m`, `t` and `p`, so verification uses the parameters stored with each account, not the configured ones — and verification is the hot path. `CheckPassword` only rehashed when the *algorithm* changed, never when its cost parameters did, so on an established shard the new defaults would have applied to new accounts only. `IPasswordProtection.NeedsRehash` closes that: it defaults to `false`, so PBKDF2 and the `HashAlgorithm` protections are untouched — only Argon2 carries its cost inside the stored value. **`SetPassword` picked the phrase rule from the wrong algorithm.** SHA1 and SHA2 salt the phrase with the username; Argon2 and PBKDF2 do not. It chose the rule from the *outgoing* algorithm while storing under the *incoming* one, so any algorithm change wrote a credential its own next verify could not reproduce. It now assigns `PasswordAlgorithm` first and derives the phrase from that. Note this ordering is load-bearing and invisible — `UpgradingAlgorithm_DoesNotLockTheAccountOut` is what pins it. ## Cost Verification is re-derivation, so these are login numbers. A full login calls `CheckPassword` twice — `AccountLogin` (0x80) then `GameLogin` (0x91): **~20 ms before, ~17 ms after**, plus a one-time ~8.5 ms rehash on each account's migrating login. That cost is still paid on the game loop. Moving hashing off-loop is deliberately **not** in this PR — it needs a pending-auth state in the login handlers, bounding of in-flight hashes, and login rate limiting.
283 lines
7.9 KiB
Markdown
283 lines
7.9 KiB
Markdown
# ModernUO Configuration System
|
|
|
|
This document covers ModernUO's configuration system, including ServerConfiguration for global settings, JsonConfig for custom config files, and best practices.
|
|
|
|
## Overview
|
|
|
|
ModernUO has two configuration mechanisms:
|
|
1. **ServerConfiguration**: Global key-value settings stored in `modernuo.json`
|
|
2. **JsonConfig**: Custom JSON configuration files for complex data structures
|
|
|
|
## ServerConfiguration
|
|
|
|
Defined in `Projects/Server/Configuration/ServerConfiguration.cs`.
|
|
|
|
### Reading Settings
|
|
|
|
#### GetSetting (Read-Only)
|
|
|
|
Returns the configured value or the default. Does NOT write the default to the config file.
|
|
|
|
```csharp
|
|
int statMax = ServerConfiguration.GetSetting("stats.statMax", 100);
|
|
bool enabled = ServerConfiguration.GetSetting("mySystem.enabled", true);
|
|
TimeSpan delay = ServerConfiguration.GetSetting("autosave.saveDelay", TimeSpan.FromMinutes(5));
|
|
double rate = ServerConfiguration.GetSetting("stats.gainChanceMultiplier", 1.0);
|
|
Expansion exp = ServerConfiguration.GetSetting("core.expansion", Expansion.ML);
|
|
```
|
|
|
|
Supported types:
|
|
- `int`
|
|
- `bool`
|
|
- `double`
|
|
- `TimeSpan`
|
|
- `T where T : struct, Enum`
|
|
|
|
#### GetOrUpdateSetting (Read-Write)
|
|
|
|
Returns the configured value. If the key doesn't exist, writes the default to the config file and returns it.
|
|
|
|
```csharp
|
|
int maxAccounts = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1);
|
|
bool autoCreate = ServerConfiguration.GetOrUpdateSetting("accountHandler.enableAutoAccountCreation", true);
|
|
int poolSize = ServerConfiguration.GetOrUpdateSetting("timer.initialPoolCapacity", 1024);
|
|
```
|
|
|
|
Use this when you want new settings to appear in `modernuo.json` automatically with sensible defaults.
|
|
|
|
#### SetSetting
|
|
|
|
Directly sets a value and immediately persists to disk:
|
|
|
|
```csharp
|
|
ServerConfiguration.SetSetting("mySystem.enabled", "true");
|
|
ServerConfiguration.SetSetting("mySystem.maxItems", "100");
|
|
```
|
|
|
|
### Configuration Pattern
|
|
|
|
Read settings in your `Configure()` static method:
|
|
|
|
```csharp
|
|
namespace Server.Custom;
|
|
|
|
public static class MySystem
|
|
{
|
|
private static bool _enabled;
|
|
private static int _maxItems;
|
|
private static TimeSpan _cooldown;
|
|
|
|
public static void Configure()
|
|
{
|
|
_enabled = ServerConfiguration.GetOrUpdateSetting("mySystem.enabled", true);
|
|
_maxItems = ServerConfiguration.GetOrUpdateSetting("mySystem.maxItems", 100);
|
|
_cooldown = ServerConfiguration.GetOrUpdateSetting("mySystem.cooldown", TimeSpan.FromMinutes(5));
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
if (!_enabled)
|
|
return;
|
|
|
|
// System initialization that depends on config values
|
|
}
|
|
}
|
|
```
|
|
|
|
### Key Naming Convention
|
|
|
|
Use dot-separated hierarchical keys:
|
|
|
|
```
|
|
systemName.settingName
|
|
systemName.subSystem.settingName
|
|
```
|
|
|
|
Examples from the codebase:
|
|
```
|
|
accountHandler.enableAutoAccountCreation
|
|
accountHandler.enablePlayerPasswordCommand
|
|
accountHandler.maxAccountsPerIP
|
|
accountSecurity.encryptionAlgorithm
|
|
autosave.enabled
|
|
autosave.saveDelay
|
|
world.savePath
|
|
world.useMultithreadedSaves
|
|
movement.delay.walkFoot
|
|
movement.delay.runFoot
|
|
stats.statMax
|
|
stats.gainChanceMultiplier
|
|
stats.primaryStatGainChance
|
|
stats.gainDelay
|
|
stats.petGainDelay
|
|
stats.usePub45StatGain
|
|
timer.initialPoolCapacity
|
|
timer.maxPoolCapacity
|
|
core.enableIdleCPU
|
|
```
|
|
|
|
### modernuo.json Structure
|
|
|
|
Located at `Distribution/Configuration/modernuo.json`:
|
|
|
|
```json
|
|
{
|
|
"assemblyDirectories": ["./Assemblies"],
|
|
"dataDirectories": ["C:\\Ultima Online Classic"],
|
|
"listeners": ["0.0.0.0:2593"],
|
|
"settings": {
|
|
"accountHandler.enableAutoAccountCreation": "True",
|
|
"accountHandler.maxAccountsPerIP": "1",
|
|
"autosave.enabled": "True",
|
|
"autosave.saveDelay": "00:05:00",
|
|
"world.savePath": "Saves",
|
|
"stats.statMax": "100"
|
|
}
|
|
}
|
|
```
|
|
|
|
Key points:
|
|
- All settings are stored as strings in the `settings` dictionary
|
|
- Top-level fields (`assemblyDirectories`, `dataDirectories`, `listeners`) are structural
|
|
- `GetOrUpdateSetting` adds new entries to `settings` automatically
|
|
|
|
---
|
|
|
|
## JsonConfig
|
|
|
|
For complex configuration that doesn't fit in flat key-value pairs, use `JsonConfig`.
|
|
|
|
Defined in `Projects/Server/Json/JsonConfig.cs`.
|
|
|
|
### API
|
|
|
|
```csharp
|
|
// Deserialize from file (returns default if file doesn't exist)
|
|
T config = JsonConfig.Deserialize<T>(filePath);
|
|
T config = JsonConfig.Deserialize<T>(filePath, customOptions);
|
|
|
|
// Serialize to file (creates directory if needed)
|
|
JsonConfig.Serialize(filePath, config);
|
|
JsonConfig.Serialize(filePath, config, customOptions);
|
|
|
|
// Default options (available for customization)
|
|
JsonSerializerOptions options = JsonConfig.DefaultOptions;
|
|
```
|
|
|
|
### Default JSON Options
|
|
|
|
```csharp
|
|
WriteIndented = true // Pretty-printed
|
|
AllowTrailingCommas = true // Forgiving parser
|
|
ReadCommentHandling = JsonCommentHandling.Skip // Comments allowed
|
|
DefaultIgnoreCondition = WhenWritingNull // Null values omitted
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
|
```
|
|
|
|
### Built-in Converters
|
|
|
|
JsonConfig includes converters for ModernUO types:
|
|
- `ClientVersion`
|
|
- `Guid`
|
|
- `Map`
|
|
- `Point3D`
|
|
- `Rectangle3D`
|
|
- `TimeSpan`
|
|
- `IPEndPoint`
|
|
- `Type`
|
|
- `WorldLocation`
|
|
- `TextDefinition`
|
|
- All enums (as strings via `JsonStringEnumConverter`)
|
|
|
|
### Custom Config File Pattern
|
|
|
|
```csharp
|
|
using Server.Json;
|
|
|
|
namespace Server.Custom;
|
|
|
|
public class MySystemConfig
|
|
{
|
|
public bool Enabled { get; set; } = true;
|
|
public int MaxItems { get; set; } = 100;
|
|
public TimeSpan Cooldown { get; set; } = TimeSpan.FromMinutes(5);
|
|
public List<string> BlockedNames { get; set; } = new();
|
|
public Dictionary<string, int> Scores { get; set; } = new();
|
|
}
|
|
|
|
public static class MySystem
|
|
{
|
|
private static MySystemConfig _config;
|
|
private static readonly string ConfigPath =
|
|
Path.Combine(Core.BaseDirectory, "Configuration/MySystem/config.json");
|
|
|
|
public static void Configure()
|
|
{
|
|
_config = JsonConfig.Deserialize<MySystemConfig>(ConfigPath);
|
|
|
|
if (_config == null)
|
|
{
|
|
_config = new MySystemConfig();
|
|
JsonConfig.Serialize(ConfigPath, _config);
|
|
}
|
|
}
|
|
|
|
public static void SaveConfig()
|
|
{
|
|
JsonConfig.Serialize(ConfigPath, _config);
|
|
}
|
|
}
|
|
```
|
|
|
|
This creates a config file like:
|
|
```json
|
|
{
|
|
"Enabled": true,
|
|
"MaxItems": 100,
|
|
"Cooldown": "00:05:00",
|
|
"BlockedNames": [],
|
|
"Scores": {}
|
|
}
|
|
```
|
|
|
|
### Custom Converters
|
|
|
|
Add custom converters via `JsonConfig.GetOptions()`:
|
|
|
|
```csharp
|
|
var options = JsonConfig.GetOptions(new MyCustomConverterFactory());
|
|
var data = JsonConfig.Deserialize<MyType>(path, options);
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration File Locations
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `Distribution/Configuration/modernuo.json` | Main server settings |
|
|
| `Distribution/Configuration/expansion.json` | Target expansion |
|
|
| `Distribution/Data/expansions.json` | Expansion metadata |
|
|
| `Distribution/Configuration/` | Custom config directory |
|
|
|
|
Custom config files should be placed under `Distribution/Configuration/` in a subdirectory named after your system.
|
|
|
|
## Best Practices
|
|
|
|
1. **Read in `Configure()`** -- called before `Initialize()`, ensures settings are available early
|
|
2. **Use `GetOrUpdateSetting`** for new features -- ensures defaults appear in config file
|
|
3. **Use `GetSetting`** for optional/advanced settings -- doesn't clutter config file
|
|
4. **Use JsonConfig for complex data** -- lists, dictionaries, nested objects
|
|
5. **Provide sensible defaults** -- system should work without manual configuration
|
|
6. **Use era-aware defaults** -- `Core.LBR ? 125 : 100` for values that vary by expansion
|
|
7. **Document key names** -- use clear hierarchical naming
|
|
|
|
## Key File References
|
|
|
|
| File | Description |
|
|
|---|---|
|
|
| `Projects/Server/Configuration/ServerConfiguration.cs` | ServerConfiguration class |
|
|
| `Projects/Server/Json/JsonConfig.cs` | JsonConfig utility |
|
|
| `Distribution/Configuration/modernuo.json` | Main config file |
|
|
| `Projects/UOContent/Skills/SkillCheck.cs` | Config usage example |
|
|
| `Projects/Server/Timer/Timer.Pool.cs` | Config usage example |
|