다음을 통해 공유


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

정의된 DLC(다운로드 가능한 콘텐츠)는 구매할 때 사용자에게 다운로드 가능한 패키지를 제공하는 제품으로 구성됩니다. 해당 콘텐츠를 액세스하기 전에 독립적으로 라이선스를 부여하고 탑재할 수 있어야 합니다.

DLC 패키지는 게임 제품 공유 모델에서 설명한 대로 각 장치에 대한 콘텐츠 공유 동작에 따라 라이선스할 수 있습니다.

파트너 센터의 새로운 기능은 패키지가 없는 지속성 있는 제품입니다. 이는 게임에서 사용할 수 있는 데이터가 포함되지 않은 라이선스 전용 제품에 적합합니다. 이렇게 하면 파트너 센터에 제출하는 빈 패키지를 만들어야 할 필요가 없으며, 사용자는 저장소 공간을 차지하지만 사용하지는 않는 패키지를 다운로드할 필요가 없습니다. 자세한 내용은 패키지 없이 지속성 콘텐츠를 사용하는 방법을 참조하세요.

DLC용 개발 워크플로

MicrosoftGame.config을 통해 기본 게임과 연결하는 방법을 포함하여 DLC 콘텐츠를 구성하는 방법에 대한 자세한 내용은 DLC(다운로드 가능한 콘텐츠) 패키지를 참조하세요.

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

  1. 느슨한 DLC 배포
  2. 로컬에 DLC 패키지 설치
  3. 스토어에서 DLC 패키지 설치

1. 느슨한 DLC 배포

느슨한 DLC MicrosoftGame.config와 자산 파일을 가리키는 디렉터리를 설치합니다.

Xbox:

> xbapp deploy <DLC directory>

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

PC:

> 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:

> 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:

> 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. Microsoft Store에서 DLC 패키지 설치

dev 샌드박스에 로그인한 테스트 계정을 사용하여 스토어 앱의 DLC 제품 패키지에 대한 링크를 검색하거나 직접 연결되어 설치합니다.

DLC 설치 유효성 검사

Xbox:

> xbapp listdlc

Registered DLC by Package Full Name:

   41336MicrosoftATG.DLC1_2020.10.14.0_neutral__dspnxghe87tn0

The operation completed successfully.

PC:

> 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를 볼 수 있습니다. 관련하여 종속성 섹션을 참조하세요.

> 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\ATGgit\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에서 설치된 패키지는 Store로 표시됩니다.

DLC 구입 및 설치

카탈로그를 열거하고 추가 기능을 구입하는 기능을 제공하는 방법에 대한 자세한 내용은 기본 스토어 운영 문서를 참조하세요. 추가 기능 제품은 XStoreProduct.hasDigitalDownload을(를) 검토하여 DLC로 결정할 수 있습니다.

스토어에서 DLC를 구매하면 DLC가 다운로드 대기됩니다.

XStoreShowPurchaseUiAsync를 사용하여 DLC를 구입한 경우 DLC가 다운로드 대기되지 않습니다. 대신, 게임은 아래 코드에 따라 수동으로 다운로드를 요청해야 합니다. 필요에 따라 진행률을 추적하기 위해 모니터를 만들 수 있습니다.

package identifier는 특정 패키지를 식별하는 불투명 문자열입니다. 이 문자열은 패키지마다 고유하지만 실행된 게임 인스턴스마다 다르므로 현재 세션 이후에 다시 사용하기 위해 저장해서는 안 됩니다.

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

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를 열거해야 합니다. 여기서 패키지 식별자는 일반적으로 먼저 얻어집니다.

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 패키지가 설치된 경우 감지

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

예상 대로 적중되지 않는 경우 문제 해결 섹션을 참조하세요. 이는 DLC 패키지가 위에서 설명한 세 가지 가능성으로부터 설치되는 방법과 무관하게 트리거됩니다.

DLC에 대한 라이선스 획득

개발 시 개발 중인 DLC 라이선스 테스트의 참고를 참조하세요.

