Persistent Storage SDK

Overview

The GameSpy Persistent Storage SDK allows a developer to associate any data they want with a player profile and have it stored on a secure central server. Both binary and ASCII data can be used, and there is no fixed size limit to the amount of data that can be stored. The Persistent Storage SDK is designed to allow many possible uses including: secure storage of character data, player settings / configuration, ranking / ladder information, and personal player information (home page / clan, etc).

Backend processes can also access the data to dynamically update a player's ranking, ladder, or tournament information. No fixed data structures are imposed, and developers are allowed to use the storage space in any reasonable manner. Access to the data is securely controlled through either personal logins (Presence and Messaging SDK) or unique CD Keys (CD Key SDK).

The Presence and Messaging SDK allows each account to have multiple profiles, and each CD Key can have multiple profiles associated with it via nicknames. Each profile can have multiple "data records" associated with it (for example, to store multiple character records separately), and each record can have 4 different types of data associated with it:

Private data can only be accessed by the authenticated user whose profile it is associated with. This is typically used to store player configuration/settings, private character data, and favorites. Public data can be accessed by any other player, and is typically used for things like ranking and personal information (home page, clan affiliation, etc). Read/Write data can be updated at-will by the client that owns the profile. Read-Only data can only be updated by a process running on our backend, for example, in conjunction with the Stats and Tracking SDK to update ladder and ranking information.

The Persistent Storage SDK is built on top of the Stats and Tracking SDK, and uses much of the same code and terminology. However, you can choose to implement Persistent Storage without Stats and Tracking (or vice-versa).

Data Storage

Data storage records are keyed off of the combination of profileid and index. In each record there are four separate bins with separate access control. For CD Key authentication, the profileid is unique based on the CD Key and nickname. A function is included to lookup the profileid based on the CD Key Hash and the nick. When you request to get or set data, you pass in the profileid, index, and bin type.

Although you can have multiple indexes of data per profile, we recommend that you try to store all your data in index 0. It's better to have more keys\values (or a single, large binary structure) in a single record than having a bunch of small key\value sets or binary structures under different indexes in the database. If you do choose to use multiple indexes, you are not constrained to using consecutive numbers (if you don't need to). You can use any integer as the index value.

You have two choices for the format in which to save data. You can save it in our standard key\value delimited ASCII format, or a custom format of your choosing (binary or ASCII).

The advantages and disadvantages are as follows:

ASCII key\value format:

Binary / Custom format:

If you use the key\value format, all data should be in the form of key\value pairs. A data set consists of key\value pairs, beginning with the '\' character, and ending with the last value. For example:

" \key1\value1\mykeyname\mykeyvalue\keyhere\valuehere "

Binary or custom formats don't have a fixed spec - they are treated as raw blocks of data. You simply request the whole block and get a pointer with a length, and set the whole block by passing in a pointer with the length.

Authentication

The Persistent Storage SDK does authentication in a unique two-part fashion that allows for the highest level of flexibility in the SDK implementation.

For most games, it will be appropriate to use the Persistent Storage SDK on each client - for example, to allow a client to update their own information, or query for other players' information. In this case, the authentication process is straight-forward:

  1. Connect to the Persistent Storage Server using InitStatsConnection()
  2. Call GetChallenge() to get the challenge string to use for authentication
  3. Call GenerateAuth() with the challenge string and plain-text password or CD Key to generate the validation token
  4. Call PreAuthenticatePlayerCD or PreAuthenticatePlayerPM to authenticate the player, allowing them to query for their private data, and update their read-write data.

For other games, it may be better to have all communications with the Persistent Storage Server done by the multiplayer game host (server). In this scenario, the server will need to authenticate each client (so that it can read their private data, and update their read-write data), but this needs to be done in a manner whereby the server never sees the plaintext passwords or CD Keys of their clients. To do this, the server needs to implement the two-part authentication:

  1. On server startup, connect to the Persistent Storage Server using InitStatsConnection()
  2. When a client connects, call GetChallenge() and send that challenge string to the client
  3. On the client side, take the challenge string received from the game server, and use the GenerateAuth() function with the plain-text password or CD Key. GenerateAuth() will return a validation token which is NOT reversible to determine the password or CD Key
  4. Send the validation token, along with the login information (profileid or CD Key Hash) to the game server
  5. On the game server, call PreAuthenticatePlayerCD() or PreAuthenticatePlayerPM() to authenticate the player
  6. Repeat the authentication process for each client as they connect to make sure they are all authenticated

