Storage

Microsoft::Xna::Framework::Storage — StorageDevice, StorageContainer, StorageDeviceNotConnectedException

Implementation status: The Storage namespace is implemented. Headers are present and the API is functional for file I/O on all supported platforms.

Overview

The Microsoft::Xna::Framework::Storage namespace provides a platform-independent abstraction for save-game and user-data persistence. On the original Xbox 360, StorageDevice represented a physical memory unit or hard drive, and device selection was presented to the player as a system UI. On PC, the selector returned immediately with the local user profile directory.

CNA preserves the full XNA 4.0 API shape, including the Begin/End asynchronous pattern, but all operations complete synchronously on current platforms. The StorageContainer maps directly to a directory on the local filesystem, and StorageDevice represents that location. The async wrappers exist purely for source-level XNA compatibility and return immediately.

Namespace members at a glance

Type Description Status
StorageDevice Represents a storage location (local disk). Obtained via the async selector. Implemented
StorageContainer Opened from a StorageDevice; provides file and directory access within a named save scope. Implemented
StorageDeviceNotConnectedException Thrown when a previously valid StorageDevice is no longer available. Implemented

StorageDevice

StorageDevice is the entry point into the storage system. On PC (and in CNA on all platforms) it represents the local filesystem. To obtain a device, use the static Begin/End async selector pattern. On a PC target the selector returns immediately with a device backed by the current user's profile directory.

Device selection (Begin/End pattern)

MethodDescription
StorageDevice::BeginShowSelector(callback, state) Starts the (synchronous on CNA) device-selection operation. callback is an AsyncCallback invoked when the result is ready; state is an arbitrary user object passed through to the callback.
StorageDevice::EndShowSelector(result) Retrieves the selected StorageDevice from the IAsyncResult returned by BeginShowSelector. Returns nullptr if the user cancelled selection (not applicable on CNA, where a device is always available).

Opening a container

MethodDescription
device.BeginOpenContainer(name, callback, state) Begins opening a StorageContainer with the given display name. The container maps to a subdirectory within the device root.
device.EndOpenContainer(result) Retrieves the opened StorageContainer from the IAsyncResult. The caller is responsible for closing the container when done.

Platform storage paths

PlatformRoot path
Linux / macOS~/.local/share/<AppName>/
Windows%APPDATA%\<AppName>\
AndroidgetFilesDir() / <AppName> / (internal app storage)
Web (Emscripten)Emscripten persistent FS mount (limited; requires explicit flush)

On the Web target, writes to the Emscripten persistent filesystem are buffered in memory. Call EM_ASM(FS.syncfs(false, function(){})) after writing to flush data to IndexedDB. Failing to sync means data is lost when the tab closes.

StorageContainer

StorageContainer provides file and directory access scoped to a named save context within a device. It implements the XNA 4.0 API almost entirely. All paths passed to its methods are relative to the container root directory.

File operations

MethodReturnsDescription
OpenFile(path, mode) Stream Opens or creates a file at path using the given FileMode (Create, Open, OpenOrCreate, Append, Truncate). Returns a Stream for reading or writing.
FileExists(path) bool Returns true if the file exists within the container.
DeleteFile(path) void Deletes the file at path. Throws if the file does not exist.
GetFileNames() std::vector<std::string> Returns the names of all files in the container root (non-recursive).
GetFileNames(pattern) std::vector<std::string> Returns file names matching a wildcard pattern (e.g. "*.sav").

Directory operations

MethodReturnsDescription
CreateDirectory(path) void Creates a subdirectory within the container (creates intermediate directories as needed).
DirectoryExists(path) bool Returns true if the directory exists within the container.
GetDirectoryNames() std::vector<std::string> Returns the names of all subdirectories in the container root (non-recursive).
GetDirectoryNames(pattern) std::vector<std::string> Returns directory names matching a wildcard pattern.

StorageDeviceNotConnectedException

StorageDeviceNotConnectedException is thrown when a StorageDevice that was previously valid is no longer accessible — for example, if a memory unit was ejected on Xbox 360. In CNA, because storage always maps to the local filesystem, this exception is raised only in exceptional circumstances such as the backing directory being removed by an external process after the device was opened.

It derives from ExternalException and carries a human-readable message. Catch it wherever you call BeginOpenContainer or perform file operations if your application needs to handle a missing device gracefully.

try {
    auto result = device.BeginOpenContainer("SaveData", nullptr, nullptr);
    auto container = device.EndOpenContainer(result);
    // ... use container ...
    container->Dispose();
} catch (const StorageDeviceNotConnectedException& ex) {
    // Storage unavailable — prompt the user to reconnect or select another device
    ShowErrorDialog(ex.what());
}

IAsyncResult pattern

XNA 4.0 used the standard .NET IAsyncResult Begin/End async pattern so that storage operations could show system UI (device selector, container namer) without blocking the game loop. The pattern works as follows:

  1. Call the Begin* method, supplying an optional AsyncCallback and state object. It returns an IAsyncResult.
  2. In the callback (or by polling IAsyncResult::IsCompleted), call the matching End* method to retrieve the result.

In CNA, all storage operations complete synchronously: the Begin* call performs the operation immediately and the returned IAsyncResult has IsCompleted == true before the callback is even invoked. The pattern is preserved verbatim so that XNA save-game code ports without changes.

Code examples

Saving a file

// Obtain a StorageDevice
StorageDevice* device = nullptr;
StorageDevice::BeginShowSelector(
    [](IAsyncResult* result) {
        device = StorageDevice::EndShowSelector(result);
    }, nullptr);

// Open a container named after your save slot
auto openResult = device->BeginOpenContainer("Slot1", nullptr, nullptr);
auto container  = device->EndOpenContainer(openResult);

// Write a save file
if (!container->DirectoryExists("profile"))
    container->CreateDirectory("profile");

auto stream = container->OpenFile("profile/save.dat", FileMode::Create);
stream->Write(saveData.data(), 0, static_cast<int>(saveData.size()));
stream->Close();

container->Dispose();

Loading a file

auto openResult = device->BeginOpenContainer("Slot1", nullptr, nullptr);
auto container  = device->EndOpenContainer(openResult);

auto stream = container->OpenFile("profile/save.dat", FileMode::Open);

std::vector<uint8_t> buffer(stream->Length());
stream->Read(buffer.data(), 0, static_cast<int>(buffer.size()));
stream->Close();

container->Dispose();

// Deserialise from buffer ...
LoadGameState(buffer);

Checking file existence before loading

auto openResult = device->BeginOpenContainer("Slot1", nullptr, nullptr);
auto container  = device->EndOpenContainer(openResult);

if (container->FileExists("profile/save.dat")) {
    auto stream = container->OpenFile("profile/save.dat", FileMode::Open);
    // ... read and deserialise ...
    stream->Close();
} else {
    // No save file found — start a new game
    StartNewGame();
}

container->Dispose();

Always call container->Dispose() when you are finished with a StorageContainer. On CNA this releases the directory handle; on Xbox 360 it was required to flush and unlock the device. Keeping containers open longer than necessary is discouraged.