> ⚠️ **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.
7.9 KiB
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:
- ServerConfiguration: Global key-value settings stored in
modernuo.json - 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.
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:
intbooldoubleTimeSpanT 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.
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:
ServerConfiguration.SetSetting("mySystem.enabled", "true");
ServerConfiguration.SetSetting("mySystem.maxItems", "100");
Configuration Pattern
Read settings in your Configure() static method:
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:
{
"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
settingsdictionary - Top-level fields (
assemblyDirectories,dataDirectories,listeners) are structural GetOrUpdateSettingadds new entries tosettingsautomatically
JsonConfig
For complex configuration that doesn't fit in flat key-value pairs, use JsonConfig.
Defined in Projects/Server/Json/JsonConfig.cs.
API
// 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
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:
ClientVersionGuidMapPoint3DRectangle3DTimeSpanIPEndPointTypeWorldLocationTextDefinition- All enums (as strings via
JsonStringEnumConverter)
Custom Config File Pattern
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:
{
"Enabled": true,
"MaxItems": 100,
"Cooldown": "00:05:00",
"BlockedNames": [],
"Scores": {}
}
Custom Converters
Add custom converters via JsonConfig.GetOptions():
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
- Read in
Configure()-- called beforeInitialize(), ensures settings are available early - Use
GetOrUpdateSettingfor new features -- ensures defaults appear in config file - Use
GetSettingfor optional/advanced settings -- doesn't clutter config file - Use JsonConfig for complex data -- lists, dictionaries, nested objects
- Provide sensible defaults -- system should work without manual configuration
- Use era-aware defaults --
Core.LBR ? 125 : 100for values that vary by expansion - 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 |