Skip to content

Data Persistence System

A robust, configurable data persistence solution for Unity applications built with the LoL Engine framework.

Quick Start

IMPORTANT: Config Assets Must Be In Resources Folder

  1. Create LoLEngineConfig (Right-click → Create → LoLEngine → Config → LoLEngineConfig):
  2. Save at: Assets/[YourProject]/Resources/Configs/DefaultLoLEngineConfig.asset
  3. Configure: obfuscation (enabled by default), encryption (off by default), compression, save paths

  4. Create SaveConfig (Right-click → Create → LoLEngine → Data Persistence → Save Configuration):

  5. Save at: Assets/[YourProject]/Resources/Configs/DefaultSaveConfig.asset
  6. Configure: save slots, auto-save interval, quick-save settings

  7. Enable Services in your ServiceConfiguration.asset:

  8. Enable Data Persistence Service (core save system)
  9. Enable Serialization Service (required for JSON)
  10. Enable Compression Service (if using compression)
  11. Enable Encryption Service (if using encryption)
  12. Enable Auto Save Service (optional, for auto-saving)
  13. Enable Quick Save Service (optional, for F5/F9 quick save)

  14. Add ImprovedGameInitializer to your first scene and assign your ServiceConfiguration

Basic usage:

using LoLEngine.Core.DataPersistence.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;

var saveSystem = ServiceLocator.Instance.Get<ISaveSystem>();

// Access or create data
var player = saveSystem.GetData<PlayerData>();
player.PlayerName = "Hero";
player.Health = 100;

// Save/Load
saveSystem.SaveGame("save1");
bool ok = saveSystem.LoadGame("save1");

Note: This file reflects recent improvements to the Save System. It aligns with the current LoLEngine implementation: engine-wide compression/obfuscation/encryption live in LoLEngineConfig, AutoSave uses SaveConfig if present, saves use atomic writes, and the save management API is available.

Overview

The LoL Engine Data Persistence system provides a service-based approach to saving and loading game data. Built on the Service Locator pattern, it offers JSON serialization with optional compression and protection (obfuscation or encryption), plus an organized structure for managing persistent game data.

Features

  • Type-safe data persistence through PersistableData inheritance
  • Multiple formats: JSON, JSON with obfuscation, or JSON with encryption
  • Obfuscation for fast save protection (enabled by default)
  • Optional compression to reduce save file sizes
  • Optional encryption for sensitive game data (configured in LoLEngineConfig)
  • Save slots for multiple save files
  • Asynchronous operations for performance
  • Events for save begin/complete (via GameEvent)
  • Auto-save functionality
  • Checksum stored with save container (validation available to call)
  • Configuration via LoLEngineConfig (engine-wide compression/obfuscation/encryption, paths, extensions) and SaveConfig (game behavior for autosave/quicksave slots/intervals).
  • Atomic save operations (writing to temporary files first) for enhanced data integrity.
  • Data versioning support within save files.
  • Checksum validation for save file integrity.
  • Asynchronous operations by default for non-blocking save/loads.

Overhaul Summary and How To Use It

The Save System received a major update. This section explains what changed and how to adopt it.

What Changed

  • SECURITY: Removed committed encryption keys from the repository. Use environment variables or build-time injection for production keys.
  • ARCHITECTURE: Consolidated configuration. Protection/compression now live in LoLEngineConfig (single source of truth). SaveConfig focuses on gameplay behavior (slots/intervals/toggles for auto/quick save).
  • PRODUCTION PATHS: Saves now use Application.persistentDataPath for builds. Editor paths remain predictable for debugging.
  • FLEXIBILITY: File extensions are resolved dynamically via SaveSettings.GetFileExtension(); do not hardcode extensions.
  • SCENE-AWARE AUTOSAVE: AutoSave service can exclude common non-gameplay scenes using a built-in list (e.g., MainMenu/Boot/Loading). Toggle with RestrictAutoSaveToGameplay.
  • API ENHANCEMENT: Added ListSavesAsync, DeleteAsync, GetLatestAsync, HasAnySavesAsync for richer file management.
  • VERSION SUPPORT: Added on-load version validation and a foundation for save migrations.
  • INTERFACE CONSISTENCY: Clarified ISaveSystem vs IAutoSaveService responsibilities.

Setup Steps (New)

1. LoLEngineConfig (engine-wide) - Create/locate Resources/Configs/DefaultLoLEngineConfig.asset (created via menu: LoLEngine/Config/LoLEngineConfig). - Set compression/obfuscation/encryption here. Do NOT place production keys in the repo; inject via environment variables/CI and write into LoLEngineConfig at build/startup.

