Asset - Extension Google Play Licensing: modification

Andrey

Member
Hello!
The Marketplace has an extension from YoYo Google Play Licensing. However, it has one significant limitation — interruption of the game in case of problems with the license.
Because of the interruption, we have trouble getting into the Designed for Families program.
Also, the extension does not give any choice in actions. It's inflexible.
 
Last edited:

Andrey

Member
[!] This modification allows you to abandon the dialog box and get through GML server response (or from the cache), when you want it yourself.

The function GM_LicenseCheck_Init(1);
installed in the game makes a request and receives a response asynchronously:

- the time of the request;
- response code;
- error code (if any).



1. To install the modification you need to replace all the contents of the file ../extensions/GooglePlayLicensingAsExt/AndroidSource/Java/GooglePlayLicensingAsExt.java
on this code:
Code:
package ${YYAndroidPackageName};

import ${YYAndroidPackageName}.R;
import ${YYAndroidPackageName}.RunnerActivity;
import com.yoyogames.runner.RunnerJNILib;

import android.util.Log;
import android.os.Handler;

import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;
import com.google.android.vending.licensing.Obfuscator;
import com.google.android.vending.licensing.ServerManagedPolicy;
import com.google.android.vending.licensing.AESObfuscator;
import com.google.android.vending.licensing.Policy;

import android.net.Uri;
import android.provider.Settings.Secure;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;

public class GooglePlayLicensingAsExt extends RunnerSocial
{

    final int EVENT_OTHER_SOCIAL = 70;
    final int GM_LICENSECHECK_INIT = 1000;
    final int GM_LICENSECHECK_REASON = 2000;
    final int GM_LICENSECHECK_ERROR = 3000;
    private int GM_licensecheck_start = 0;
 
 
    // License checking
    private LicenseCheckerCallback mLicenseCheckerCallback;
    private LicenseChecker mChecker; 
    private Handler mLicenseHandler = new Handler();
 
 
 
    private class MyLicenseCheckerCallback implements LicenseCheckerCallback
    {
        public void allow(int reason)
        {
         
            if (RunnerActivity.CurrentActivity.isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            Log.i("yoyo", "!!!!##### Successful license check #####!!!!!! ");
         
            GM_LicenseCheck_Reason(reason);
        }

     
     
        public void dontAllow(int reason)
        {
            if (RunnerActivity.CurrentActivity.isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
              }
           
            Log.i("yoyo", "!!!!##### failed license check reason=" + reason + " #####!!!!!! ");
         
            GM_LicenseCheck_Reason(reason);
         
        }
     
     
        public void applicationError( int _error )
        {
            // log the error
            Log.i("yoyo", "License Error - " + _error);
         
            // then call dontAllow
              dontAllow(0);
         
            GM_LicenseCheck_Error(_error);
        }
    }
 


 
 
    @Override
    public void Init()
    {
        Log.i("yoyo", "Google Play Licensing extension initialising" );
    }
 
 
 
    /* Checks the security for the application */
    public void checkLicensing()
    {
        if(GM_licensecheck_start == 1)
        {
            if(RunnerActivity.CurrentActivity.checkCallingOrSelfPermission("com.android.vending.CHECK_LICENSE")==PackageManager.PERMISSION_GRANTED)
            {
                mLicenseCheckerCallback = new MyLicenseCheckerCallback();
                String deviceId = Secure.getString(RunnerActivity.CurrentActivity.getContentResolver(), Secure.ANDROID_ID);
                Log.i("yoyo", "deviceId = "+deviceId);
                ServerManagedPolicy policy = new ServerManagedPolicy( RunnerActivity.CurrentActivity, new AESObfuscator( RunnerActivity.CurrentActivity.SALT, RunnerActivity.CurrentActivity.getPackageName(), deviceId));
             
                if( RunnerActivity.BASE64_PUBLIC_KEY == null || RunnerActivity.BASE64_PUBLIC_KEY == "")
                {
                    Log.i("yoyo", "Invalid license key found");
                }
                try
                {
                    mChecker = new LicenseChecker( RunnerActivity.CurrentActivity, policy, RunnerActivity.BASE64_PUBLIC_KEY);
                    mChecker.checkAccess( mLicenseCheckerCallback );
                }
                catch ( IllegalArgumentException _ex )
                {
                    Log.i("yoyo", "exception while doing license check! invalid license key????"+ _ex);
                }
            }
            else
            {
                Log.i("yoyo", "@@@@@@ Google Licensing permission not set" );
            }
         
            GM_licensecheck_start = 0;
        }
    }
 
 
 
 
 
 
 