기본 게임은 게임 콘텐츠에 대한 액세스를 사용자에게 제공해야 할지의 여부를 결정하기 위해 DLC에 대한 라이선스를 취득해야 합니다. 라이선스를 취득하지 않으면 임대 만료 등 여러 이유로 플랫폼에서 라이선스를 무효화할 수 있으므로 게임이 종료될 수 있습니다.

게임에서는 일반적으로 제한적 라이선싱을 사용하는데, 이는 게임에서 패키지에 대한 라이선스를 취득했으며 해당 장치 및 제품 인스턴스를 위해 패키지에 대한 액세스 권한을 보유한다는 의미입니다. 다른 인스턴스 또는 장치가 액세스 권한 및 라이선스를 취득하기 전에 게임은 라이선스를 릴리스해야 합니다. Microsoft 계정 담당자에게 문의하여 게임이 제한적 라이선싱(이는 파트너 센터에서 구성되지 않음)을 사용하도록 구성되었는지 확인합니다. 제한적 라이선싱에 대한 자세한 내용은 오픈 라이선스 및 제한적인 라이선스를 참조하세요.

앱이 라이선스를 릴리스해야 하기 때문에 획득한 라이선스를 추적하고 더 이상 라이선스가 필요하지 않거나 라이선스가 종료되었을 때 이를 릴리스하는 것은 게임의 책임입니다. 게임이 라이선스를 릴리스하지 않으면 라이선스는 기간 만료 이후에 자동으로 릴리스됩니다.

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 또는 XStoreCanAcquireLicenseForStoreIdAsync를 사용하여 DLC가

  1. 라이선스 가능, 라이선스가 없는 경우 구매 옵션을 제시하기 위해
  2. 디스크 또는 디지털 라이선스 사용 가능
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 콘텐츠를 탑재하고 액세스할 수 있습니다.


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;
    }
}

마운트를 해제하는 경우 모든 토큰과 핸들 해제:

void UnmountPackage(XPackageMountHandle mountHandle, XStoreLicenseHandle license, XTaskQueueRegistrationToken licenseLostToken)
{
    XStoreUnregisterPackageLicenseLost(license, licenseLostToken, false);

    XPackageCloseMountHandle(mountHandle);

    XStoreCloseLicenseHandle(license);
}

DLC 제거 중

XPackageUninstallPackage를 사용하여 DLC 패키지를 제거합니다. 먼저 패키지를 분리해야 합니다.

스마트 딜리버리 및 DLC

Xbox Series X/S 제품에는 Xbox One 제품용으로 만든 DLC를 라이선스하고 탑재할 수 있습니다. 이는 DLC 패키지에 게임에서 사용되는 데이터가 실제로 포함되지 않았을 때 일반적으로 발생하는 시나리오입니다. 유일하게 검사하는 방법은 GUID인 package.appxmanifest에 있는 ERA DLC의 AllowedProduct ID가 파트너 센터에서 제품에 할당된 레거시 Xbox 제품 ID와 일치하는지 확인하는 것뿐입니다.

일치하지 않는 경우에는 XDP에서 마이그레이션된 타이틀일 수 있습니다. 이는 Xbox Series X/S에 Xbox One의 제품 ID가 할당되기 때문에 Store에서 다운로드한 패키지만 작동합니다. 개발을 위해 문제 해결 섹션의 참고 사항을 참조하세요.

여러 제품의 DLC

여러 제품의 DLC를 열거 및 사용할 수 있습니다. 다른 제품의 DLC를 사용하려는 타이틀의 경우 파트너 센터의 제품 관계 설정 섹션에서 DLC 제품에 "판매 및 사용 가능" 관계를 할당합니다. 사용 가능한 제품 선택은 게시자 계정으로 제한됩니다.

개발 중인 DLC 라이선스 테스트

특히 콘솔에 대한 자세한 내용은 라이선스 테스트 활성화를 참조하세요.

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

각 DLC 패키지의 EKBID가 기본값에서 재정의되어야 합니다.

참고 항목

상거래 개요

라이선스 테스트 활성화

패키지가 없는 지속성 콘텐츠를 사용하는 방법

XStore API 참조