2. SaveConfig (game behavior) - Create via: Create → LoLEngine → Data Persistence → Save Configuration. - Configure: default/quick/auto save slots, quick-save limits, auto-save interval, and whether to restrict auto-save to gameplay scenes. - Placement: put the asset at Resources/Configs/DefaultSaveConfig.asset to be auto-discovered by AutoSave.

3. ServiceConfiguration - Enable: Serialization, (optional) Compression, (optional) Encryption services. - Enable: Data Persistence (Save System), Auto Save Service (if needed), Quick Save Service (optional).

4. Game Initializer - Use ImprovedGameInitializer and assign your ServiceConfiguration. - The initializer registers SaveSystem and (optionally) AutoSaveService. AutoSave picks up settings from DefaultSaveConfig if present; SaveSystem reads engine-level settings from LoLEngineConfig.

Where to Put Obfuscation/Encryption Configuration

  • Put obfuscation and encryption configuration in LoLEngineConfig ONLY.
  • Obfuscation (default): Set obfuscateSaveFiles = true for fast protection.
  • Encryption: Set encryptSaveFiles = true only for sensitive data. This takes precedence over obfuscation at runtime.
  • In production, inject encryption keys via environment variables or CI/build scripts. Do not commit production keys.

Save Paths and Extensions

  • Use SaveSettings.GetSaveFilePath(slot) to get the full path under Application.persistentDataPath.
  • Use SaveSettings.GetFileExtension() to get the correct extension for current settings (do not hardcode .sav/.osav/encrypted ext).

AutoSave Scene Filtering

  • Configure an excluded-scenes list (e.g., Boot, MainMenu, Loading) in SaveConfig.
  • The AutoSave service subscribes to scene changes and pauses/resumes accordingly.

New Save Management API

// List existing save files (with file system metadata)
IReadOnlyList<SaveFileInfo> files = await _saveSystem.ListSavesAsync();

// Delete a save
await _saveSystem.DeleteAsync("save1");

// Get the most recent save
var latest = await _saveSystem.GetLatestAsync();

// Do we have any saves at all?
bool any = await _saveSystem.HasAnySavesAsync();

SaveFileInfo provides file-level metadata (name, timestamp, size) and is separate from your game's SaveMetadata.

Atomic Writes and Integrity

  • Saves write to a temporary file and then perform an atomic move to prevent partial/corrupted files.
  • Checksums validate integrity during load.

Versioning and Migration

  • Override SchemaVersion on each PersistableData subclass whose serialized fields change, then register a per-domain migration with _saveSystem.RegisterDomainMigration(...) (a bundle-level ISaveMigration via RegisterMigration(...) is also available).
  • The system validates each domain's stored version at load and routes incompatible versions to your registered migration steps.

Security Checklist

  • Never commit production keys.
  • Use environment variables or CI secrets to inject keys.
  • Keep Encryption/Compression config in LoLEngineConfig only (do not duplicate in SaveConfig).

Architecture

The Data Persistence system is organized as follows:

DataPersistence/
├── Data/                       # Core data structures
│   ├── GameSaveData.cs         # Container for all game data
│   └── PersistableData.cs      # Base class for all save data
├── Events/                     # Save-related events
│   └── SaveEvents.cs           # Event definitions
├── Interfaces/                 # Service interfaces
│   ├── IAutoSaveService.cs     # Auto-save functionality
│   ├── IQuickSaveService.cs    # Quick save functionality
│   └── ISaveSystem.cs          # Main persistence interface
├── Service/                    # Service implementations
│   ├── AutoSaveService.cs      # Auto-save implementation
│   ├── QuickSaveService.cs     # Quick save implementation
│   └── SaveSystem.cs           # Main persistence implementation
└── Settings/                   # Configuration
    ├── SaveConfig.cs           # ScriptableObject for editor-time configuration
    └── SaveSettings.cs         # Runtime persistence setting (derived from SaveConfig)

Getting Started

1. Configure and Initialize the Save System

