Tutorial 162: Gamer Services setup, local profiles and sign-in
What you’ll learn: how to add GamerServicesComponent, sign players in to local offline profiles, react to SignedIn and SignedOut, keep achievements and leaderboards locally, and which calls need a server account.
Status: everything on this page works with no network and no server: it is CNA’s default mode. The code follows CNA’s headers and demos at snapshot b0e97bb1; it was not compiled or run for this page. Connecting the same game to a server is Tutorial 164, and Gamer Services & Avatars is the overview. Before you start, you should know the game loop from Tutorial 04 and components from Tutorial 47.
Two modes, one API
CNA implements XNA’s Microsoft::Xna::Framework::GamerServices namespace. Which mode it runs in is decided outside the game’s code. With no service endpoint configured — the default — every player is a local offline profile, as on an Xbox 360 without Xbox LIVE. With an endpoint, players sign in to accounts on a self-hosted CNA Gamer Services server instead. The XNA calls are the same in both modes, so the code below keeps working when a server is added later; the places where behaviour differs are marked.
Nobody is signed in when the game starts. Gamer::getSignedInGamersProperty() is empty until a player signs in through the Guide, or until a profile configured for automatic sign-in appears at the first update.
1. Add the component
As in XNA, gamer services are switched on by adding a GamerServicesComponent to the game’s components. Keep a GraphicsDeviceManager as well: the Guide (CNA’s system UI) and avatars draw through the graphics device service it registers.
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/GamerServices/GamerServicesComponent.hpp"
#include <memory>
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::GamerServices;
class MyGame : public Game
{
public:
MyGame()
{
// CNAEXT shared_ptr overload: the components collection keeps the component alive.
getComponentsProperty().Add(std::make_shared<GamerServicesComponent>(*this));
}
protected:
void Update(GameTime& gameTime) override
{
Game::Update(gameTime); // runs GamerServicesDispatcher::Update
// ... the game ...
}
private:
GraphicsDeviceManager graphics_{this}; // the Guide and avatars draw through it
};
The component initializes GamerServicesDispatcher with the game’s service container during Initialize, and calls GamerServicesDispatcher::Update() from every Game::Update. That update is where players appear and disappear, where events are raised and where asynchronous results complete, so an Update override must call Game::Update(gameTime). A program without a Game can call GamerServicesDispatcher::Initialize(services) once and GamerServicesDispatcher::Update() every frame itself; a second Initialize throws InvalidOperationException.
2. Sign a player in
Guide::ShowSignIn(paneCount, onlineOnly) opens the sign-in picker with one, two or four panes (any other count throws ArgumentException). Offline, each pane offers the stored profiles and New profile; a new name is 1 to 15 ASCII letters, digits and single spaces, starts with a letter and is unique ignoring case. Pass onlineOnly = false: without a server, true throws GamerServicesNotAvailableException because only local profiles exist.
#include "Microsoft/Xna/Framework/GamerServices/Gamer.hpp"
#include "Microsoft/Xna/Framework/GamerServices/Guide.hpp"
#include "Microsoft/Xna/Framework/GamerServices/SignedInGamer.hpp"
#include "Microsoft/Xna/Framework/GamerServices/SignedInGamerCollection.hpp"
#include "Microsoft/Xna/Framework/Input/Keyboard.hpp"
// in MyGame::Update, after Game::Update(gameTime)
SignedInGamer* player = (*Gamer::getSignedInGamersProperty())[PlayerIndex::One]; // nullptr if nobody
const bool start = Input::Keyboard::GetState().IsKeyDown(Input::Keys::Enter);
if (player == nullptr && start && !Guide::getIsVisibleProperty())
Guide::ShowSignIn(1, false); // throws GuideAlreadyVisibleException if a Guide screen is up
Indexing the collection with a PlayerIndex returns the gamer signed in as that player, or nullptr; indexing with an int walks the collection by position. A game does not even have to call ShowSignIn: in a game that draws, the Home key or a controller’s Guide button opens the Guide, which shows the sign-in picker when nobody is signed in on that controller. The Guide’s own pages also offer Sign out.
3. React to SignedIn and SignedOut
The two static events are XNA’s. A handler added after players are already signed in is told about each of them at once, so subscribing late loses nothing. The events are static, so a handler that captures this must be removed before the game object goes away:
#include "Microsoft/Xna/Framework/GamerServices/GamerPresenceMode.hpp"
#include "Microsoft/Xna/Framework/GamerServices/SignedInEventArgs.hpp"
#include "Microsoft/Xna/Framework/GamerServices/SignedOutEventArgs.hpp"
// members
System::EventHandler<SignedInEventArgs>::Token signedIn_{};
System::EventHandler<SignedOutEventArgs>::Token signedOut_{};
// in the constructor
signedIn_ = SignedInGamer::SignedIn.Add([this](System::Object*, const SignedInEventArgs& e) {
SignedInGamer* gamer = e.getGamerProperty();
gamer->getPresenceProperty().setPresenceModeProperty(GamerPresenceMode::AtMenu);
// gamer->getGameDefaultsProperty(): the profile's difficulty, invert-Y and so on
});
signedOut_ = SignedInGamer::SignedOut.Add([this](System::Object*, const SignedOutEventArgs& e) {
// forget e.getGamerProperty() and return that player to the title screen
});
// in the destructor
SignedInGamer::SignedIn.Remove(signedIn_);
SignedInGamer::SignedOut.Remove(signedOut_);
CNA keeps a signed-out gamer object alive until shutdown, so an old pointer does not dangle, but it reports getIsDisposedProperty() as true: treat that player as gone. Rich presence can be set on a local profile, but it is only published for server accounts, during the dispatcher update; offline, nobody else can see it.
Automatic sign-in for tests and CI
A test run or a kiosk build does not want a picker. Name up to four profiles, comma-separated, in CNA_GAMER_SERVICES_AUTO_SIGN_IN; missing ones are created. They are signed in when gamer services start and appear, raising SignedIn, at the first Update — the way XNA reported profiles already signed in at startup. Point CNA_GAMER_SERVICES_PROFILES_DIR at a scratch directory so the run does not touch the player’s real profiles:
export CNA_GAMER_SERVICES_PROFILES_DIR="$PWD/test-profiles"
export CNA_GAMER_SERVICES_AUTO_SIGN_IN=Alice,Bob
./MyGame
A profile can also be marked "autoSignIn": true in the profile store, and may carry a "gameDefaults" object (gameDifficulty, controllerSensitivity, racingCameraAngle, primaryColor, secondaryColor, invertYAxis and the other GameDefaults flags) that CNA copies into SignedInGamer::getGameDefaultsProperty() at sign-in. A field CNA cannot read keeps XNA’s unset value. Automatic sign-in only applies offline: with a server configured, sign-in uses accounts.
4. Achievements and leaderboards without a server
Local profiles keep achievements and leaderboards on this computer. The calls are XNA’s:
#include "Microsoft/Xna/Framework/GamerServices/LeaderboardEntry.hpp"
#include "Microsoft/Xna/Framework/GamerServices/LeaderboardIdentity.hpp"
#include "Microsoft/Xna/Framework/GamerServices/LeaderboardKey.hpp"
#include "Microsoft/Xna/Framework/GamerServices/LeaderboardReader.hpp"
#include "Microsoft/Xna/Framework/GamerServices/LeaderboardWriter.hpp"
player->AwardAchievement("first-steps"); // stored at once; the Guide shows "Achievement unlocked"
const LeaderboardIdentity board = LeaderboardIdentity::Create(LeaderboardKey::BestScoreLifeTime);
player->getLeaderboardWriterProperty().GetLeaderboard(board)->setRatingProperty(score); // stored on every set
LeaderboardReader top = LeaderboardReader::Read(board, 0, 10); // first page, ten rows
for (const LeaderboardEntry& row : top.getEntriesProperty())
{
// row.getGamerProperty()->getGamertagProperty(), row.getRatingProperty()
}
- Achievements. Without a catalog, the earned keys are all that is stored. A title can ship
GamerServices/Achievements.jsonin its title directory (key, name, description, how to earn, score 0–1000, display flag and a title-relative PNG picture); thenGetAchievementslists every defined achievement,AwardAchievementrefuses keys the catalog does not define, andAchievement::GetPicture()opens the PNG. - Leaderboards. Offline, the writer stores the rating every time it is set; no network session is needed. With a server account, writes are allowed only during network gameplay and are committed when the host ends the game. A local read lists only the rows of profiles currently signed in on this computer, always with the highest rating first (the
BestTimekeys included), and offline boards keep no stream columns (CNA-GAP-073).
Tutorial 123 covers achievement catalogs, paging and the server-backed rules in full.
Where local data lives
| Data | Location |
|---|---|
| Profiles (names, automatic sign-in, game defaults, each profile’s avatar) | $CNA_GAMER_SERVICES_PROFILES_DIR/profiles.json, else $XDG_DATA_HOME/cna/gamer-services/profiles.json, else ~/.local/share/cna/gamer-services/profiles.json; %LOCALAPPDATA%\CNA\gamer-services on Windows |
| Achievements | GamerServices/achievements/, one JSON file per profile, under the storage root that StorageDevice uses (see Storage) |
| Leaderboards | GamerServices/leaderboards/, one JSON file per board and game mode, under the same root |
Profile writes are atomic and serialized between processes; achievement and leaderboard files are replaced through a temporary file under a lock. A store that cannot be read is never overwritten. All of this is durable as far as the operating system’s cache: CNA does not promise that the last write survives a crash or a power cut. Local profiles are never sent to a server.
Do not name a save container GamerServices. It shares its directory with the local achievement and leaderboard store, so deleting that container deletes them too (CNA-BUG-154).
Browser builds. Local profiles, the Guide and avatars run in the browser. By reading the source (not by a run): the profile store resolves to XDG_DATA_HOME/HOME paths, which are not under the IndexedDB-backed mounts CNA’s storage module persists, so profiles are not expected to survive a page reload; the achievement and leaderboard files sit under the StorageDevice root, which is one of those mounts. Neither case was run for this page.
What a local profile cannot do
Social features need a server account. A local profile gets the exceptions XNA used for a profile without LIVE:
| Call | With a local profile |
|---|---|
SignedInGamer::GetFriends, IsFriend | GamerPrivilegeException |
Guide::ShowFriends, ShowMessages, ShowComposeMessage, ShowGamerCard, ShowFriendRequest, ShowParty, ShowPlayers, ShowPlayerReview, ShowGameInvite | GamerServicesNotAvailableException |
Guide::ShowMarketplace | GamerPrivilegeException |
Gamer::GetFromGamertag | NotSupportedException (there is no directory to search) |
GamerProfile::GetGamerPicture | returns nullptr: local profiles have no gamer picture |
NetworkSession of type PlayerMatch or Ranked | GamerServicesNotAvailableException; Local, LocalWithLeaderboards and SystemLink work |
A local profile reports getIsSignedInToLiveProperty() as false and has every privilege except online sessions and purchases; there are no guests without a server. GetProfile() works offline: it counts the profile’s earned achievements in this title and, when the title ships a catalog, totals their gamer score.
Evidence and qualification
- CNA’s final audit run of this work (2 October 2026, Linux): the GamerServices suite 649 passed and the Net suite 524 passed, with no failures; the server’s 40 registered tests: 33 passed, 7 network-isolation tests skipped for lack of a test tool, none failed. The GamerServices suite includes CNA’s local-profile, offline-store durability and concurrency tests. These are CNA-recorded runs; this site did not re-run them.
- Not qualified: Windows and macOS (the Windows paths above are read from the source, not run), a real wide-area network with NAT, voice on physical audio devices, and avatar pixels on Direct3D and Metal.
- Browser builds have no service, relay or voice; offline profiles and the Guide are what they offer.
Next
Tutorial 163 covers the Guide itself: message boxes, keyboard input and the system pages. To give players server accounts instead of local profiles, continue with Tutorial 164.