# How the Shadowsocks Windows Configuration System Loads, Saves, and Processes Changes

> Discover how Shadowsocks Windows loads, saves, and processes configuration changes using static methods in Configuration.cs. Learn about JSON deserialization, validation, and runtime defaults.

- Repository: [shadowsocks/shadowsocks-windows](https://github.com/shadowsocks/shadowsocks-windows)
- Tags: internals
- Published: 2026-03-05

---

**The Shadowsocks Windows configuration system uses three static methods—`Load()`, `Process()`, and `Save()`—in [`shadowsocks-csharp/Model/Configuration.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Model/Configuration.cs) to deserialize *gui-config.json* into a `Configuration` object, validate and enrich it with runtime defaults (geosite groups, server indices, IPv6 checks), and persist changes back to disk while synchronizing NLog logging levels.**

The `Configuration` class in the `shadowsocks/shadowsocks-windows` repository manages the complete lifecycle of user settings through a structured three-phase pipeline. This system handles everything from initial file I/O and JSON deserialization to runtime validation and persistent storage, ensuring that proxy settings, server lists, and UI preferences remain consistent across application sessions.

## Loading the Configuration from Disk

The ** `Load()` ** method (lines [61‑74](https://github.com/shadowsocks/shadowsocks-windows/blob/v4/shadowsocks-csharp/Model/Configuration.cs#L61-L74)) serves as the entry point for reading persisted settings. It first checks for the existence of *gui-config.json* using `File.Exists(CONFIG_FILE)`. If the file is present, the method reads its contents and deserializes the JSON using **Newtonsoft.Json** with `ObjectCreationHandling.Replace` to ensure collections are recreated rather than appended to existing objects.

If the file is missing or contains malformed JSON, the method falls back to instantiating a fresh `Configuration` object with sensible defaults defined in the constructor. This defensive approach ensures the application always boots with a valid configuration state.

```csharp
public static Configuration Load()
{
    if (File.Exists(CONFIG_FILE))
    {
        try
        {
            string configContent = File.ReadAllText(CONFIG_FILE);
            return JsonConvert.DeserializeObject<Configuration>(configContent,
                new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace });
        }
        catch (Exception e) { logger.LogUsefulException(e); }
    }
    return new Configuration();          // defaults
}

```

## Processing and Validating Configuration Data

After loading, the ** `Process(ref Configuration)` ** method (lines [85‑124](https://github.com/shadowsocks/shadowsocks-windows/blob/v4/shadowsocks-csharp/Model/Configuration.cs#L85-L124)) performs a comprehensive validation and enrichment pass. This method is called exactly once during application startup to normalize data and adapt settings to the current runtime environment.

### Geosite Group Validation

The system validates geosite categorization lists for both direct and proxied connections using `ValidateGeositeGroupList()`. If any configured group is unknown or invalid, the method resets the respective list to safe defaults via `ResetGeositeDirectGroup()` or `ResetGeositeProxiedGroup()`.

### Version Migration and Server List Management

The method detects application upgrades by comparing `UpdateChecker.Version` against the stored `config.version`. When the application version is newer, it sets the `firstRunOnNewVersion` flag to trigger welcome flows or migration logic. The processor also ensures the server list is never empty by injecting a `GetDefaultServer()` when `configs.Count == 0`, and it corrects the `index` pointer to ensure it points to a valid server or defaults to `0`.

### System Capability and Proxy Checks

Runtime environment validation includes disabling `isIPv6Enabled` automatically when `System.Net.Sockets.Socket.OSSupportsIPv6` returns false. The method invokes `config.proxy.CheckConfig()` to validate forward-proxy settings and interpolates the user-agent string by replacing the `$version` placeholder with the actual version number.

### NLog Integration

The processing phase synchronizes logging verbosity by loading the NLog XML configuration via `NLogConfig.LoadXML()`. It inspects the current log level and sets the `isVerboseLogging` boolean to true if the level is `Debug` or `Trace`, enabling conditional verbose output throughout the application.

```csharp
public static void Process(ref Configuration config)
{
    // Geosite validation
    if (!ValidateGeositeGroupList(config.geositeDirectGroups))
        ResetGeositeDirectGroup(ref config.geositeDirectGroups);
    if (!ValidateGeositeGroupList(config.geositeProxiedGroups))
        ResetGeositeProxiedGroup(ref config.geositeProxiedGroups);

    // Version detection
    var appVersion = new Version(UpdateChecker.Version);
    var cfgVersion = new Version(config.version);
    if (appVersion.CompareTo(cfgVersion) > 0) config.firstRunOnNewVersion = true;

    // Server list sanity
    if (config.configs.Count == 0) config.configs.Add(GetDefaultServer());
    if (config.index == -1 && string.IsNullOrEmpty(config.strategy)) config.index = 0;
    if (config.index >= config.configs.Count) config.index = config.configs.Count - 1;

    // System capability checks
    if (!System.Net.Sockets.Socket.OSSupportsIPv6) config.isIPv6Enabled = false;
    config.proxy.CheckConfig();
    config.userAgentString = config.userAgent.Replace("$version", config.version);

    // NLog level detection
    try
    {
        config.nLogConfig = NLogConfig.LoadXML();
        config.isVerboseLogging = config.nLogConfig.GetLogLevel()
            is NLogConfig.LogLevel.Debug or NLogConfig.LogLevel.Trace;
    }
    catch (Exception e) { /* error handling */ }
}

```

## Saving Configuration Changes

The ** `Save(Configuration)` ** method (lines [136‑166](https://github.com/shadowsocks/shadowsocks-windows/blob/v4/shadowsocks-csharp/Model/Configuration.cs#L136-L166)) handles persistence when the application shuts down or the user explicitly triggers a save. Before writing, it reorders the server list using `SortByOnlineConfig()` to ensure ungrouped servers appear first, maintaining a deterministic order for online configuration sources.

The method opens *gui-config.json* with `FileMode.Create`, effectively truncating and rewriting the file atomically. It serializes the configuration using `Formatting.Indented` for human readability. Finally, it synchronizes the NLog log level based on `isVerboseLogging` and persists the XML configuration back to disk, ensuring logging settings survive application restarts.

```csharp
public static void Save(Configuration config)
{
    config.configs = SortByOnlineConfig(config.configs);

    try
    {
        using var fs = File.Open(CONFIG_FILE, FileMode.Create);
        using var writer = new StreamWriter(fs);
        writer.Write(JsonConvert.SerializeObject(config, Formatting.Indented));

        config.nLogConfig.SetLogLevel(
            config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
        NLogConfig.SaveXML(config.nLogConfig);
    }
    catch (Exception e) { logger.LogUsefulException(e); }
}

```

## Practical Implementation Examples

### Basic Load-Modify-Save Workflow

The standard pattern for manipulating settings follows a three-step sequence: load the existing state, apply the processing phase to ensure validation, modify properties, and persist changes.

```csharp
// Load existing configuration or create defaults
var cfg = Configuration.Load();

// Apply validation, migration, and enrichment
Configuration.Process(ref cfg);

// Modify specific settings
cfg.useOnlinePac = true;
cfg.pacUrl = "https://example.com/proxy.pac";

// Persist to disk
Configuration.Save(cfg);

```

### Adding Servers Programmatically

When integrating new server entries from UI dialogs or external sources, use the configuration's list management methods before saving.

```csharp
Server newServer = new Server
{
    server = "192.168.1.100",
    server_port = 8388,
    password = "securePassword123",
    method = "aes-256-gcm",
    remarks = "Home Proxy"
};

Configuration.AddDefaultServerOrServer(cfg, newServer);
Configuration.Save(cfg);

```

### Detecting Version Upgrades

The processing phase sets flags that downstream components can inspect to trigger version-specific onboarding or migration logic.

```csharp
Configuration.Process(ref cfg);
if (cfg.firstRunOnNewVersion)
{
    MessageBox.Show($"Welcome to Shadowsocks {cfg.version}! Check the release notes for new features.");
}

```

## Summary

- The ** `Configuration` ** class in [`shadowsocks-csharp/Model/Configuration.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Model/Configuration.cs) provides the static trio `Load()`, `Process()`, and `Save()` to manage settings lifecycle.
- **Loading** uses Newtonsoft.Json with `ObjectCreationHandling.Replace` and falls back to default constructors on any I/O or parsing error.
- **Processing** validates geosite groups, corrects server indices, detects version upgrades, checks OS IPv6 support, validates proxy settings, and synchronizes NLog verbosity levels.
- **Saving** reorders servers via `SortByOnlineConfig()`, writes indented JSON to *gui-config.json*, and persists NLog configuration changes atomically.
- The system gracefully handles missing files, schema changes, and runtime environment variations without user intervention.

## Frequently Asked Questions

### Where does Shadowsocks Windows store its configuration file?

The application persists all user settings to ** *gui-config.json* ** in the executable directory. The `Configuration` class uses this filename (defined as `CONFIG_FILE`) for all read and write operations via the `Load()` and `Save()` methods.

### What happens if the configuration file is corrupted or missing?

If `File.Exists(CONFIG_FILE)` returns false or if `JsonConvert.DeserializeObject` throws an exception during `Load()`, the system catches the error, logs it via `LogUsefulException()`, and returns a new `Configuration()` instance populated with hardcoded defaults from the constructor.

### How does the system handle application version upgrades?

During `Process()`, the system compares `UpdateChecker.Version` against the stored `config.version`. If the application version is newer, it sets the boolean `firstRunOnNewVersion` to true, allowing UI components to display welcome dialogs or trigger migration scripts specific to the new release.

### Why are servers reordered before saving?

The `Save()` method calls `SortByOnlineConfig()` to ensure that servers without an online configuration grouping appear first in the JSON array. This deterministic ordering prevents configuration drift when users mix manually configured servers with those fetched from remote subscription URLs.