The Data Persistence system is initialized as part of the LoL Engine's startup sequence when using the ImprovedGameInitializer. Configuration is primarily handled through ScriptableObject assets:

  1. Enable Data Persistence Services:

    • In your ServiceConfiguration asset (assigned to ImprovedGameInitializer), ensure the relevant data persistence services are enabled:
      • EnableDataPersistenceService (for the core ISaveSystem)
      • EnableAutoSaveService (if you need auto-save functionality)
      • EnableQuickSaveService (if you need quick save/load)
      • Ensure EnableSerializationService, EnableCompressionService, and EnableEncryptionService are enabled if you intend to use their respective features with saving.
  2. Create a SaveConfig Asset:

    • Create a SaveConfig asset in your project (Right-click in Project window → CreateLoLEngineData PersistenceSave Configuration).
    • Customize settings within this SaveConfig asset: default/quick/auto save slots, max quick saves, auto-save interval, and restrict-to-gameplay toggle.
    • Place the asset at Resources/Configs/DefaultSaveConfig.asset so AutoSave can auto-discover it.
  3. Ensure ImprovedGameInitializer is Set Up:

    • Have an ImprovedGameInitializer component in your first scene with your ServiceConfiguration assigned to it.

The engine will then use these configurations to initialize the SaveSystem and related services.

2. Define Data Classes

Create data classes by inheriting from PersistableData:

using System;
using LoLEngine.Core.DataPersistence.Data;
using UnityEngine;

namespace YourGame.Data
{
    [Serializable]
    public class PlayerData : PersistableData
    {
        // Unique identifier for this data class
        public override string DataId => "player";

        // Serialized fields
        [SerializeField] private string playerName;
        [SerializeField] private int health;
        [SerializeField] private Vector3 position;

        // Properties with getters/setters
        public string PlayerName
        {
            get => playerName;
            set => playerName = value;
        }

        public int Health
        {
            get => health;
            set => health = value;
        }

        public Vector3 Position
        {
            get => position;
            set => position = value;
        }

        // Optional lifecycle hooks
        public override void OnBeforeSave()
        {
            // Prepare data before saving (e.g., update timestamps)
        }

        public override void OnAfterLoad()
        {
            // Process data after loading (e.g., validate values)
        }
    }
}

3. Register and Use Data

Access the save system and register your data objects:

using LoLEngine.Core.DataPersistence.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using UnityEngine;
using YourGame.Data;

public class GameManager : MonoBehaviour
{
    private ISaveSystem _saveSystem;

    private void Awake()
    {
        // Get save system through service locator
        _saveSystem = ServiceLocator.Instance.Get<ISaveSystem>();

        // Initialize game data
        InitializeGameData();
    }

    private void InitializeGameData()
    {
        // Create or get player data
        var playerData = _saveSystem.GetData<PlayerData>();

        // Initialize with default values if needed
        playerData.PlayerName = "Hero";
        playerData.Health = 100;
        playerData.Position = Vector3.zero;

        // Register other game data types
        // Note: GetData automatically registers a new instance if not found
        var inventoryData = _saveSystem.GetData<InventoryData>();
        var questData = _saveSystem.GetData<QuestData>();
    }

    // Call this to manually save the game
    public void SaveGame()
    {
        // Update values before saving
        var playerData = _saveSystem.GetData<PlayerData>();
        playerData.Position = transform.position;

        try
        {
            // Save to "save1" slot
            _saveSystem.SaveGame("save1");
            Debug.Log("Game saved successfully");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"Save failed: {e.Message}");
        }
    }

    // Call this to load a saved game
    public void LoadGame()
    {
        // Check if save exists before loading
        if (_saveSystem.HasSaveFile("save1"))
        {
            if (_saveSystem.LoadGame("save1"))
            {
                // Access loaded data
                var playerData = _saveSystem.GetData<PlayerData>();

                // Apply loaded values to game state
                transform.position = playerData.Position;
                Debug.Log($"Loaded player: {playerData.PlayerName}, Health: {playerData.Health}");
            }
            else
            {
                Debug.LogError("Failed to load save file");
            }
        }
        else
        {
            Debug.Log("No save file found");
        }
    }

    // For large save files, use async methods
    public async void SaveGameAsync()
    {
        try
        {
            await _saveSystem.SaveGameAsync("save1");
            Debug.Log("Game saved asynchronously");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"Async save failed: {e.Message}");
        }
    }
}

Configuration Options

Configuration is split between LoLEngineConfig (engine-wide) and SaveConfig (game behavior):

  • LoLEngineConfig (Resources/Configs/DefaultLoLEngineConfig):
  • saveDirectory, saveFileExtension, obfuscatedSaveFileExtension, encryptedSaveFileExtension
  • compressSaveFiles, compressionLevelForSaveFiles
  • obfuscateSaveFiles (default), encryptSaveFiles, defaultEncryptionKey, encryptionKeySeed

  • SaveConfig (Resources/Configs/DefaultSaveConfig):

  • DefaultSaveSlot, QuickSaveSlot, AutoSaveSlot
  • MaxSaveSlots, EnableQuickSave, EnableAutoSave
  • AutoSaveInterval, RestrictAutoSaveToGameplay

