Checking for updates

Note

Starting with the June 2022 GDK, checking for mandatory updates is no longer required at game launch on PC. The PC Bootstrapper manages this for MSIXVC packaged games built against the June 2022 GDK and future releases.

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)
{
    auto pThis = reinterpret_cast<Sample*>(async->context);

    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 Sample::CheckForUpdates()
{
    auto asyncBlock = new XAsyncBlock{};
    asyncBlock->context = this;
    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.

  1. Create V1 package with the update check and download code
  2. Create V2 package with the same identity but incremented version number
  3. xbapp/wdapp install V1
  4. xbapp/wdapp update V2 /a
  5. 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 staged 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.

See also

Commerce Overview

Enabling XStore development and testing

XStore API reference