If you are implementing the Stats & Tracking SDK as well, you'll note that the second process describes exactly what must be done to get authentication data for clients to include in the stats snapshot. The validation token used with PreAuthenticatePlayer is the same value that should be included in the "auth_N" key for that player. See the Stats & Tracking SDK for more details.

Note that you do NOT need to authenticate a client to just read public values. You can easily implement "guest" players in this fashion, who do not need to provide login or CD Key information - they can still see data for other users, they simply cannot save data for themselves. Just call InitStatsConnection() and use the GetPersistData() and GetPersistDataValues() functions directly.

File Manifest

The following files should be included with this package. If any of the files are missing, please contact [email protected].

File
Description
gstats.c,h
Stats and Tracking header file and code
statstest.c
Example and test code for the Stats / Tracking SDK
gstats.dsw
Devstudio project for SDK / sample code
gpersist.h
Persistent Storage SDK header
\persisttest\
Example and test code for Persistent Storage SDK
md5c.c, md5.h
MD5 hash code and header
gbucket.c,h
Bucket helper code and header
nonport.c,h
Platform-specific code
darray.c,h
Code for managing dynamic arrays
hashtable.c,h
Hash table code and header

Implementation

The following is a quick rundown of the basic steps needed to support the SDK. The gpersist.h file contains much more extensive documentation for each function. The persisttest.c sample contains sample usage of all of these functions.

Step 1: Initialize the Stats / Persistent Storage Server Connection

Before calling any of the actual Persistent Storage functions, you'll need to connect to the Persistent Storage server.

First, set the global gcd_gamename and gcd_secret_key variables to your gamename and secret key. If these values aren't set correctly, you will be unable to connect to the persistent storage server.

Once these values are set, call

int InitStatsConnection(int gameport)

...with the game port that the host is running on. If your game doesn't use multiple ports (or doesn't support more than one host per machine) then you can just use 0.

This call is blocking and make take 1-2 seconds to complete the authentication process. This is the only blocking call in the SDK; all other calls will return immediately. When you are done with the Persistent Storage functions you can call

void CloseStatsConnection(void);

Step 2: Authenticate Player

Before you can request private records, or set any read/write records, you need to authenticate the player. If you are just reading public records you do not need to authenticate. This is done using the two-step process described in the Authentication section of this document. The calls you will need to use are:

char *GetChallenge(statsgame_t game);
char *GenerateAuth(char *challenge, char *password, char response[33]);
void PreAuthenticatePlayerPM(int localid, int profileid,  char *challengeresponse, PersAuthCallbackFn callback, void *instance);
void PreAuthenticatePlayerCD(int localid, char *nick, char *keyhash,  char *challengeresponse, PersAuthCallbackFn callback, void *instance);
void PreAuthenticatePlayerPartner(int localid, const char* authtoken, const char *challengeresponse, PersAuthCallbackFn callback, void *instance);

You first call GetChallenge to get the challenge value used for authentication. If you aren't using the Stats and Tracking SDK, just pass in NULL for game. This challenge value is then passed along with the password (either a profile password or an unhashed CD Key or the partner challenge) to the GenerateAuth function to create a validation token (response). This validation token is then used with either PreAuthenticatePlayerPM or PreAuthenticatePlayerCD or PreAuthenticatePlayerPartner to begin the authentication process.

The callback specified as "callback" in those functions will be called when the authentication is complete (either successful or not).

Step 3: Get / Set Data

Getting / Setting of data is done through four functions.

void GetPersistData(int localid, int profileid, persisttype_t type, int index, PersDataCallbackFn callback, void *instance);
void SetPersistData(int localid, int profileid, persisttype_t type, int index, char *data, int len, PersDataSaveCallbackFn callback, void *instance);
void GetPersistDataValues(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance);
void SetPersistDataValues(int localid, int profileid, persisttype_t type,int index, char *keyvalues, PersDataSaveCallbackFn callback,void *instance);

GetPersistData / SetPersistData work with the entire record as a binary blob. When you call Get, you receive the entire data block, and when you call Set, the entire data block is replaced with your new data.

GetPersistDataValues and SetPersistDataValues are designed to work with data stored in ASCII key\value format. You can pass a set of keys to GetPersistDataValues to only get a subset of the data stored in the record, and when you pass key\value pairs to SetPersistDataValues only those values are updated - any other existing keys in the record are maintained.

Step 4: Think

You need to call the PersistThink function any time an asynchronous operation is in that you call this in your main loop at all times while you are connected to the stats server, so that if the stats server disconnects it can be detected immediately.

Converted from CHM to HTML with chm2web Pro 2.85 (unicode)