diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b772512..9216bc2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -31,6 +31,7 @@ + implements /** * Supported filename suffixes */ - public static final String[] SUFFIXES = { "Z3", "Z5", "Z8" }; + public static final String[] SUFFIXES = { "Z3", "Z5", "Z8", "ZBLORB" }; /** * The import candidates from which the user may choose. diff --git a/src/de/onyxbits/textfiction/LoaderTask.java b/src/de/onyxbits/textfiction/LoaderTask.java index eed8ee0..826d20e 100644 --- a/src/de/onyxbits/textfiction/LoaderTask.java +++ b/src/de/onyxbits/textfiction/LoaderTask.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -12,15 +11,13 @@ import de.onyxbits.textfiction.zengine.ZMachine5; import de.onyxbits.textfiction.zengine.ZMachine8; import de.onyxbits.textfiction.zengine.ZScreen; -import de.onyxbits.textfiction.zengine.ZState; import de.onyxbits.textfiction.zengine.ZStatus; import android.app.Activity; import android.os.AsyncTask; import android.util.Log; -import android.widget.Toast; /** - * Takes care of creating an engine from a story file + * Loader for z-code games. Supports z3, z5, z8 and zblorb files. * * @author patrick * @@ -29,6 +26,10 @@ public class LoaderTask extends AsyncTask { private RetainerFragment retainer; + /** + * + * @param retainer where to deliver the loaded engine. + */ public LoaderTask(RetainerFragment retainer) { this.retainer = retainer; } @@ -48,6 +49,11 @@ protected ZMachine doInBackground(File... story) { ZMachine engine = null; try { byte[] memImage = createMemImage(new FileInputStream(story[0])); + + if (isBlorb(memImage)) { + memImage = extractGame(memImage); + } + switch (memImage[0]) { case 3: { engine = new ZMachine3(new ZScreen(), new ZStatus(), memImage); @@ -61,9 +67,6 @@ protected ZMachine doInBackground(File... story) { engine = new ZMachine8(new ZScreen(), memImage); break; } - default: { - // TODO: create a dummy machine? - } } } catch (IOException e) { @@ -79,6 +82,7 @@ protected ZMachine doInBackground(File... story) { engine.run(); } catch (GrueException e) { + Log.w(getClass().getName(), e); engine = null; } } @@ -105,7 +109,7 @@ protected void onPostExecute(ZMachine result) { } /** - * Load the Rom code + * Load a binary file into RAM * * @param mystream * where to read from @@ -144,4 +148,92 @@ private byte[] createMemImage(InputStream mystream) throws IOException { return buffer; } + /** + * Check if a file is an zblorb archive + * + * @param image + * the memory image of the file + * @return true if the file starts with a FORM header. + */ + public static boolean isBlorb(byte[] image) { + // detect "FORM" header + return (image != null && image.length >= 40 && image[0] == 'F' + && image[1] == 'O' && image[2] == 'R' && image[3] == 'M'); + } + + /** + * + * @param image + * @param index + * @return + */ + static int readInt(byte[] image, int index) { + return image[index] << 24 | (image[index + 1] & 0xFF) << 16 + | (image[index + 2] & 0xFF) << 8 | (image[index + 3] & 0xFF); + } + + /** + * Pull the z-code out of a zblorb. + * + * @param image + * raw memory of the zblorb file + * @return either the z-code of the game or an empty array if no z-code was + * found. + */ + public static byte[] extractGame(byte[] image) { + int size = readInt(image, 4); + + // allow for 4 bytes "FORM" and 4 bytes size + int offset = 8; + if ((size + offset) != image.length) { + // size check failed! + return new byte[0]; + } + + if (image[offset] != 'I' || image[offset + 1] != 'F' + || image[offset + 2] != 'R' || image[offset + 3] != 'S') { + // IFRS chunk header not found! + return new byte[0]; + } + + offset += 4; + if (image[offset] != 'R' || image[offset + 1] != 'I' + || image[offset + 2] != 'd' || image[offset + 3] != 'x') { + // RIdx not found + return new byte[0]; + } + + offset += 4; + offset += 4; + int resources = readInt(image, offset); + offset += 4; + + // iterate through 12-byte long index entries + int start = -1; + int i; + for (i = 0; i < resources; i += 12) { + if (image[offset] == 'E' && image[offset + 1] == 'x' + && image[offset + 2] == 'e' && image[offset + 3] == 'c') { + int number = readInt(image, offset + 4); + if (number == 0) { + // start of ZCOD chunk, hopefully + start = readInt(image, offset + 8); + } + } + } + if (start > -1) { + // check "ZCOD", 4 bytes + if (image[start] == 'Z' && image[start + 1] == 'C' + && image[start + 2] == 'O' && image[start + 3] == 'D') { + int zlength = readInt(image, start + 4); + offset = start + 8; + byte[] result = new byte[zlength]; + for (i = 0; i < zlength; i++) { + result[i] = image[offset + i]; + } + return result; + } + } + return new byte[0]; + } }