Checking for updates
The title can optionally check for and apply updates if it is critical for the game to be up to date. This is often important when the game involves multiplayer features where it is crucial for the connected game clients to all have the same functionality.
The title can check for updates using XStoreQueryGameAndDlcPackageUpdatesAsync as shown in the following example code:
void CALLBACK QueryGameAndDlcPackageUpdatesCallback(XAsyncBlock* async)
{
unsigned int numUpdates = 0;
HRESULT hr = XStoreQueryGameAndDlcPackageUpdatesResultCount(async, &numUpdates);
if (SUCCEEDED(hr))
{
if (numUpdates > 0)
{
std::vector<XStorePackageUpdate> packages(numUpdates);
hr = XStoreQueryGameAndDlcPackageUpdatesResult(async, numUpdates, packages.data());
if (SUCCEEDED(hr))
{
for (auto &package : packages)
{
printf("Update available %s\n", package.packageIdentifier);
}
}
}
else
{
printf("No updates are available\n");
}
}
delete async;
}
void CheckForUpdates()
{
auto asyncBlock = new XAsyncBlock{};
asyncBlock->queue = m_asyncQueue;
asyncBlock->callback = QueryGameAndDlcPackageUpdatesCallback;
if (FAILED(XStoreQueryGameAndDlcPackageUpdatesAsync(m_xStoreContext, asyncBlock)))
{
delete asyncBlock;
}
}
In the case of a title that has many DLC that are not important to check updates for (e.g. empty DLC packages, used only for licensing), the above may result in unnecessary overhead from checking for each one.
Instead, use XStoreQueryPackageUpdatesAsync filtering the output of XPackageEnumeratePackages:
void CALLBACK QueryPackageUpdatesCallback(XAsyncBlock* async)
{
unsigned int numUpdates = 0;
HRESULT hr = XStoreQueryPackageUpdatesResultCount(async, &numUpdates);
if (FAILED(hr))
{
printf("XStoreQueryPackageUpdatesResultCount failed : 0x%x\n", hr);
return;
}
printf("Number of updates: %d", numUpdates);
if (count > 0)
{
std::vector<XStorePackageUpdate> packages(numUpdates);
hr = XStoreQueryPackageUpdatesResult(async, numUpdates, packages.data());
if (SUCCEEDED(hr))
{
for (auto &package : packages)
{
printf("Update available %s\n", package.packageIdentifier);
}
}
}
}
void QueryPackageUpdates()
{
std::vector<std::string> packageIds;
HRESULT hr = XPackageEnumeratePackages(
XPackageKind::Game,
XPackageEnumerationScope::ThisOnly, // this will return for the base game only
&packageIds, [](void* context, const XPackageDetails* details) -> bool
{
auto packageIds = reinterpret_cast<std::vector<std::string>*>(context);
printf("Identifier: %s name: %s\n", details->packageIdentifier, details->displayName);
packageIds->push_back(details->packageIdentifier);
});
// packageIds now populated with just the base game package Id
auto asyncBlock = new XAsyncBlock();
asyncBlock->queue = m_asyncQueue;
asyncBlock->context = m_storeContext;
asyncBlock->callback = QueryPackageUpdatesCallback;
hr = XStoreQueryPackageUpdatesAsync(
m_storeContext,
packageIds.data(),
packageIds.size(),
asyncBlock);
if (FAILED(hr))
{
printf("XStoreQueryPackageUpdatesAsync failed: 0x%x\n", hr);
return;
}
}
Download and install updates
Once the set of updates is identified, there are two options:
1. Download and install the update in one operation
Using XStoreDownloadAndInstallPackageUpdatesAsync will queue and download all the updates, and once the download is complete, the game will terminate to apply the update. This will occur without warning. This may be acceptable if the game does not allow any functionality during the download and shows a notice saying an update is in progress and that game will terminate.
void DownloadUpdates()
{
std::vector<const char*> packageIds;
for (XStorePackageUpdate package : m_updates)
{
// m_updates populated from the update check
packageIds.push_back(package.packageIdentifier);
}
if (!packageIds.empty())
{
auto asyncBlock = new XAsyncBlock{};
asyncBlock->context = this;
asyncBlock->queue = m_asyncQueue;
asyncBlock->callback = [](XAsyncBlock* asyncBlockInner)
{
// Called when update is complete
auto pThis = reinterpret_cast<Sample*>(asyncBlockInner->context);
HRESULT hr = XStoreDownloadAndInstallPackageUpdatesResult(asyncBlockInner);
delete asyncBlockInner;
};
HRESULT hr = XStoreDownloadAndInstallPackageUpdatesAsync(m_xStoreContext, packageIds.data(), packageIds.size(), asyncBlock);
}
}
2. Download first, then install
There is the option to separate the download and installation portion. The game can start the download and allow the player to continue playing select portions of the game that can be played without consideration of the update, such as an offline game mode.
To do this, simply call XStoreDownloadPackageUpdatesAsync first
HRESULT hr = XStoreDownloadPackageUpdatesAsync(m_xStoreContext, packageIds.data(), packageIds.size(), asyncBlock);
When this is complete and callback verifies that XStoreDownloadPackageUpdatesResult is successful, then the game can wait until an appropriate time (such as after a match or in the main menu), where it can then make the same
HRESULT hr = XStoreDownloadAndInstallPackageUpdatesAsync(m_xStoreContext, packageIds.data(), packageIds.size(), asyncBlock);
call, at which point the game will terminate right away to install the update, as the download is already complete. Right before calling this API would be an appropriate time to notify the player and ask for confirmation of shut down.
Monitoring download progress
During the time the game is running and an update download is in progress, XPackageCreateInstallationMonitor can be used to monitor the progress of the download:
void CreateInstallationMonitor(const char* packageId)
{
XPackageInstallationMonitorHandle pimHandle;
HRESULT hr = XPackageCreateInstallationMonitor(packageId, 0, nullptr, 1000, m_asyncQueue, &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);
}
}
Testing updates
During development, it is only possible to test the availability updates using local packages.
- Create V1 package with the update check and download code
- Create V2 package with the same identity but incremented version number
xbapp/wdapp install
V1xbapp/wdapp update
V2/a
- Launch V1
Ensure the content ID matches between packages.
The result of this is that V1's XStoreQueryGameAndDlcPackageUpdatesAsync
call will find an available update and XStoreDownload(AndInstall)PackageUpdatesAsync
will update V1 using the advertised V2 package.
When the "download" of V2 is in progress, this will show in the Microsoft Store app as an item in the queue.
Once the installation is complete, V2 should be installed which can be verified using wdapp list
or get-appxpackage
.
Using /m
instead of /a
makes the update "mandatory", which simply affects theXStorePackageUpdate.isMandatory
field.
It is not possible to update from a development build to the package that downloads from the Store due to signing differences.
For more information about how to create efficient updates and different ways to test, see the content update pages referenced below.
See also
Enabling XStore development and testing
Content update best practicesAutorisation obligatoire