管理和许可可下载内容 (DLC)

根据定义,可下载内容 (DLC) 由产品组成,其在购买时为用户提供一个可下载的包。 需要对这些内容进行独立许可和装载,然后才能访问其内容。

DLC 包可根据每台设备的内容共享行为获得许可,如游戏产品共享模型中所述。

合作伙伴中心的新增功能是创建一种没有包的耐用品。 它们适用于只有许可证、不包含游戏可用的任何数据的产品。 这样就无需创建虚拟包来提交到合作伙伴中心,用户也不需要下载占用存储空间但不使用的包。 有关详细信息,请参阅如何使用不带包的耐用品

DLC 开发工作流

请参阅可下载内容 (DLC) 包,详细了解如何配置 DLC 内容,包括如何通过 MicrosoftGame.config 与基础游戏进行关联。

可通过 3 种方法安装 DLC:

  1. 松散 DLC 部署
  2. 本地安装 DLC 包
  3. 从 Microsoft Store 安装 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.

电脑:

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

电脑:

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

测试帐户登录到开发者沙盒后,搜索或直接链接到 Microsoft Store 应用中的 DLC 产品页面并进行安装。

验证 DLC 安装

Xbox:

> xbapp listdlc

Registered DLC by Package Full Name:

   41336MicrosoftATG.DLC1_2020.10.14.0_neutral__dspnxghe87tn0

The operation completed successfully.

电脑:

> wdapp listdlc

Registered DLC packages by Package Full Name:

41336MicrosoftATG.DLC1_2020.10.14.0_x64__dspnxghe87tn0

The operation completed successfully.

在电脑上,还可在 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 将指示安装了哪种包,本地构建和安装的包为,从 Microsoft Store 安装的包为 Store

购买和安装 DLC

请参阅商店的基本操作文章,了解如何枚举目录和提供购买附加内容的能力。 可以通过检查 XStoreProduct.hasDigitalDownload来确定附加产品为 DLC。

如果从应用商店购买了 DLC,则 DLC 将排队等待下载。

如果使用 XStoreShowPurchaseUiAsync 购买 DLC,则 DLC 将不会 排队等待下载。 相反,游戏应按照以下代码手动请求下载。 (可选)可以创建监视器来跟踪进度。

包标识符是标识特定包的不透明字符串。 它对于每个程序包是唯一的,但与游戏的每个启动实例不同,因此不应将其存储为在当前会话之外重用。

可以使用以下方法获取包的包标识符,具体取决于以下方案:

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 的许可证源

使用 XStoreCanAcquireLicenseForPackageAsyncXStoreCanAcquireLicenseForStoreIdAsync (,具体取决于标识符类型) ,可以确定 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 包实际上不包含游戏使用的数据时,这种方案很典型。 检查的唯一方法是,如果 ERA DLC AllowedProduct 包中的 ID.appxmanifest(GUID)与合作伙伴中心中分配给该产品的旧版 Xbox 产品 ID 匹配。

如果不匹配,则这可能是从 XDP 迁移的游戏,并且这仅适用于从 Microsoft Store 下载的包,因为 Xbox Series X/S 将获得 Xbox One 的产品 ID。 出于开发目的,请参阅疑难解答部分中的说明。

不同产品中的 DLC

可枚举和使用不同产品的 DLC。 对于希望从其他产品使用 DLC 的游戏,请在合作伙伴中心的产品关系设置部分中为 DLC 产品分配"可销售和使用"关系。 所选的可用产品将仅限于发布者帐户。

在开发中测试 DLC 许可

有关详细信息,请参阅 启用许可证测试 ,尤其是控制台。

必须使用 /contentid 参数创建本地 DLC 包。

每个 DLC 包都必须从默认值中重写其 EKBID。

另请参阅

商业概述

启用许可证测试

如何使用不带包的耐用品

XStore API 参考