根据定义,可下载内容 (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。
如果从 Microsoft Store 购买 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 在其 package.appxmanifest(即 GUID)中是否与合作伙伴中心内分配给产品的旧版 Xbox 产品 ID 匹配。
如果不匹配,则这可能是从 XDP 迁移的游戏,并且这仅适用于从 Microsoft Store 下载的包,因为 Xbox Series X/S 将获得 Xbox One 的产品 ID。 出于开发目的,请参阅疑难解答部分中的说明。
不同产品中的 DLC
可枚举和使用不同产品的 DLC。 对于希望从其他产品使用 DLC 的游戏,请在合作伙伴中心的产品关系设置部分中为 DLC 产品分配"可销售和使用"关系。 所选的可用产品将仅限于发布者帐户。
在开发中测试 DLC 许可
有关详细信息,请参阅启用许可证测试,尤其是针对主机。
本地 DLC 包必须使用 /contentid 参数制作。
每个 DLC 包必须从默认值重写其 EKBID。