diff options
Diffstat (limited to 'backends/platform/android/asset-archive.cpp')
-rw-r--r-- | backends/platform/android/asset-archive.cpp | 414 |
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 |