> ## 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.

# 將 Steam Cloud 儲存移植到 XBOX GDK

> 以 XBOX GDK XGameSave 包裝函式取代 Steam Cloud 和 ISteamRemoteStorage，並提供讀取、寫入和同步存檔的並排程式碼範例。

Steam 以兩種方式之一啟用雲端儲存。您可以使用 `ISteamRemoteStorage` API 方法進行所有讀取和寫入，這些方法會將檔案寫入本機硬碟上遊戲的儲存資料夾，並將它們同步到雲端。或者，您可以直接對電腦的檔案系統進行所有讀取和寫入，然後使用 Steam Auto-Cloud 將包含遊戲資料的本機資料夾同步到雲端。

Microsoft Game Development Kit (GDK) 支援這兩種方法：

* 對於以程式碼為基礎的雲端存檔，GDK 提供較複雜之 `XGameSave` API 的[簡化包裝函式](/zh-TW/reference/system/Wrappers/xgamesave_wrapper_members)，提供與 `ISteamRemoteStorage` 方法類似的功能。
* 對於類似 Steam Auto-Cloud 的方法，GDK 支援[使用無程式碼雲端存檔將舊版遊戲移植到 PC 遊戲存檔](/zh-TW/build/core-features/common/game-save/game-saves-walkthroughs-and-samples#porting-previous-titles-to-pc-game-saves-with-no-code-cloud-saves)，可將指定的本機資料夾同步到雲端，而不需要變更您的檔案 I/O 程式碼。

<Warning>
  XBOX 主機或 XBOX Cloud Gaming 不支援無程式碼雲端存檔。如果您的遊戲除了 Windows PC 之外還鎖定主機或雲端，請改用 `XGameSave` 包裝函式或完整的 `XGameSave` API。
</Warning>

對於在主機或雲端上發行的遊戲，建議使用以程式碼為基礎的包裝函式。它與在本機寫入檔案或使用 Steam Remote Storage API 的方式非常接近。本主題著重於簡化的包裝函式。

<Note>
  完整、未包裝的 `XGameSave` API 比簡單的包裝函式提供更多功能和彈性。如果您需要其中任何功能，請勿使用包裝函式。遊戲不得在兩者之間切換，也不得混用兩者的 API 呼叫。如需 `XGameSave` API 的詳細資訊，請參閱[遊戲存檔](/zh-TW/build/core-features/common/game-save/game-saves-overview)。
</Note>

下列程式碼範例示範基本檔案作業在 Steamworks SDK 和 Microsoft Game Development Kit (GDK) 中的運作方式，以及這兩個 API 之間的一些細微差異。

## 檔案作業比較

下列程式碼範例示範如何在 Steamworks Remote Storage API 和 GDK 對等項目中完成基本檔案作業。這些範例假設 `provider` 變數保存指向已初始化之 `Microsoft::Xbox::Wrappers::GameSave::Provider` 物件的指標。

如果您不是使用 Steam Remote Storage API，而是選擇使用 Steam Auto-Cloud，請將 Remote Storage API 呼叫取代為其對等的檔案系統 API。

### 讀取檔案

#### 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);
}           
```

或

```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.
}
```

#### 參考文件

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

### 寫入檔案

#### Steamworks

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

或

```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.
  }
}
```

#### 參考文件

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

### 刪除檔案

在 Steam 上，您可以刪除雲端中的檔案但保留本機複本 (`FileForget`)，或從兩個位置刪除檔案 (`FileDelete`)。`XGameSave` 包裝函式 API 沒有與 `FileForget` 對等的項目。它的 `Delete` 函式運作方式與 Steamworks 中的 `FileDelete` 相同。

#### Steamworks

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

或

```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");
```

或

```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);
```

或

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

#### 參考文件

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

### 取得所有檔案

#### 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.
    }
}
```

#### 參考文件

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

### 檢查可用空間

在下列範例中，`totalBytes` 是雲端儲存提供者提供給您遊戲的空間量。`availableBytes` 是剩餘的可用空間量（也就是 `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();
```

#### 參考文件

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

## 術語差異

在 Steam 上，遠端儲存資料是以檔案的形式管理，運作方式就像本機硬碟上的檔案。若要讀取或寫入，您需要指定檔案，然後取得或設定其所包含的位元組。

在 Microsoft Game Development Kit (GDK) 中，與 Steam 檔案對等的是\_blob\_。Blob 會群組在稱為\_容器\_的結構中。容器是具名的 blob 群組。例如，您可以使用容器來支援每位使用者多個存檔位置，每個位置中使用相同的檔案名稱。如果您不需要容器所提供的額外組織層，請將所有 blob（檔案）放在同一個容器中。

<Note>
  容器名稱不能包含空格。嘗試存取或建立包含空格的容器名稱，會導致提供者方法傳回 `0x80830001` 的 HRESULT：指定的磁碟區不支援儲存層。
</Note>

## 儲存限制

Microsoft Game Development Kit (GDK) 的 blob 或檔案寫入大小上限及整體儲存限制都比 Steam 低。在 Steam 上，每個檔案寫入作業限制為 100 mebibyte (MiB)，且每個檔案不能大於 200 MiB。相較之下，`XGameSave` API 及其包裝函式將每個 blob 限制為 16 MB，且每位使用者每個遊戲最多允許 256 MB。

如果您需要在一個 blob 中儲存超過 16 MB 的資料，請將資料分割成多個 blob。然後實作循序讀取和寫入函式，一次處理一個 blob 的資料。

## 包裝函式會造成封鎖

`ISteamRemoteStorage` 介面提供兩種版本的讀取和寫入函式：`FileRead` 和 `FileWrite`，以及 `FileReadAsync` 和 `FileWriteAsync`。`Async` 版本會在檔案讀取或寫入完成時回呼。簡化的 `XGameSave` 包裝函式並未提供其 `FileRead` 和 `FileWrite` 對等項目的非同步版本。`Provider::Load` 和 `Provider::Save` 都會造成封鎖，因此在遊戲中使用它們時請記住此行為。

因此，*如果您從 UI 執行緒呼叫 `Provider::Initialize`，它會擲回例外狀況*。

## 初始化

在使用任何包裝函式方法之前，請在遊戲的方案中包含包裝函式的標頭檔。您可以在 *%GRDKLatest%\GameKit\Include\xgamesavewrappers.hpp* 找到它。

在使用 `XGameSave` 包裝函式的方法之前，請建立 `Provider` 類別的執行個體，並呼叫 `Provider::Initialize` 方法。在遊戲的整個生命週期中保存指向 `Provider` 執行個體的指標。請從 UI 執行緒以外的執行緒呼叫 `Provider::Initialize`。如果您從 UI 執行緒呼叫此方法，它會擲回例外狀況。若要初始化包裝函式提供者，您需要目前使用者的 `XUserHandle` 和遊戲的服務設定識別碼 (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...
```

#### 參考文件

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


## Related topics

- [Steam 移植指南概觀](/zh-TW/build/steam-porting-guide/overview.md)
- [從 Steam 移植](/zh-TW/paths/porting/from-steam.md)
- [移植到 XBOX GDK 的指南](/zh-TW/home/build-first-title/porting-guides.md)
- [將現有的輸入程式碼移植到 GameInput](/zh-TW/build/core-features/common/input/porting/index.md)
- [統計資料和成就](/zh-TW/build/steam-porting-guide/features/stats-and-achievements.md)
