# How Unciv's Save/Load System Works Across Desktop, Linux X11, and Android

> Discover how Unciv's save/load system works on desktop, Linux X11, and Android. Learn about JSON serialization and platform-specific implementations for seamless cross-platform saving.

- Repository: [Yair Morgenstern/Unciv](https://github.com/yairm210/Unciv)
- Tags: internals
- Published: 2026-06-18

---

**Unciv stores game states and maps as JSON strings (optionally Gzip-compressed) and delegates file I/O to platform-specific `PlatformSaverLoader` implementations, allowing the same serialization logic to run natively on Windows, macOS, Linux X11, and Android.**

Unciv, the open-source Civilization V clone maintained by yairm210, persists game data using a clean separation between platform-agnostic serialization and operating system-specific file operations. The **Unciv save/load system** relies on a single-string contract where the core game logic handles JSON conversion while injected platform loaders manage native file choosers and storage access.

## Core Architecture

The system is built around three components that isolate platform differences from game logic:

- **`PlatformSaverLoader`** – An interface defined in [`core/src/com/unciv/logic/files/PlatformSaverLoader.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/files/PlatformSaverLoader.kt) that specifies `saveGame()` and `loadGame()` methods accepting serialized strings and callback handlers.
- **`UncivFiles.saverLoader`** – A globally accessible singleton that UI screens call when players initiate save or load actions. The concrete implementation is injected at launch by platform-specific entry points.
- **`MapSaver` and `GameSaver` logic** – Platform-agnostic utilities in [`core/src/com/unciv/logic/files/MapSaver.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/files/MapSaver.kt) that serialize `TileMap` and `GameInfo` objects to JSON, optionally compress with Gzip, and pass the resulting string to the active loader.

## Platform-Specific Loader Selection

When Unciv starts, the launcher detects the operating environment and injects the appropriate loader into `UncivFiles.saverLoader`.

**Desktop (Windows/macOS)** – [`DesktopLauncher.kt`](https://github.com/yairm210/Unciv/blob/main/DesktopLauncher.kt) instantiates `DesktopSaverLoader`, which uses the standard Swing file chooser.

**Linux X11** – [`LinuxX11SaverLoader.kt`](https://github.com/yairm210/Unciv/blob/main/LinuxX11SaverLoader.kt) provides `isRequired()` to detect X11 environments. When true, [`DesktopLauncher.kt`](https://github.com/yairm210/Unciv/blob/main/DesktopLauncher.kt) injects `LinuxX11SaverLoader` instead of the default to avoid AWT blocking bugs on Linux.

**Android** – [`AndroidLauncher.kt`](https://github.com/yairm210/Unciv/blob/main/AndroidLauncher.kt) constructs `AndroidSaverLoader(activity)`, which wraps the Android Storage Access Framework (SAF).

```kotlin
// From DesktopLauncher.kt
UncivFiles.saverLoader = if (LinuxX11SaverLoader.isRequired())
    LinuxX11SaverLoader() else DesktopSaverLoader()

```

## Saving Games

The save workflow begins in the UI layer and delegates through the abstraction to native storage.

1. The [`SaveGameScreen.kt`](https://github.com/yairm210/Unciv/blob/main/SaveGameScreen.kt) gathers the current `GameInfo` object and serializes it to JSON.
2. The serialized string is passed to the injected loader with suggested filename and callbacks.
3. The platform loader displays the native file picker, writes the UTF-8 string to the selected location, and invokes `onSaved` or `onError`.

```kotlin
// Inside SaveGameScreen.kt
val serializedGame = json().toJson(UncivGame.Current.gameInfo)
UncivFiles.saverLoader.saveGame(
    data = serializedGame,
    suggestedLocation = "MyGame.save",
    onSaved = { location -> 
        Gdx.app.postRunnable { toast("Saved to $location") } 
    },
    onError = { ex -> 
        if (ex !is PlatformSaverLoader.Cancelled) 
            toast("Error: ${ex.message}") 
    }
)

```

## Loading Games

Loading mirrors the save process but returns the file content as a string that the game deserializes.

1. [`LoadGameScreen.kt`](https://github.com/yairm210/Unciv/blob/main/LoadGameScreen.kt) invokes the loader, which opens the platform's file picker.
2. The loader reads the selected file as a UTF-8 string and returns it via the `onLoaded` callback.
3. The UI deserializes the JSON into a `GameInfo` instance and replaces the current game state.

```kotlin
// Inside LoadGameScreen.kt
UncivFiles.saverLoader.loadGame(
    onLoaded = { data, location ->
        val loadedGame = json().fromJson(GameInfo::class.java, data)
        UncivGame.Current.gameInfo = loadedGame
        toast("Loaded $location")
    },
    onError = { ex -> 
        if (ex !is PlatformSaverLoader.Cancelled) 
            toast("Load failed: ${ex.message}") 
    }
)

```

## Map-Specific Operations

Map files use the same serialization pipeline but often bypass the external file picker for internal storage. [`MapSaver.kt`](https://github.com/yairm210/Unciv/blob/main/MapSaver.kt) handles `TileMap` objects used by the map editor and map-selection UI.

When saving to internal storage, `MapSaver.saveMap()` writes to `UncivGame.Current.files.getLocalFile("maps")`. When explicit user selection is required, it uses the same `PlatformSaverLoader` contract. The utility respects the `MapSaver.saveZipped` boolean to apply Gzip compression before writing.

```kotlin
// Save a map from the map editor
val mapName = "MyCustomMap"
MapSaver.saveMap(mapName, editorScreen.currentMap)

```

## Summary

- **Platform abstraction** – `PlatformSaverLoader` defines a single-string contract (`saveGame`/`loadGame`) while `DesktopSaverLoader`, `LinuxX11SaverLoader`, and `AndroidSaverLoader` handle OS-native file dialogs.
- **Injection pattern** – `UncivFiles.saverLoader` is set at launch by [`DesktopLauncher.kt`](https://github.com/yairm210/Unciv/blob/main/DesktopLauncher.kt) or [`AndroidLauncher.kt`](https://github.com/yairm210/Unciv/blob/main/AndroidLauncher.kt) based on the detected platform.
- **Consistent serialization** – `MapSaver` and `GameInfo` JSON conversion run identically on all targets, with optional Gzip compression for map data.
- **Graceful cancellation** – The `PlatformSaverLoader.Cancelled` exception allows UI screens to silently handle user-canceled dialogs without error messages.

## Frequently Asked Questions

### Why does Unciv use a separate loader for Linux X11?

The standard Java AWT file chooser blocks indefinitely on some Linux X11 window managers. `LinuxX11SaverLoader` works around this by using an X11-specific native dialog, while `DesktopSaverLoader` handles standard Windows and macOS Swing dialogs.

### What file format does Unciv use for saves?

Unciv uses plain-text JSON for game saves and maps, optionally wrapped in Gzip compression for map files when `MapSaver.saveZipped` is enabled. The save files are human-readable when uncompressed and store complete `GameInfo` object states.

### How does Unciv handle user cancellation during save/load operations?

Each `PlatformSaverLoader` implementation throws a `PlatformSaverLoader.Cancelled` exception when the user dismisses the file picker without selecting a location. UI screens in [`SaveGameScreen.kt`](https://github.com/yairm210/Unciv/blob/main/SaveGameScreen.kt) and [`LoadGameScreen.kt`](https://github.com/yairm210/Unciv/blob/main/LoadGameScreen.kt) catch this exception specifically to suppress error toasts, treating cancellation as a silent no-op rather than a failure.