Tip: You generally do not need to construct SaveSettings yourself. SaveSystem reads engine-wide options from LoLEngineConfig; AutoSaveService reads game behavior from DefaultSaveConfig if present.

Save File Types

The system supports three formats with automatic detection (names configurable in LoLEngineConfig): - Obfuscated JSON (default, fast) — default extension .osav - Encrypted JSON (for sensitive data) — default extension .esave - Plain JSON — default extension .sav (not recommended)

Priority when loading: obfuscated > encrypted > plain. Always use SaveSettings.GetFileExtension() rather than hardcoding extensions.

Priority when loading: obfuscated > encrypted > plain. Always use SaveSettings.GetFileExtension() rather than hardcoding extensions.

See Save protection below for obfuscation vs encryption guidance.

Advanced Features

Save Slots

The system supports multiple save files:

// Save to different slots
_saveSystem.SaveGame("quicksave");
_saveSystem.SaveGame("slot1");
_saveSystem.SaveGame("autosave");

// Load from specific slot
_saveSystem.LoadGame("slot1");

// Check if a save exists
if (_saveSystem.HasSaveFile("quicksave"))
{
    // Save exists
}

Auto-Save (IAutoSaveService)

Enable in ServiceConfiguration: Enable Auto Save Service (requires Data Persistence + Serialization).

Configure behavior in SaveConfig:

Auto Save Settings:
├─ Auto Save Enabled: true
├─ Auto Save Interval: 300 (seconds)
├─ Auto Save Slot: "autosave"
└─ Restrict Auto Save To Gameplay: true

Auto-save starts automatically when the service initializes (if enabled in config). Use DefaultSaveConfig at Resources/Configs/DefaultSaveConfig.asset so AutoSave can auto-discover settings.

using LoLEngine.Core.DataPersistence.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;

var autoSave = ServiceLocator.Instance.Get<IAutoSaveService>();

autoSave.StartAutoSave();   // resume periodic saves
autoSave.StopAutoSave();    // pause (e.g. main menu)
autoSave.SaveNow();         // force immediate save (scene transitions, quit)

Scene filtering: When RestrictAutoSaveToGameplay is true, AutoSave pauses in scenes whose names match the six built-in exclusions (MainMenu, Boot, Loading, Splash, Settings, Credits). Add your own (e.g. CharacterSelect, Lobby, Tutorial) via SaveConfig.excludedSceneNames.

Recommended intervals: 60–120s (action/permadeath), 300–600s (RPG/strategy), 900–1800s (casual games with manual saves).

Quick Save (IQuickSaveService)

Enable in ServiceConfiguration: Enable Quick Save Service (requires Data Persistence + Serialization).

Configure in SaveConfig:

Quick Save Settings:
├─ Quick Save Enabled: true
└─ Max Quick Saves: 5
using LoLEngine.Core.DataPersistence.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using UnityEngine;

public class QuickSaveHandler : MonoBehaviour
{
    private IQuickSaveService _quickSave;

    void Start() => _quickSave = ServiceLocator.Instance.Get<IQuickSaveService>();

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.F5)) _quickSave.QuickSave();
        if (Input.GetKeyDown(KeyCode.F9)) _quickSave.QuickLoad();
    }
}

API highlights:

Method / property Purpose
QuickSave() Create a timestamped quick save
QuickLoad(index = 0) Load most recent (0) or older slot (1, 2, …)
HasQuickSave() Whether any quick save exists
GetLatestQuickSaveSlot() Most recent slot name
QuickSaveSlots All current quick save slot names
MaxQuickSaves Rotation limit (oldest removed when exceeded)

Quick saves use timestamped names: quicksave_yyyyMMdd_HHmmss (e.g. quicksave_20250108_143025).

QuickSaveService exposes BeforeQuickSave / AfterQuickSave events (on the concrete class) for UI feedback — pause, show indicators, notifications.

Save Protection (Obfuscation and Encryption)

Configure in LoLEngineConfig only (not SaveConfig). Enable Serialization Service; obfuscation/encryption run through the serializer pipeline automatically when saving.

Priority at runtime: obfuscation → encryption → plain JSON.

Mode Extension (default) When to use
Obfuscation (default) .osav Game progress, settings — fast, stops casual editing
Encryption .esav Payment data, PII, compliance requirements
Plain .sav Debugging only — not recommended for shipping

Performance comparison:

Obfuscation (XOR) Encryption (AES-256)
Speed ~10× faster Slower (PBKDF2 key derivation)
Security Casual protection Strong
Best for Frequent saves, mobile Sensitive data, rare saves

Recommended defaults (most games):

// LoLEngineConfig
obfuscateSaveFiles = true;   // on by default
encryptSaveFiles = false;      // only for sensitive data

Obfuscation — automatic when enabled; no extra code at save sites:

await _saveSystem.SaveGameAsync("save1");  // writes save1.osav
await _saveSystem.LoadGameAsync("save1");  // auto-detects .osav / .esav / .sav

Direct service access (advanced):

var obfuscation = ServiceLocator.Instance.Get<IObfuscationService>();
string obfuscated = obfuscation.Obfuscate(plainText, key: "");
string restored = obfuscation.Deobfuscate(obfuscated, key: "");

Encryption — enable encryptSaveFiles in LoLEngineConfig. Keys come from SecureKeyProvider + optional encryptionKeySeed. Never commit production keys — inject via CI/environment at build time.

// Keys are wired by ConfigurableServiceInitializer from LoLEngineConfig
var encryption = ServiceLocator.Instance.Get<IEncryptionService>();
string encrypted = encryption.EncryptString(sensitiveData, password);
string decrypted = encryption.DecryptString(encrypted, password);

Security tips:

  • Obfuscation does not stop determined reverse engineering — validate loaded data (sanity checks, checksums).
  • For multiplayer, validate critical values server-side.
  • Migrating encryption → obfuscation: new saves use .osav; old .esav / .sav files still load.

Compression (ICompressionService)

Enable in ServiceConfiguration: Enable Compression Service. Toggle save compression in LoLEngineConfig (compressSaveFiles, compressionLevelForSaveFiles).

When enabled, the save/serializer pipeline compresses JSON before obfuscation/encryption. Useful for large save files; skip for tiny saves if CPU matters more than disk.

Direct service access (non-save use cases):

using LoLEngine.Core.Compression.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using System.IO.Compression;

var compression = ServiceLocator.Instance.Get<ICompressionService>();

string compressed = compression.CompressString(largeJson, CompressionLevel.Optimal);
string restored = compression.DecompressString(compressed);

compression.CompressFile(sourcePath, outputZipPath, CompressionLevel.Fastest);
compression.DecompressFile(inputZipPath, outputDirectory);

Services register automatically via ImprovedGameInitializer — do not manually register unless writing custom bootstrap code.

Event System Integration

The system publishes events for save begin/complete via GameEvent. Subscribe with GameEvent.Subscribe and implement IEventListener<T>:

using LoLEngine.Core.DataPersistence.Events;
using LoLEngine.Core.Events.GameEvents;
using LoLEngine.Core.Events.Interfaces;
using UnityEngine;

public class SaveEventsListener : MonoBehaviour,
    IEventListener<SaveBeginEvent>,
    IEventListener<SaveCompleteEvent>
{
    private void OnEnable()
    {
        GameEvent.Subscribe(this as IEventListener<SaveBeginEvent>);
        GameEvent.Subscribe(this as IEventListener<SaveCompleteEvent>);
    }

    private void OnDisable()
    {
        GameEvent.Unsubscribe(this as IEventListener<SaveBeginEvent>);
        GameEvent.Unsubscribe(this as IEventListener<SaveCompleteEvent>);
    }

    public void OnGameEvent(SaveBeginEvent e)
    {
        Debug.Log($"Save starting for slot: {e.SaveSlot}");
    }

    public void OnGameEvent(SaveCompleteEvent e)
    {
        Debug.Log($"Save completed for slot: {e.SaveSlot}, Success: {e.Success}");
    }
}

Note: Quick/Auto save/load event types exist but are not emitted by all services yet; rely on the save begin/complete events for now.

