Game networking - Galaxy1036/GalaxyLifeReborn GitHub Wiki

The game basically use http protocol to communicate with the server, the http request body is being "crafted" & encoded through the native side while the rest of the http request is being crafted, (optionnaly encrypted) and sent through the java side. The java side also receive, decode, (and optionnaly decrypt) the answer while the lib is responsible of parsing and interpret packet data. Let's look at that more in details.

Quick summary:

gl_network_diagram

How it work in the deep:

Let's use the example of the first request sent by the game which is called "device command"

Our key/value pairs get crafted in bcn::NetworkInterface::sendDeviceCommand as we can see it: image image

Then bcn::UbiNetworkClient::uploadPacket is called. After some analysis it seems the first args is the method of the packet (we'll see later what it correspond to), the second args is the variable that contains key/value pairs, the third args is a bool that define if our request body should be AES encrypted and the last args is also a bool that define if the answer should be decrypted. Let's look at bcn::UbiNetworkClient::uploadPacket:

image

The first sub called is bcn::UbiNetworkClient::getURLForMethod.

image

This sub call bcn::UbiNetworkClient::getBaseURL that basically return ios.galaxylifegame.com which is the base game server url. Then bcn::UbiNetworkClient::getRelativeURLForMethod is called, this sub return the route on which the request will be sent. To get this route the sub will simply look through the method url values using the method (first args of bcn::UbiNetworkClient::uploadPacket) as key. Thoses key/value pairs can be found in assets/server/serverConfig.json, even if they don't load them from this file the values are right (they get registered in bcn::UbiNetworkClient::Initialise instead).

image

Our sub finally return the concatened url (http://ios.galaxylifegame.com/star/ in our example)

After that bcn::UbiNetworkClient::getPostDataFromParams is called. This sub is simply gonna urlencode our request body. Then bcn::UbiNetworkClient::uploadPacket is called. The first args is the method, second one is the url where the post request should be sent, the third one is the encoded body, fourth and fifth one are the encrypt/decrypt bool and the last one is not even used. This sub is gonna create a UbiNetworkRequest::UbiNetworkRequest object that contains all our packet property from the below args, this object will be pushed in a queue. This object will be processed in bcn::UbiNetworkClient::Update that will call bcn::UbiNetworkClientInterface::UbiNetworkClient_uploadPacket.

image

This sub will call the java sub UbiNetworkClient.uploadPacket through the JNI bridge. Let's look at the java code :)

  public void uploadPacket(final String paramString1, final String paramString2, final String paramString3, final boolean paramBoolean1, final boolean paramBoolean2)
  {
	Log.d("NetworkClient", "uploadPacket");
	UbiDebug.i("UbiNetworkClient", "uploadPacket : method=" + paramString1 + ", url=" + paramString2 + ", requestEncoded=" + paramBoolean1 + ", responseEncoded=" + paramBoolean2);
	if (isOnline())
	{
	  UbiNativeActivity.s_gameActivity.runOnUiThread(new Runnable()
	  {
		public void run()
		{
		  if (paramBoolean1)
		  {
			String str = new AESEncryptionUtils(UbiNetworkClient.APPLICATION_SECRET_KEY).base64encrypt(paramString3);
			new UbiNetworkClient.AsyncHttpClient(UbiNetworkClient.this, null).makeCall(paramString1, paramString2, str, paramBoolean2);
			return;
		  }
		  new UbiNetworkClient.AsyncHttpClient(UbiNetworkClient.this, null).makeCall(paramString1, paramString2, paramString3, paramBoolean2);
		}
	  });
	  return;
	}
	UbiDebug.i("UbiNetworkClient", "uploadPacket : It is not online");
	onRequestFailed(paramString1, 5678, "");
  }

This sub will AES encrypt + base64 encode our request body (paramString3) if paramBoolean1 is true, then create a UbiNetworkClient.AsyncHttpClient instance and call it's makeCall method.

public void makeCall(String paramString1, String paramString2, String paramString3, boolean paramBoolean)
{
  this.encoded = Boolean.valueOf(paramBoolean);
  super.execute(new String[] { paramString1, paramString2, paramString3 });
}

As we can see it this method is gonna define the encoded attribute of our class to Boolean.valueOf(paramBoolean) this bool will be used later to know if we have to decrypt the answer body. Then it call super.execute with an array of string containing the method, the url and the request body. Our AsyncHttpClient herit of AsyncTask class which is a class that come from the android API. After looking a bit to the doc (https://developer.android.com/reference/android/os/AsyncTask) it seems that execute method is creating a task that goes through 3 steps:

  1. It first call onPreExecute
  2. Then it call doInBackground passing the string array from execute as arg
  3. It finish by calling onPostExecute with the return of doInBackground as arg

As onPreExecute is just empty in our class we'll look at doInBackground

protected String doInBackground(String... paramVarArgs)
{
  this.code = 0;
  this.method = paramVarArgs[0];
  return postData(paramVarArgs[1], paramVarArgs[2]);
}

This method will simply return the postData call result. Unfortunately our java decompiler fail to decompile postData so let's look at the smali code (i'm not gonna past it here since it's too big): https://pastebin.com/ZUy4MCNz. This method is gonna craft the rest of our request and send it to the url passed as it's first arg, then it wait for an answer. Once it receive an answer it define code attribute to the answer status code and return the answer body.

As supposed onPostExecute get called with the doInBackground return as arg once all that is done.

protected void onPostExecute(String paramString)
{
  UbiDebug.i("UbiNetworkClient", "onPostExecute : code=" + this.code);
  if ((this.code >= 200) && (this.code < 400))
  {
    if (this.encoded.booleanValue())
    {
      UbiDebug.i("UbiNetworkClient", "onPostExecute : result=" + paramString);
      paramString = new AESEncryptionUtils(UbiNetworkClient.APPLICATION_SECRET_KEY).base64decrypt(paramString);
      UbiDebug.i("UbiNetworkClient", "onPostExecute : decodedResult=" + paramString);
      UbiNetworkClient.onRequestFinished(this.method, this.code, paramString);
      return;
    }
    UbiDebug.i("UbiNetworkClient", "onPostExecute : result=" + paramString);
    UbiNetworkClient.onRequestFinished(this.method, this.code, paramString);
    return;
  }
  UbiNetworkClient.onRequestFailed(this.method, this.code, paramString);
}

onPostExecute will check at the status code to see if everything worked right. If the answer status code is ok (btw 200 and 400) it optionally decrypt the answer body if this.encoded is true (this.encoded as been defined in makeCall) and call UbiNetworkClient.onRequestFinished with the answer body as last args. Else UbiNetworkClient.onRequestFailed is called with the same args as UbiNetworkClient.onRequestFinished. Thoses 2 sub are native sub called through the JNI bridge. Let's look at the native implementation of UbiNetworkClient.onRequestFinished.

image

This sub will simply call bcn::UbiNetworkClient::onNetworkEvent that just call bcn::NetworkInterface::onNetworkEvent. This last sub will push our answer in a queue. The answer will be processed in bcn::NetworkInterface::processNetworkEvents

image

This sub is gonna pull out our answer from the queue and call bcn::NetworkInterface::onRequestFinished that is the sub where parsing and packet handling is done