The manual instructs android users start the program and then bring up the menu to connect the phone to the robot using the phone's bluetooth connection. One review indicated that the problem occurs because the app hard codes the display of the connection screen with a physical button press. Newer versions of android phones (including my own) lack physical buttons and are thus unable to bring up the screen.
My goal was to download the apk, decompile it into java source code, and fix that small error.
Warning, this is a very long post. Constructive advice on how to shorten it would be appreciated!
Building the Project
Early research on reaching this goal led me to an RSA presentation on reverse engineering android apps which provides a nice guideline on available tools and methodologies. I supplemented some of those steps with google searches.
Step one was to acquire the apk. From the presentation, there's a method of retrieving the apk from the phone, but using the online tool apk-downloader and providing a link to the app on the play store was simple enough. I renamed the download as Attacknids.apk for easier handling and was finished with step one of this experiment.
Step two was to extract the assets from the apk. Android applications are more than source code and both the presentation and my own searches brought up apktool to turn an apk into usable sources. Apktool also creates smali source code, but as I'm not familiar with the language, I just ignored it. A quick execution of the program and I had all of the graphical assets used for the application.
apktool d Attacknids.apk
Step three was to convert the apk into a jar file. I had previous experience using JD-Gui to decompile a jar, but I first needed the jar file to decompile. The reverse engineering presentation told me about dex2jar. I read up on the documentation, ran the program, and got Attacknids-dex2jar.jar.
d2j-dex2jar Attacknids.apk
Step four was to decompile the jar into Java source code. I had used JD-Gui in the past and as such, it was the first decompiler I tried. I eventually discovered that a single decompiler by itself isn't able to cleanly decompile the entire program. The ultimate process involved using three decompilers (JD-Gui, Procyon, and CFR) and combining pieces that worked or using the decompiled source as reference. I came across Procyon from searching for java decompilers and the Procyon wiki led me to CFR.
// Procyon source, with the program renamed to decompiler.jar java -jar decompiler.jar -jar Attacknids-dex2jar.jar -o Procyon // CFR source java -jar cfr_0_79.jar Attacknids-dex2jar.jar --outputdir CFR // JD-Gui source // generated with the guiI finally had all of the pieces necessary to get a working android project. I combined the apktool assets with the JD-Gui source code and got a project with 1278 errors and 359 warnings. This wasn't going to be easy.
Replacing AndEngine
A large portion of the errors stemmed from the org.anddev.andengine.* package. After searching online, it appeared that this app utilized AndEngine, an open-source, OpenGL, 2D Android game library. I'm not sure why the decompilers had such a hard time with the AndEngine source code, but it became my problem.
I tried replacing the decompiled source code with the latest version of AndEngine from GitHub, but there have been major changes since the app was initially released. The next step was to searching through the AndEngine commit history to find version that contained all of the function calls that the app was expecting. Finding the right version was an iterative process. I would: add in a version of the AndEngine source code, check to see if there were any undefined function calls, find the commit that changed or deleted that function call, checkout the previous commit, and repeat. My best guess as of now is git commit 8fd900bd527ec36fc13dc63f29894c8dc460359f dated 16 December 2010 because it is the latest version that still contained the correct Layer class and ILayer interface.
The decompiled source code also contains references to older extensions of AndEngine (eg. input and multiplayer). The git history for these extensions don't go as far back as the December 2010 version of AndEngine that I believe the app is using, so I had to grab the source code from the older code.google.com mercurial repositories. As an aside, I had never used mercurial before but the commands for cloning and reverting revisions were easy enough to figure out. I went through the same process as the AndEngine source code with the extensions. The latest versions available on code.google.com had changed the interface that the app was expecting and so I had to go through the revision history to find the correct versions.
There are also references to the com.badlogic.gdx.* package. This is actually just another extension to AndEngine created to handle physics. It was a relatively simple process of adding in the code to the project.
After adding in the correct version of AndEngine and its extensions, there was about an order of magnitude decrease in the number of errors. I was starting to feel quite confident.
Online Code Examples
The next order of business that I needed to handle was taking care of the code that handles the bluetooth connection. There were two files, BluetoothChatService.java and DeviceListActivity.java, inside the com.SixClawWorm.application package that were generating the top errors. I first tried the usual process of fixing errors manually, but needed a better method. So I typed BluetoothChatService.java into google, and got a hit! I'm guessing that BluetoothChatService is a sample class because of the multiple online versions that have slightly different variations for specific purposes. To find the right version, I cross-referenced the function signatures found in the decompiled source and the source I found online. The decompilers had a hard time decompiling function bodies, but they all kept function names and parameter types intact. Using this method, I was able to find a version of BluetoothChatService and DeviceList that seemed to work. More errors gone, yay!
The next cluster of errors were coming from the com.SixClawWorm.utils package. I wanted to figure out how much code in the app was available online so I duplicated the method I used with the bluetooth code and was only semi-surprised to find matching results. I found quite a few of the utility classes on a chinese blog site. If I could read Chinese or interpret google's translations, I might be able to figure out why this website has a lot of code used in the app; for now, it remains a mystery.
The Last Few Errors
At this point, I started running into classes I couldn't find online. The decompiled source still had errors, but the total number was much more manageable. This is where the usefulness of multiple decompilers really showed. For example, Procyon may not have been able to decompile one particular function at all but JD-Gui or CFR was. Similarly, there may be errors in a switch statement generated by JD-Gui, but comparing that function with Procyon shows simple syntax errors that can be fixed by adding in the Procyon code. Mixing and matching source code from the multiple decompilers let me get rid of a majority of the remaining errors.
The last remaining errors had to deal with two overriden function calls to onControlKnobUp and onControlKnobDown. The decompiled source showed those functions being called in BaseOnScreenControl.java, a file belonging to AndEngine, but I couldn't find those function calls anywhere in that file's commit history. I believe that the original developers manually added those functions to the AndEngine source code, so I had to do the same. Ultimately, the fix consisted of adding two lines (made even easier by comparing the AndEngine source with the decompiled version of AndEngine) and asking Eclipse to generate the two functions in the IOnScreenControlListener interface. At this point, all of the errors are gone!
Throughout the process, I also came across API calls that would require me to increase the minSdkVersion in the manifest file. Oddly enough, I believe this may have been the only change required to actually fix the app. I'm still not completely sure, however.
The First Test
I activated development mode on my phone and pushed the newly created apk over USB. To my surprise, it seemed to be working! The little menu compatibility icon was showing up (huh? why? I don't remember adding that in.) and I could bring up the connection menu that I never could using the original app. I eagerly grabbed the robot, turned it on, and tried connecting to it. I clicked on scan and the robot showed up on the list of devices! It showed up a bunch of times, but still, it showed up... I connected to one of the robots on the list and, with bated breath, hit the play button.
My patched application crashed. It's never that easy, is it?
I ran the program through the debugger to figure out what was causing the crash. There was an assertion being raised inside AndEngine because of some invalid bounds being passed to a texture creation function. I looked at the stack trace and saw the function being called through PlayControlActivity. I looked at the code that was calling the function that raised the exception and saw what caused the error. I'm not sure if it's an error in the original app or the decompiled code, but the given bounds were just wrong. The function accepts an x-position for a texture and raises an exception if the x-position + width is greater than the total width of the texture. In PlayControlActivity, the total screen width is being passed in as the x-position which always result in an exception. I replaced this.CAMERA_WIDTH with 0 and tried running the program again.
A Working Copy, Sort of
It works! I can connect to the robot, hit play, and the screen to control the robot pops up. There are some weird artifacts (there are four copies of the controls that each take up an less than a quarter of the screen and some weird distortions in between) but it works! I can move the robot around, aim, and fire his little nerf gatling cannon. I could easily end the experiment here, but I really wanted to fix that graphical bug so I could actually use the robot without staring at a broken UI. My project is not quite finished.
The app contains three classes called PlayControlActivity, PlayControlActivity1280, and PlayControlActivity1920. Not the best example of software engineering practices, but it's what I had to work with. Given these three classes and the graphical error that occurred on my phone, I wondered why the stack trace was going through PlayControlActivity and not PlayControlActivity1920 (the resolution on the Nexus 5 is 1920x1080). I found the function that determines the screen's resolution inside of MenuActivity.java. The function did a simple
if (width == 1280) instantiate PlayControlActirivity1280 else if (width == 1920) instantiate PlayControlActivity1920 else instantiate PlayControlActivityI placed a breakpoint at this spot and ran the program. It seems like the function to calculate the width of the screen was not accounting for the width of the menu. On my phone, the width was 1794, not 1920. I didn't want to engineer a robust solution; I just wanted to see if it works. I added the check for 1794 to instantiate PlayControlActivity1920 and ran the program again. It worked!
I now have an Android app that works on my Nexus 5 and allows me to control my AttackNid. What's next?
The Next Step
I originally wanted to patch up the application, submit it to the store, and post links to the updated app as comments to posts that I came across when I was first trying to get the original app to work. At this point, however, I'm not even sure what kind of interest really exists for a patched app. Also, I don't know the codebase well enough to have any sort of trust that it works on any phone besides my own. To continue down this path, I'll need to fix up the code a little bit more and at least test it on various emulator configurations before releasing it to the wild. That's a discussion for another time.
Final Thoughts
Phew, that was a lengthy post and a lengthy experiment. I had a lot of fun working with the decompilers and trying to get everything to work together but there are still quite a few questions and unsolved mysteries. Why is so much of the source code on a Chinese blog site? Why does the program still work with so many missing components (the decompiled source has references to augmented reality and multiplayer extensions, but the updated app doesn't)? How much work will it take to get this to work on multiple phones?
In any case, here's a link to all of the work I've done so far. When I can fix up the codebase a little bit more, I'll be adding it to my main repository.
https://github.com/rLadia-demo/AttacknidPatch
Thanks for reading!
So can I get a download of the app to my nexus 4? Vcvdcatmsndotcom
ReplyDelete