aboutsummaryrefslogtreecommitdiff
path: root/backends/platform/android/asset-archive.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backends/platform/android/asset-archive.cpp')
-rw-r--r--backends/platform/android/asset-archive.cpp414
1 files changed, 414 insertions, 0 deletions
diff --git a/backends/platform/android/asset-archive.cpp b/backends/platform/android/asset-archive.cpp
new file mode 100644
index 0000000000..20c6a653c0
--- /dev/null
+++ b/backends/platform/android/asset-archive.cpp
@@ -0,0 +1,414 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#if defined(ANDROID)
+
+#include <jni.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common/str.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/archive.h"
+#include "common/debug.h"
+
+#include "backends/platform/android/asset-archive.h"
+
+extern JNIEnv* JNU_GetEnv();
+
+// Must match android.content.res.AssetManager.ACCESS_*
+const jint ACCESS_UNKNOWN = 0;
+const jint ACCESS_RANDOM = 1;
+
+// This might be useful to someone else. Assumes markSupported() == true.
+class JavaInputStream : public Common::SeekableReadStream {
+public:
+ JavaInputStream(JNIEnv* env, jobject is);
+ virtual ~JavaInputStream();
+ virtual bool eos() const { return _eos; }
+ virtual bool err() const { return _err; }
+ virtual void clearErr() { _eos = _err = false; }
+ virtual uint32 read(void *dataPtr, uint32 dataSize);
+ virtual int32 pos() const { return _pos; }
+ virtual int32 size() const { return _len; }
+ virtual bool seek(int32 offset, int whence = SEEK_SET);
+private:
+ void close(JNIEnv* env);
+ jmethodID MID_mark;
+ jmethodID MID_available;
+ jmethodID MID_close;
+ jmethodID MID_read;
+ jmethodID MID_reset;
+ jmethodID MID_skip;
+ jobject _input_stream;
+ jsize _buflen;
+ jbyteArray _buf;
+ uint32 _pos;
+ jint _len;
+ bool _eos;
+ bool _err;
+};
+
+JavaInputStream::JavaInputStream(JNIEnv* env, jobject is) :
+ _eos(false), _err(false), _pos(0)
+{
+ _input_stream = env->NewGlobalRef(is);
+ _buflen = 8192;
+ _buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen)));
+
+ jclass cls = env->GetObjectClass(_input_stream);
+ MID_mark = env->GetMethodID(cls, "mark", "(I)V");
+ assert(MID_mark);
+ MID_available = env->GetMethodID(cls, "available", "()I");
+ assert(MID_mark);
+ MID_close = env->GetMethodID(cls, "close", "()V");
+ assert(MID_close);
+ MID_read = env->GetMethodID(cls, "read", "([BII)I");
+ assert(MID_read);
+ MID_reset = env->GetMethodID(cls, "reset", "()V");
+ assert(MID_reset);
+ MID_skip = env->GetMethodID(cls, "skip", "(J)J");
+ assert(MID_skip);
+
+ // Mark start of stream, so we can reset back to it.
+ // readlimit is set to something bigger than anything we might
+ // want to seek within.
+ env->CallVoidMethod(_input_stream, MID_mark, 10*1024*1024);
+ _len = env->CallIntMethod(_input_stream, MID_available);
+}
+
+JavaInputStream::~JavaInputStream() {
+ JNIEnv* env = JNU_GetEnv();
+ close(env);
+ env->DeleteGlobalRef(_buf);
+ env->DeleteGlobalRef(_input_stream);
+}
+
+void JavaInputStream::close(JNIEnv* env) {
+ env->CallVoidMethod(_input_stream, MID_close);
+ if (env->ExceptionCheck())
+ env->ExceptionClear();
+}
+
+uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) {
+ JNIEnv* env = JNU_GetEnv();
+
+ if (_buflen < dataSize) {
+ _buflen = dataSize;
+ env->DeleteGlobalRef(_buf);
+ _buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen)));
+ }
+
+ jint ret = env->CallIntMethod(_input_stream, MID_read, _buf, 0, dataSize);
+ if (env->ExceptionCheck()) {
+ warning("Exception during JavaInputStream::read(%p, %d)",
+ dataPtr, dataSize);
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ _err = true;
+ ret = -1;
+ } else if (ret == -1) {
+ _eos = true;
+ ret = 0;
+ } else {
+ env->GetByteArrayRegion(_buf, 0, ret, static_cast<jbyte*>(dataPtr));
+ _pos += ret;
+ }
+ return ret;
+}
+
+bool JavaInputStream::seek(int32 offset, int whence) {
+ JNIEnv* env = JNU_GetEnv();
+ uint32 newpos;
+ switch (whence) {
+ case SEEK_SET:
+ newpos = offset;
+ break;
+ case SEEK_CUR:
+ newpos = _pos + offset;
+ break;
+ case SEEK_END:
+ newpos = _len + offset;
+ break;
+ default:
+ debug("Unknown 'whence' arg %d", whence);
+ return false;
+ }
+
+ jlong skip_bytes;
+ if (newpos > _pos) {
+ skip_bytes = newpos - _pos;
+ } else {
+ // Can't skip backwards, so jump back to start and skip from there.
+ env->CallVoidMethod(_input_stream, MID_reset);
+ if (env->ExceptionCheck()) {
+ warning("Failed to rewind to start of asset stream");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return false;
+ }
+ _pos = 0;
+ skip_bytes = newpos;
+ }
+
+ while (skip_bytes > 0) {
+ jlong ret = env->CallLongMethod(_input_stream, MID_skip, skip_bytes);
+ if (env->ExceptionCheck()) {
+ warning("Failed to skip %ld bytes into asset stream",
+ static_cast<long>(skip_bytes));
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return false;
+ } else if (ret == 0) {
+ warning("InputStream->skip(%ld) didn't skip any bytes. Aborting seek.",
+ static_cast<long>(skip_bytes));
+ return false; // No point looping forever...
+ }
+ _pos += ret;
+ skip_bytes -= ret;
+ }
+ _eos = false;
+ return true;
+}
+
+
+// Must match android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH
+const jlong UNKNOWN_LENGTH = -1;
+
+// Reading directly from a fd is so much more efficient, that it is
+// worth optimising for.
+class AssetFdReadStream : public Common::SeekableReadStream {
+public:
+ AssetFdReadStream(JNIEnv* env, jobject assetfd);
+ virtual ~AssetFdReadStream();
+ virtual bool eos() const { return _eos; }
+ virtual bool err() const { return _err; }
+ virtual void clearErr() { _eos = _err = false; }
+ virtual uint32 read(void *dataPtr, uint32 dataSize);
+ virtual int32 pos() const { return _pos; }
+ virtual int32 size() const { return _declared_len; }
+ virtual bool seek(int32 offset, int whence = SEEK_SET);
+private:
+ void close(JNIEnv* env);
+ int _fd;
+ jmethodID MID_close;
+ jobject _assetfd;
+ jlong _start_off;
+ jlong _declared_len;
+ uint32 _pos;
+ bool _eos;
+ bool _err;
+};
+
+AssetFdReadStream::AssetFdReadStream(JNIEnv* env, jobject assetfd) :
+ _eos(false), _err(false), _pos(0)
+{
+ _assetfd = env->NewGlobalRef(assetfd);
+
+ jclass cls = env->GetObjectClass(_assetfd);
+ MID_close = env->GetMethodID(cls, "close", "()V");
+ assert(MID_close);
+
+ jmethodID MID_getStartOffset =
+ env->GetMethodID(cls, "getStartOffset", "()J");
+ assert(MID_getStartOffset);
+ _start_off = env->CallLongMethod(_assetfd, MID_getStartOffset);
+
+ jmethodID MID_getDeclaredLength =
+ env->GetMethodID(cls, "getDeclaredLength", "()J");
+ assert(MID_getDeclaredLength);
+ _declared_len = env->CallLongMethod(_assetfd, MID_getDeclaredLength);
+
+ jmethodID MID_getFileDescriptor =
+ env->GetMethodID(cls, "getFileDescriptor", "()Ljava/io/FileDescriptor;");
+ assert(MID_getFileDescriptor);
+ jobject javafd = env->CallObjectMethod(_assetfd, MID_getFileDescriptor);
+ assert(javafd);
+ jclass fd_cls = env->GetObjectClass(javafd);
+ jfieldID FID_descriptor = env->GetFieldID(fd_cls, "descriptor", "I");
+ assert(FID_descriptor);
+ _fd = env->GetIntField(javafd, FID_descriptor);
+}
+
+AssetFdReadStream::~AssetFdReadStream() {
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_assetfd, MID_close);
+ if (env->ExceptionCheck())
+ env->ExceptionClear();
+ env->DeleteGlobalRef(_assetfd);
+}
+
+uint32 AssetFdReadStream::read(void *dataPtr, uint32 dataSize) {
+ if (_declared_len != UNKNOWN_LENGTH) {
+ jlong cap = _declared_len - _pos;
+ if (dataSize > cap)
+ dataSize = cap;
+ }
+ int ret = ::read(_fd, dataPtr, dataSize);
+ if (ret == 0)
+ _eos = true;
+ else if (ret == -1)
+ _err = true;
+ else
+ _pos += ret;
+ return ret;
+}
+
+bool AssetFdReadStream::seek(int32 offset, int whence) {
+ if (whence == SEEK_SET) {
+ if (_declared_len != UNKNOWN_LENGTH && offset > _declared_len)
+ offset = _declared_len;
+ offset += _start_off;
+ } else if (whence == SEEK_END && _declared_len != UNKNOWN_LENGTH) {
+ whence = SEEK_SET;
+ offset = _start_off + _declared_len + offset;
+ }
+ int ret = lseek(_fd, offset, whence);
+ if (ret == -1)
+ return false;
+ _pos = ret - _start_off;
+ _eos = false;
+ return true;
+}
+
+AndroidAssetArchive::AndroidAssetArchive(jobject am) {
+ JNIEnv* env = JNU_GetEnv();
+ _am = env->NewGlobalRef(am);
+
+ jclass cls = env->GetObjectClass(_am);
+ MID_open = env->GetMethodID(cls, "open",
+ "(Ljava/lang/String;I)Ljava/io/InputStream;");
+ assert(MID_open);
+ MID_openFd = env->GetMethodID(cls, "openFd",
+ "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
+ assert(MID_openFd);
+ MID_list = env->GetMethodID(cls, "list",
+ "(Ljava/lang/String;)[Ljava/lang/String;");
+ assert(MID_list);
+}
+
+AndroidAssetArchive::~AndroidAssetArchive() {
+ JNIEnv* env = JNU_GetEnv();
+ env->DeleteGlobalRef(_am);
+}
+
+bool AndroidAssetArchive::hasFile(const Common::String &name) {
+ JNIEnv* env = JNU_GetEnv();
+ jstring path = env->NewStringUTF(name.c_str());
+ jobject result = env->CallObjectMethod(_am, MID_open, path, ACCESS_UNKNOWN);
+ if (env->ExceptionCheck()) {
+ // Assume FileNotFoundException
+ //warning("Error while calling AssetManager->open(%s)", name.c_str());
+ //env->ExceptionDescribe();
+ env->ExceptionClear();
+ env->DeleteLocalRef(path);
+ return false;
+ }
+ env->DeleteLocalRef(result);
+ env->DeleteLocalRef(path);
+ return true;
+}
+
+int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) {
+ JNIEnv* env = JNU_GetEnv();
+ Common::List<Common::String> dirlist;
+ dirlist.push_back("");
+
+ int count = 0;
+ while (!dirlist.empty()) {
+ const Common::String dir = dirlist.back();
+ dirlist.pop_back();
+
+ jstring jpath = env->NewStringUTF(dir.c_str());
+ jobjectArray jpathlist = static_cast<jobjectArray>(env->CallObjectMethod(_am, MID_list, jpath));
+ if (env->ExceptionCheck()) {
+ warning("Error while calling AssetManager->list(%s). Ignoring.",
+ dir.c_str());
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ continue; // May as well keep going ...
+ }
+ env->DeleteLocalRef(jpath);
+
+ for (jsize i = 0; i < env->GetArrayLength(jpathlist); ++i) {
+ jstring elem = (jstring)env->GetObjectArrayElement(jpathlist, i);
+ const char* p = env->GetStringUTFChars(elem, NULL);
+ Common::String thispath = dir;
+ if (!thispath.empty())
+ thispath += "/";
+ thispath += p;
+
+ // Assume files have a . in them, and directories don't
+ if (strchr(p, '.')) {
+ member_list.push_back(getMember(thispath));
+ ++count;
+ } else
+ dirlist.push_back(thispath);
+
+ env->ReleaseStringUTFChars(elem, p);
+ env->DeleteLocalRef(elem);
+ }
+
+ env->DeleteLocalRef(jpathlist);
+ }
+
+ return count;
+}
+
+Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::String &name) {
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const {
+ JNIEnv* env = JNU_GetEnv();
+ jstring jpath = env->NewStringUTF(path.c_str());
+
+ // Try openFd() first ...
+ jobject afd = env->CallObjectMethod(_am, MID_openFd, jpath);
+ if (env->ExceptionCheck())
+ env->ExceptionClear();
+ else if (afd != NULL) {
+ // success :)
+ env->DeleteLocalRef(jpath);
+ return new AssetFdReadStream(env, afd);
+ }
+
+ // ... and fallback to normal open() if that doesn't work
+ jobject is = env->CallObjectMethod(_am, MID_open, jpath, ACCESS_RANDOM);
+ if (env->ExceptionCheck()) {
+ // Assume FileNotFoundException
+ //warning("Error opening %s", path.c_str());
+ //env->ExceptionDescribe();
+ env->ExceptionClear();
+ env->DeleteLocalRef(jpath);
+ return NULL;
+ }
+
+ return new JavaInputStream(env, is);
+}
+
+#endif