Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 49 GamerServices and Local Identity

XNA tied GamerServices to Xbox Live. FNA retains much of the API surface on PC but leaves the service-dependent paths inert. CNA supplies offline identities, achievements, leaderboards, and two Guide overlays. It does not provide authentication, friends, matchmaking, or a remote account service.

This chapter uses three statuses:

Implemented

The API has local in-memory behavior.

Locally persisted

State survives through CNA’s file store, with CNA-defined policy where Xbox Live supplied the original policy.

Stub

The API shape exists, but no local service can perform the operation.

These statuses describe the pinned revision; they are not claims of Xbox Live compatibility.

49.1 Local identities and object lifetime

Gamer is the base of SignedInGamer. Initialization creates four fixed local identities: Stub Gamer and Stub Gamer (1) through (3). Player One is not a guest; the other three are guests. All four report IsSignedInToLive=true, although no online sign-in occurs.

GetFromGamertag, GetPartnerToken, and their asynchronous pairs are stubs. IsFriend returns false, and GetFriends() returns an empty FriendCollection. The friend types themselves work as containers; CNA has no source from which to populate them.

Limitation. Address stability A Gamer owns a LeaderboardWriter that stores the owning Gamer*. The default copy and move operations copy that pointer. Moving a gamer after construction can therefore leave its writer pointing at the old address. Keep gamers at stable addresses—heap allocation is the documented choice—once their leaderboard writer may be used.

XNA exposes Presence as a get-only reference property whose returned object is mutable. CNA adds a non-const getPresenceProperty() overload so that the same usage remains possible in C++:

1 SignedInGamer* gamer = /* obtain from SignedInGamer::SignedIn */;
2 gamer->AwardAchievement("ACH_FIRST_BOSS_DEFEATED");
3 AchievementCollection earned = gamer->GetAchievements();
4
5 gamer->getPresenceProperty().setPresenceModeProperty(
6 GamerPresenceMode::SinglePlayer);

The fragment assumes the corresponding GamerServices headers and namespace imports.

49.2 Persistence contract

The store is rooted at StorageDevice::GetStorageRootEXT(), which is based on SDL_GetPrefPath. Achievement files live below GamerServices/achievements; leaderboard files live below GamerServices/leaderboards. Calling StorageDevice::SetAppNameEXT changes the root for both systems. Chapter 50 defines that shared boundary.

Achievements use one JSON file per sanitized gamertag, with records of the form {"key":...,"earnedTicks":...}. Leaderboards use one file per sanitized (key)_(gameMode). Characters outside [A-Za-z0-9._-] become underscores; an empty name becomes _. Writes use an adjacent temporary file followed by rename, with direct writing as a fallback. Missing or malformed files produce an empty store.

JSON numbers are represented as double. Achievement ticks, leaderboard ratings, and 64-bit property values therefore lose low bits above 253. For a 2026 DateTime, the quantization step is about 128 ticks, or 12.8 microseconds. The stored integer remains stable after the first serialization, but the original 64-bit value is not a bit-exact round trip.

49.3 Achievements

AwardAchievement and GetAchievements use the local store. Returned entries contain a persisted Key, IsEarned=true, and the earned time, subject to the numeric limitation above. Name, Description, and GamerScore remain empty or zero; DisplayBeforeEarned remains true. Xbox Live supplied that catalog metadata outside the AwardAchievement(string) call, and CNA has no equivalent catalog. Applications that display achievement names or scores must maintain their own mapping.

Achievement::GetPicture() throws NotImplementedException. Artwork is another service-owned field for which the local store has no source.

49.4 Leaderboards

LeaderboardReader, LeaderboardWriter, and LeaderboardEntry are disk-backed. Their policy is CNA-specific because FNA exposes no functioning PC implementation from which to inherit sorting or paging behavior:

  • entries sort by rating, descending;

  • paging around an absent gamer starts at the top;

  • persisted entries whose gamertags have no matching live Gamer are omitted;

  • the asynchronous pairs complete synchronously;

  • assigning Rating on a writer-created entry persists it immediately.

A reader-created entry has no persistence hook. Changing its in-memory rating does not update the file.

1 LeaderboardWriter& writer = gamer->getLeaderboardWriterProperty();
2 LeaderboardIdentity board = LeaderboardIdentity::Create(
3 LeaderboardKey::BestScoreLifeTime);
4
5 LeaderboardEntry* entry = writer.GetLeaderboard(board);
6 entry->setRatingProperty(newHighScore); // the assignment is the commit