    // INIT
    public void GM_LicenseCheck_Init(double arg0)
    {
        long GM_licensecheck_time = System.currentTimeMillis();
        if(arg0 == 1)
        {
            arg0 = 0;
            GM_licensecheck_start = 1;
            Log.i("yoyo", "GM_LicenseCheck_Init: YES");
            Log.i("yoyo", "GM_licensecheck_time: " + GM_licensecheck_time);
                     
            int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
            RunnerJNILib.DsMapAddDouble( dsMapIndex, "type", GM_LICENSECHECK_INIT);
            RunnerJNILib.DsMapAddDouble( dsMapIndex, "time", GM_licensecheck_time);
            RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
         
            checkLicensing();
        }
    }
 
    // REASON
    public void GM_LicenseCheck_Reason(double res0)
    {
        Log.i("yoyo", "REASON: " + res0);
     
        int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "type", GM_LICENSECHECK_REASON);
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "reason", res0);
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
    }
 
    // ERROR
    public void GM_LicenseCheck_Error(double err0)
    {
        Log.i("yoyo", "ERROR: " + err0);
     
        int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "type", GM_LICENSECHECK_ERROR);
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "error", err0);
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
    }


 

}


2. Next, configure the extension in GMS as in the picture (see attachment file at the bottom of the page):



3. Now we can send a request via the GM_LicenseCheck_Init(1); function whenever we want; and wait for a response:
Code:
var type = async_load[? "type"];

switch(type)
{
   // LICENSED
   case 2000:
       // LICENSED = 256 (LICENSED means that the server returned back a valid license response)
       // LICENSED_OLD_KEY = 256 (LICENSED means that the server returned back a valid license response)
       // NOT_LICENSED = 561 (NOT_LICENSED means that the server returned back a valid license response that indicated that the user definitively is not licensed)
       // RETRY = 291 (RETRY means that the license response was unable to be determined - perhaps as a result of faulty networking)

var licensing_reason = async_load[? "reason"];

   break;
 
 
   // ERRORS
   case 3000:
       // ERROR_NOT_MARKET_MANAGED = 3
       // ERROR_SERVER_FAILURE = 4
       // ERROR_OVER_QUOTA = 5
       // ERROR_CONTACTING_SERVER = 257
       // ERROR_INVALID_PACKAGE_NAME = 258
       // ERROR_NON_MATCHING_UID = 259

       var licensing_error = async_load[? "error"];
   break;
 
 
   default:
   break;
}


4. Now you can decide what you want to do with this answer.

5. See implementation example >>>
 

Attachments

Last edited:
Hello Andrey!
Is it possible to make such a modification to google play service extension for GM 1.4?

Currently after the google + API has been disabled, no google play service functionality works any more.
 

Andrey

Member
Hello Andrey!
Is it possible to make such a modification to google play service extension for GM 1.4?
Hello!
I'm not using 1.4 anymore, so I can't check.
This modification only removes the interrupt caused by the pop-up window. Instead of an interrupt, a response is sent.
 

Dan

GameMaker Staff
GameMaker Dev.
We are going to review this change and see if it needs to be included in the extension, as I can see the comment in the 2.2.4 beta thread which says this still blocks Designed For Families submission. In future, please do report the issue to us directly via the ticket system, though - we could probably have fixed this months ago if we'd known about it ;)


Hello Andrey!
Is it possible to make such a modification to google play service extension for GM 1.4?

Currently after the google + API has been disabled, no google play service functionality works any more.
1.4 does not support Android API 26+ anyway, so Google won't accept any updates or new app submissions. You would have to use GMS2 to update that project, using the current versions of Android Studio / Android APIs and the Google Play Services extension at that time.
 

Andrey

