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 | 