49.5 Guide overlays and stubs

Most Guide UI calls are no-ops: compose message, friend request, friends, game invite, gamer card, marketplace, messages, party, player review, players, sign-in, and ShowAchievementsEXT. DelayNotifications is also inert.

Two asynchronous families are CNA-rendered overlays: BeginShowMessageBox/EndShowMessageBox and BeginShowKeyboardInput/EndShowKeyboardInput. They stay pending until the game’s Draw() path invokes the matching RenderPending*EXT hook with a SpriteBatch, SpriteFont, and white pixel. The hooks render the prompt and poll keyboard or mouse input. Guide.IsVisible reports whether either operation is pending.

Keyboard input preserves UTF-16 surrogate pairs. Password mode masks displayed code units but returns the unmasked text. Escape cancels the operation. Because std::string cannot distinguish XNA’s null-on-cancel result from a confirmed empty string, callers must query WasKeyboardInputCanceledEXT before interpreting the result.

1 System::IAsyncResult* pending = Guide::BeginShowKeyboardInput(
2 PlayerIndex::One, "Enter Passphrase", "Protect this save file",
3 "", nullptr, std::any{}, true);
4
5 // Call once per Draw while the request is pending.
6 Guide::RenderPendingKeyboardInputEXT(
7 *graphicsDevice, spriteBatch, uiFont, whitePixel);
8
9 if (!Guide::getIsVisibleProperty()) {
10 if (!Guide::WasKeyboardInputCanceledEXT(pending)) {
11 std::string passphrase = Guide::EndShowKeyboardInput(pending);
12 }
13 delete pending; // the caller owns the IAsyncResult
14 }

Only one Guide overlay can be pending. The test corpus covers pending state, callback identity, Enter and Escape, prompt fields, password display, and competing requests.

Historical note. Why the overlay contract is stated explicitly An independent review found that an earlier implementation ignored the title and description, reported IsVisible=false, displayed password text without masking, and had no cancel path. The current implementation and tests cover all four corrections. The episode is useful because a completed task record had overstated the observable behavior.

49.6 Presence, privileges, and profile

GamerPresence supplies all 60 XNA display strings and formats parameterized modes. Its SetPresenceModeStringEXT publication hook is empty: no service consumes the result. GamerPrivileges is fixed and permissive, and nothing enforces it. GamerProfile uses synthetic defaults except for host-derived Region. These objects carry useful local state, not authentication or policy authority.

49.7 Dispatcher, component, and the historical hang

GamerServicesComponent must be added to Game.Components before using the Gamer or Guide APIs, as on XNA 4.0 for Windows. Its dispatcher creates and frees the local gamer set. The Update() method on GamerServicesDispatcher is empty; the local asynchronous implementations complete their own actions.

Historical note. Cross-namespace completion bug After initialization, GamerServicesDispatcher::UpdateAsync() returns true indefinitely. FNA’s NetworkSession action logic waited for it to become false; combining GamerServices initialization with network creation could therefore spin forever. SignedInGamer::BeginGetAchievements once had the same dependency.

CNA’s network create, find, and join work is performed synchronously by the corresponding End* path. Its NetworkSessionAction now starts completed instead of waiting for an event that cannot occur. A separate-process harness exercises the initialized dispatcher under a ten-second watchdog because its process-lifetime statics have no test reset hook. The harness also checks the four local identities, sign-in events, achievement completion, and reinitialization cleanup. Chapter 51 covers the network side.

49.8 Evidence and user implications

The pinned headers and implementations establish the API shapes, fixed identities, file layout, overlay hooks, and stubs. Unit tests cover persistence behavior and Guide state; the isolated harness covers the dispatcher interaction. These tests do not establish interoperation with Xbox Live, because CNA has no such transport.

Feature Pinned-revision status
Local gamers Four fixed identities; implemented in memory
Achievements Locally persisted; catalog metadata and pictures absent
Leaderboards Locally persisted with CNA-defined sorting and paging
Presence, privileges, profile Local or synthetic state; no service enforcement
Friends and remote gamer lookup Empty or unsupported
Most Guide.Show* calls No-op stubs
Message-box and keyboard input CNAEXT-rendered overlays
Avatar base API Inert; see Chapter 52

Type at least three characters. Results are ranked by how often and where the words occur.