> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Port Steam Cloud storage to the XBOX GDK

> Replace Steam Cloud and ISteamRemoteStorage with the XBOX GDK XGameSave wrapper, with side-by-side code samples for reading, writing, and syncing saves.

Steam enables cloud storage in one of two ways. You can make all your reads and writes by using the `ISteamRemoteStorage` API methods, which write files to the game's storage folder on the local hard drive and sync them to the cloud. Alternatively, you can make all your reads and writes directly to the computer's file system, and then use Steam Auto-Cloud to sync the local folder containing the game data to the cloud.

The Microsoft Game Development Kit (GDK) supports both approaches:

* For code-based cloud saves, the GDK offers a [simplified wrapper](/reference/system/Wrappers/xgamesave_wrapper_members) of the more complex `XGameSave` API that provides similar functionality to the methods of `ISteamRemoteStorage`.
* For an approach similar to Steam Auto-Cloud, the GDK supports [porting previous titles to PC Game Saves with no-code cloud saves](/build/core-features/common/game-save/game-saves-walkthroughs-and-samples#porting-previous-titles-to-pc-game-saves-with-no-code-cloud-saves), which syncs a designated local folder to the cloud without requiring changes to your file I/O code.

<Warning>
  No-code cloud saves aren't supported on XBOX consoles or XBOX Cloud Gaming. If your title targets console or cloud in addition to Windows PC, use the `XGameSave` wrapper or the full `XGameSave` API instead.
</Warning>

The code-based wrapper is the recommended path for titles that ship on console or cloud. It maps closely to writing files locally or by using the Steam Remote Storage API. This topic focuses on the simplified wrapper.

<Note>
  The full, nonwrapped `XGameSave` API offers more functionality and flexibility than the simple wrapper. If you need any of that functionality, don't use the wrapper. Games must not switch between the two or mix API calls from both. For more information about the `XGameSave` API, see [Game saves](/build/core-features/common/game-save/game-saves-overview).
</Note>

The following code examples show how basic file operations work in the Steamworks SDK and the Microsoft Game Development Kit (GDK), along with some subtle differences between the two APIs.

## File operation comparisons

The following code examples show how to accomplish basic file operations in the Steamworks Remote Storage API and the GDK equivalent. They assume that the `provider` variable holds a pointer to an initialized `Microsoft::Xbox::Wrappers::GameSave::Provider` object.

If you aren't using the Steam Remote Storage API but instead opted to use Steam Auto-Cloud, replace the Remote Storage API calls with their file system API equivalents.

### Reading a file

#### Steamworks

```cpp theme={null}
int32 size = SteamRemoteStorage()->GetFileSize("MyFile.json");
if (size > 0) 
{
    char *buffer = new char[size];
    bool result = SteamRemoteStorage()->FileRead("MyFile.json", buffer, size);
}           
```

or

```cpp theme={null}
int32 size = GetFileSize("MyFile.json");
if (size > 0)
{
    m_readResult = SteamRemoteStorage()->FileReadAsync("MyFile.json", 0, size);
    // Check the value of m_readResult in callback for error handling.
    STEAM_CALLBACK(MyGameClass, OnFileReadCompleted, RemoteStorageFileReadAsyncComplete_t);
}
```

#### GDK

```cpp theme={null}
BlobData data = provider->Load("SaveSlot1", "MyData");
if(!data.empty())
{
    // Iterate over the data to read bytes from the file.
}
else
{
    // Couldn't find the container/blob name.
}
```

#### Reference documentation

[Microsoft.Xbox.Wrappers.XGameSave.Provider.Load](/reference/system/Wrappers/xgamesave_wrapper_members)

### Writing a file

#### Steamworks

```cpp theme={null}
std::string saveData = "{progress: 25}";
SteamRemoteStorage()->FileWrite("MyFile.json", saveData.c_str(), saveData.size());
```

or

```cpp theme={null}
std::string saveData = "{progress: 25}";
m_writeResult = SteamRemoteStorage()->FileWriteAsync("MyFile.json", saveData.c_str(), saveData.size());
STEAM_CALLBACK(MyGameClass, OnFileWriteCompleted, RemoteStorageFileWriteAsyncComplete_t);
```

#### GDK

```cpp theme={null}
std::vector<uint8_t> saveData; // Contains the player's data.
HRESULT hr = provider->Save("SaveSlot1", "MyData", saveData.size(), saveData.data());
if(FAILED(hr))
{
  if(hr == E_GS_QUOTA_EXCEEDED)
  {
     // Message that the user must clear out saves for this game.
  }
  else if(hr == E_GS_OUT_OF_LOCAL_STORAGE)
  {
     // Message to the user that they have run out of save space on the local device.
  }
  else if(hr == E_GS_UPDATE_TOO_BIG)
  {
     // Your save size was over 16 MB (GS_MAX_BLOB_SIZE).
  }
  else if(hr == E_GS_HANDLE_EXPIRED)
  {
     // Need to re-create the provider and try again.
     // This can happen if your game was suspended and, during that time, another
     // device initialized a provider for the same user.
  }
  else
  {
     // Log error.
  }
}
```

#### Reference documentation

[Microsoft.Xbox.Wrappers.XGameSave.Provider.Save](/reference/system/Wrappers/xgamesave_wrapper_members)

### Delete a file

On Steam, you can either delete a file in the cloud but keep the local copy (`FileForget`), or delete a file from both locations (`FileDelete`). The `XGameSave` wrapper API doesn't have an equivalent to `FileForget`. Its `Delete` function works like `FileDelete` in Steamworks.

#### Steamworks

```cpp theme={null}
// Delete a file from the cloud but keep it locally.
bool result = SteamRemoteStorage()->FileForget("MyFile.json");
```

or

```cpp theme={null}
// Delete a file locally AND from the cloud.
bool result = SteamRemoteStorage()->FileDelete("MyFile.json");
```

#### GDK

```cpp theme={null}
// Delete a specific file.
HRESULT hr = provider->Delete("MyContainer", "MyData");
```

or

```cpp theme={null}
// Delete a set of files in a container. 
std::vector<std::string> toDelete = { "blob1", "blob2", "blob3" };
HRESULT hr = provider->Delete("MyContainer", toDelete);
```

or

```cpp theme={null}
// Delete all the files in a container.
HRESULT hr = provider->Delete("MyContainer");
```

#### Reference documentation

* [Microsoft.Xbox.Wrappers.XGameSave.Provider.Delete(std::string)](/reference/system/Wrappers/xgamesave_wrapper_members)
* [Microsoft.Xbox.Wrappers.XGameSave.Provider.Delete(std::string, std::string)](/reference/system/Wrappers/xgamesave_wrapper_members)
* [Microsoft.Xbox.Wrappers.XGameSave.Provider.Delete(std::string, BlobNames)](/reference/system/Wrappers/xgamesave_wrapper_members)

### Get all files

#### Steamworks

```cpp theme={null}
int32 fileCount = SteamRemoteStorage()->GetFileCount();
for ( int i = 0; i < fileCount; ++i ) {
    int32 fileSize;
    const char *fileName = SteamRemoteStorage()->GetFileNameAndSize( i, &fileSize );
    // Do something with fileSize and fileName.
}
```

#### GDK

```cpp theme={null}
// To get all the files across all containers for the game, make this = ""
// Otherwise, make it a prefix of whichever container's files you'd like.
std::string containerQuery = "";
std::vector<std::string> containers = provider->QueryContainers(containerQuery);

for (auto&& container : containers)
{
    BlobInfoSet blobs = provider->QueryContainerBlobs(container);
    for (auto&& blob : blobs)
    {
        uint32_t blobSize = blob.size;
        std::string blobName = blob.name;
        // Do something with blobSize and blobName.
    }
}
```

#### Reference documentation

* [Microsoft.Xbox.Wrappers.XGameSave.Provider.QueryContainers](/reference/system/Wrappers/xgamesave_wrapper_members)
* [Microsoft.Xbox.Wrappers.XGameSave.Provider.QueryContainerBlobs](/reference/system/Wrappers/xgamesave_wrapper_members)

### Check available space

In the following examples, `totalBytes` is the amount of space your game is given on the cloud storage provider. `availableBytes` is the amount of free space remaining (that is, `availableBytes` = `totalBytes` – `bytesUsed`).

#### Steamworks

```cpp theme={null}
uint64 totalBytes, availableBytes;
SteamRemoteStorage()->GetQuota(&totalBytes, &availableBytes);
```

#### GDK

```cpp theme={null}
// totalBytes is always 256 MB.
int64_t availableBytes = provider->GetQuota();
```

#### Reference documentation

[Microsoft.Xbox.Wrappers.XGameSave.Provider.GetQuota](/reference/system/Wrappers/xgamesave_wrapper_members)

## Terminology differences

On Steam, remote storage data is managed as files, which work like files on a local hard drive. To read or write, you specify the file and then get or set the bytes that it contains.

In the Microsoft Game Development Kit (GDK), the equivalent of Steam files are *blobs*. Blobs are grouped together in a structure called a *container*. A container is a named group of blobs. For example, you can use containers to support multiple save slots per user, with the same file names in each slot. If you don't need the extra organizational layer that containers provide, place all your blobs (files) in the same container.

<Note>
  Container names can't include spaces. Attempting to access or create a container name that includes a space causes the provider method to return an HRESULT of `0x80830001`: the specified volume doesn't support storage tiers.
</Note>

## Storage limits

The Microsoft Game Development Kit (GDK) has a lower maximum blob or file write size and overall storage limit than Steam. On Steam, each file write operation is limited to 100 mebibytes (MiB), and each file can't be larger than 200 MiB. In contrast, the `XGameSave` API and its wrapper limit each blob to 16 MB and allow a maximum of 256 MB per user per game.

If you need to store more than 16 MB of data in a blob, split the data into multiple blobs. Then implement a sequential read and write function to process the data one blob at a time.

## Wrapper functions are blocking

The `ISteamRemoteStorage` interface offers two versions of read and write functions: `FileRead` and `FileWrite`, plus `FileReadAsync` and `FileWriteAsync`. The `Async` versions call back when the file has been read or written. The simplified `XGameSave` wrapper functions don't offer asynchronous versions of its equivalents to `FileRead` and `FileWrite`. `Provider::Load` and `Provider::Save` are both blocking, so keep this behavior in mind when you use them in your game.

For this reason, `Provider::Initialize` *throws an exception if you call it from the UI thread*.

## Initialization

Include the header file for the wrapper in your game's solution before you use any wrapper methods. You can find it at *%GRDKLatest%\GameKit\Include\xgamesavewrappers.hpp*.

Before using the `XGameSave` wrapper's methods, create an instance of the `Provider` class and call the `Provider::Initialize` method. Hold a pointer to the `Provider` instance for the lifetime of your game. Call `Provider::Initialize` from a thread other than the UI thread. The method throws an exception if you call it from the UI thread. To initialize the wrapper provider, you need an `XUserHandle` for the current user and your game's service configuration identifier (SCID).

```cpp theme={null}
using namespace Microsoft::Xbox::Wrappers::GameSave;

Provider provider = new Provider();
if(SUCCEEDED(provider->Initialize(userHandle, mySCID)) {
    // Start using the XGameSave wrapper...
```

#### Reference documentation

[Microsoft.Xbox.Wrappers.XGameSave.Provider.Initialize](/reference/system/Wrappers/xgamesave_wrapper_members)


## Related topics

- [Port from Steam](/paths/porting/from-steam.md)
- [Porting guides to the XBOX GDK](/home/build-first-title/porting-guides.md)
- [Steam porting guide overview](/build/steam-porting-guide/overview.md)
- [Port from PC (non-Steam)](/paths/porting/from-pc.md)
- [Cloud Storage](/services/xbox-services/storage/live-cloud-storage-nav.md)
