Allow Android platforms to read from .apk files via the RWOPS interface.
Fixes Bugzilla #1261. Thanks to Tim Angus for the patch!
This commit is contained in:
parent
58faae483b
commit
247346d526
5 changed files with 276 additions and 2 deletions
|
@ -114,6 +114,10 @@ public class SDLActivity extends Activity {
|
||||||
mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
|
mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Context getContext() {
|
||||||
|
return mSingleton;
|
||||||
|
}
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
private static Object buf;
|
private static Object buf;
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,21 @@ typedef struct SDL_RWops
|
||||||
Uint32 type;
|
Uint32 type;
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
#ifdef __WIN32__
|
#if defined(ANDROID)
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
void *fileName;
|
||||||
|
void *fileNameRef;
|
||||||
|
void *inputStream;
|
||||||
|
void *inputStreamRef;
|
||||||
|
void *skipMethod;
|
||||||
|
void *readableByteChannel;
|
||||||
|
void *readableByteChannelRef;
|
||||||
|
void *readMethod;
|
||||||
|
long position;
|
||||||
|
int size;
|
||||||
|
} androidio;
|
||||||
|
#elif defined(__WIN32__)
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
SDL_bool append;
|
SDL_bool append;
|
||||||
|
@ -95,6 +109,7 @@ typedef struct SDL_RWops
|
||||||
} buffer;
|
} buffer;
|
||||||
} windowsio;
|
} windowsio;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_STDIO_H
|
#ifdef HAVE_STDIO_H
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
|
|
@ -259,4 +259,234 @@ extern "C" void Android_JNI_CloseAudioDevice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int Android_JNI_FileOpen(SDL_RWops* ctx)
|
||||||
|
{
|
||||||
|
jstring fileNameJString = (jstring)ctx->hidden.androidio.fileName;
|
||||||
|
|
||||||
|
// context = SDLActivity.getContext();
|
||||||
|
jmethodID mid = mEnv->GetStaticMethodID(mActivityClass,
|
||||||
|
"getContext","()Landroid/content/Context;");
|
||||||
|
jobject context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
|
||||||
|
|
||||||
|
// assetManager = context.getAssets();
|
||||||
|
mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
|
||||||
|
"getAssets","()Landroid/content/res/AssetManager;");
|
||||||
|
jobject assetManager = mEnv->CallObjectMethod(context, mid);
|
||||||
|
|
||||||
|
// inputStream = assetManager.open(<filename>);
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
|
||||||
|
"open", "(Ljava/lang/String;)Ljava/io/InputStream;");
|
||||||
|
jobject inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
|
||||||
|
if (mEnv->ExceptionOccurred()) {
|
||||||
|
mEnv->ExceptionDescribe();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
ctx->hidden.androidio.inputStream = inputStream;
|
||||||
|
ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store .skip id for seeking purposes
|
||||||
|
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||||
|
"skip", "(J)J");
|
||||||
|
ctx->hidden.androidio.skipMethod = mid;
|
||||||
|
|
||||||
|
// Despite all the visible documentation on [Asset]InputStream claiming
|
||||||
|
// that the .available() method is not guaranteed to return the entire file
|
||||||
|
// size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
|
||||||
|
// android/apis/content/ReadAsset.java imply that Android's
|
||||||
|
// AssetInputStream.available() /will/ always return the total file size
|
||||||
|
|
||||||
|
// size = inputStream.available();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||||
|
"available", "()I");
|
||||||
|
ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
|
||||||
|
if (mEnv->ExceptionOccurred()) {
|
||||||
|
mEnv->ExceptionDescribe();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// readableByteChannel = Channels.newChannel(inputStream);
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
jclass channels = mEnv->FindClass("java/nio/channels/Channels");
|
||||||
|
mid = mEnv->GetStaticMethodID(channels,
|
||||||
|
"newChannel",
|
||||||
|
"(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
|
||||||
|
jobject readableByteChannel = mEnv->CallStaticObjectMethod(
|
||||||
|
channels, mid, inputStream);
|
||||||
|
if (mEnv->ExceptionOccurred()) {
|
||||||
|
mEnv->ExceptionDescribe();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
ctx->hidden.androidio.readableByteChannel = readableByteChannel;
|
||||||
|
ctx->hidden.androidio.readableByteChannelRef =
|
||||||
|
mEnv->NewGlobalRef(readableByteChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store .read id for reading purposes
|
||||||
|
mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
|
||||||
|
"read", "(Ljava/nio/ByteBuffer;)I");
|
||||||
|
ctx->hidden.androidio.readMethod = mid;
|
||||||
|
|
||||||
|
ctx->hidden.androidio.position = 0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
|
||||||
|
const char* fileName, const char*)
|
||||||
|
{
|
||||||
|
if (!ctx) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring fileNameJString = mEnv->NewStringUTF(fileName);
|
||||||
|
ctx->hidden.androidio.fileName = fileNameJString;
|
||||||
|
ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
|
||||||
|
|
||||||
|
return Android_JNI_FileOpen(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
|
||||||
|
size_t size, size_t maxnum)
|
||||||
|
{
|
||||||
|
int bytesRemaining = size * maxnum;
|
||||||
|
int bytesRead = 0;
|
||||||
|
|
||||||
|
jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
|
||||||
|
jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
|
||||||
|
jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
|
||||||
|
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
while (bytesRemaining > 0) {
|
||||||
|
// result = readableByteChannel.read(...);
|
||||||
|
int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
|
||||||
|
|
||||||
|
if (mEnv->ExceptionOccurred()) {
|
||||||
|
mEnv->ExceptionDescribe();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRemaining -= result;
|
||||||
|
bytesRead += result;
|
||||||
|
ctx->hidden.androidio.position += result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesRead / size;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
|
||||||
|
size_t size, size_t num)
|
||||||
|
{
|
||||||
|
SDL_SetError("Cannot write to Android package filesystem");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (ctx) {
|
||||||
|
if (release) {
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
|
||||||
|
|
||||||
|
// inputStream.close();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||||
|
"close", "()V");
|
||||||
|
mEnv->CallVoidMethod(inputStream, mid);
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
|
||||||
|
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
|
||||||
|
if (mEnv->ExceptionOccurred()) {
|
||||||
|
result = -1;
|
||||||
|
mEnv->ExceptionDescribe();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (release) {
|
||||||
|
SDL_FreeRW(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
|
||||||
|
{
|
||||||
|
long newPosition;
|
||||||
|
|
||||||
|
switch (whence) {
|
||||||
|
case RW_SEEK_SET:
|
||||||
|
newPosition = offset;
|
||||||
|
break;
|
||||||
|
case RW_SEEK_CUR:
|
||||||
|
newPosition = ctx->hidden.androidio.position + offset;
|
||||||
|
break;
|
||||||
|
case RW_SEEK_END:
|
||||||
|
newPosition = ctx->hidden.androidio.size + offset;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDL_SetError("Unknown value for 'whence'");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (newPosition < 0) {
|
||||||
|
newPosition = 0;
|
||||||
|
}
|
||||||
|
if (newPosition > ctx->hidden.androidio.size) {
|
||||||
|
newPosition = ctx->hidden.androidio.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
long movement = newPosition - ctx->hidden.androidio.position;
|
||||||
|
jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
|
||||||
|
jmethodID skipMethod = (jmethodID)ctx->hidden.androidio.skipMethod;
|
||||||
|
|
||||||
|
if (movement > 0) {
|
||||||
|
// The easy case where we're seeking forwards
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
while (movement > 0) {
|
||||||
|
// inputStream.skip(...);
|
||||||
|
movement -= mEnv->CallLongMethod(inputStream, skipMethod, movement);
|
||||||
|
if (mEnv->ExceptionOccurred()) {
|
||||||
|
mEnv->ExceptionDescribe();
|
||||||
|
mEnv->ExceptionClear();
|
||||||
|
SDL_SetError("Exception while seeking");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (movement < 0) {
|
||||||
|
// We can't seek backwards so we have to reopen the file and seek
|
||||||
|
// forwards which obviously isn't very efficient
|
||||||
|
Android_JNI_FileClose(ctx, false);
|
||||||
|
Android_JNI_FileOpen(ctx);
|
||||||
|
Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->hidden.androidio.position = newPosition;
|
||||||
|
|
||||||
|
return ctx->hidden.androidio.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
|
||||||
|
{
|
||||||
|
return Android_JNI_FileClose(ctx, true);
|
||||||
|
}
|
||||||
|
|
||||||
/* vi: set ts=4 sw=4 expandtab: */
|
/* vi: set ts=4 sw=4 expandtab: */
|
||||||
|
|
|
@ -39,6 +39,14 @@ extern void* Android_JNI_GetAudioBuffer();
|
||||||
extern void Android_JNI_WriteAudioBuffer();
|
extern void Android_JNI_WriteAudioBuffer();
|
||||||
extern void Android_JNI_CloseAudioDevice();
|
extern void Android_JNI_CloseAudioDevice();
|
||||||
|
|
||||||
|
#include "SDL_rwops.h"
|
||||||
|
|
||||||
|
int Android_JNI_FileOpen(SDL_RWops* ctx, const char* fileName, const char* mode);
|
||||||
|
long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence);
|
||||||
|
size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer, size_t size, size_t maxnum);
|
||||||
|
size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer, size_t size, size_t num);
|
||||||
|
int Android_JNI_FileClose(SDL_RWops* ctx);
|
||||||
|
|
||||||
/* Ends C function definitions when using C++ */
|
/* Ends C function definitions when using C++ */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
/* *INDENT-OFF* */
|
/* *INDENT-OFF* */
|
||||||
|
|
|
@ -31,6 +31,10 @@
|
||||||
#include "cocoa/SDL_rwopsbundlesupport.h"
|
#include "cocoa/SDL_rwopsbundlesupport.h"
|
||||||
#endif /* __APPLE__ */
|
#endif /* __APPLE__ */
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#include "../core/android/SDL_android.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __NDS__
|
#ifdef __NDS__
|
||||||
/* include libfat headers for fatInitDefault(). */
|
/* include libfat headers for fatInitDefault(). */
|
||||||
#include <fat.h>
|
#include <fat.h>
|
||||||
|
@ -441,7 +445,20 @@ SDL_RWFromFile(const char *file, const char *mode)
|
||||||
SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
|
SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#if defined(__WIN32__)
|
#if defined(ANDROID)
|
||||||
|
rwops = SDL_AllocRW();
|
||||||
|
if (!rwops)
|
||||||
|
return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
|
||||||
|
if (Android_JNI_FileOpen(rwops, file, mode) < 0) {
|
||||||
|
SDL_FreeRW(rwops);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
rwops->seek = Android_JNI_FileSeek;
|
||||||
|
rwops->read = Android_JNI_FileRead;
|
||||||
|
rwops->write = Android_JNI_FileWrite;
|
||||||
|
rwops->close = Android_JNI_FileClose;
|
||||||
|
|
||||||
|
#elif defined(__WIN32__)
|
||||||
rwops = SDL_AllocRW();
|
rwops = SDL_AllocRW();
|
||||||
if (!rwops)
|
if (!rwops)
|
||||||
return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
|
return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue