# How config.c and boxpreferences.c Manage Persistent User Configurations Across Platforms in ArmorPaint

> Learn how ArmorPaint uses config.c and boxpreferences.c with Kinc to manage persistent user configurations and UI layouts across different platforms via JSON files.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: internals
- Published: 2026-09-14

---

**ArmorPaint persists user settings via JSON-based configuration files stored in platform-specific directories, using config.c for global application preferences and boxpreferences.c for UI layout states, both leveraging the Kinc abstraction layer for cross-platform file I/O.**

ArmorPaint implements a robust, platform-agnostic configuration system that preserves user data across Windows, macOS, and Linux sessions. The architecture relies on two dedicated C modules located in [`paint/sources/config.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/config.c) and [`paint/sources/boxpreferences.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/boxpreferences.c) to handle persistent storage. These modules utilize the Kinc framework's path and file utilities to ensure settings remain consistent regardless of the underlying operating system.

## Platform-Aware Storage Locations

Both configuration modules determine the appropriate filesystem location at runtime using `kinc_path_user()` and `kinc_path_join()` from the Kinc library. This abstraction eliminates hardcoded paths and ensures compliance with each platform's user data conventions.

### Windows Configuration Paths

On Windows, ArmorPaint stores configuration data in the `%APPDATA%\ArmorPaint\` directory. The `config_init()` function constructs this path by joining the result of `kinc_path_user()` with `"ArmorPaint"` and appending the filename.

- **Global config**: `%APPDATA%\ArmorPaint\config.json`
- **UI preferences**: `%APPDATA%\ArmorPaint\preferences.json` (managed by boxpreferences.c)

### macOS and Linux Configuration Paths

Unix-like platforms follow the XDG Base Directory specification and macOS application support conventions:

| Platform | Configuration Directory | File |
|----------|------------------------|------|
| **macOS** | `~/Library/Application Support/ArmorPaint/` | [`config.json`](https://github.com/armory3d/armorpaint/blob/main/config.json) |
| **Linux** | `~/.config/armorpaint/` | [`config.json`](https://github.com/armory3d/armorpaint/blob/main/config.json) |

The same `kinc_path_join()` logic applies, with the directory component adjusted per platform during compilation.

## JSON-Based Serialization Architecture

Both modules use a lightweight JSON parser ([`json.h`](https://github.com/armory3d/armorpaint/blob/main/json.h)/[`json.c`](https://github.com/armory3d/armorpaint/blob/main/json.c)) to serialize and deserialize configuration data. The schema includes a version field (`"version": 2`) to enable safe migration of older configuration files when the application updates.

### Saving Configuration Data

The `config_save()` and `boxpreferences_save()` functions populate JSON objects with current state values and write them using `kinc_file_save_bytes()`:

```c
void config_save(void) {
    // Build JSON object with current settings
    json_object *root = json_object_new_object();
    json_object_object_add(root, "version", json_object_new_int(2));
    json_object_object_add(root, "brush_size", json_object_new_double(brush_size));
    
    const char *json_str = json_object_to_json_string(root);
    kinc_file_save_bytes(config_path, (unsigned char *)json_str, strlen(json_str));
    json_object_put(root);
}

```

### Loading Configuration Data

On startup, `config_load()` and `boxpreferences_load()` read existing files via `kinc_file_load_bytes()`, parse the JSON, and populate internal structures:

```c
void config_load(void) {
    unsigned char *data = kinc_file_load_bytes(config_path, &size);
    if (data) {
        json_object *root = json_tokener_parse((char *)data);
        json_object *version;
        if (json_object_object_get_ex(root, "version", &version)) {
            // Migrate or load based on version number
        }
        json_object_put(root);
        free(data);
    }
}

```

## Separation of Concerns in Configuration Modules

The codebase deliberately splits responsibilities between the two modules to maintain clean architecture:

- **[`paint/sources/config.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/config.c)**: Manages global application state including default brush parameters, recent projects list, last opened directory, and GPU backend selection. This module initializes early in `main()` via `config_init()`.

- **[`paint/sources/boxpreferences.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/boxpreferences.c)**: Handles dockable UI panel states specifically. It stores each panel's position, dimensions, and collapsed/expanded status. The module saves immediately when users drag or resize panels through `boxpreferences_save()`.

Both modules expose parallel APIs:

```c
// Global configuration API
void config_init(void);
void config_load(void);
void config_save(void);

// UI preferences API  
void boxpreferences_init(void);
void boxpreferences_load(void);
void boxpreferences_save(void);

```

## Cross-Platform Abstraction via Kinc

All filesystem operations route through Kinc functions to ensure portability:

- **`kinc_path_user()`**: Returns the base user data directory appropriate for the current platform
- **`kinc_path_join()`**: Safely concatenates path components with correct directory separators
- **`kinc_file_load_bytes()` / `kinc_file_save_bytes()`**: Handle raw binary I/O without platform-specific file handles

This abstraction allows identical source code to compile and execute on Windows, macOS, and Linux without conditional compilation blocks in the configuration logic itself.

## Thread Safety and Error Handling

To prevent race conditions when the UI thread writes preferences while the main thread reads them, both modules protect file I/O with a global mutex:

```c
static kinc_mutex_t config_mutex;

void config_save(void) {
    kinc_mutex_lock(&config_mutex);
    // ... file operations ...
    kinc_mutex_unlock(&config_mutex);
}

```

Error handling follows a defensive pattern: if `kinc_file_load_bytes()` returns NULL (missing file) or JSON parsing fails, the modules log the error and initialize default values. The application then writes a fresh configuration file on the next save operation, ensuring corrupted or deleted configs never crash the program.

## Summary

- **config.c** and **boxpreferences.c** in `paint/sources/` implement ArmorPaint's persistent storage using JSON serialization.
- Storage paths are determined at runtime using `kinc_path_user()` and `kinc_path_join()`, yielding `%APPDATA%\ArmorPaint\` on Windows and `~/.config/armorpaint/` on Linux.
- The system uses a versioned JSON schema (currently version 2) to support configuration migration between application updates.
- File I/O operations are abstracted through Kinc functions (`kinc_file_load_bytes`, `kinc_file_save_bytes`) for cross-platform compatibility.
- A global mutex (`config_mutex`) ensures thread-safe access to configuration files during concurrent UI and main thread operations.

## Frequently Asked Questions

### Where does ArmorPaint store its configuration files on Windows?

ArmorPaint stores configuration files in `%APPDATA%\ArmorPaint\config.json`, resolved at runtime using `kinc_path_user()` joined with the application name. This path appears in [`paint/sources/config.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/config.c) where the global `config_path` variable is initialized during `config_init()`.

### How does ArmorPaint handle corrupted or missing configuration files?

When `config_load()` or `boxpreferences_load()` encounters a missing file or invalid JSON, the functions fall back to hardcoded default values and log the error. The application continues normally and regenerates a valid configuration file on the next save operation, ensuring user workflow interruption is minimal.

### What is the difference between config.c and boxpreferences.c?

[`config.c`](https://github.com/armory3d/armorpaint/blob/main/config.c) manages global application settings such as brush defaults, recent projects, and hardware preferences, while [`boxpreferences.c`](https://github.com/armory3d/armorpaint/blob/main/boxpreferences.c) specifically tracks UI panel geometry and visibility states. The former saves primarily on application shutdown, whereas the latter triggers saves immediately when users manipulate dockable panels.

### Is the ArmorPaint configuration format versioned for upgrades?

Yes, both configuration modules write a `"version": 2` field into their JSON output. The loading functions check this version number to determine if migration logic is required, allowing newer application versions to safely upgrade older configuration files without losing user data.