Flat API: GS5_Intf.h, GS5_Intf.cpp
Provides low-level api interface to native gsCore.dll.
Based on flat api, these two files provides Object-Oriented programming api.
Extension: GS5_Ext.h, GS5_Ext.cpp
Based on OOP API, these two files are needed to develop customized license model, easy license event handling, etc.
Before you can call most SDK apis, you must initialize the SDK first with the project-specific parameters. Depending on how you pass in the license file, there are two methods to initialize the SDK:
#include "GS5.h"
using namespace gs;
//These information are copied from IDE:
const char* productId = "8d11ec62-bfa0-4794-9688-41f8aa04630d";
const char* password = "walml_9282&APNQ&18163";
const char* license_filename = "Advanced Tutorial of Notepad2.lic";
//Resolve full path to license file
//Depending where you put the license file, its path can be resolved in different way.
std::string getFullPathToLicenseFile()
{
char* dir = getenv("MY_DEV_PATH");
assert(dir != NULL); //
return std::string(dir) + "/" + license_filename;
}
TGSCore * core = TGSCore::getInstance();
bool ok = core->init(productId, getFullPathToLicenseFile().c_str(), password);
if(!ok)
{
std::cerr << "Error initializing SDK, error code: " << core->lastErrorCode()
<< ", error message: " << core->lastErrorMessage() << std::endl;
}
In this demo, we copy our license file to a folder and setup an environment variable MY_DEV_PATH to point to it, so that the app can locate the license file easily.
You can also put the license file side by side with your exe, and resolve the exe path at run-time on Windows via Win32 API GetModuleFileName, anyway, you must pass in the full path of the license file, otherwise the SDK will blinkdly assume the license file is in current directory, which will fail if the app is launched from another folder.
Instead of dealing with the license file path, you can also pass the license information as raw data array:
//The license info must be set in other function before calling init()
const unsigned char* pLicData;
int licSize;
TGSCore * core = TGSCore::getInstance();
bool ok = core->init(productId, pLicData, licSize, password);
So given a license file, how do you transfer it to a block of memory that can be accessed by C++ code? there are several ways:
bin2c: Convert binary file to C/C++ source
Usage: bin2c inputfile [ outfile ]
Parameters:
input: binary data file to convert
outfile: [Optional] the output pascal source file
if not specified, outfile = input + ".cpp"
The output looks like:
/* f:SoftwareShield53_Ne2_test1.lic */
const unsigned char[645] BinaryData = {
0x47,0x53,0x03,0x00,0x01,0x01,0x0C,0x03,0x00,0x00,0x6B,0x02,0x00,0x00,0x95,0x88,
0x47,0xB8,0x1E,0x57,0xE0,0x82,0xC1,0x87,0x8D,0x6A,0xDF,0x6F,0xA6,0x14,0x88,0x23,
0x4B,0x27,0x14,0x60,0xE4,0x3C,0x65,0x17,0x09,0xB9,0x43,0x38,0x45,0x23,0x36,0x67,
... };
Windows provides win32 api to allow you retrieve resource data in memory.
To query license information, you first need to find the entity objects:
-Enumerate All Entities
#include <memory> //for std::auto_ptr
int N = core->getTotalEntities();
for(int i = 0; i < N; i++){
std::auto_ptr<TGSEntity> entity(core->getEntityByIndex(i));
//dump entity information...
std::cout << "entity name: " << entity->name() << " id: " << entity->id();
}
You can also get the specific entity by its entityId:
//EntityId is copied from IDE
const char* entityId = "b15c8ac2-d87c-4483-be77-5a8cbc89a62b";
std::auto_ptr<TGSEntity> entity( core->getEntityById( entityId ));
If your app has only one entity, let's get it by a single line:
std::auto_ptr<TGSEntity> entity( core->getEntityByIndex( 0 ));
This is because the entity is the only single entity with index 0.
Given an entity object, we can easily retrieve its properties:
const char* id = entity->id();
const char* name = entity->name();
const char* description = entity->description();
If your entity is associated with a trial license model (Expire by Access Times, Expire By Hard Date, Expire By Period, Expire by Duration, Expire by Session Time), you can test the entity's license status as following:
if(entity->isUnlocked()){
//this entity is already activated / fully purchased.
//app should run in full featured mode for this entity (game level, app module, etc.)...
run_in_full_featured_mode();
} else if (entity->isLocked()){
//this entity is locked or trial already expired.
//we may pop up info to prompt for purchase.
cannot_run();
} else {
//this entity still in trial period or duration
run_in_trial_mode();
}
If your entity is using License Model Always Lock, then the logic can be simplied as:
if(entity->isUnlocked()){
//this entity is already activated / fully purchased.
//app should run in full featured mode for this entity (game level, app module, etc.)...
run_in_full_featured_mode();
} else {
//this entity is still locked, we need a license to continue.
//we may pop up info to prompt for purchase.
cannot_run();
}
In the above section, we have detected that an entity is still in trial mode, so in order to display proper license information to end user on app UI, such as tell the game player how many trial time left before the game will stop running, we must be able to get more details of the license model.
std::auto_ptr<TGSLicense> lic( entity->getLicense() );
Now that the license object is ready, we can inspect it in various perspectives:
As a developer we have designed the license project in SoftwareShield IDE before, so we should have known which kind of license model being used for an entity. However, if you are developing a universal logic to parse any entity, it is helpful to find out the license model type being bundled with the input entity:
const char* model_id = lic->id();
const char* model_name = lic->name();
const char* model_description = lic->description();
For example, for Expire By Period, its LicenseID is "gs.lm.expire.period.1", so by comparing the license id you can figure out the correct license model type to inspect further.
Now we have known which license model to inspect, let's retrieve the license parameters according to its model type:
This license model (Expire by Access Times) has two parameters of int type: "usedTimes" and "maxAccessTimes"
int usedTimes = lic->getParamInt("usedTimes");
int maxAccessTimes = lic->getParamInt("maxAccessTimes");
//How many times left the app can launch in trial mode?
int how_many_access_left = maxAccessTimes - usedTimes;
This model (Expire By Hard Date) is a bit complex since it have three different use cases, in this tutorial we only demonstrate the most commonly used one: "Expire After", in this case, the license has a predefined expiry date "timeEnd", the app trial mode expires after this date.
#include <time.h>
time_t expiryDate = lic->getParamUTCTime("timeEnd");
//Convert UTC time_t to local time
struct tm * timeinfo;
timeinfo = localtime (&expiryDate);
std::cout << "Current local time and date: " << asctime(timeinfo);
The interest part is that SoftwareShield SDK api always returns time_t value, which is an integral value representing the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC (i.e., a unix timestamp), you might have to convert it to local time to display the expiry date in local time zone.
This license model (Expire By Period) has a UTC datetime parameter "timeFirstAccess", specify the timestamp the entity is first accessed, and an integer parameter "periodInSeconds", specify the total trial period allowed. The following code tries to figure out when the license will expire and how many trial time left before expire.
#include <time.h>
//Total trial period in seconds
int periodInSeconds = lic->getParamInt("periodInSeconds");
//Figure out when the entity is first accessed (aka. entity->beginAccess() is first called)
//if never accessed before, the _timeFirstAccess_ parameter won't hold a valid datetime and the api can throw exception.
try {
time_t timeFirstAccess = lic->getParamUTCTime("timeFirstAccess");
//accessed before, so we can calculate the expiry date
time_t expiryDate = timeFirstAccess + periodInSeconds;
//How many trial time left?
time_t now;
time(&now);
int timeLeftInSeconds = expiryDate - now;
}catch(...){
//Never accessed, the expiry date is still unknown!
//How many trial time left = full trial period.
int timeLeftInSeconds = periodInSeconds;
}
The interesting part of the above code is that: if the "timeFirstAccess" does not hold a valid timestamp (the default value is undefined), which occurs when the entity is never accessed before, we cannot deduce the exact expiry date, however we can deduce how many time left before expire.
This license model (Expire by Duration) is simple, it just adds up all elapsed trial time cumulatively (in "usedDurationInSeconds") until the total trial time exceeds the pre-defined maximum value ("maxDurationInSeconds"). We cannot figure out the expiry date.
//Total trial period in seconds
int usedDurationInSeconds = lic->getParamInt("usedDurationInSeconds");
int maxDurationInSeconds = lic->getParamInt("maxDurationInSeconds");
//How many trial time left?
int timeLeftInSeconds = maxDurationInSeconds - usedDurationInSeconds;
This license model (Expire by Session Time) allows an entity can always be accessed (never expire) but in a limited time, we can get the session time left but cannot get the expiry date.
int sessionTimeUsed = lic->getParamInt("sessionTimeUsed");
int maxSessionTime = lic->getParamInt("maxSessionTime");
//How many trial time left?
int timeLeftInSeconds = maxSessionTime - sessionTimeUsed;
There are two methods to activate your app, online and offline.
After user've made purchase of your app, you send a serial number to the customer as a proof of valid customer and the key to activate your app.
if(core->isServerAlive()){
int rc; //returned error code
bool success = core->applySN(serial, &rc);
}else{
std::cerr << "license server is not available!";
//optionally fall back to offline activation...
}
In order to do offline activation, we need to go through three steps:
std::string requestCode = core->getDummyRequestCode();
We are using dummy request code because the license server can deduce all valid actions from the customer's serial number.
You display the request code on UI, asking the customer to contact your support team for a valid license code, or if possible, let the customer uses another machine (or a mobile device) to generate the license code by himself/herself via the app's public App Web Activator.
Once the user input a license code, we can apply it to the app:
const char* licenseCode = readLicenseCodeFromUI(); //user input the license code.
bool success = core->applyLicenseCode(licenseCode, serial);
SoftwareShield SDK allows you to revoke serial numbers to license server so that the revoked serial numbers can be reused in another machine or the same machine (after hardware upgrade, system re-install, etc.)
const char* serialToRevoke;
bool success = core->revokeSN(serialToRevoke);
After successful revoke, all those entities previously unlocked by this serial are locked.
bool success = core->revokeApp();
After successful revoke, all previously unlocked entities are locked.
Revoke serial number is good for most cases, but there are other scenerios that the serial-based revoke won't work. For example, your license model is based on Pay-as-you-go concept, your app is not fully activated, the serial is used to extend the app's life span, so the unlock-lock based serial-number revoke does not make sense at all.
On machine A (sender):
std::string receiptSN = core->uploadApp();
On machine B (receiver):
bool success = core->applySN(receiptSN);
On machine A (sender):
std::string lic_backup = core->exportApp();
On machine B (receiver):
std::auto_ptr<TMovePackage> mv (core->createMovePackage( lic_backup ));
if(core->isServerAlive()){
//online import
bool success = mv->importOnline( serial );
}else{
//offline import
std::string moveRequestCode = mv->getImportOfflineRequestCode();
// the user reads the request code from app UI and gets license code from support team (or via App Web Activator)
const char* licenseCode;
bool success = mv->importOffline(licenseCode);
}
The workflow seems a little messy, but you do not have to fully support all online/offline cases in your app. The SoftwareShield SDK provides complete apis to support all possible use cases.
Sometimes you want to implement a logic to test if a serial number is still valid before app can run at client side. You can disable or delete a serial number at CheckPoint2 license management web portal, as a result you can remote control if the app should run by manipulating the serial number on the server side.
const char* serial; //the serial issued from app vendor
if(!core->isValidSN( serial )){
core->lockAllEntities();
exit(0); //quit app
}
To handle SoftwareShield license events, you can sub-class the TGSApp, overriding any event handler as needed:
In MyApp.h:
#include "GS5_Ext.h"
void initSDK();
class TMyApp : public TGSApp {
DECLARE_APP(TMyApp);
private:
virtual void OnAppBegin(){
//App launches, say hello...
}
virtual void OnAppExit(){
//Render Exit-UI
std::auto_ptr<TGSEntity> entity( core()->getEntityByIndex(0) );
if(entity->isUnlocked()){
//activated, do nothing
}else if(entity->isLocked()){
//trial already expired, need license to run
messagebox("your trial is expired, please activate now");
}else {
//trial mode...
messagebox("your trial will expire in XXX");
}
}
virtual void OnEntityAccessStarting(TGSEntity* entity){//EVENT_ENTITY_TRY_ACCESS
if(entity->isUnlocked()){
//activated, do nothing
}else if(entity->isLocked()){
//trial already expired, need license to run
render_activate_UI( entity );
}else {
//trial mode...
}
}
virtual void OnEntityAccessInvalid(TGSEntity* entity, bool inGame){//EVENT_ENTITY_ACCESS_INVALID
//trial expired!
render_activate_UI( entity );
}
virtual void OnEntityHeartBeat(TGSEntity* entity){ //EVENT_ENTITY_ACCESS_HEARTBEAT
//check when the entity will expire, inspect its license parameter to give user feedback
//when 5 minutes left before expire...
}
};
In MyApp.cpp:
#include "MyApp.h"
IMPLEMENT_APP(TMyApp);
void initSDK(){
GS_INIT_APP;
TGSCore* core = TGSCore::getInstance();
bool ok = core->init(productId, full_path_to_license_file, password);
...
}
Upon app launching, you should call initSDK() to initialize SoftwareShield SDK first.