50Ply Blog

Building Things

Loading Compressed Android Assets With File Pointer

| Comments

My game engine now runs on the Ouya (a new Android-based device using a Tegra 3 ARM processor!)

One of the big challenges that I encountered during the port was asset loading. Android packages, which are delivered as .apk files, typically load assets at run-time by reaching back into the .apk file. The Android provided API for accomplishing asset loading in Java is appropriate but the Android NDK C API has some shortcomings. Why would I say that when the Java API and the C API are almost identical? C applications that handle streams of data from files are typically designed around the file pointer abstraction (FILE*) and the Android API isn’t compatible. This means, typically, that you would need to alter the code of any third-party libraries that you are using (if they deal with files) in order to port them the Android asset API… until now!

My engine uses quite a few third-party libraries, specifically:

  1. Box2D for physics

  2. lobogg / tremor for sound and music

  3. Lua for scripting

  4. stb_image for image loading

All of these libraries, except Box2D, provide features that already exist behind some other Android API. The reason I explicitly provide these features again through my own libraries is for portability. I do my quick-turnaround build testing on my Mac but I want the same code to work exactly the same way on the Ouya. The simplest way to achieve this is to provide a lot of my own low-level support. The more “pure” way to achieve this would be to create an abstraction layer that used the Android APIs and the OSX APIs under the hood.

libogg, Lua, and stb_image all use the FILE* API to load files from disk. They call fopen when they want to open a new file and fread to extract data from it. They also occasionally use fseek to jump around.

One amazing and well-hidden fact about the FILE* API is that it is actually polymorphic and extensible! (A surprise from C, right?) We can create a FILE* that uses code we wrote to satisfy fopen, fread, fseek, and fwrite requests. We can use this little-known feature to wrap the Android asset API in a FILE* API instead.

Here’s how:

1
2
3
4
5
6
7
8
FILE* android_fopen(const char* fname, const char* mode) {
  if(mode[0] == 'w') return NULL;

  AAsset* asset = AAssetManager_open(android_asset_manager, fname, 0);
  if(!asset) return NULL;

  return funopen(asset, android_read, android_write, android_seek, android_close);
}

The funopen function creates a new file pointer that will delegate control to the functions that you specify when the user uses fread and friends on that file pointer. The first parameter to funopen is the “cookie” to associate with the FILE*. This is arbitrary data that will be given to your delegate functions whenever they are called. OOP programmers should think of it as the self (or this) pointer.

Let’s write those delegate functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int android_read(void* cookie, char* buf, int size) {
  return AAsset_read((AAsset*)cookie, buf, size);
}

static int android_write(void* cookie, const char* buf, int size) {
  return EACCES; // can't provide write access to the apk
}

static fpos_t android_seek(void* cookie, fpos_t offset, int whence) {
  return AAsset_seek((AAsset*)cookie, offset, whence);
}

static int android_close(void* cookie) {
  AAsset_close((AAsset*)cookie);
  return 0;
}

Now we can use the FILE* API to access our assets!

1
2
3
FILE* android_file = android_fopen("something.txt");
fread(buffer, size, 1, android_file);
fclose(android_file);

This is great but we still have to call android_fopen to create our special FILE*. If we stopped hacking here then we would still need to open up all those third party libraries to change the fopen calls to android fopen instead. We can do better.

If we’re willing to say that ALL file access should go through the asset API then we can achieve that quickly. We’ll just create a header for our new android fopen library and redefine fopen using a macro:

1
2
3
4
5
6
7
8
#ifndef ANDROID_FOPEN_H
#define ANDROID_FOPEN_H

FILE* android_fopen(const char* fname, const char* mode);

#define fopen(name, mode) android_fopen(name, mode)

#endif

Now we just include this header in our code and we can use fopen as normal! But, again, we’re still adding code to those third party libraries. Thankfully, gcc has the answer.

1
gcc -o foo foo.c -include "android_fopen.h"

The -include argument causes gcc to treat your code as-if you had included the provided file at the very top of your code. Now, to port Lua and libogg and any other big-fancy library that uses FILE* to Android, all we need to do is tweak the build system to include our magic header.

The full source is here and here.

Comments