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

# 다운로드 가능한 콘텐츠(DLC) 관리 및 라이선스

> 타이틀에서 durable 제품을 다운로드 가능한 콘텐츠(DLC)로 사용합니다 — Partner Center 설정, 라이선스 검사, 설치 관리, 런타임 사용 권한 코드.

다운로드 가능한 콘텐츠(DLC)는 구매 시 사용자에게 다운로드 가능한 패키지를 제공하는 제품으로 구성됩니다. DLC는 콘텐츠에 액세스하기 전에 독립적으로 라이선스가 부여되고 마운트되어야 합니다.

DLC 패키지는 [게임의 제품 공유 모델](/publishing/xstore-commerce/xstore-product-sharing)에 설명된 각 디바이스의 콘텐츠 공유 동작에 따라 라이선스가 부여될 수 있습니다.

Partner Center는 또한 패키지가 없는 Durable 제품을 지원합니다. 패키지가 없는 durable은 이미 기본 게임과 함께 설치된 콘텐츠를 활성화할 수 있게 하여 라이선스 전용 제품에 적합합니다. 이러한 유형의 라이선스 전용 제품은 사용자가 다운로드해야 하는 빈 패키지를 만드는 단계를 절약합니다. 자세한 내용은 [패키지 없는 durable 사용 방법](/publishing/xstore-commerce/xstore-dwob)을 참고하세요.

## DLC 개발 워크플로

DLC 콘텐츠 구성 방법 및 MicrosoftGame.config를 통해 기본 게임과 연결하는 방법에 대해 자세히 알아보려면 Downloadable content (DLC) packages 문서를 참고하세요.

DLC를 설치하는 방법에는 세 가지가 있습니다.

