> ## 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로 이식하기

> Steam Cloud와 ISteamRemoteStorage를 XBOX GDK의 XGameSaves 래퍼로 대체하며, 저장 파일 읽기, 쓰기, 동기화를 위한 나란한 코드 샘플을 함께 제공합니다.

Steam은 두 가지 방법 중 하나로 클라우드 스토리지를 제공합니다. `ISteamRemoteStorage` API 메서드를 사용하여 모든 읽기/쓰기를 수행할 수 있으며, 이 API는 로컬 하드 드라이브의 게임 스토리지 폴더에 파일을 기록하고 이를 클라우드와 동기화합니다. 또는 컴퓨터의 파일 시스템에 직접 모든 읽기/쓰기를 수행한 다음, Steam Auto-Cloud를 사용하여 게임 데이터를 포함하는 로컬 폴더를 자동으로 클라우드와 동기화할 수도 있습니다.

XBOX Game Development Kit(GDK)는 두 가지 접근 방식 모두를 지원합니다.

* 코드 기반 클라우드 저장의 경우, GDK는 `ISteamRemoteStorage`의 메서드와 유사한 기능을 제공하는 보다 복잡한 `XGameSaves` API의 [간소화된 래퍼](/reference/system/Wrappers/xgamesave_wrapper_members)를 제공합니다.
* Steam Auto-Cloud와 유사한 방식의 경우, GDK는 파일 I/O 코드를 변경할 필요 없이 지정된 로컬 폴더를 클라우드와 동기화하는 [코드 없는 클라우드 저장을 사용하여 이전 타이틀을 PC 게임 저장으로 이식하기](/build/core-features/common/game-save/game-saves-walkthroughs-and-samples#porting-previous-titles-to-pc-game-saves-with-no-code-cloud-saves)를 지원합니다.

<Warning>
  코드 없는 클라우드 저장은 XBOX 콘솔이나 XBOX 클라우드 게이밍에서는 지원되지 않습니다. 타이틀이 Windows PC 외에 콘솔이나 클라우드도 대상으로 하는 경우, `XGameSaves` 래퍼나 전체 `XGameSaves` API를 대신 사용하세요.
</Warning>

코드 기반 래퍼는 콘솔이나 클라우드에 출시되는 타이틀에 권장되는 경로이며, 로컬로 파일을 쓰거나 Steam Remote Storage API를 사용하는 방식과 밀접하게 매핑되므로, 이 문서에서는 간소화된 래퍼를 사용하는 것에 중점을 둡니다.

<Note>
  래핑되지 않은 전체 `XGameSaves` API는 이 단순한 래퍼보다 더 많은 기능과 유연성을 제공합니다. 해당 API의 기능을 사용하고자 하는 경우에는 래퍼를 사용하지 않아야 하는데, 이는 게임이 두 방식 사이를 전환하거나 API 호출을 혼합하여 사용해서는 안 되기 때문입니다. `XGameSaves` API에 대한 자세한 내용은 [게임 저장](/build/core-features/common/game-save/game-saves-overview)을 참조하세요.
</Note>

다음 코드 예제는 Steamworks SDK와 XBOX 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](/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);
```

#### XBOX Game Development Kit(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](/reference/system/Wrappers/xgamesave_wrapper_members)

### 파일 삭제

Steam에서는 클라우드의 파일을 삭제하고 로컬 사본은 유지하거나(`FileForget`), 두 위치에서 모두 파일을 삭제(F`ileDelete`)할 수 있습니다. `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");
```

#### XBOX Game Development Kit(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)](/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)

### 모든 파일 가져오기

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

#### XBOX Game Development Kit(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](/reference/system/Wrappers/xgamesave_wrapper_members)
* [Microsoft.Xbox.Wrappers.XGameSave.Provider.QueryContainerBlobs](/reference/system/Wrappers/xgamesave_wrapper_members)

### 사용 가능한 공간 확인

다음 예제에서 `totalBytes`는 클라우드 스토리지 공급자에서 게임에 부여된 공간의 양이며, `availableBytes`는 남아 있는 여유 공간의 양(즉, `availableBytes` = `totalBytes` – `bytesUsed`)입니다.

#### Steamworks

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

#### XBOX Game Development Kit(GDK)

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

#### 참조 문서

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

## 용어의 차이

Steam에서는 원격 저장 데이터가 로컬 하드 드라이브의 파일처럼 동작하는 파일로 관리됩니다. 읽기 및 쓰기는 읽거나 쓸 파일을 지정하고, 해당 파일에 포함된 바이트를 가져오거나 설정하는 방식으로 수행됩니다.

XBOX Game Development Kit(GDK)에서는 Steam 파일에 상응하는 것이 \_blob\_이며, blob은 \_container\_라고 하는 구조로 함께 그룹화됩니다. 컨테이너는 단순히 이름이 지정된 blob 그룹입니다. 예를 들어, 각 슬롯에 동일한 파일 이름이 있는 여러 사용자당 저장 슬롯을 허용하기 위해 컨테이너를 사용할 수 있습니다. 컨테이너가 제공하는 추가적인 조직화 계층이 필요하지 않다면, 모든 blob(파일)을 동일한 컨테이너에 배치하면 됩니다.

<Note>
  컨테이너 이름에는 공백을 포함할 수 없습니다. 공백이 포함된
</Note>

컨테이너 이름에 액세스하거나 이를 만들려고 시도하면 공급자 메서드가 HRESULT `0x80830001`(지정된 볼륨이 스토리지 계층을 지원하지 않음)을 반환합니다.

## 스토리지 제한

XBOX Game Development Kit(GDK)는 Steam보다 최대 blob/파일 쓰기 크기와 전체 스토리지 한도가 더 낮습니다. Steam에서는 각 파일 쓰기 작업이 100메비바이트(MiB)로 제한됩니다. 각 파일은 200MiB보다 클 수 없지만, `XGameSave` API와 해당 래퍼는 각 blob이 16MB보다 크지 않도록 허용하며 사용자당 게임당 최대 저장 허용량이 256MB입니다.

blob에 16MB보다 많은 데이터를 저장해야 하는 경우, 데이터를 여러 blob으로 나누고 한 번에 하나의 blob씩 데이터를 읽고 쓰기 위한 순차적 읽기/쓰기 함수를 구현해야 합니다.

## 래퍼 함수는 블로킹입니다

`ISteamRemoteStorage` 인터페이스는 읽기/쓰기 함수의 두 가지 버전(`FileRead`/`FileWrite` 및 `FileReadAsync`/`FileWriteAsync`)을 제공합니다. 후자는 파일 읽기/쓰기가 완료되면 콜백을 호출하는 비동기 함수입니다. 간소화된 `XGameSave` 래퍼 함수는 `FileRead`/`FileWrite`에 상응하는 비동기 버전을 제공하지 않습니다. 그러나 `Provider::Load`와 `Provider::Save`는 모두 블로킹이므로, 게임에서 사용할 때 이 점을 반드시 염두에 두세요.

이러한 이유로 `Provider::Initialize`는 *UI 스레드에서 호출되면 예외를 던집니다*.

## 초기화

다른 작업을 수행하기 전에 게임 솔루션에 래퍼용 헤더 파일을 포함시켜야 합니다. 이 파일은 \_%GRDKLatest%\GameKit\Include\xgamesavewrappers.hpp\_에서 찾을 수 있습니다.

`XGameSaves` 래퍼의 메서드를 사용하기 전에, `Provider` 클래스의 인스턴스를 생성하고(게임의 생애 주기 동안 이에 대한 포인터를 보유해야 함) `Provider::Initialize` 메서드를 호출해야 합니다. 다시 강조하지만, 이 메서드는 UI와 별개의 스레드에서 호출되어야 하며 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](/reference/system/Wrappers/xgamesave_wrapper_members)


## Related topics

- [기존 입력 코드를 GameInput으로 이식하기](/ko/build/core-features/common/input/porting/index.md)
- [XR-133 로컬 스토리지 쓰기 제한](/ko/publishing/certification/xr/xr-133.md)
- [타이틀 스토리지](/ko/services/xbox-services/storage/title-storage/index.md)
- [XPersistentLocalStorageSpaceInfo](/ko/reference/system/xpersistentlocalstorage/structs/xpersistentlocalstoragespaceinfo.md)
- [Steam에서 포팅](/ko/paths/porting/from-steam.md)
