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:
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:
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
:
The first sub called is bcn::UbiNetworkClient::getURLForMethod
.
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).
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
.
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:
- It first call
onPreExecute
- Then it call
doInBackground
passing the string array from execute as arg - It finish by calling
onPostExecute
with the return ofdoInBackground
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
.
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
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