1. [Loose DLC 배포](#1-loose-dlc-deployment)
2. [DLC 패키지 로컬 설치](#2-locally-install-dlc-package)
3. [스토어에서 DLC 패키지 설치](#3-install-dlc-package-from-store)

### 1. Loose DLC 배포

loose DLC MicrosoftGame.config 및 자산 파일을 가리키는 디렉터리를 설치합니다.

**XBOX:**

```cmd theme={null}
> xbapp deploy <DLC directory>

Package Full Name: 41336MicrosoftATG.DLC1_2020.10.14.0_neutral__dspnxghe87tn0
The operation completed successfully.
```

**PC:**

```cmd theme={null}
> wdapp register <DLC directory>  

Registered 41336MicrosoftATG.DLC1_2020.10.14.0_x64__dspnxghe87tn0
Copied temporary generated AppXManifest.xml file to C:\Users\<ntuser>\AppData\Local\Temp\41336MicrosoftATG.DLC1_2020.10.14.0_x64__dspnxghe87tn0_AppXManifest.xml
The operation completed successfully.
```

### 2. DLC 패키지 로컬 설치

makepkg로 생성한 .xvc/.msixvc를 설치합니다.

**XBOX:**

```cmd theme={null}
> xbapp install <xvc package>  

12:16:21.800 Registered for streaming: 41336MicrosoftATG.DLC1_2020.10.14.0_neutral__dspnxghe87tn0_xs.xvc
12:16:22.645 Launch   0.00% Package   0.00%
12:16:32.650 Launch 100.00% Package 100.00%
12:16:32.651 Streaming install finished

The operation completed successfully.
```

**PC:**

```cmd theme={null}
> wdapp install <.msixvc package>  

Starting installation. See the Microsoft Store app for further details.
Launch   100% Package   100%
Installed 100%.
Installed 41336MicrosoftATG.DLC1_2020.10.14.0_x64__dspnxghe87tn0
```

### 3. 스토어에서 DLC 패키지 설치

개발 샌드박스에 로그인된 테스트 계정으로 Store 앱에서 DLC 제품 페이지를 검색하거나 직접 연결하여 설치합니다.

## DLC 설치 확인

**XBOX:**

```cmd theme={null}
> xbapp listdlc

Registered DLC by Package Full Name:

   41336MicrosoftATG.DLC1_2020.10.14.0_neutral__dspnxghe87tn0

The operation completed successfully.
```

**PC:**

```cmd theme={null}
> wdapp listdlc

Registered DLC packages by Package Full Name:

41336MicrosoftATG.DLC1_2020.10.14.0_x64__dspnxghe87tn0

The operation completed successfully.
```

PC에서는 PowerShell에서 `get-appxpackage`를 사용해 특정 타이틀에 대해 설치된 DLC를 확인할 수도 있습니다. **Dependencies** 섹션을 참고하세요.

```cmd theme={null}
> get-appxpackage 41336MicrosoftATG.DownloadableContent

Name              : 41336MicrosoftATG.DownloadableContent
Publisher         : CN=A4954634-DF4B-47C7-AB70-D3215D246AF1
Architecture      : X64
ResourceId        :
Version           : 2020.10.14.0
PackageFullName   : 41336MicrosoftATG.DownloadableContent_2020.10.14.0_x64__dspnxghe87tn0
InstallLocation   : E:\Repos\ATG\gx_dev\Samples\Live\DownloadableContent\Gaming.Desktop.x64\Debug
IsFramework       : False
PackageFamilyName : 41336MicrosoftATG.DownloadableContent_dspnxghe87tn0
PublisherId       : dspnxghe87tn0
IsResourcePackage : False
IsBundle          : False
IsDevelopmentMode : True
NonRemovable      : False
Dependencies      : {41336MicrosoftATG.DLC1_2020.10.14.0_x64__dspnxghe87tn0}
IsPartiallyStaged : False
SignatureKind     : None
Status            : Ok
```

**SignatureKind**는 설치된 패키지의 유형을 나타냅니다. 로컬에서 빌드하고 설치한 패키지의 경우 *None*, 스토어에서 설치한 패키지의 경우 *Store* 입니다.

## DLC 구매 및 설치

카탈로그를 열거하고 추가 기능 구매 기능을 제공하는 방법에 대해 알아보려면 [기본 스토어 작업](/publishing/xstore-commerce/xstore-basic-operations) 문서를 참고하세요. 추가 기능 제품은 `[XStoreProduct](/reference/system/xstore/xstore_members).hasDigitalDownload`를 검사하여 DLC로 판단할 수 있습니다.

DLC가 스토어에서 구매된 경우 DLC는 다운로드를 위해 큐에 대기됩니다.

[XStoreShowPurchaseUiAsync](/reference/system/xstore/xstore_members)를 사용해 DLC를 구매하면 DLC가 다운로드를 위해 큐에 대기되지 않습니다. 대신 게임은 다음 코드에 따라 수동으로 다운로드를 요청해야 합니다. 선택적으로 진행 상황을 추적하기 위한 모니터를 만들 수 있습니다.

**패키지 식별자**는 특정 패키지를 식별하는 불투명 문자열입니다. 이 식별자는 패키지마다 고유하지만 게임이 실행될 때마다 다릅니다. 따라서 식별자를 현재 세션을 넘어서 저장하거나 재사용하지 마세요.

시나리오에 따라 다음 방법을 사용해 패키지의 패키지 식별자를 얻을 수 있습니다.

* 패키지를 다운로드하고 설치하기 위해 [XStoreDownloadAndInstallPackagesAsync](/reference/system/xstore/xstore_members)를 호출한 후 [XStoreDownloadAndInstallPackagesResult](/reference/system/xstore/xstore_members)를 호출하여 해당 패키지의 패키지 식별자를 얻을 수 있습니다.
* [XPackageEnumeratePackages](/reference/system/xstore/xstore_members)를 호출한 다음 각 열거된 패키지에 대해 [XPackageEnumerationCallback](/reference/system/xstore/xstore_members) 콜백 함수로 전달된 [XPackageDetails](/reference/system/xstore/xstore_members) 구조체에서 패키지 식별자를 검색하여 이미 다운로드되고 설치된 패키지의 패키지 식별자를 얻을 수 있습니다.
* [XPackageGetCurrentProcessPackageIdentifier](/reference/system/xstore/xstore_members)를 호출하여 현재 게임의 패키지 식별자를 얻을 수 있습니다.

```cpp theme={null}
void StartDownload()
{
    auto async = new XAsyncBlock{};
    async->queue = m_asyncQueue;
    async->context = this;
    async->callback = [](XAsyncBlock* asyncBlockInner)
    {
        uint32_t count = 0;
        HRESULT hr = XStoreDownloadAndInstallPackagesResultCount(asyncBlockInner, &count);

        std::vector<char[XPACKAGE_IDENTIFIER_MAX_LENGTH]> packageIds(count);

        XStoreDownloadAndInstallPackagesResult(asyncBlockInner, count, packageIds.data());

        for(auto packageId : packageIds)
        {
            hr = XPackageCreateInstallationMonitor(
                packageId,  
                0,  
                nullptr,  
                1000,  
                m_asyncQueue,
                &m_pimHandle);

            if(SUCCEEDED(hr))
            {
                XTaskQueueRegistrationToken callbackToken;

                XPackageRegisterInstallationProgressChanged(
                    m_pimHandle,
                    this,
                    [](void* context, XPackageInstallationMonitorHandle pimHandle)
                    {
                        XPackageInstallationProgress progress;
                        XPackageGetInstallationProgress(pimHandle, &progress);

                        if(!progress.completed)
                        {
                            printf("%llu%% installed\n", static_cast<double>(progress.installedBytes) / static_cast<double>(progress.totalBytes);
                        }
                        else
                        {
                            XPackageCloseInstallationMonitorHandle(pimHandle);
                        }
                    }, &callbackToken);

                // Persist callbackToken to unregister upon completion
            }
        }

        delete asyncBlockInner;
    }

    const char* storeIds[] =  
    {
        "9PLNMXRKNM4C",  
        "9PLNMXRKNM5D"
    };

    HRESULT hr = XStoreDownloadAndInstallPackagesAsync(storeContext, storeIds, ARRAYSIZE(storeIds), async);

    if (FAILED(hr))
    {
        delete async;
        return;
    }
}
```

## DLC 패키지 열거

DLC가 설치되는 방식과 관계없이 사용 전에 게임에서 설치된 DLC를 열거해야 합니다. 설치된 DLC를 열거하는 것은 패키지 식별자를 얻는 곳입니다.

```cpp theme={null}
bool CALLBACK DlcCallback(void* context, const XPackageDetails* details)
{
    printf("DLC found: name: %s packageId: %s\n", details->displayName, details->packageIdentifier);

    return true;
}

void RefreshInstalledPackages()
{
    HRESULT hr = XPackageEnumeratePackages(
        XPackageKind::Content,  
        XPackageEnumerationScope::ThisAndRelated,  
        this,  
        DlcCallback);
}
```

## DLC 패키지가 설치되었음을 감지

```cpp theme={null}
void RegisterPackageInstalledEvent()
{
    XPackageRegisterPackageInstalled(
        m_asyncQueue,
        this,  
        [](void *context, const XPackageDetails *package)
        {
            printf("Package Installed event received: %s\n", package->displayName);
        },  
        &m_packageInstallToken);
}
```

코드 예제에 도달하지 않는 경우 [문제 해결](/publishing/xstore-commerce/xstore-troubleshooting) 섹션을 참고하세요. DLC 패키지가 어떤 방법으로 설치되었는지에 관계없이 이벤트가 트리거되어야 합니다.

## DLC 라이선스 획득

개발 시에는 [개발 중 DLC 라이선싱 테스트](#testing-dlc-licensing-in-development)의 참고 사항을 참고하세요.

기본 게임은 DLC의 라이선스를 획득하여 콘텐츠에 대한 액세스를 사용자에게 부여할지 여부를 결정해야 합니다.

일반적으로 게임은 Restrictive Licensing을 사용합니다. 이는 게임이 패키지에 대한 라이선스를 획득한 후 패키지에 대한 액세스가 해당 디바이스와 제품 인스턴스에 잠기는 방식입니다. 다른 인스턴스 또는 디바이스가 액세스 권한과 라이선스를 획득하려면 게임이 라이선스를 해제해야 합니다. 게임이 Restrictive Licensing(Partner Center에서 구성되지 않음)을 사용하도록 구성되어 있는지 Microsoft 계정 담당자에게 확인하세요. 자세한 내용은 [개방형 및 제한적 라이선싱](/publishing/xstore-commerce/xstore-open-restrictive-licensing)을 참고하세요.

게임이 라이선스를 해제해야 하므로, 게임은 획득한 모든 DLC 라이선스를 추적하고 더 이상 필요하지 않거나 종료 시 라이선스를 해제할 책임이 있습니다. 게임이 라이선스를 해제하지 못하면 타임아웃 기간 이후 자동으로 라이선스가 해제됩니다.

```cpp theme={null}
void CALLBACK AcquireLicenseForPackageCallback(XAsyncBlock* async)
{
    XStoreLicenseHandle licenseHandle = nullptr;

    HRESULT hr = XStoreAcquireLicenseForPackageResult(
        async,
        &licenseHandle);

    if (FAILED(hr))
    {
        printf("Failed retrieve the license handle: 0x%x\n", hr);
        return;
    }

    bool isValid = XStoreIsLicenseValid(licenseHandle);

    printf("isValid: %s\n", isValid ? "true" : "false");

    hr = XStoreRegisterPackageLicenseLost(licenseHandle, m_asyncQueue, context,
       [](void *context)  
       {
           // Check if the license lost corresponded to any mounted DLC
           // If so, it is up to the game to determine an appropriate time
           // to unmount the DLC, e.g. after the current match is completed
       });

    delete async;
}

void AcquireLicenseForPackage(const char* packageIdentifier)
{
    auto async = new XAsyncBlock{};
    async->context = this;
    async->queue = m_asyncQueue;
    async->callback = AcquireLicenseForPackageCallback;

    HRESULT hr = XStoreAcquireLicenseForPackageAsync(
        m_storeContext,
        packageIdentifier,
        async);

    if (FAILED(hr))
    {
        delete async;
        return;
    }
}
```

## DLC의 라이선스 소스 판단

식별자 유형에 따라 [XStoreCanAcquireLicenseForPackageAsync](/reference/system/xstore/xstore_members) 또는 [XStoreCanAcquireLicenseForStoreIdAsync](/reference/system/xstore/xstore_members)를 사용해 다음을 판단할 수 있습니다.

1. 라이선스 부여 가능한지, 그렇지 않다면 구매 옵션을 표시할 수 있습니다.
2. 디스크 또는 디지털 라이선스로 라이선스 부여 가능한지

```cpp theme={null}
void PreviewLicense(const char* storeId)
{
    auto async = new XAsyncBlock{};
    async->queue = m_asyncQueue;
    async->callback = [](XAsyncBlock* async)
    {
        XStoreCanAcquireLicenseResult result;

        HRESULT hr = XStoreCanAcquireLicenseForStoreIdResult(
            async,
            &result);

        if (FAILED(hr))
        {
            printf("Error calling XStoreCanAcquireLicenseForStoreIdResult: 0x%x\n", hr);
        }
        else
        {
            // Status = 1 Licensable and
            // a. licensableSku = "DISC" if disc licensed
            // b. licensableSku = "0010" or similar if digital licensed
            printf("Status: %u LicensableSku: %s\n", result.status, result.licensableSku);
        }

        delete async;
    };

    HRESULT hr = XStoreCanAcquireLicenseForStoreIdAsync(
        m_xStoreContext,
        storeId,
        async);

    if (FAILED(hr))
    {
        delete async;

        printf("Error calling XStoreCanAcquireLicenseForStoreIdAsync: 0x%x", hr);
        return;
    }
}
```

## DLC 마운트 및 마운트 해제

DLC 라이선스가 성공적으로 획득되면 DLC 콘텐츠를 마운트하고 액세스할 수 있습니다.

```cpp theme={null}

void CALLBACK MountPackageCallback(XAsyncBlock* async)
{
    XPackageMountHandle mountHandle = {};

    HRESULT hr = XPackageMountWithUiResult(async, &mountHandle);

    if (SUCCEEDED(hr))
    {
        // Access DLC goodness
    }
    else
    {
        XStoreCloseLicenseHandle(license);
        printf("Error mounting package: 0x%x\n", hr);
    }

    delete async;
};

void MountPackage(const char* packageIdentifier)
{
    auto async = new XAsyncBlock{};
    async->queue = m_asyncQueue;
    async->context = context;
    async->callback = MountPackageCallback;

    HRESULT hr = XPackageMountWithUiAsync(packageIdentifier, async);

    if (FAILED(hr))
    {
        printf("XPackageMountWithUiAsync failed : 0x%x\n", hr);
        delete async;
    }
}
```

마운트 해제 시에는 모든 토큰과 핸들을 해제하세요.

```cpp theme={null}
void UnmountPackage(XPackageMountHandle mountHandle, XStoreLicenseHandle license, XTaskQueueRegistrationToken licenseLostToken)
{
    XStoreUnregisterPackageLicenseLost(license, licenseLostToken, false);

    XPackageCloseMountHandle(mountHandle);

    XStoreCloseLicenseHandle(license);
}
```

## DLC 제거

DLC 패키지를 제거하려면 [XPackageUninstallPackage](/reference/system/xstore/xstore_members)를 사용하세요. 패키지는 먼저 마운트 해제되어야 합니다.

## Smart Delivery 및 DLC

XBOX Series X/S 타이틀이 XBOX One 타이틀용으로 만들어진 DLC를 라이선스 부여하고 마운트할 수 있습니다. 일반적으로 이 시나리오는 DLC 패키지에 게임에서 사용되는 데이터가 포함되어 있지 않을 때 발생합니다. 확인할 수 있는 유일한 방법은 ERA DLC의 package.appxmanifest에 있는 GUID인 `AllowedProduct` ID가 Partner Center에서 제품에 할당된 Legacy XBOX Product ID와 일치하는지 확인하는 것입니다.

그렇지 않은 경우, 타이틀은 아마도 폐기된 XBOX Developer Portal(XDP)에서 마이그레이션되었을 수 있으며, XBOX Series X/S 버전이 XBOX One의 제품 ID에 할당되어 있으므로 스토어에서 다운로드한 패키지에서만 작동합니다. 개발 목적으로는 [문제 해결](/publishing/xstore-commerce/xstore-troubleshooting) 섹션의 참고 사항을 참고하세요.

## 다른 제품의 DLC

다른 제품의 DLC를 열거하고 사용할 수 있습니다. 다른 제품의 DLC를 사용하려는 타이틀의 경우 Partner Center의 **Product relationship setup** 섹션에서 DLC 제품에 "Can sell and use" 관계를 할당하세요. 사용 가능한 제품 선택은 게시자 계정으로 제한됩니다.

## 개발 중 DLC 라이선싱 테스트

자세한 내용은 [라이선스 테스트 활성화](/publishing/xstore-commerce/xstore-licensing-setup)를 참고하세요.

로컬 DLC 패키지는 /contentid 매개 변수로 만들어야 합니다.

각 DLC 패키지는 기본 값에서 EKBID를 재정의해야 합니다.

## 참조 API 문서

* [XStore (API 내용)](/reference/system/xstore/xstore_members)
  * 함수
    * [XStoreShowPurchaseUiAsync](/reference/system/xstore/xstore_members)
    * [XStoreDownloadAndInstallPackagesAsync](/reference/system/xstore/xstore_members)
    * [XStoreDownloadAndInstallPackagesResult](/reference/system/xstore/xstore_members)
    * [XStoreCanAcquireLicenseForPackageAsync](/reference/system/xstore/xstore_members)
    * [XStoreCanAcquireLicenseForStoreIdAsync](/reference/system/xstore/xstore_members)
  * 구조체
    * [xstoreproduct](/reference/system/xstore/xstore_members)
* [XPackage (API 내용)](/reference/system/xstore/xstore_members)
  * 함수
    * [XPackageEnumeratePackages](/reference/system/xstore/xstore_members)
    * [XPackageEnumerationCallback](/reference/system/xstore/xstore_members)
    * [XPackageGetCurrentProcessPackageIdentifier](/reference/system/xstore/xstore_members)
    * [XPackageUninstallPackage](/reference/system/xstore/xstore_members)
  * 구조체
    * [XPackageDetails](/reference/system/xstore/xstore_members)

## 참고

[커머스 개요](/publishing/xstore-commerce/xstore-commerce-overview)

[라이선스 테스트 활성화](/publishing/xstore-commerce/xstore-licensing-setup)

[패키지 없는 durable 사용 방법](/publishing/xstore-commerce/xstore-dwob)

[XStore API 참조](/reference/system/xstore/xstore_members)


## Related topics

- [XPackageEnumeratePackages](/ko/reference/system/xpackage/functions/xpackageenumeratepackages.md)
- [XPackageEnumerationCallback](/ko/reference/system/xpackage/functions/xpackageenumerationcallback.md)
- [XPackageUninstallPackage](/ko/reference/system/xpackage/functions/xpackageuninstallpackage.md)
- [XPackageDetails](/ko/reference/system/xpackage/structs/xpackagedetails.md)
- [XPackageGetCurrentProcessPackageIdentifier](/ko/reference/system/xpackage/functions/xpackagegetcurrentprocesspackageidentifier.md)
