管理和许可可下载内容 (DLC)
根据定义,可下载内容 (DLC) 由产品组成,其在购买时为用户提供一个可下载的包。 需要对这些内容进行独立许可和装载,然后才能访问其内容。
DLC 包可根据每台设备的内容共享行为获得许可,如游戏产品共享模型中所述。
合作伙伴中心的新增功能是创建一种没有包的耐用品。 它们适用于只有许可证、不包含游戏可用的任何数据的产品。 这样就无需创建虚拟包来提交到合作伙伴中心,用户也不需要下载占用存储空间但不使用的包。 有关详细信息,请参阅如何使用不带包的耐用品。
DLC 开发工作流
请参阅可下载内容 (DLC) 包,详细了解如何配置 DLC 内容,包括如何通过 MicrosoftGame.config 与基础游戏进行关联。
可通过 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.
电脑:
> 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 将不会 排队等待下载。 相反,游戏应按照以下代码手动请求下载。 (可选)可以创建监视器来跟踪进度。
包标识符是标识特定包的不透明字符串。 它对于每个程序包是唯一的,但与游戏的每个启动实例不同,因此不应将其存储为在当前会话之外重用。
可以使用以下方法获取包的包标识符,具体取决于以下方案:
- 调用 XStoreDownloadAndInstallPackagesAsync 以下载和安装包后,可以通过调用 XStoreDownloadAndInstallPackagesResult来获取这些包的包标识符。
- 可以先调用XPackageEnumeratePackages,然后从传递回XPackageEnumerationCallback回调函数的XPackageDetails结构中检索包标识符,以获取已下载并安装的包的包标识符。
- 可以通过调用 XPackageGetCurrentProcessPackageIdentifier来获取当前游戏的包标识符。
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 是否为
- 可许可,以便提供购买选项(如果不是)
- 可通过光盘或数字许可证获得许可
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。