Member
We are going to review this change and see if it needs to be included in the extension, as I can see the comment in the 2.2.4 beta thread which says this still blocks Designed For Families submission. In future, please do report the issue to us directly via the ticket system, though - we could probably have fixed this months ago if we'd known about it ;)
Thank You, Dan!
And yes, of course I sent report about this a year ago ( #149955 )
 
@Andrey ,
  1. could you please tell me where to do step no. 3? I can't seems to locate the location of "GM_LicenseCheck_Init(1);" to add the codes to
  2. also an example for no. 4 regarding "decide what you want to do with this answer"
Thank you.
 

Andrey

Member
@Andrey ,
  1. could you please tell me where to do step no. 3? I can't seems to locate the location of "GM_LicenseCheck_Init(1);" to add the codes to
  2. also an example for no. 4 regarding "decide what you want to do with this answer"
Thank you.
This modification gives You the right to decide when to make request license verification.
Function "GM_LicenseCheck_Init(1);" You can use 1 time in the code Create some object.
Can use she in your code Step if you want to make requests under certain conditions.
Item 3 is the sample code asynchronous events through which You will get the answers after the feature request GM_LicenseCheck_Init(1);
After receiving the answer (256, 561 or 291), You decide what to do next (to block game or not).

Here is an example implementation:

Create event:
Code:
global.licensing_reason = 0;
Step event:
Code:
if(global.licensing_reason == 0)
{
    GM_LicenseCheck_Init(1); // the request for verification of license
    global.licensing_reason = 1;
}
else if(global.licensing_reason == 256)
{
    // LICENSED means that the server returned back a valid license response
    //show_message_async("LICENSED");
}
else if(global.licensing_reason == 291)
{
    // RETRY means that the license response was unable to be determined - perhaps as a result of faulty networking
    //show_message_async("RETRY");
}
else if(global.licensing_reason == 561)
{
    // NOT_LICENSED
    //show_message_async("NOT_LICENSED");
}
Async – Social event:
Code:
// RECEIVING RESPONSES FROM THE SERVER DURING LICENSE CHECKING

var type = async_load[? "type"];


switch(type)
{
    // RESPONSE TIME CHECK OF THE LICENSE
    case 1000:
        var game_licensing_lasttime = date_current_datetime();
    break;
    
    
    
    // RESPONSE TO THE LICENSE
    case 2000:
        // LICENSED = 256 (LICENSED means that the server returned back a valid license response)
        // LICENSED_OLD_KEY = 256 (LICENSED means that the server returned back a valid license response)
        // NOT_LICENSED = 561 (NOT_LICENSED means that the server returned back a valid license response that indicated that the user definitively is not licensed)
        // RETRY = 291 (RETRY means that the license response was unable to be determined - perhaps as a result of faulty networking)
        global.licensing_reason = async_load[? "reason"];
    break;
    
    
    
    // ERRORS
    case 3000:
        // ERROR_NOT_MARKET_MANAGED = 3
        // ERROR_SERVER_FAILURE = 4
        // ERROR_OVER_QUOTA = 5
        // ERROR_CONTACTING_SERVER = 257
        // ERROR_INVALID_PACKAGE_NAME = 258
        // ERROR_NON_MATCHING_UID = 259
        var check_licensing_error = async_load[? "error"];
    break;
    
    
    default:
    break;
    
    
}
 
Thanks @Andrey , I'll try doing the above and report back. I think I'll try place the above into the creation code of the splash screen object since that's the only object found in the first room the game run. Alternatively I might add it to the title screen object of the 2nd room (main menu) in the game.

Anyway I just received feedback from GP, they rejected all my app updates to .aab using beta 2.2.4 for the same reason as the first rejection. Something is seriously wrong which I haven't experience before 2.2.4 & GPLicense Ext 2.3.2. I've only until Oct 1st to fix the issues before they decide what they'll do with my apps. Joining the family program is a huge mistake. :confused:
 

Dan

GameMaker Staff
GameMaker Dev.
Could you send us a helpdesk ticket with all your rejection info from Google, please? We'll see if there's anything we need to address there.
 
@Dan , I just got reply from GP, they decide to approve the first app that I report as rejected (which I had fix yesterday) after I chooses to change my target market and opt out of the family program. I haven't resubmit the apps that was rejected today. I'm going to try the same route, see if they'll approve it once I remove them from the family program regardless the warning messages related to licensing.

Note: The now approved (rejected app) still contain the 2 warnings related to the Licensing screen - I had only fixed the Access_Network_State, which they decide to let through after I remove my app from the family program. You need not look into the case with Access_Network_State, since as I mentioned in earlier posts it was a mistake on my end. The case which Andrey reported is what's relevant.

PS: I'll send you the report tonight.

Edit:

@Dan ,
I've decided to post the report in the Feedback topic. I can't decide which category to choose in the helpdesk. Sorry and thanks.
 
Last edited:
S

shalom cohen

Guest
This modification gives You the right to decide when to make request license verification.
Function "GM_LicenseCheck_Init(1);" You can use 1 time in the code Create some object.
Can use she in your code Step if you want to make requests under certain conditions.
Item 3 is the sample code asynchronous events through which You will get the answers after the feature request GM_LicenseCheck_Init(1);
After receiving the answer (256, 561 or 291), You decide what to do next (to block game or not).

Here is an example implementation:

Create event:
Code:
global.licensing_reason = 0;
Step event:
Code:
if(global.licensing_reason == 0)
{
    GM_LicenseCheck_Init(1); // the request for verification of license
    global.licensing_reason = 1;
}
else if(global.licensing_reason == 256)
{
    // LICENSED means that the server returned back a valid license response
    //show_message_async("LICENSED");
}
else if(global.licensing_reason == 291)
{
    // RETRY means that the license response was unable to be determined - perhaps as a result of faulty networking
    //show_message_async("RETRY");
}
else if(global.licensing_reason == 561)
{
    // NOT_LICENSED
    //show_message_async("NOT_LICENSED");
}
Async – Social event:
Code:
// RECEIVING RESPONSES FROM THE SERVER DURING LICENSE CHECKING

var type = async_load[? "type"];


switch(type)
{
    // RESPONSE TIME CHECK OF THE LICENSE
    case 1000:
        var game_licensing_lasttime = date_current_datetime();
    break;
 
 
 
    // RESPONSE TO THE LICENSE
    case 2000:
        // LICENSED = 256 (LICENSED means that the server returned back a valid license response)
        // LICENSED_OLD_KEY = 256 (LICENSED means that the server returned back a valid license response)
        // NOT_LICENSED = 561 (NOT_LICENSED means that the server returned back a valid license response that indicated that the user definitively is not licensed)
        // RETRY = 291 (RETRY means that the license response was unable to be determined - perhaps as a result of faulty networking)
        global.licensing_reason = async_load[? "reason"];
    break;
 
 
 
    // ERRORS
    case 3000:
        // ERROR_NOT_MARKET_MANAGED = 3
        // ERROR_SERVER_FAILURE = 4
        // ERROR_OVER_QUOTA = 5
        // ERROR_CONTACTING_SERVER = 257
        // ERROR_INVALID_PACKAGE_NAME = 258
        // ERROR_NON_MATCHING_UID = 259
        var check_licensing_error = async_load[? "error"];
    break;
 
 
    default:
    break;
 
 
}

Hi. I followed the instructions but when I run the project on android nothing shown on the screen and GMS shows me
this error:


--------- beginning of main
03-04 14:12:27.605 5980 6769 I yoyo : License Error - 3
03-04 14:12:27.605 5980 6769 I yoyo : !!!!##### failed license check reason=0 #####!!!!!!
03-04 14:12:27.605 5980 6769 I yoyo : REASON: 0.0
03-04 14:12:27.606 5980 6769 I yoyo : ERROR: 3.0
03-04 14:12:27.658 5980 6001 I yoyo : GM_LicenseCheck_Init: YES
03-04 14:12:27.658 5980 6001 I yoyo : GM_licensecheck_time: 1583323947658
03-04 14:12:27.661 5980 6001 I yoyo : deviceId = cfbc8d89ecdf970e



EDIT: nevermind, I understand this error make sense because i didn't wait enough for the test app to be published on the market..
 
Last edited by a moderator:

Andrey

Member
EDIT: nevermind, I understand this error make sense because i didn't wait enough for the test app to be published on the market..
Yes, the app must be published on the store.
This modification does not display anything on the screen. It only checks and passes values to variables. And then You decide what to do next - block the app somehow or not.
 
Top