Best Practices

  1. DataId Uniqueness: Ensure each PersistableData class has a unique DataId

  2. Data Registration: Use GetData<T>() which auto-registers new data instances if needed

  3. Data Updates: Update your data objects before calling SaveGame()

  4. Error Handling: Always handle exceptions from save operations

  5. Save File Management:

  6. Use meaningful slot names
  7. Implement a save management UI
  8. Clean up old saves to prevent storage bloat

  9. Performance:

  10. Use asynchronous methods for large saves
  11. Configure compression level based on your needs
  12. Use obfuscation for normal game saves (10x faster than encryption)
  13. Only encrypt truly sensitive data (payment info, personal details)

  14. Testing:

  15. Test with various save sizes
  16. Verify data integrity between saves
  17. Test on target platforms (mobile devices may have different storage behavior)

  18. Use SaveConfig for autosave/quicksave behavior and LoLEngineConfig for engine-wide protection/compression settings.

Integration with Other Systems

  1. UI Integration: Create a save/load UI that displays save slots and timestamps

  2. Scene Management: Save current scene information to restore game state properly

  3. Player Settings: Save user preferences such as graphics quality, sound volume, etc.

Example: Complete Save System Implementation

using LoLEngine.Core.DataPersistence.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Core.Events.GameEvents;
using LoLEngine.Core.DataPersistence.Events;
using UnityEngine;
using System;
using YourGame.Data;

public class SaveManager : MonoBehaviour,
    LoLEngine.Core.Events.Interfaces.IEventListener<SaveBeginEvent>,
    LoLEngine.Core.Events.Interfaces.IEventListener<SaveCompleteEvent>
{
    private ISaveSystem _saveSystem;
    private IQuickSaveService _quickSaveService;

    [SerializeField] private string defaultSlot = "save1";

    public event Action<string> OnSaveCompleted;
    public event Action<string> OnLoadCompleted;

    private void Awake()
    {
        _saveSystem = ServiceLocator.Instance.Get<ISaveSystem>();
        _quickSaveService = ServiceLocator.Instance.Get<IQuickSaveService>();

        // Subscribe to events (save begin/complete)
        GameEvent.Subscribe(this as IEventListener<SaveBeginEvent>);
        GameEvent.Subscribe(this as IEventListener<SaveCompleteEvent>);
    }

    private void OnDestroy()
    {
        // Unsubscribe from events
        GameEvent.Unsubscribe(this as IEventListener<SaveBeginEvent>);
        GameEvent.Unsubscribe(this as IEventListener<SaveCompleteEvent>);
    }

    public void QuickSave()
    {
        _quickSaveService.QuickSave();
    }

    public void QuickLoad()
    {
        _quickSaveService.QuickLoad();
    }

    public void SaveToSlot(string slotName)
    {
        try
        {
            UpdateAllData();
            _saveSystem.SaveGame(slotName);
        }
        catch (Exception e)
        {
            Debug.LogError($"Error saving to slot {slotName}: {e.Message}");
        }
    }

    public bool LoadFromSlot(string slotName)
    {
        if (!_saveSystem.HasSaveFile(slotName))
        {
            Debug.LogWarning($"No save file found in slot: {slotName}");
            return false;
        }

        bool success = _saveSystem.LoadGame(slotName);
        if (success)
        {
            ApplyLoadedData();
        }
        else
        {
            Debug.LogError($"Failed to load from slot: {slotName}");
        }

        return success;
    }

    public string[] GetAllSaveSlots()
    {
        // Implementation would depend on additional methods in SaveSystem
        // This is just a conceptual example
        return new string[] { "save1", "save2", "quicksave", "autosave" };
    }

    private void UpdateAllData()
    {
        // Update player data before saving
        var playerData = _saveSystem.GetData<PlayerData>();
        playerData.Position = FindFirstObjectByType<PlayerController>().transform.position;
        playerData.Health = FindFirstObjectByType<PlayerHealth>().CurrentHealth;

        // Update other game data...
    }

    private void ApplyLoadedData()
    {
        // Get loaded data
        var playerData = _saveSystem.GetData<PlayerData>();

        // Apply to game objects
        var player = FindFirstObjectByType<PlayerController>();
        if (player != null)
        {
            player.transform.position = playerData.Position;
        }

        var healthComp = FindFirstObjectByType<PlayerHealth>();
        if (healthComp != null)
        {
            healthComp.SetHealth(playerData.Health);
        }

        // Apply other game data...
    }

    public void OnGameEvent(SaveBeginEvent e)
    {
        Debug.Log($"Starting save to slot: {e.SaveSlot}");
    }

    public void OnGameEvent(SaveCompleteEvent e)
    {
        Debug.Log($"Save {(e.Success ? "succeeded" : "failed")} for slot: {e.SaveSlot}");
        if (e.Success) OnSaveCompleted?.Invoke(e.SaveSlot);
    }
}