aboutsummaryrefslogtreecommitdiff
path: root/backends
diff options
context:
space:
mode:
authorAlejandro Marzini2010-06-08 03:31:27 +0000
committerAlejandro Marzini2010-06-08 03:31:27 +0000
commit7ea78b10364d34ae607a9a1da00e4d42ad691aa1 (patch)
treec16c3d224b2ad1179dbf3e7c54b5c0212b0581a4 /backends
parent7e9d54a69a3444f5335b8cf6ecabdeffe2830644 (diff)
parentea2e2053f25c216342c74bb7a74dabc682766720 (diff)
downloadscummvm-rg350-7ea78b10364d34ae607a9a1da00e4d42ad691aa1.tar.gz
scummvm-rg350-7ea78b10364d34ae607a9a1da00e4d42ad691aa1.tar.bz2
scummvm-rg350-7ea78b10364d34ae607a9a1da00e4d42ad691aa1.zip
Merged from trunk.
svn-id: r49499
Diffstat (limited to 'backends')
-rw-r--r--backends/fs/psp/psp-stream.cpp294
-rw-r--r--backends/fs/psp/psp-stream.h32
-rw-r--r--backends/graphics/sdl/sdl-graphics.cpp1
-rw-r--r--backends/platform/android/README.build84
-rw-r--r--backends/platform/android/android.cpp1413
-rw-r--r--backends/platform/android/android.mk52
-rw-r--r--backends/platform/android/asset-archive.cpp414
-rw-r--r--backends/platform/android/asset-archive.h53
-rw-r--r--backends/platform/android/module.mk85
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java59
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/Event.java330
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java52
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/ScummVM.java317
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java446
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java29
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/Unpacker.java370
-rw-r--r--backends/platform/android/scummvm-android-themeengine.patch135
-rw-r--r--backends/platform/ds/arm7/source/main.cpp4
-rw-r--r--backends/platform/ds/arm9/dist/readme_ds.txt6
-rw-r--r--backends/platform/ds/arm9/makefile2
-rw-r--r--backends/platform/ds/arm9/source/dsmain.cpp7
-rw-r--r--backends/platform/ds/commoninclude/NDS/scummvm_ipc.h12
-rw-r--r--backends/platform/gp2x/gp2x-common.h31
-rw-r--r--backends/platform/gp2x/gp2x.cpp38
-rw-r--r--backends/platform/gp2x/graphics.cpp142
-rwxr-xr-xbackends/platform/gp2xwiz/build/build.sh4
-rwxr-xr-xbackends/platform/gp2xwiz/build/bundle.sh6
-rwxr-xr-xbackends/platform/gp2xwiz/build/clean.sh2
-rwxr-xr-xbackends/platform/gp2xwiz/build/config.sh2
-rw-r--r--backends/platform/gp2xwiz/gp2xwiz-events.cpp1
-rw-r--r--backends/platform/gp2xwiz/gp2xwiz-graphics.cpp10
-rw-r--r--backends/platform/iphone/iphone_common.h1
-rw-r--r--backends/platform/iphone/iphone_video.h2
-rw-r--r--backends/platform/iphone/iphone_video.m6
-rw-r--r--backends/platform/iphone/osys_main.cpp3
-rw-r--r--backends/platform/iphone/osys_video.cpp11
-rw-r--r--backends/platform/linuxmoto/linuxmoto-graphics.cpp10
-rw-r--r--backends/platform/ps2/systemps2.cpp17
-rw-r--r--backends/platform/ps2/systemps2.h6
-rw-r--r--backends/platform/psp/Makefile6
-rw-r--r--backends/platform/psp/display_client.cpp15
-rw-r--r--backends/platform/psp/module.mk3
-rw-r--r--backends/platform/psp/mp3.cpp487
-rw-r--r--backends/platform/psp/mp3.h121
-rw-r--r--backends/platform/psp/osys_psp.cpp3
-rw-r--r--backends/platform/psp/osys_psp.h2
-rw-r--r--backends/platform/psp/psp.spec2
-rw-r--r--backends/platform/psp/thread.cpp41
-rw-r--r--backends/platform/psp/thread.h16
-rw-r--r--backends/platform/samsungtv/samsungtv.cpp9
-rw-r--r--backends/platform/symbian/src/SymbianOS.cpp1
-rw-r--r--backends/platform/wince/wince-sdl.cpp61
52 files changed, 4847 insertions, 409 deletions
diff --git a/backends/fs/psp/psp-stream.cpp b/backends/fs/psp/psp-stream.cpp
index 8cb7dfea17..83ff095aa8 100644
--- a/backends/fs/psp/psp-stream.cpp
+++ b/backends/fs/psp/psp-stream.cpp
@@ -24,6 +24,8 @@
*/
#ifdef __PSP__
+#include <pspiofilemgr_stat.h>
+#include <pspiofilemgr.h>
#include <SDL/SDL_thread.h>
#include <SDL/SDL_mutex.h>
@@ -32,28 +34,48 @@
#include <errno.h>
-//#define __PSP_PRINT_TO_FILE__
-//#define __PSP_DEBUG_FUNCS__ /* For debugging function calls */
+#define MIN2(a,b) ((a < b) ? a : b)
+#define MIN3(a,b,c) ( (a < b) ? (a < c ? a : c) : (b < c ? b : c) )
+
+//#define __PSP_PRINT_TO_FILE__ /* For debugging suspend stuff, we have no screen output */
+//#define __PSP_DEBUG_FUNCS__ /* For debugging function calls */
//#define __PSP_DEBUG_PRINT__ /* For debug printouts */
+
#include "backends/platform/psp/trace.h"
+//#define DEBUG_BUFFERS /* to see the contents of the buffers being read */
+
+#ifdef DEBUG_BUFFERS
+void printBuffer(byte *ptr, uint32 len) {
+ uint32 printLen = len <= 10 ? len : 10;
+
+ for (int i = 0; i < printLen; i++) {
+ PSP_INFO_PRINT("%x ", ptr[i]);
+ }
+
+ if (len > 10) {
+ PSP_INFO_PRINT("... ");
+ for (int i = len - 10; i < len; i++)
+ PSP_INFO_PRINT("%x ", ptr[i]);
+ }
+
+ PSP_INFO_PRINT("\n");
+}
+#endif
+
+
PSPIoStream::PSPIoStream(const Common::String &path, bool writeMode)
- : StdioStream((void *)1), _path(path), _writeMode(writeMode) {
+ : StdioStream((void *)1), _path(path), _writeMode(writeMode),
+ _ferror(false), _pos(0),
+ _physicalPos(0), _fileSize(0), _inCache(false), _eos(false),
+ _cacheStartOffset(-1), _cache(0),
+ _errorSuspend(0), _errorSource(0),
+ _errorPos(0), _errorHandle(0), _suspendCount(0) {
DEBUG_ENTER_FUNC();
- assert(!path.empty());
+ // assert(!path.empty()); // do we need this?
_handle = (void *)0; // Need to do this since base class asserts not 0.
- _ferror = false;
- _feof = false;
- _pos = 0;
-
- /* for error checking */
- _errorSuspend = 0;
- _errorSource = 0;
- _errorPos = 0;
- _errorHandle = 0;
- _suspendCount = 0;
}
PSPIoStream::~PSPIoStream() {
@@ -63,9 +85,12 @@ PSPIoStream::~PSPIoStream() {
PSP_DEBUG_PRINT_FUNC("Suspended\n");
PowerMan.unregisterSuspend(this); // Unregister with powermanager to be suspended
- // Must do this before fclose() or resume() will reopen.
+ // Must do this before fclose() or resume() will reopen.
- fclose((FILE *)_handle); // We don't need a critical section(?). Worst case, the handle gets closed on its own
+ fclose((FILE *)_handle); // We don't need a critical section. Worst case, the handle gets closed on its own
+
+ if (_cache)
+ free(_cache);
PowerMan.endCriticalSection();
}
@@ -82,6 +107,17 @@ void *PSPIoStream::open() {
_handle = fopen(_path.c_str(), _writeMode ? "wb" : "rb"); // open
+ if (_handle) {
+ // Get the file size. This way is much faster than going to the end of the file and back
+ SceIoStat stat;
+ sceIoGetstat(_path.c_str(), &stat);
+ _fileSize = *((uint32 *)(void *)&stat.st_size); // 4GB file is big enough for us
+ PSP_DEBUG_PRINT("%s filesize = %d\n", _path.c_str(), _fileSize);
+
+ // Allocate the cache
+ _cache = (char *)memalign(64, CACHE_SIZE);
+ }
+
PowerMan.registerSuspend(this); // Register with the powermanager to be suspended
PowerMan.endCriticalSection();
@@ -91,100 +127,183 @@ void *PSPIoStream::open() {
bool PSPIoStream::err() const {
DEBUG_ENTER_FUNC();
- if (_ferror)
- PSP_ERROR("mem_ferror[%d], source[%d], suspend error[%d], pos[%d], _errorPos[%d], _errorHandle[%p], suspendCount[%d]\n",
- _ferror, _errorSource, _errorSuspend, _pos, _errorPos, _errorHandle, _suspendCount);
+
+ if (_ferror) // We dump since no printing to screen with suspend
+ PSP_ERROR("mem_ferror[%d], source[%d], suspend error[%d], pos[%d], \
+ _errorPos[%d], _errorHandle[%p], suspendCount[%d]\n",
+ _ferror, _errorSource, _errorSuspend, _pos,
+ _errorPos, _errorHandle, _suspendCount);
return _ferror;
}
void PSPIoStream::clearErr() {
- _ferror = false; // Remove regular error bit
+ _ferror = false;
}
bool PSPIoStream::eos() const {
- return _feof;
+ return _eos;
}
int32 PSPIoStream::pos() const {
return _pos;
}
-
int32 PSPIoStream::size() const {
- DEBUG_ENTER_FUNC();
- if (PowerMan.beginCriticalSection() == PowerManager::Blocked)
- PSP_DEBUG_PRINT_FUNC("Suspended\n");
-
- fseek((FILE *)_handle, 0, SEEK_END);
- int32 length = ftell((FILE *)_handle);
- fseek((FILE *)_handle, _pos, SEEK_SET);
-
- if (_pos < 0 || length < 0) { // Check for errors
- _errorSource = 2;
- PSP_ERROR("pos[%d] or length[%d] < 0!\n", _pos, length);
- _ferror = true;
- length = -1; // If our oldPos is bad, we want length to be bad too to signal
- clearerr((FILE *)_handle);
- }
-
- PowerMan.endCriticalSection();
-
- return length;
+ return _fileSize;
}
bool PSPIoStream::seek(int32 offs, int whence) {
DEBUG_ENTER_FUNC();
-
- // Check if we can access the file
- if (PowerMan.beginCriticalSection() == PowerManager::Blocked)
- PSP_DEBUG_PRINT_FUNC("Suspended\n");
-
- int ret = fseek((FILE *)_handle, offs, whence);
-
- if (ret != 0) {
+ PSP_DEBUG_PRINT_FUNC("offset[0x%x], whence[%d], _pos[0x%x], _physPos[0x%x]\n", offs, whence, _pos, _physicalPos);
+ _eos = false;
+
+ int32 posToSearchFor = 0;
+ switch (whence) {
+ case SEEK_CUR:
+ posToSearchFor = _pos;
+ break;
+ case SEEK_END:
+ posToSearchFor = _fileSize; // unsure. Does it take us here or to EOS - 1?
+ break;
+ }
+ posToSearchFor += offs;
+
+ // Check for bad values
+ if (posToSearchFor < 0) {
_ferror = true;
- PSP_ERROR("fseek returned with [%d], non-zero\n", ret);
- clearerr((FILE *)_handle);
- _feof = feof((FILE *)_handle);
- _errorSource = 3;
- } else { // everything ok
- _feof = false; // Reset eof flag since we know it was ok
+ return false;
}
-
- _pos = ftell((FILE *)_handle); // update pos
-
- PowerMan.endCriticalSection();
-
- return (ret == 0);
+
+ if (posToSearchFor > _fileSize) {
+ _ferror = true;
+ _eos = true;
+ return false;
+ }
+
+ // See if we can find it in cache
+ if (isOffsetInCache(posToSearchFor)) {
+ PSP_DEBUG_PRINT("seek offset[0x%x] found in cache. Cache starts[0x%x]\n", posToSearchFor, _cacheStartOffset);
+ _inCache = true;
+ } else { // not in cache
+ _inCache = false;
+ }
+ _pos = posToSearchFor;
+ return true;
}
uint32 PSPIoStream::read(void *ptr, uint32 len) {
DEBUG_ENTER_FUNC();
- // Check if we can access the file
+ PSP_DEBUG_PRINT_FUNC("filename[%s], len[0x%x], ptr[%p]\n", _path.c_str(), len, ptr);
+
+ if (_ferror || _eos)
+ return 0;
+
+ byte *destPtr = (byte *)ptr;
+ uint32 lenFromFile = len; // how much we read from the actual file
+ uint32 lenFromCache = 0; // how much we read from cache
+ uint32 lenRemainingInFile = _fileSize - _pos;
+
+ if (lenFromFile > lenRemainingInFile) {
+ lenFromFile = lenRemainingInFile;
+ _eos = true;
+ }
+
+ // Are we in cache?
+ if (_inCache && isCacheValid()) {
+ uint32 offsetInCache = _pos - _cacheStartOffset;
+ // We can read at most what's in the cache or the remaining size of the file
+ lenFromCache = MIN2(lenFromFile, CACHE_SIZE - offsetInCache); // unsure
+
+ PSP_DEBUG_PRINT("reading 0x%x bytes from cache to %p. pos[0x%x] physPos[0x%x] cacheStart[0x%x]\n", lenFromCache, destPtr, _pos, _physicalPos, _cacheStartOffset);
+
+ memcpy(destPtr, &_cache[offsetInCache], lenFromCache);
+ _pos += lenFromCache;
+
+ if (lenFromCache < lenFromFile) { // there's more to copy from the file
+ lenFromFile -= lenFromCache;
+ lenRemainingInFile -= lenFromCache; // since we moved pos
+ destPtr += lenFromCache;
+ } else { // we're done
+#ifdef DEBUG_BUFFERS
+ printBuffer((byte *)ptr, len);
+#endif
+
+ return lenFromCache; // how much we actually read
+ }
+ }
+
if (PowerMan.beginCriticalSection() == PowerManager::Blocked)
PSP_DEBUG_PRINT_FUNC("Suspended\n");
-
- PSP_DEBUG_PRINT_FUNC("filename[%s], len[%d]\n", _path.c_str(), len);
-
- size_t ret = fread((byte *)ptr, 1, len, (FILE *)_handle);
-
- _pos += ret; // Update pos
-
- if (ret != len) { // Check for eof
- _feof = feof((FILE *)_handle);
- if (!_feof) { // It wasn't an eof. Must be an error
+
+
+ synchronizePhysicalPos(); // we need to update our physical position
+
+ if (lenFromFile <= MIN_READ_SIZE) { // We load the cache in case the read is small enough
+ // This optimization is based on the principle that reading 1 byte is as expensive as 1000 bytes
+ uint32 lenToCopyToCache = MIN2((uint32)MIN_READ_SIZE, lenRemainingInFile); // at most remaining file size
+
+ PSP_DEBUG_PRINT("filling cache with 0x%x bytes from physicalPos[0x%x]. cacheStart[0x%x], pos[0x%x], fileSize[0x%x]\n", lenToCopyToCache, _physicalPos, _cacheStartOffset, _pos, _fileSize);
+
+ size_t ret = fread(_cache, 1, lenToCopyToCache, (FILE *)_handle);
+ if (ret != lenToCopyToCache) {
+ PSP_ERROR("in filling cache, failed to get 0x%x bytes. Only got 0x%x\n", lenToCopyToCache, ret);
+ _ferror = true;
+ clearerr((FILE *)_handle);
+ }
+ _cacheStartOffset = _physicalPos;
+ _inCache = true;
+
+ _physicalPos += ret;
+
+ PSP_DEBUG_PRINT("copying 0x%x bytes from cache to %p\n", lenFromFile, destPtr);
+
+ // Copy to the destination buffer from cache
+ memcpy(destPtr, _cache, lenFromFile);
+ _pos += lenFromFile;
+
+ } else { // Too big for cache. No caching
+ PSP_DEBUG_PRINT("reading 0x%x bytes from file to %p. Pos[0x%x], physPos[0x%x]\n", lenFromFile, destPtr, _pos, _physicalPos);
+ size_t ret = fread(destPtr, 1, lenFromFile, (FILE *)_handle);
+
+ _physicalPos += ret; // Update pos
+ _pos = _physicalPos;
+
+ if (ret != lenFromFile) { // error
+ PSP_ERROR("fread returned [0x%x] instead of len[0x%x]\n", ret, lenFromFile);
_ferror = true;
clearerr((FILE *)_handle);
- _pos = ftell((FILE *)_handle); // Update our position
- _errorSource = 4;
- PSP_ERROR("fread returned ret[%d] instead of len[%d]\n", ret, len);
+ _errorSource = 4;
}
+ _inCache = false;
}
PowerMan.endCriticalSection();
- return ret;
+#ifdef DEBUG_BUFFERS
+ printBuffer((byte *)ptr, len);
+#endif
+
+ return lenFromCache + lenFromFile; // total of what was copied
+}
+
+// TODO: Test if seeking backwards/forwards has any effect on performance
+inline bool PSPIoStream::synchronizePhysicalPos() {
+ if (_pos != _physicalPos) {
+ if (fseek((FILE *)_handle, _pos - _physicalPos, SEEK_CUR) != 0)
+ return false;
+ _physicalPos = _pos;
+ }
+
+ return true;
+}
+
+inline bool PSPIoStream::isOffsetInCache(uint32 offset) {
+ if (_cacheStartOffset != -1 &&
+ offset >= (uint32)_cacheStartOffset &&
+ offset < (uint32)(_cacheStartOffset + CACHE_SIZE))
+ return true;
+ return false;
}
uint32 PSPIoStream::write(const void *ptr, uint32 len) {
@@ -193,18 +312,30 @@ uint32 PSPIoStream::write(const void *ptr, uint32 len) {
if (PowerMan.beginCriticalSection() == PowerManager::Blocked)
PSP_DEBUG_PRINT_FUNC("Suspended\n");
- PSP_DEBUG_PRINT_FUNC("filename[%s], len[%d]\n", _path.c_str(), len);
+ PSP_DEBUG_PRINT_FUNC("filename[%s], len[0x%x]\n", _path.c_str(), len);
+ if (_ferror)
+ return 0;
+
+ _eos = false; // we can't have eos with write
+ synchronizePhysicalPos();
+
size_t ret = fwrite(ptr, 1, len, (FILE *)_handle);
- _pos += ret;
+ // If we're making the file bigger, adjust the size
+ if (_physicalPos + (int)ret > _fileSize)
+ _fileSize = _physicalPos + ret;
+ _physicalPos += ret;
+ _pos = _physicalPos;
+ _inCache = false;
+ _cacheStartOffset = -1; // invalidate cache
if (ret != len) { // Set error
_ferror = true;
clearerr((FILE *)_handle);
_pos = ftell((FILE *)_handle); // Update pos
_errorSource = 5;
- PSP_ERROR("fwrite returned[%d] instead of len[%d]\n", ret, len);
+ PSP_ERROR("fwrite returned[0x%x] instead of len[0x%x]\n", ret, len);
}
PowerMan.endCriticalSection();
@@ -224,7 +355,7 @@ bool PSPIoStream::flush() {
_ferror = true;
clearerr((FILE *)_handle);
_errorSource = 6;
- PSP_ERROR("fflush returned ret[%u]\n", ret);
+ PSP_ERROR("fflush returned ret[%d]\n", ret);
}
PowerMan.endCriticalSection();
@@ -286,6 +417,9 @@ int PSPIoStream::resume() {
// Resume our previous position
if (_handle > 0 && _pos > 0) {
ret = fseek((FILE *)_handle, _pos, SEEK_SET);
+
+ _physicalPos = _pos;
+ _inCache = false;
if (ret != 0) { // Check for problem
_errorSuspend = ResumeError;
diff --git a/backends/fs/psp/psp-stream.h b/backends/fs/psp/psp-stream.h
index 673630b685..9fd1ad0470 100644
--- a/backends/fs/psp/psp-stream.h
+++ b/backends/fs/psp/psp-stream.h
@@ -35,25 +35,39 @@
*/
class PSPIoStream : public StdioStream, public Suspendable {
protected:
- Common::String _path; /* Need to maintain for reopening after suspend */
- bool _writeMode; /* "" */
- int _pos; /* "" */
- mutable int _ferror; /* Save file ferror */
- mutable bool _feof; /* and eof */
-
+ Common::String _path;
+ int _fileSize;
+ bool _writeMode; // for resuming in the right mode
+ int _physicalPos; // position in the real file
+ int _pos; // position. Sometimes virtual
+ bool _inCache; // whether we're in cache (virtual) mode
+ bool _eos; // EOS flag
+
enum {
SuspendError = 2,
ResumeError = 3
};
- int _errorSuspend;
+ enum {
+ CACHE_SIZE = 1024,
+ MIN_READ_SIZE = 1024 // reading less than 1024 takes exactly the same time as 1024
+ };
+
+ // For caching
+ char *_cache;
+ int _cacheStartOffset; // starting offset of the cache. -1 when cache is invalid
+
+ mutable int _ferror; // file error state
+ int _errorSuspend; // for debugging
mutable int _errorSource;
-
- // Error checking
int _errorPos;
void * _errorHandle;
int _suspendCount;
+ bool synchronizePhysicalPos(); // synchronize the physical and virtual positions
+ bool isOffsetInCache(uint32 pos); // check if an offset is found in cache
+ bool isCacheValid() { return _cacheStartOffset != -1; }
+
public:
/**
diff --git a/backends/graphics/sdl/sdl-graphics.cpp b/backends/graphics/sdl/sdl-graphics.cpp
index 295448811d..5276cbbf30 100644
--- a/backends/graphics/sdl/sdl-graphics.cpp
+++ b/backends/graphics/sdl/sdl-graphics.cpp
@@ -195,7 +195,6 @@ bool SdlGraphicsManager::hasFeature(OSystem::Feature f) {
return
(f == OSystem::kFeatureFullscreenMode) ||
(f == OSystem::kFeatureAspectRatioCorrection) ||
- (f == OSystem::kFeatureAutoComputeDirtyRects) ||
(f == OSystem::kFeatureCursorHasPalette) ||
(f == OSystem::kFeatureIconifyWindow);
}
diff --git a/backends/platform/android/README.build b/backends/platform/android/README.build
new file mode 100644
index 0000000000..fa56bfc180
--- /dev/null
+++ b/backends/platform/android/README.build
@@ -0,0 +1,84 @@
+Building the ScummVM Android port
+=================================
+
+You will need these things to build:
+1. Android EGL headers and library
+2. Android SDK
+3. An arm-android-eabi GCC toolchain
+
+In the example commands, we are going to build against the Android 1.5
+native ABI (but using the Android 1.6 SDK tools). Other version
+combinations might/should be possible with a bit of tweaking.
+
+In detail:
+
+1. Android EGL headers and library
+
+You can build these from the full Android source, but it is far easier
+to just download the 3 Android EGL headers from here:
+ http://android.git.kernel.org/?p=platform/frameworks/base.git;a=tree;f=opengl/include/EGL;hb=HEAD
+ (copy them to a directory called "EGL" somewhere)
+
+... and grab libEGL.so off an existing phone/emulator:
+ adb pull /system/lib/libEGL.so /tmp
+
+2. Android SDK
+
+Download and install somewhere.
+
+3. arm-android-eabi GCC toolchain
+
+You have several choices for toolchains:
+
+- Use Google arm-eabi prebuilt toolchain.
+
+This is shipped with both the Android source release and Android NDK.
+The problem is that "arm-eabi-gcc" can't actually link anything
+successfully without extra command line flags. To use this with the
+ScummVM configure/build environment you will need to create a family
+of shell wrapper scripts that convert "arm-android-eabi-foo" to
+"arm-eabi-foo -mandroid".
+
+For example, I use this script:
+ #!/bin/sh
+ exec arm-eabi-${0##*-} -mandroid -DANDROID "$@"
+
+... and create a family of symlinks/hardlinks pointing to it called
+arm-android-eabi-gcc, arm-android-eabi-g++, etc. For tools that don't
+take a "-mandroid" argument - like arm-eabi-strip - I bypass the shell
+wrapper and just create an arm-android-eabi-strip symlink to the tool
+directly.
+
+- Build your own arm-android-eabi toolchain from GCC source.
+
+This is lots of fun. I suggest my Android openembedded patches, see:
+ http://wiki.github.com/anguslees/openembedded-android/
+(You just need to have lots of disk space and type a few commands)
+If you get stuck, ask
+
+Alternatively, do a websearch - there are several other cross-compile
+toolchains around.
+
+
+Building ScummVM
+================
+
+ export ANDROID_SDK=<root of Android SDK>
+
+ PATH=$ANDROID_SDK/platforms/android-1.6/tools:$ANDROID_SDK/tools:$PATH
+ # You also want to ensure your arm-android-eabi toolchain is in your $PATH
+
+ export ANDROID_TOP=<root of built Android source>
+
+ EGL_INC="-I<location of EGL/ header directory>"
+ EGL_LIBS="-L<location of libEGL.so>"
+
+ CPPFLAGS="$EGL_INC" \
+ LDFLAGS="-g $EGL_LIBS" \
+ ./configure --backend=android --host=android --enable-zlib #and any other flags
+ make scummvm.apk
+
+This will build a "monolithic" ScummVM package, with the engines
+statically linked in. If you want to build separate engine packages,
+like on the market, add "--enable-plugins --default-dynamic" to
+configure and also make scummvm-engine-scumm.apk, etc.
diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
new file mode 100644
index 0000000000..a6258df554
--- /dev/null
+++ b/backends/platform/android/android.cpp
@@ -0,0 +1,1413 @@
+/* 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$
+ *
+ */
+
+#include "backends/base-backend.h"
+#include "base/main.h"
+#include "graphics/surface.h"
+
+#include "backends/platform/android/video.h"
+
+#if defined(ANDROID_BACKEND)
+
+#define ANDROID_VERSION_GE(major,minor) \
+ (ANDROID_MAJOR_VERSION > (major) || \
+ (ANDROID_MAJOR_VERSION == (major) && ANDROID_MINOR_VERSION >= (minor)))
+
+#include <jni.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#include <EGL/egl.h>
+#include <android/log.h>
+
+#include "common/archive.h"
+#include "common/util.h"
+#include "common/rect.h"
+#include "common/queue.h"
+#include "common/mutex.h"
+#include "common/events.h"
+#include "common/config-manager.h"
+
+#include "backends/fs/posix/posix-fs-factory.h"
+#include "backends/keymapper/keymapper.h"
+#include "backends/saves/default/default-saves.h"
+#include "backends/timer/default/default-timer.h"
+#include "backends/plugins/posix/posix-provider.h"
+#include "sound/mixer_intern.h"
+
+#include "backends/platform/android/asset-archive.h"
+
+#undef LOG_TAG
+#define LOG_TAG "ScummVM"
+
+#if 0
+#define ENTER(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, args)
+#else
+#define ENTER(args...) /**/
+#endif
+
+// Fix JNIEXPORT declaration to actually do something useful
+#undef JNIEXPORT
+#define JNIEXPORT __attribute__ ((visibility("default")))
+
+static JavaVM *cached_jvm;
+static jfieldID FID_Event_type;
+static jfieldID FID_Event_synthetic;
+static jfieldID FID_Event_kbd_keycode;
+static jfieldID FID_Event_kbd_ascii;
+static jfieldID FID_Event_kbd_flags;
+static jfieldID FID_Event_mouse_x;
+static jfieldID FID_Event_mouse_y;
+static jfieldID FID_Event_mouse_relative;
+static jfieldID FID_ScummVM_nativeScummVM;
+static jmethodID MID_Object_wait;
+
+JNIEnv* JNU_GetEnv() {
+ JNIEnv* env;
+ bool version_unsupported =
+ cached_jvm->GetEnv((void**)&env, JNI_VERSION_1_2);
+ assert(! version_unsupported);
+ return env;
+}
+
+static void JNU_ThrowByName(JNIEnv* env, const char* name, const char* msg) {
+ jclass cls = env->FindClass(name);
+ // if cls is NULL, an exception has already been thrown
+ if (cls != NULL)
+ env->ThrowNew(cls, msg);
+ env->DeleteLocalRef(cls);
+}
+
+// floating point. use sparingly.
+template <class T>
+static inline T scalef(T in, float numerator, float denominator) {
+ return static_cast<float>(in) * numerator / denominator;
+}
+
+static inline GLfixed xdiv(int numerator, int denominator) {
+ assert(numerator < (1<<16));
+ return (numerator << 16) / denominator;
+}
+
+#ifdef DYNAMIC_MODULES
+class AndroidPluginProvider : public POSIXPluginProvider {
+protected:
+ virtual void addCustomDirectories(Common::FSList &dirs) const;
+};
+#endif
+
+
+#if 0
+#define CHECK_GL_ERROR() checkGlError(__FILE__, __LINE__)
+static const char* getGlErrStr(GLenum error) {
+ switch (error) {
+ case GL_NO_ERROR: return "GL_NO_ERROR";
+ case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
+ case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
+ case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW";
+ case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW";
+ case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
+ }
+
+ static char buf[40];
+ snprintf(buf, sizeof(buf), "(Unknown GL error code 0x%x)", error);
+ return buf;
+}
+static void checkGlError(const char* file, int line) {
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR)
+ warning("%s:%d: GL error: %s", file, line, getGlErrStr(error));
+}
+#else
+#define CHECK_GL_ERROR() do {} while (false)
+#endif
+
+class OSystem_Android : public BaseBackend {
+private:
+ jobject _back_ptr; // back pointer to (java) peer instance
+ jmethodID MID_displayMessageOnOSD;
+ jmethodID MID_setWindowCaption;
+ jmethodID MID_initBackend;
+ jmethodID MID_audioSampleRate;
+ jmethodID MID_showVirtualKeyboard;
+ jmethodID MID_getSysArchives;
+ jmethodID MID_getPluginDirectories;
+ jmethodID MID_setupScummVMSurface;
+ jmethodID MID_destroyScummVMSurface;
+
+ int _screen_changeid;
+ EGLDisplay _egl_display;
+ EGLSurface _egl_surface;
+ EGLint _egl_surface_width;
+ EGLint _egl_surface_height;
+
+ bool _force_redraw;
+
+ // Game layer
+ GLESPaletteTexture* _game_texture;
+ int _shake_offset;
+ bool _full_screen_dirty;
+ Common::Array<Common::Rect> _dirty_rects;
+
+ // Overlay layer
+ GLES4444Texture* _overlay_texture;
+ bool _show_overlay;
+
+ // Mouse layer
+ GLESPaletteATexture* _mouse_texture;
+ Common::Point _mouse_hotspot;
+ int _mouse_targetscale;
+ bool _show_mouse;
+ bool _use_mouse_palette;
+
+ Common::Queue<Common::Event> _event_queue;
+ MutexRef _event_queue_lock;
+
+ bool _timer_thread_exit;
+ pthread_t _timer_thread;
+ static void* timerThreadFunc(void* arg);
+
+ bool _virtkeybd_on;
+
+ Common::SaveFileManager *_savefile;
+ Audio::MixerImpl *_mixer;
+ Common::TimerManager *_timer;
+ FilesystemFactory *_fsFactory;
+ Common::Archive *_asset_archive;
+ timeval _startTime;
+
+ void setupScummVMSurface();
+ void destroyScummVMSurface();
+ void setupKeymapper();
+ void _setCursorPalette(const byte *colors, uint start, uint num);
+
+public:
+ OSystem_Android(jobject am);
+ virtual ~OSystem_Android();
+ bool initJavaHooks(JNIEnv* env, jobject self);
+
+ static OSystem_Android* fromJavaObject(JNIEnv* env, jobject obj);
+ virtual void initBackend();
+ void addPluginDirectories(Common::FSList &dirs) const;
+
+ virtual bool hasFeature(Feature f);
+ virtual void setFeatureState(Feature f, bool enable);
+ virtual bool getFeatureState(Feature f);
+ virtual const GraphicsMode *getSupportedGraphicsModes() const;
+ virtual int getDefaultGraphicsMode() const;
+ bool setGraphicsMode(const char *name);
+ virtual bool setGraphicsMode(int mode);
+ virtual int getGraphicsMode() const;
+ virtual void initSize(uint width, uint height,
+ const Graphics::PixelFormat *format);
+ virtual int getScreenChangeID() const { return _screen_changeid; }
+ virtual int16 getHeight();
+ virtual int16 getWidth();
+ virtual void setPalette(const byte *colors, uint start, uint num);
+ virtual void grabPalette(byte *colors, uint start, uint num);
+ virtual void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h);
+ virtual void updateScreen();
+ virtual Graphics::Surface *lockScreen();
+ virtual void unlockScreen();
+ virtual void setShakePos(int shakeOffset);
+ virtual void fillScreen(uint32 col);
+ virtual void setFocusRectangle(const Common::Rect& rect);
+ virtual void clearFocusRectangle();
+
+ virtual void showOverlay();
+ virtual void hideOverlay();
+ virtual void clearOverlay();
+ virtual void grabOverlay(OverlayColor *buf, int pitch);
+ virtual void copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h);
+ virtual int16 getOverlayHeight();
+ virtual int16 getOverlayWidth();
+ virtual Graphics::PixelFormat getOverlayFormat() const {
+ // RGBA 4444
+ Graphics::PixelFormat format;
+ format.bytesPerPixel = 2;
+ format.rLoss = 8 - 4;
+ format.gLoss = 8 - 4;
+ format.bLoss = 8 - 4;
+ format.aLoss = 8 - 4;
+ format.rShift = 3*4;
+ format.gShift = 2*4;
+ format.bShift = 1*4;
+ format.aShift = 0*4;
+ return format;
+ }
+
+ virtual bool showMouse(bool visible);
+
+ virtual void warpMouse(int x, int y);
+ virtual void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format);
+ virtual void setCursorPalette(const byte *colors, uint start, uint num);
+ virtual void disableCursorPalette(bool disable);
+
+ virtual bool pollEvent(Common::Event &event);
+ void pushEvent(const Common::Event& event);
+ virtual uint32 getMillis();
+ virtual void delayMillis(uint msecs);
+
+ virtual MutexRef createMutex(void);
+ virtual void lockMutex(MutexRef mutex);
+ virtual void unlockMutex(MutexRef mutex);
+ virtual void deleteMutex(MutexRef mutex);
+
+ virtual void quit();
+
+ virtual void setWindowCaption(const char *caption);
+ virtual void displayMessageOnOSD(const char *msg);
+ virtual void showVirtualKeyboard(bool enable);
+
+ virtual Common::SaveFileManager *getSavefileManager();
+ virtual Audio::Mixer *getMixer();
+ virtual void getTimeAndDate(TimeDate &t) const;
+ virtual Common::TimerManager *getTimerManager();
+ virtual FilesystemFactory *getFilesystemFactory();
+ virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
+};
+
+OSystem_Android::OSystem_Android(jobject am)
+ : _back_ptr(0),
+ _egl_display(EGL_NO_DISPLAY),
+ _egl_surface(EGL_NO_SURFACE),
+ _screen_changeid(0),
+ _force_redraw(false),
+ _game_texture(NULL),
+ _overlay_texture(NULL),
+ _mouse_texture(NULL),
+ _use_mouse_palette(false),
+ _show_mouse(false),
+ _show_overlay(false),
+ _savefile(0),
+ _mixer(0),
+ _timer(0),
+ _fsFactory(new POSIXFilesystemFactory()),
+ _asset_archive(new AndroidAssetArchive(am)),
+ _shake_offset(0),
+ _full_screen_dirty(false),
+ _event_queue_lock(createMutex()) {
+}
+
+OSystem_Android::~OSystem_Android() {
+ ENTER("~OSystem_Android()");
+ delete _game_texture;
+ delete _overlay_texture;
+ delete _mouse_texture;
+ destroyScummVMSurface();
+ JNIEnv* env = JNU_GetEnv();
+ //env->DeleteWeakGlobalRef(_back_ptr);
+ env->DeleteGlobalRef(_back_ptr);
+ delete _savefile;
+ delete _mixer;
+ delete _timer;
+ delete _fsFactory;
+ delete _asset_archive;
+ deleteMutex(_event_queue_lock);
+}
+
+OSystem_Android* OSystem_Android::fromJavaObject(JNIEnv* env, jobject obj) {
+ jlong peer = env->GetLongField(obj, FID_ScummVM_nativeScummVM);
+ return (OSystem_Android*)peer;
+}
+
+bool OSystem_Android::initJavaHooks(JNIEnv* env, jobject self) {
+ // weak global ref to allow class to be unloaded
+ // ... except dalvik doesn't implement NewWeakGlobalRef (yet)
+ //_back_ptr = env->NewWeakGlobalRef(self);
+ _back_ptr = env->NewGlobalRef(self);
+
+ jclass cls = env->GetObjectClass(_back_ptr);
+
+#define FIND_METHOD(name, signature) do { \
+ MID_ ## name = env->GetMethodID(cls, #name, signature); \
+ if (MID_ ## name == NULL) \
+ return false; \
+ } while (0)
+
+ FIND_METHOD(setWindowCaption, "(Ljava/lang/String;)V");
+ FIND_METHOD(displayMessageOnOSD, "(Ljava/lang/String;)V");
+ FIND_METHOD(initBackend, "()V");
+ FIND_METHOD(audioSampleRate, "()I");
+ FIND_METHOD(showVirtualKeyboard, "(Z)V");
+ FIND_METHOD(getSysArchives, "()[Ljava/lang/String;");
+ FIND_METHOD(getPluginDirectories, "()[Ljava/lang/String;");
+ FIND_METHOD(setupScummVMSurface, "()V");
+ FIND_METHOD(destroyScummVMSurface, "()V");
+
+#undef FIND_METHOD
+
+ return true;
+}
+
+static void ScummVM_create(JNIEnv* env, jobject self, jobject am) {
+ OSystem_Android* cpp_obj = new OSystem_Android(am);
+ if (!cpp_obj->initJavaHooks(env, self))
+ // Exception already thrown by initJavaHooks
+ return;
+
+ env->SetLongField(self, FID_ScummVM_nativeScummVM, (jlong)cpp_obj);
+
+#ifdef DYNAMIC_MODULES
+ PluginManager::instance().addPluginProvider(new AndroidPluginProvider());
+#endif
+}
+
+static void ScummVM_nativeDestroy(JNIEnv* env, jobject self) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+ delete cpp_obj;
+}
+
+static void ScummVM_audioMixCallback(JNIEnv* env, jobject self,
+ jbyteArray jbuf) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+ jsize len = env->GetArrayLength(jbuf);
+ jbyte* buf = env->GetByteArrayElements(jbuf, NULL);
+ if (buf == NULL) {
+ warning("Unable to get Java audio byte array. Skipping.");
+ return;
+ }
+ Audio::MixerImpl* mixer =
+ static_cast<Audio::MixerImpl*>(cpp_obj->getMixer());
+ assert(mixer);
+ mixer->mixCallback(reinterpret_cast<byte*>(buf), len);
+ env->ReleaseByteArrayElements(jbuf, buf, 0);
+}
+
+static void ScummVM_setConfManInt(JNIEnv* env, jclass cls,
+ jstring key_obj, jint value) {
+ ENTER("setConfManInt(%p, %d)", key_obj, (int)value);
+ const char* key = env->GetStringUTFChars(key_obj, NULL);
+ if (key == NULL)
+ return;
+ ConfMan.setInt(key, value);
+ env->ReleaseStringUTFChars(key_obj, key);
+}
+
+static void ScummVM_setConfManString(JNIEnv* env, jclass cls, jstring key_obj,
+ jstring value_obj) {
+ ENTER("setConfManStr(%p, %p)", key_obj, value_obj);
+ const char* key = env->GetStringUTFChars(key_obj, NULL);
+ if (key == NULL)
+ return;
+ const char* value = env->GetStringUTFChars(value_obj, NULL);
+ if (value == NULL) {
+ env->ReleaseStringUTFChars(key_obj, key);
+ return;
+ }
+ ConfMan.set(key, value);
+ env->ReleaseStringUTFChars(value_obj, value);
+ env->ReleaseStringUTFChars(key_obj, key);
+}
+
+void* OSystem_Android::timerThreadFunc(void* arg) {
+ OSystem_Android* system = (OSystem_Android*)arg;
+ DefaultTimerManager* timer = (DefaultTimerManager*)(system->_timer);
+
+ struct timespec tv;
+ tv.tv_sec = 0;
+ tv.tv_nsec = 100 * 1000 * 1000; // 100ms
+
+ while (!system->_timer_thread_exit) {
+ timer->handler();
+ nanosleep(&tv, NULL);
+ }
+
+ return NULL;
+}
+
+void OSystem_Android::initBackend() {
+ ENTER("initBackend()");
+ JNIEnv* env = JNU_GetEnv();
+
+ ConfMan.setInt("autosave_period", 0);
+ ConfMan.setInt("FM_medium_quality", true);
+
+ // must happen before creating TimerManager to avoid race in
+ // creating EventManager
+ setupKeymapper();
+
+ // BUG: "transient" ConfMan settings get nuked by the options
+ // screen. Passing the savepath in this way makes it stick
+ // (via ConfMan.registerDefault)
+ _savefile = new DefaultSaveFileManager(ConfMan.get("savepath"));
+ _timer = new DefaultTimerManager();
+
+ gettimeofday(&_startTime, NULL);
+
+ jint sample_rate = env->CallIntMethod(_back_ptr, MID_audioSampleRate);
+ if (env->ExceptionCheck()) {
+ warning("Error finding audio sample rate - assuming 11025HZ");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ sample_rate = 11025;
+ }
+ _mixer = new Audio::MixerImpl(this, sample_rate);
+ _mixer->setReady(true);
+
+ env->CallVoidMethod(_back_ptr, MID_initBackend);
+ if (env->ExceptionCheck()) {
+ error("Error in Java initBackend");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ _timer_thread_exit = false;
+ pthread_create(&_timer_thread, NULL, timerThreadFunc, this);
+
+ OSystem::initBackend();
+
+ setupScummVMSurface();
+}
+
+void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const {
+ ENTER("OSystem_Android::addPluginDirectories()");
+ JNIEnv* env = JNU_GetEnv();
+
+ jobjectArray array =
+ (jobjectArray)env->CallObjectMethod(_back_ptr, MID_getPluginDirectories);
+ if (env->ExceptionCheck()) {
+ warning("Error finding plugin directories");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return;
+ }
+
+ jsize size = env->GetArrayLength(array);
+ for (jsize i = 0; i < size; ++i) {
+ jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
+ if (path_obj == NULL)
+ continue;
+ const char* path = env->GetStringUTFChars(path_obj, NULL);
+ if (path == NULL) {
+ warning("Error getting string characters from plugin directory");
+ env->ExceptionClear();
+ env->DeleteLocalRef(path_obj);
+ continue;
+ }
+ dirs.push_back(Common::FSNode(path));
+ env->ReleaseStringUTFChars(path_obj, path);
+ env->DeleteLocalRef(path_obj);
+ }
+}
+
+bool OSystem_Android::hasFeature(Feature f) {
+ return (f == kFeatureCursorHasPalette ||
+ f == kFeatureVirtualKeyboard ||
+ f == kFeatureOverlaySupportsAlpha);
+}
+
+void OSystem_Android::setFeatureState(Feature f, bool enable) {
+ ENTER("setFeatureState(%d, %d)", f, enable);
+ switch (f) {
+ case kFeatureVirtualKeyboard:
+ _virtkeybd_on = enable;
+ showVirtualKeyboard(enable);
+ break;
+ default:
+ break;
+ }
+}
+
+bool OSystem_Android::getFeatureState(Feature f) {
+ switch (f) {
+ case kFeatureVirtualKeyboard:
+ return _virtkeybd_on;
+ default:
+ return false;
+ }
+}
+
+const OSystem::GraphicsMode* OSystem_Android::getSupportedGraphicsModes() const {
+ static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
+ {"default", "Default", 1},
+ {0, 0, 0},
+ };
+ return s_supportedGraphicsModes;
+}
+
+
+int OSystem_Android::getDefaultGraphicsMode() const {
+ return 1;
+}
+
+bool OSystem_Android::setGraphicsMode(const char *mode) {
+ ENTER("setGraphicsMode(%s)", mode);
+ return true;
+}
+
+bool OSystem_Android::setGraphicsMode(int mode) {
+ ENTER("setGraphicsMode(%d)", mode);
+ return true;
+}
+
+int OSystem_Android::getGraphicsMode() const {
+ return 1;
+}
+
+void OSystem_Android::setupScummVMSurface() {
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_back_ptr, MID_setupScummVMSurface);
+ if (env->ExceptionCheck())
+ return;
+
+ // EGL set up with a new surface. Initialise OpenGLES context.
+
+ _egl_display = eglGetCurrentDisplay();
+ _egl_surface = eglGetCurrentSurface(EGL_DRAW);
+
+ static bool log_version = true;
+ if (log_version) {
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "Using EGL %s (%s); GL %s/%s (%s)",
+ eglQueryString(_egl_display, EGL_VERSION),
+ eglQueryString(_egl_display, EGL_VENDOR),
+ glGetString(GL_VERSION),
+ glGetString(GL_RENDERER),
+ glGetString(GL_VENDOR));
+ log_version = false; // only log this once
+ }
+
+ GLESTexture::initGLExtensions();
+
+ if (!eglQuerySurface(_egl_display, _egl_surface,
+ EGL_WIDTH, &_egl_surface_width) ||
+ !eglQuerySurface(_egl_display, _egl_surface,
+ EGL_HEIGHT, &_egl_surface_height)) {
+ JNU_ThrowByName(env, "java/lang/RuntimeException",
+ "Error fetching EGL surface width/height");
+ return;
+ }
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+ "New surface is %dx%d",
+ _egl_surface_width, _egl_surface_height);
+
+ CHECK_GL_ERROR();
+
+ // Turn off anything that looks like 3D ;)
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_LIGHTING);
+ glDisable(GL_FOG);
+ glDisable(GL_DITHER);
+ glShadeModel(GL_FLAT);
+ glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ glEnable(GL_TEXTURE_2D);
+
+ if (!_game_texture)
+ _game_texture = new GLESPaletteTexture();
+ else
+ _game_texture->reinitGL();
+
+ if (!_overlay_texture)
+ _overlay_texture = new GLES4444Texture();
+ else
+ _overlay_texture->reinitGL();
+
+ if (!_mouse_texture)
+ _mouse_texture = new GLESPaletteATexture();
+ else
+ _mouse_texture->reinitGL();
+
+ glViewport(0, 0, _egl_surface_width, _egl_surface_height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ CHECK_GL_ERROR();
+
+ _force_redraw = true;
+}
+
+void OSystem_Android::destroyScummVMSurface() {
+ _egl_surface = EGL_NO_SURFACE;
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_back_ptr, MID_destroyScummVMSurface);
+ // Can't use OpenGLES functions after this
+}
+
+void OSystem_Android::initSize(uint width, uint height,
+ const Graphics::PixelFormat *format) {
+ ENTER("initSize(%d,%d,%p)", width, height, format);
+
+ _game_texture->allocBuffer(width, height);
+
+ // Cap at 320x200 or the ScummVM themes abort :/
+ GLuint overlay_width = MIN(_egl_surface_width, 320);
+ GLuint overlay_height = MIN(_egl_surface_height, 200);
+ _overlay_texture->allocBuffer(overlay_width, overlay_height);
+
+ // Don't know mouse size yet - it gets reallocated in
+ // setMouseCursor. We need the palette allocated before
+ // setMouseCursor however, so just take a guess at the desired
+ // size (it's small).
+ _mouse_texture->allocBuffer(20, 20);
+}
+
+int16 OSystem_Android::getHeight() {
+ return _game_texture->height();
+}
+
+int16 OSystem_Android::getWidth() {
+ return _game_texture->width();
+}
+
+void OSystem_Android::setPalette(const byte* colors, uint start, uint num) {
+ ENTER("setPalette(%p, %u, %u)", colors, start, num);
+
+ if (!_use_mouse_palette)
+ _setCursorPalette(colors, start, num);
+
+ byte* palette = _game_texture->palette() + start*3;
+ do {
+ for (int i = 0; i < 3; ++i)
+ palette[i] = colors[i];
+ palette += 3;
+ colors += 4;
+ } while (--num);
+}
+
+void OSystem_Android::grabPalette(byte *colors, uint start, uint num) {
+ ENTER("grabPalette(%p, %u, %u)", colors, start, num);
+ const byte* palette = _game_texture->palette_const() + start*3;
+ do {
+ for (int i = 0; i < 3; ++i)
+ colors[i] = palette[i];
+ colors[3] = 0xff; // alpha
+
+ palette += 3;
+ colors += 4;
+ } while (--num);
+}
+
+void OSystem_Android::copyRectToScreen(const byte *buf, int pitch,
+ int x, int y, int w, int h) {
+ ENTER("copyRectToScreen(%p, %d, %d, %d, %d, %d)",
+ buf, pitch, x, y, w, h);
+
+ _game_texture->updateBuffer(x, y, w, h, buf, pitch);
+}
+
+void OSystem_Android::updateScreen() {
+ //ENTER("updateScreen()");
+
+ if (!_force_redraw &&
+ !_game_texture->dirty() &&
+ !_overlay_texture->dirty() &&
+ !_mouse_texture->dirty())
+ return;
+
+ _force_redraw = false;
+
+ glPushMatrix();
+
+ if (_shake_offset != 0) {
+ // This is the only case where _game_texture doesn't
+ // cover the entire screen.
+ glClearColorx(0, 0, 0, 1 << 16);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Move everything up by _shake_offset (game) pixels
+ glTranslatex(0, -_shake_offset << 16, 0);
+ }
+
+ _game_texture->drawTexture(0, 0,
+ _egl_surface_width, _egl_surface_height);
+
+ CHECK_GL_ERROR();
+
+ if (_show_overlay) {
+ _overlay_texture->drawTexture(0, 0,
+ _egl_surface_width,
+ _egl_surface_height);
+ CHECK_GL_ERROR();
+ }
+
+ if (_show_mouse) {
+ glPushMatrix();
+
+ glTranslatex(-_mouse_hotspot.x << 16,
+ -_mouse_hotspot.y << 16,
+ 0);
+
+ // Scale up ScummVM -> OpenGL (pixel) coordinates
+ int texwidth, texheight;
+ if (_show_overlay) {
+ texwidth = getOverlayWidth();
+ texheight = getOverlayHeight();
+ } else {
+ texwidth = getWidth();
+ texheight = getHeight();
+ }
+ glScalex(xdiv(_egl_surface_width, texwidth),
+ xdiv(_egl_surface_height, texheight),
+ 1 << 16);
+
+ // Note the extra half texel to position the mouse in
+ // the middle of the x,y square:
+ const Common::Point& mouse = getEventManager()->getMousePos();
+ glTranslatex((mouse.x << 16) | 1 << 15,
+ (mouse.y << 16) | 1 << 15, 0);
+
+ // Mouse targetscale just seems to make the cursor way
+ // too big :/
+ //glScalex(_mouse_targetscale << 16, _mouse_targetscale << 16,
+ // 1 << 16);
+
+ _mouse_texture->drawTexture();
+
+ glPopMatrix();
+ }
+
+ glPopMatrix();
+
+ CHECK_GL_ERROR();
+
+ if (!eglSwapBuffers(_egl_display, _egl_surface)) {
+ EGLint error = eglGetError();
+ warning("eglSwapBuffers exited with error 0x%x", error);
+ // Some errors mean we need to reinit GL
+ if (error == EGL_CONTEXT_LOST) {
+ destroyScummVMSurface();
+ setupScummVMSurface();
+ }
+ }
+}
+
+Graphics::Surface *OSystem_Android::lockScreen() {
+ ENTER("lockScreen()");
+ Graphics::Surface* surface = _game_texture->surface();
+ assert(surface->pixels);
+ return surface;
+}
+
+void OSystem_Android::unlockScreen() {
+ ENTER("unlockScreen()");
+ assert(_game_texture->dirty());
+}
+
+void OSystem_Android::setShakePos(int shake_offset) {
+ ENTER("setShakePos(%d)", shake_offset);
+ if (_shake_offset != shake_offset) {
+ _shake_offset = shake_offset;
+ _force_redraw = true;
+ }
+}
+
+void OSystem_Android::fillScreen(uint32 col) {
+ ENTER("fillScreen(%u)", col);
+ assert(col < 256);
+ _game_texture->fillBuffer(col);
+}
+
+void OSystem_Android::setFocusRectangle(const Common::Rect& rect) {
+ ENTER("setFocusRectangle(%d,%d,%d,%d)",
+ rect.left, rect.top, rect.right, rect.bottom);
+#if 0
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(rect.left, rect.right, rect.top, rect.bottom, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ _force_redraw = true;
+#endif
+}
+
+void OSystem_Android::clearFocusRectangle() {
+ ENTER("clearFocusRectangle()");
+#if 0
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ _force_redraw = true;
+#endif
+}
+
+void OSystem_Android::showOverlay() {
+ ENTER("showOverlay()");
+ _show_overlay = true;
+ _force_redraw = true;
+}
+
+void OSystem_Android::hideOverlay() {
+ ENTER("hideOverlay()");
+ _show_overlay = false;
+ _force_redraw = true;
+}
+
+void OSystem_Android::clearOverlay() {
+ ENTER("clearOverlay()");
+ _overlay_texture->fillBuffer(0);
+}
+
+void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) {
+ ENTER("grabOverlay(%p, %d)", buf, pitch);
+ // We support overlay alpha blending, so the pixel data here
+ // shouldn't actually be used. Let's fill it with zeros, I'm sure
+ // it will be fine...
+ const Graphics::Surface* surface = _overlay_texture->surface_const();
+ assert(surface->bytesPerPixel == sizeof(buf[0]));
+ int h = surface->h;
+ do {
+ memset(buf, 0, surface->w * sizeof(buf[0]));
+ buf += pitch; // This 'pitch' is pixels not bytes
+ } while (--h);
+}
+
+void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch,
+ int x, int y, int w, int h) {
+ ENTER("copyRectToOverlay(%p, %d, %d, %d, %d, %d)",
+ buf, pitch, x, y, w, h);
+ const Graphics::Surface* surface = _overlay_texture->surface_const();
+ assert(surface->bytesPerPixel == sizeof(buf[0]));
+
+ // This 'pitch' is pixels not bytes
+ _overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0]));
+}
+
+int16 OSystem_Android::getOverlayHeight() {
+ return _overlay_texture->height();
+}
+
+int16 OSystem_Android::getOverlayWidth() {
+ return _overlay_texture->width();
+}
+
+bool OSystem_Android::showMouse(bool visible) {
+ ENTER("showMouse(%d)", visible);
+ _show_mouse = visible;
+ return true;
+}
+
+void OSystem_Android::warpMouse(int x, int y) {
+ ENTER("warpMouse(%d, %d)", x, y);
+ // We use only the eventmanager's idea of the current mouse
+ // position, so there is nothing extra to do here.
+}
+
+void OSystem_Android::setMouseCursor(const byte *buf, uint w, uint h,
+ int hotspotX, int hotspotY,
+ uint32 keycolor, int cursorTargetScale,
+ const Graphics::PixelFormat *format) {
+ ENTER("setMouseCursor(%p, %u, %u, %d, %d, %d, %d, %p)",
+ buf, w, h, hotspotX, hotspotY, (int)keycolor, cursorTargetScale,
+ format);
+
+ assert(keycolor < 256);
+
+ _mouse_texture->allocBuffer(w, h);
+
+ // Update palette alpha based on keycolor
+ byte* palette = _mouse_texture->palette();
+ int i = 256;
+ do {
+ palette[3] = 0xff;
+ palette += 4;
+ } while (--i);
+ palette = _mouse_texture->palette();
+ palette[keycolor*4 + 3] = 0x00;
+ _mouse_texture->updateBuffer(0, 0, w, h, buf, w);
+
+ _mouse_hotspot = Common::Point(hotspotX, hotspotY);
+ _mouse_targetscale = cursorTargetScale;
+}
+
+void OSystem_Android::_setCursorPalette(const byte *colors,
+ uint start, uint num) {
+ byte* palette = _mouse_texture->palette() + start*4;
+ do {
+ for (int i = 0; i < 3; ++i)
+ palette[i] = colors[i];
+ // Leave alpha untouched to preserve keycolor
+
+ palette += 4;
+ colors += 4;
+ } while (--num);
+}
+
+void OSystem_Android::setCursorPalette(const byte *colors,
+ uint start, uint num) {
+ ENTER("setCursorPalette(%p, %u, %u)", colors, start, num);
+ _setCursorPalette(colors, start, num);
+ _use_mouse_palette = true;
+}
+
+void OSystem_Android::disableCursorPalette(bool disable) {
+ ENTER("disableCursorPalette(%d)", disable);
+ _use_mouse_palette = !disable;
+}
+
+void OSystem_Android::setupKeymapper() {
+#ifdef ENABLE_KEYMAPPER
+ using namespace Common;
+
+ Keymapper *mapper = getEventManager()->getKeymapper();
+
+ HardwareKeySet *keySet = new HardwareKeySet();
+ keySet->addHardwareKey(
+ new HardwareKey("n", KeyState(KEYCODE_n), "n (vk)",
+ kTriggerLeftKeyType,
+ kVirtualKeyboardActionType));
+ mapper->registerHardwareKeySet(keySet);
+
+ Keymap *globalMap = new Keymap("global");
+ Action *act;
+
+ act = new Action(globalMap, "VIRT", "Display keyboard",
+ kVirtualKeyboardActionType);
+ act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0));
+
+ mapper->addGlobalKeymap(globalMap);
+
+ mapper->pushKeymap("global");
+#endif
+}
+
+bool OSystem_Android::pollEvent(Common::Event &event) {
+ //ENTER("pollEvent()");
+ lockMutex(_event_queue_lock);
+ if (_event_queue.empty()) {
+ unlockMutex(_event_queue_lock);
+ return false;
+ }
+ event = _event_queue.pop();
+ unlockMutex(_event_queue_lock);
+
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ // TODO: only dirty/redraw move bounds
+ _force_redraw = true;
+ // fallthrough
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ case Common::EVENT_MBUTTONDOWN:
+ case Common::EVENT_MBUTTONUP: {
+ if (event.kbd.flags == 1) { // relative mouse hack
+ // Relative (trackball) mouse hack.
+ const Common::Point& mouse_pos =
+ getEventManager()->getMousePos();
+ event.mouse.x += mouse_pos.x;
+ event.mouse.y += mouse_pos.y;
+ event.mouse.x = CLIP(event.mouse.x, (int16)0, _show_overlay ?
+ getOverlayWidth() : getWidth());
+ event.mouse.y = CLIP(event.mouse.y, (int16)0, _show_overlay ?
+ getOverlayHeight() : getHeight());
+ } else {
+ // Touchscreen events need to be converted
+ // from device to game coords first.
+ const GLESTexture* tex = _show_overlay
+ ? static_cast<GLESTexture*>(_overlay_texture)
+ : static_cast<GLESTexture*>(_game_texture);
+ event.mouse.x = scalef(event.mouse.x, tex->width(),
+ _egl_surface_width);
+ event.mouse.y = scalef(event.mouse.y, tex->height(),
+ _egl_surface_height);
+ event.mouse.x -= _shake_offset;
+ }
+ break;
+ }
+ case Common::EVENT_SCREEN_CHANGED:
+ debug("EVENT_SCREEN_CHANGED");
+ _screen_changeid++;
+ destroyScummVMSurface();
+ setupScummVMSurface();
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void OSystem_Android::pushEvent(const Common::Event& event) {
+ lockMutex(_event_queue_lock);
+
+ // Try to combine multiple queued mouse move events
+ if (event.type == Common::EVENT_MOUSEMOVE &&
+ !_event_queue.empty() &&
+ _event_queue.back().type == Common::EVENT_MOUSEMOVE) {
+ Common::Event tail = _event_queue.back();
+ if (event.kbd.flags) {
+ // relative movement hack
+ tail.mouse.x += event.mouse.x;
+ tail.mouse.y += event.mouse.y;
+ } else {
+ // absolute position
+ tail.kbd.flags = 0; // clear relative flag
+ tail.mouse.x = event.mouse.x;
+ tail.mouse.y = event.mouse.y;
+ }
+ }
+ else
+ _event_queue.push(event);
+
+ unlockMutex(_event_queue_lock);
+}
+
+static void ScummVM_pushEvent(JNIEnv* env, jobject self, jobject java_event) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+
+ Common::Event event;
+ event.type = (Common::EventType)env->GetIntField(java_event,
+ FID_Event_type);
+ event.synthetic =
+ env->GetBooleanField(java_event, FID_Event_synthetic);
+
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ case Common::EVENT_KEYUP:
+ event.kbd.keycode = (Common::KeyCode)env->GetIntField(
+ java_event, FID_Event_kbd_keycode);
+ event.kbd.ascii = static_cast<int>(env->GetIntField(
+ java_event, FID_Event_kbd_ascii));
+ event.kbd.flags = static_cast<int>(env->GetIntField(
+ java_event, FID_Event_kbd_flags));
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ case Common::EVENT_MBUTTONDOWN:
+ case Common::EVENT_MBUTTONUP:
+ event.mouse.x =
+ env->GetIntField(java_event, FID_Event_mouse_x);
+ event.mouse.y =
+ env->GetIntField(java_event, FID_Event_mouse_y);
+ // This is a terrible hack. We stash "relativeness"
+ // in the kbd.flags field until pollEvent() can work
+ // it out.
+ event.kbd.flags = env->GetBooleanField(
+ java_event, FID_Event_mouse_relative) ? 1 : 0;
+ break;
+ default:
+ break;
+ }
+
+ cpp_obj->pushEvent(event);
+}
+
+uint32 OSystem_Android::getMillis() {
+ timeval curTime;
+ gettimeofday(&curTime, NULL);
+ return (uint32)(((curTime.tv_sec - _startTime.tv_sec) * 1000) + \
+ ((curTime.tv_usec - _startTime.tv_usec) / 1000));
+}
+
+void OSystem_Android::delayMillis(uint msecs) {
+ usleep(msecs * 1000);
+}
+
+OSystem::MutexRef OSystem_Android::createMutex() {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+
+ pthread_mutex_t *mutex = new pthread_mutex_t;
+ if (pthread_mutex_init(mutex, &attr) != 0) {
+ warning("pthread_mutex_init() failed!");
+ delete mutex;
+ return NULL;
+ }
+ return (MutexRef)mutex;
+}
+
+void OSystem_Android::lockMutex(MutexRef mutex) {
+ if (pthread_mutex_lock((pthread_mutex_t*)mutex) != 0)
+ warning("pthread_mutex_lock() failed!");
+}
+
+void OSystem_Android::unlockMutex(MutexRef mutex) {
+ if (pthread_mutex_unlock((pthread_mutex_t*)mutex) != 0)
+ warning("pthread_mutex_unlock() failed!");
+}
+
+void OSystem_Android::deleteMutex(MutexRef mutex) {
+ pthread_mutex_t* m = (pthread_mutex_t*)mutex;
+ if (pthread_mutex_destroy(m) != 0)
+ warning("pthread_mutex_destroy() failed!");
+ else
+ delete m;
+}
+
+void OSystem_Android::quit() {
+ ENTER("quit()");
+
+ _timer_thread_exit = true;
+ pthread_join(_timer_thread, NULL);
+}
+
+void OSystem_Android::setWindowCaption(const char *caption) {
+ ENTER("setWindowCaption(%s)", caption);
+ JNIEnv* env = JNU_GetEnv();
+ jstring java_caption = env->NewStringUTF(caption);
+ env->CallVoidMethod(_back_ptr, MID_setWindowCaption, java_caption);
+ if (env->ExceptionCheck()) {
+ warning("Failed to set window caption");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteLocalRef(java_caption);
+}
+
+void OSystem_Android::displayMessageOnOSD(const char *msg) {
+ ENTER("displayMessageOnOSD(%s)", msg);
+ JNIEnv* env = JNU_GetEnv();
+ jstring java_msg = env->NewStringUTF(msg);
+ env->CallVoidMethod(_back_ptr, MID_displayMessageOnOSD, java_msg);
+ if (env->ExceptionCheck()) {
+ warning("Failed to display OSD message");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteLocalRef(java_msg);
+}
+
+void OSystem_Android::showVirtualKeyboard(bool enable) {
+ ENTER("showVirtualKeyboard(%d)", enable);
+ JNIEnv* env = JNU_GetEnv();
+ env->CallVoidMethod(_back_ptr, MID_showVirtualKeyboard, enable);
+ if (env->ExceptionCheck()) {
+ error("Error trying to show virtual keyboard");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+}
+
+Common::SaveFileManager *OSystem_Android::getSavefileManager() {
+ assert(_savefile);
+ return _savefile;
+}
+
+Audio::Mixer *OSystem_Android::getMixer() {
+ assert(_mixer);
+ return _mixer;
+}
+
+Common::TimerManager *OSystem_Android::getTimerManager() {
+ assert(_timer);
+ return _timer;
+}
+
+void OSystem_Android::getTimeAndDate(TimeDate &td) const {
+ struct tm tm;
+ const time_t curTime = time(NULL);
+ localtime_r(&curTime, &tm);
+ td.tm_sec = tm.tm_sec;
+ td.tm_min = tm.tm_min;
+ td.tm_hour = tm.tm_hour;
+ td.tm_mday = tm.tm_mday;
+ td.tm_mon = tm.tm_mon;
+ td.tm_year = tm.tm_year;
+}
+
+FilesystemFactory *OSystem_Android::getFilesystemFactory() {
+ return _fsFactory;
+}
+
+void OSystem_Android::addSysArchivesToSearchSet(Common::SearchSet &s,
+ int priority) {
+ s.add("ASSET", _asset_archive, priority, false);
+
+ JNIEnv* env = JNU_GetEnv();
+
+ jobjectArray array =
+ (jobjectArray)env->CallObjectMethod(_back_ptr, MID_getSysArchives);
+ if (env->ExceptionCheck()) {
+ warning("Error finding system archive path");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return;
+ }
+
+ jsize size = env->GetArrayLength(array);
+ for (jsize i = 0; i < size; ++i) {
+ jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
+ const char* path = env->GetStringUTFChars(path_obj, NULL);
+ if (path != NULL) {
+ s.addDirectory(path, path, priority);
+ env->ReleaseStringUTFChars(path_obj, path);
+ }
+ env->DeleteLocalRef(path_obj);
+ }
+}
+
+
+static jint ScummVM_scummVMMain(JNIEnv* env, jobject self, jobjectArray args) {
+ OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
+
+ const int MAX_NARGS = 32;
+ int res = -1;
+
+ int argc = env->GetArrayLength(args);
+ if (argc > MAX_NARGS) {
+ JNU_ThrowByName(env, "java/lang/IllegalArgumentException",
+ "too many arguments");
+ return 0;
+ }
+
+ char* argv[MAX_NARGS];
+ int nargs; // note use in cleanup loop below
+ for (nargs = 0; nargs < argc; ++nargs) {
+ jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
+ if (arg == NULL) {
+ argv[nargs] = NULL;
+ } else {
+ const char* cstr = env->GetStringUTFChars(arg, NULL);
+ argv[nargs] = const_cast<char*>(cstr);
+ if (cstr == NULL)
+ goto cleanup; // exception already thrown
+ }
+ env->DeleteLocalRef(arg);
+ }
+
+ g_system = cpp_obj;
+ assert(g_system);
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,
+ "Entering scummvm_main with %d args", argc);
+ res = scummvm_main(argc, argv);
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Exiting scummvm_main");
+ g_system->quit();
+
+cleanup:
+ nargs--;
+ for (int i = 0; i < nargs; ++i) {
+ if (argv[i] == NULL)
+ continue;
+ jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
+ if (arg == NULL)
+ // Exception already thrown
+ return res;
+ env->ReleaseStringUTFChars(arg, argv[i]);
+ env->DeleteLocalRef(arg);
+ }
+
+ return res;
+}
+
+#ifdef DYNAMIC_MODULES
+void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const {
+ OSystem_Android* g_system_android = (OSystem_Android*)g_system;
+ g_system_android->addPluginDirectories(dirs);
+}
+#endif
+
+const static JNINativeMethod gMethods[] = {
+ { "create", "(Landroid/content/res/AssetManager;)V",
+ (void*)ScummVM_create },
+ { "nativeDestroy", "()V", (void*)ScummVM_nativeDestroy },
+ { "scummVMMain", "([Ljava/lang/String;)I",
+ (void*)ScummVM_scummVMMain },
+ { "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V",
+ (void*)ScummVM_pushEvent },
+ { "audioMixCallback", "([B)V",
+ (void*)ScummVM_audioMixCallback },
+ { "setConfMan", "(Ljava/lang/String;I)V",
+ (void*)ScummVM_setConfManInt },
+ { "setConfMan", "(Ljava/lang/String;Ljava/lang/String;)V",
+ (void*)ScummVM_setConfManString },
+};
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad(JavaVM* jvm, void* reserved) {
+ cached_jvm = jvm;
+
+ JNIEnv* env;
+ if (jvm->GetEnv((void**)&env, JNI_VERSION_1_2))
+ return JNI_ERR;
+
+ jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM");
+ if (cls == NULL)
+ return JNI_ERR;
+ if (env->RegisterNatives(cls, gMethods, ARRAYSIZE(gMethods)) < 0)
+ return JNI_ERR;
+
+ FID_ScummVM_nativeScummVM = env->GetFieldID(cls, "nativeScummVM", "J");
+ if (FID_ScummVM_nativeScummVM == NULL)
+ return JNI_ERR;
+
+ jclass event = env->FindClass("org/inodes/gus/scummvm/Event");
+ if (event == NULL)
+ return JNI_ERR;
+ FID_Event_type = env->GetFieldID(event, "type", "I");
+ if (FID_Event_type == NULL)
+ return JNI_ERR;
+ FID_Event_synthetic = env->GetFieldID(event, "synthetic", "Z");
+ if (FID_Event_synthetic == NULL)
+ return JNI_ERR;
+ FID_Event_kbd_keycode = env->GetFieldID(event, "kbd_keycode", "I");
+ if (FID_Event_kbd_keycode == NULL)
+ return JNI_ERR;
+ FID_Event_kbd_ascii = env->GetFieldID(event, "kbd_ascii", "I");
+ if (FID_Event_kbd_ascii == NULL)
+ return JNI_ERR;
+ FID_Event_kbd_flags = env->GetFieldID(event, "kbd_flags", "I");
+ if (FID_Event_kbd_flags == NULL)
+ return JNI_ERR;
+ FID_Event_mouse_x = env->GetFieldID(event, "mouse_x", "I");
+ if (FID_Event_mouse_x == NULL)
+ return JNI_ERR;
+ FID_Event_mouse_y = env->GetFieldID(event, "mouse_y", "I");
+ if (FID_Event_mouse_y == NULL)
+ return JNI_ERR;
+ FID_Event_mouse_relative = env->GetFieldID(event, "mouse_relative", "Z");
+ if (FID_Event_mouse_relative == NULL)
+ return JNI_ERR;
+
+ cls = env->FindClass("java/lang/Object");
+ if (cls == NULL)
+ return JNI_ERR;
+ MID_Object_wait = env->GetMethodID(cls, "wait", "()V");
+ if (MID_Object_wait == NULL)
+ return JNI_ERR;
+
+ return JNI_VERSION_1_2;
+}
+
+#endif
diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk
new file mode 100644
index 0000000000..0bc8fa265e
--- /dev/null
+++ b/backends/platform/android/android.mk
@@ -0,0 +1,52 @@
+# Android specific build targets
+
+AAPT = aapt
+DX = dx
+APKBUILDER = apkbuilder
+ADB = adb -e
+ANDROID_JAR = $(ANDROID_SDK)/platforms/android-1.6/android.jar
+JAVAC ?= javac
+JAVACFLAGS = -source 1.5 -target 1.5
+
+# FIXME: find/mark plugin entry points and add all this back again:
+#LDFLAGS += -Wl,--gc-sections
+#CXXFLAGS += -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden
+
+scummvm.apk: build.tmp/libscummvm.so resources.ap_ classes.dex
+ # Package installer won't delete old libscummvm.so on upgrade so
+ # replace it with a zero size file
+ $(INSTALL) -d build.stage/common/lib/armeabi
+ touch build.stage/common/lib/armeabi/libscummvm.so
+ # We now handle the library unpacking ourselves from mylib/
+ $(INSTALL) -d build.stage/common/mylib/armeabi
+ $(INSTALL) -c -m 644 build.tmp/libscummvm.so build.stage/common/mylib/armeabi/
+ $(STRIP) build.stage/common/mylib/armeabi/libscummvm.so
+ # "-nf lib/armeabi/libscummvm.so" builds bogus paths?
+ $(APKBUILDER) $@ -z resources.ap_ -f classes.dex -rf build.stage/common || { $(RM) $@; exit 1; }
+
+scummvm-engine-%.apk: plugins/lib%.so build.tmp/%/resources.ap_ build.tmp/plugins/classes.dex
+ $(INSTALL) -d build.stage/$*/apk/mylib/armeabi/
+ $(INSTALL) -c -m 644 plugins/lib$*.so build.stage/$*/apk/mylib/armeabi/
+ $(STRIP) build.stage/$*/apk/mylib/armeabi/lib$*.so
+ $(APKBUILDER) $@ -z build.tmp/$*/resources.ap_ -f build.tmp/plugins/classes.dex -rf build.stage/$*/apk || { $(RM) $@; exit 1; }
+
+release/%.apk: %.apk
+ @$(MKDIR) -p $(@D)
+ @$(RM) $@
+ $(CP) $< $@.tmp
+ # remove debugging signature
+ zip -d $@.tmp META-INF/\*
+ jarsigner $(JARSIGNER_FLAGS) $@.tmp release
+ zipalign 4 $@.tmp $@
+ $(RM) $@.tmp
+
+androidrelease: release/scummvm.apk $(patsubst plugins/lib%.so,release/scummvm-engine-%.apk,$(PLUGINS))
+
+androidtest: scummvm.apk scummvm-engine-scumm.apk scummvm-engine-kyra.apk
+ @set -e; for apk in $^; do \
+ echo $(ADB) install -r $$apk; \
+ $(ADB) install -r $$apk; \
+ done
+ $(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.inodes.gus.scummvm/.Unpacker
+
+.PHONY: androidrelease androidtest
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
diff --git a/backends/platform/android/asset-archive.h b/backends/platform/android/asset-archive.h
new file mode 100644
index 0000000000..b3f6993c50
--- /dev/null
+++ b/backends/platform/android/asset-archive.h
@@ -0,0 +1,53 @@
+/* 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 "common/str.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/archive.h"
+
+class AndroidAssetArchive : public Common::Archive {
+public:
+ AndroidAssetArchive(jobject am);
+ virtual ~AndroidAssetArchive();
+
+ virtual bool hasFile(const Common::String &name);
+ virtual int listMembers(Common::ArchiveMemberList &list);
+ virtual Common::ArchiveMemberPtr getMember(const Common::String &name);
+ virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+
+private:
+ jmethodID MID_open;
+ jmethodID MID_openFd;
+ jmethodID MID_list;
+
+ jobject _am;
+};
+
+#endif
diff --git a/backends/platform/android/module.mk b/backends/platform/android/module.mk
new file mode 100644
index 0000000000..fdb0ed2ac4
--- /dev/null
+++ b/backends/platform/android/module.mk
@@ -0,0 +1,85 @@
+MODULE := backends/platform/android
+
+MODULE_OBJS := \
+ android.o asset-archive.o video.o
+
+MODULE_DIRS += \
+ backends/platform/android/
+
+# We don't use the rules.mk here on purpose
+OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) $(OBJS)
+
+JAVA_SRC = \
+ $(MODULE)/org/inodes/gus/scummvm/ScummVM.java \
+ $(MODULE)/org/inodes/gus/scummvm/ScummVMApplication.java \
+ $(MODULE)/org/inodes/gus/scummvm/ScummVMActivity.java \
+ $(MODULE)/org/inodes/gus/scummvm/EditableSurfaceView.java \
+ $(MODULE)/org/inodes/gus/scummvm/Unpacker.java \
+ $(MODULE)/org/inodes/gus/scummvm/Manifest.java \
+ $(MODULE)/org/inodes/gus/scummvm/R.java
+
+JAVA_PLUGIN_SRC = \
+ $(MODULE)/org/inodes/gus/scummvm/PluginProvider.java
+
+RESOURCES = \
+ $(srcdir)/dists/android/res/values/strings.xml \
+ $(srcdir)/dists/android/res/layout/main.xml \
+ $(srcdir)/dists/android/res/layout/splash.xml \
+ $(srcdir)/dists/android/res/drawable/gradient.xml \
+ $(srcdir)/dists/android/res/drawable/scummvm.png \
+ $(srcdir)/dists/android/res/drawable/scummvm_big.png
+
+ASSETS = $(DIST_FILES_ENGINEDATA) $(DIST_FILES_THEMES)
+
+PLUGIN_RESOURCES = \
+ $(srcdir)/dists/android/res/values/strings.xml \
+ $(srcdir)/dists/android/res/drawable/scummvm.png
+
+# These must be incremented for each market upload
+#ANDROID_VERSIONCODE = 6 Specified in dists/android/AndroidManifest.xml.in
+ANDROID_PLUGIN_VERSIONCODE = 6
+
+# This library contains scummvm proper
+build.tmp/libscummvm.so: $(OBJS)
+ @$(MKDIR) -p $(@D)
+ $(CXX) $(PLUGIN_LDFLAGS) -shared $(LDFLAGS) -Wl,-soname,$(@F) -Wl,--no-undefined -o $@ $(PRE_OBJS_FLAGS) $(OBJS) $(POST_OBJS_FLAGS) $(LIBS)
+
+
+backends/platform/android/org/inodes/gus/scummvm/R.java backends/platform/android/org/inodes/gus/scummvm/Manifest.java: $(srcdir)/dists/android/AndroidManifest.xml $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR)
+ $(AAPT) package -m -J backends/platform/android -M $< -S $(srcdir)/dists/android/res -I $(ANDROID_JAR)
+
+build.tmp/classes/%.class: $(srcdir)/backends/platform/android/%.java $(srcdir)/backends/platform/android/org/inodes/gus/scummvm/R.java
+ @$(MKDIR) -p $(@D)
+ $(JAVAC) $(JAVACFLAGS) -cp $(srcdir)/backends/platform/android -d build.tmp/classes -bootclasspath $(ANDROID_JAR) $<
+
+build.tmp/classes.plugin/%.class: $(srcdir)/backends/platform/android/%.java
+ @$(MKDIR) -p $(@D)
+ $(JAVAC) $(JAVACFLAGS) -cp $(srcdir)/backends/platform/android -d build.tmp/classes.plugin -bootclasspath $(ANDROID_JAR) $<
+
+classes.dex: $(JAVA_SRC:backends/platform/android/%.java=build.tmp/classes/%.class)
+ $(DX) --dex --output=$@ build.tmp/classes
+
+build.tmp/plugins/classes.dex: $(JAVA_PLUGIN_SRC:backends/platform/android/%.java=build.tmp/classes.plugin/%.class)
+ @$(MKDIR) -p $(@D)
+ $(DX) --dex --output=$@ build.tmp/classes.plugin
+
+resources.ap_: $(srcdir)/dists/android/AndroidManifest.xml $(RESOURCES) $(ASSETS) $(ANDROID_JAR) $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA)
+ $(INSTALL) -d build.tmp/assets/
+ $(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) build.tmp/assets/
+ $(AAPT) package -f -M $< -S $(srcdir)/dists/android/res -A build.tmp/assets -I $(ANDROID_JAR) -F $@
+
+build.tmp/%/resources.ap_: build.tmp/%/AndroidManifest.xml build.stage/%/res/values/strings.xml build.stage/%/res/drawable/scummvm.png $(ANDROID_JAR)
+ $(AAPT) package -f -M $< -S build.stage/$*/res -I $(ANDROID_JAR) -F $@
+
+build.tmp/%/AndroidManifest.xml build.stage/%/res/values/strings.xml: dists/android/mkmanifest.pl configure dists/android/AndroidManifest.xml
+ dists/android/mkmanifest.pl --id=$* --configure=configure \
+ --version-name=$(VERSION) \
+ --version-code=$(ANDROID_PLUGIN_VERSIONCODE) \
+ --stringres=build.stage/$*/res/values/strings.xml \
+ --manifest=build.tmp/$*/AndroidManifest.xml \
+ --master-manifest=dists/android/AndroidManifest.xml \
+ --unpacklib=mylib/armeabi/lib$*.so
+
+build.stage/%/res/drawable/scummvm.png: dists/android/res/drawable/scummvm.png
+ @$(MKDIR) -p $(@D)
+ $(CP) $< $@
diff --git a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java
new file mode 100644
index 0000000000..5b71d4a3a5
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java
@@ -0,0 +1,59 @@
+package org.inodes.gus.scummvm;
+
+import android.content.Context;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.view.SurfaceView;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+
+public class EditableSurfaceView extends SurfaceView {
+ public EditableSurfaceView(Context context) {
+ super(context);
+ }
+
+ public EditableSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public EditableSurfaceView(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ private class MyInputConnection extends BaseInputConnection {
+ public MyInputConnection() {
+ super(EditableSurfaceView.this, false);
+ }
+
+ @Override
+ public boolean performEditorAction(int actionCode) {
+ if (actionCode == EditorInfo.IME_ACTION_DONE) {
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+ return super.performEditorAction(actionCode); // Sends enter key
+ }
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ outAttrs.initialCapsMode = 0;
+ outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
+ outAttrs.inputType = (InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_VARIATION_NORMAL |
+ InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+ outAttrs.imeOptions = (EditorInfo.IME_ACTION_DONE |
+ EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+
+ return new MyInputConnection();
+ }
+}
diff --git a/backends/platform/android/org/inodes/gus/scummvm/Event.java b/backends/platform/android/org/inodes/gus/scummvm/Event.java
new file mode 100644
index 0000000000..f9c7aba93b
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/Event.java
@@ -0,0 +1,330 @@
+package org.inodes.gus.scummvm;
+
+import android.view.KeyEvent;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Event {
+ // Common::EventType enum.
+ // Must be kept in sync with common/events.h
+ public final static int EVENT_INVALID = 0;
+ public final static int EVENT_KEYDOWN = 1;
+ public final static int EVENT_KEYUP = 2;
+ public final static int EVENT_MOUSEMOVE = 3;
+ public final static int EVENT_LBUTTONDOWN = 4;
+ public final static int EVENT_LBUTTONUP = 5;
+ public final static int EVENT_RBUTTONDOWN = 6;
+ public final static int EVENT_RBUTTONUP = 7;
+ public final static int EVENT_WHEELUP = 8;
+ public final static int EVENT_WHEELDOWN = 9;
+ public final static int EVENT_QUIT = 10;
+ public final static int EVENT_SCREEN_CHANGED = 11;
+ public final static int EVENT_PREDICTIVE_DIALOG = 12;
+ public final static int EVENT_MBUTTONDOWN = 13;
+ public final static int EVENT_MBUTTONUP = 14;
+ public final static int EVENT_MAINMENU = 15;
+ public final static int EVENT_RTL = 16;
+
+ // common/keyboard.h
+ public final static int ASCII_F1 = 315;
+ public final static int ASCII_F2 = 316;
+ public final static int ASCII_F3 = 317;
+ public final static int ASCII_F4 = 318;
+ public final static int ASCII_F5 = 319;
+ public final static int ASCII_F6 = 320;
+ public final static int ASCII_F7 = 321;
+ public final static int ASCII_F8 = 322;
+ public final static int ASCII_F9 = 323;
+ public final static int ASCII_F10 = 324;
+ public final static int ASCII_F11 = 325;
+ public final static int ASCII_F12 = 326;
+ public final static int KBD_CTRL = 1 << 0;
+ public final static int KBD_ALT = 1 << 1;
+ public final static int KBD_SHIFT = 1 << 2;
+
+ public final static int KEYCODE_INVALID = 0;
+ public final static int KEYCODE_BACKSPACE = 8;
+ public final static int KEYCODE_TAB = 9;
+ public final static int KEYCODE_CLEAR = 12;
+ public final static int KEYCODE_RETURN = 13;
+ public final static int KEYCODE_PAUSE = 19;
+ public final static int KEYCODE_ESCAPE = 27;
+ public final static int KEYCODE_SPACE = 32;
+ public final static int KEYCODE_EXCLAIM = 33;
+ public final static int KEYCODE_QUOTEDBL = 34;
+ public final static int KEYCODE_HASH = 35;
+ public final static int KEYCODE_DOLLAR = 36;
+ public final static int KEYCODE_AMPERSAND = 38;
+ public final static int KEYCODE_QUOTE = 39;
+ public final static int KEYCODE_LEFTPAREN = 40;
+ public final static int KEYCODE_RIGHTPAREN = 41;
+ public final static int KEYCODE_ASTERISK = 42;
+ public final static int KEYCODE_PLUS = 43;
+ public final static int KEYCODE_COMMA = 44;
+ public final static int KEYCODE_MINUS = 45;
+ public final static int KEYCODE_PERIOD = 46;
+ public final static int KEYCODE_SLASH = 47;
+ public final static int KEYCODE_0 = 48;
+ public final static int KEYCODE_1 = 49;
+ public final static int KEYCODE_2 = 50;
+ public final static int KEYCODE_3 = 51;
+ public final static int KEYCODE_4 = 52;
+ public final static int KEYCODE_5 = 53;
+ public final static int KEYCODE_6 = 54;
+ public final static int KEYCODE_7 = 55;
+ public final static int KEYCODE_8 = 56;
+ public final static int KEYCODE_9 = 57;
+ public final static int KEYCODE_COLON = 58;
+ public final static int KEYCODE_SEMICOLON = 59;
+ public final static int KEYCODE_LESS = 60;
+ public final static int KEYCODE_EQUALS = 61;
+ public final static int KEYCODE_GREATER = 62;
+ public final static int KEYCODE_QUESTION = 63;
+ public final static int KEYCODE_AT = 64;
+ public final static int KEYCODE_LEFTBRACKET = 91;
+ public final static int KEYCODE_BACKSLASH = 92;
+ public final static int KEYCODE_RIGHTBRACKET = 93;
+ public final static int KEYCODE_CARET = 94;
+ public final static int KEYCODE_UNDERSCORE = 95;
+ public final static int KEYCODE_BACKQUOTE = 96;
+ public final static int KEYCODE_a = 97;
+ public final static int KEYCODE_b = 98;
+ public final static int KEYCODE_c = 99;
+ public final static int KEYCODE_d = 100;
+ public final static int KEYCODE_e = 101;
+ public final static int KEYCODE_f = 102;
+ public final static int KEYCODE_g = 103;
+ public final static int KEYCODE_h = 104;
+ public final static int KEYCODE_i = 105;
+ public final static int KEYCODE_j = 106;
+ public final static int KEYCODE_k = 107;
+ public final static int KEYCODE_l = 108;
+ public final static int KEYCODE_m = 109;
+ public final static int KEYCODE_n = 110;
+ public final static int KEYCODE_o = 111;
+ public final static int KEYCODE_p = 112;
+ public final static int KEYCODE_q = 113;
+ public final static int KEYCODE_r = 114;
+ public final static int KEYCODE_s = 115;
+ public final static int KEYCODE_t = 116;
+ public final static int KEYCODE_u = 117;
+ public final static int KEYCODE_v = 118;
+ public final static int KEYCODE_w = 119;
+ public final static int KEYCODE_x = 120;
+ public final static int KEYCODE_y = 121;
+ public final static int KEYCODE_z = 122;
+ public final static int KEYCODE_DELETE = 127;
+ // Numeric keypad
+ public final static int KEYCODE_KP0 = 256;
+ public final static int KEYCODE_KP1 = 257;
+ public final static int KEYCODE_KP2 = 258;
+ public final static int KEYCODE_KP3 = 259;
+ public final static int KEYCODE_KP4 = 260;
+ public final static int KEYCODE_KP5 = 261;
+ public final static int KEYCODE_KP6 = 262;
+ public final static int KEYCODE_KP7 = 263;
+ public final static int KEYCODE_KP8 = 264;
+ public final static int KEYCODE_KP9 = 265;
+ public final static int KEYCODE_KP_PERIOD = 266;
+ public final static int KEYCODE_KP_DIVIDE = 267;
+ public final static int KEYCODE_KP_MULTIPLY = 268;
+ public final static int KEYCODE_KP_MINUS = 269;
+ public final static int KEYCODE_KP_PLUS = 270;
+ public final static int KEYCODE_KP_ENTER = 271;
+ public final static int KEYCODE_KP_EQUALS = 272;
+ // Arrows + Home/End pad
+ public final static int KEYCODE_UP = 273;
+ public final static int KEYCODE_DOWN = 274;
+ public final static int KEYCODE_RIGHT = 275;
+ public final static int KEYCODE_LEFT = 276;
+ public final static int KEYCODE_INSERT = 277;
+ public final static int KEYCODE_HOME = 278;
+ public final static int KEYCODE_END = 279;
+ public final static int KEYCODE_PAGEUP = 280;
+ public final static int KEYCODE_PAGEDOWN = 281;
+ // Function keys
+ public final static int KEYCODE_F1 = 282;
+ public final static int KEYCODE_F2 = 283;
+ public final static int KEYCODE_F3 = 284;
+ public final static int KEYCODE_F4 = 285;
+ public final static int KEYCODE_F5 = 286;
+ public final static int KEYCODE_F6 = 287;
+ public final static int KEYCODE_F7 = 288;
+ public final static int KEYCODE_F8 = 289;
+ public final static int KEYCODE_F9 = 290;
+ public final static int KEYCODE_F10 = 291;
+ public final static int KEYCODE_F11 = 292;
+ public final static int KEYCODE_F12 = 293;
+ public final static int KEYCODE_F13 = 294;
+ public final static int KEYCODE_F14 = 295;
+ public final static int KEYCODE_F15 = 296;
+ // Key state modifier keys
+ public final static int KEYCODE_NUMLOCK = 300;
+ public final static int KEYCODE_CAPSLOCK = 301;
+ public final static int KEYCODE_SCROLLOCK = 302;
+ public final static int KEYCODE_RSHIFT = 303;
+ public final static int KEYCODE_LSHIFT = 304;
+ public final static int KEYCODE_RCTRL = 305;
+ public final static int KEYCODE_LCTRL = 306;
+ public final static int KEYCODE_RALT = 307;
+ public final static int KEYCODE_LALT = 308;
+ public final static int KEYCODE_RMETA = 309;
+ public final static int KEYCODE_LMETA = 310;
+ public final static int KEYCODE_LSUPER = 311; // Left "Windows" key
+ public final static int KEYCODE_RSUPER = 312; // Right "Windows" key
+ public final static int KEYCODE_MODE = 313; // "Alt Gr" key
+ public final static int KEYCODE_COMPOSE = 314; // Multi-key compose key
+ // Miscellaneous function keys
+ public final static int KEYCODE_HELP = 315;
+ public final static int KEYCODE_PRINT = 316;
+ public final static int KEYCODE_SYSREQ = 317;
+ public final static int KEYCODE_BREAK = 318;
+ public final static int KEYCODE_MENU = 319;
+ public final static int KEYCODE_POWER = 320; // Power Macintosh power key
+ public final static int KEYCODE_EURO = 321; // Some european keyboards
+ public final static int KEYCODE_UNDO = 322; // Atari keyboard has Undo
+
+ // Android KeyEvent keycode -> ScummVM keycode
+ public final static Map<Integer, Integer> androidKeyMap;
+ static {
+ Map<Integer, Integer> map = new HashMap<Integer, Integer>();
+
+ map.put(KeyEvent.KEYCODE_DEL, KEYCODE_BACKSPACE);
+ map.put(KeyEvent.KEYCODE_TAB, KEYCODE_TAB);
+ map.put(KeyEvent.KEYCODE_CLEAR, KEYCODE_CLEAR);
+ map.put(KeyEvent.KEYCODE_ENTER, KEYCODE_RETURN);
+ //map.put(??, KEYCODE_PAUSE);
+ map.put(KeyEvent.KEYCODE_BACK, KEYCODE_ESCAPE);
+ map.put(KeyEvent.KEYCODE_SPACE, KEYCODE_SPACE);
+ //map.put(??, KEYCODE_EXCLAIM);
+ //map.put(??, KEYCODE_QUOTEDBL);
+ map.put(KeyEvent.KEYCODE_POUND, KEYCODE_HASH);
+ //map.put(??, KEYCODE_DOLLAR);
+ //map.put(??, KEYCODE_AMPERSAND);
+ map.put(KeyEvent.KEYCODE_APOSTROPHE, KEYCODE_QUOTE);
+ //map.put(??, KEYCODE_LEFTPAREN);
+ //map.put(??, KEYCODE_RIGHTPAREN);
+ //map.put(??, KEYCODE_ASTERISK);
+ map.put(KeyEvent.KEYCODE_PLUS, KEYCODE_PLUS);
+ map.put(KeyEvent.KEYCODE_COMMA, KEYCODE_COMMA);
+ map.put(KeyEvent.KEYCODE_MINUS, KEYCODE_MINUS);
+ map.put(KeyEvent.KEYCODE_PERIOD, KEYCODE_PERIOD);
+ map.put(KeyEvent.KEYCODE_SLASH, KEYCODE_SLASH);
+ map.put(KeyEvent.KEYCODE_0, KEYCODE_0);
+ map.put(KeyEvent.KEYCODE_1, KEYCODE_1);
+ map.put(KeyEvent.KEYCODE_2, KEYCODE_2);
+ map.put(KeyEvent.KEYCODE_3, KEYCODE_3);
+ map.put(KeyEvent.KEYCODE_4, KEYCODE_4);
+ map.put(KeyEvent.KEYCODE_5, KEYCODE_5);
+ map.put(KeyEvent.KEYCODE_6, KEYCODE_6);
+ map.put(KeyEvent.KEYCODE_7, KEYCODE_7);
+ map.put(KeyEvent.KEYCODE_8, KEYCODE_8);
+ map.put(KeyEvent.KEYCODE_9, KEYCODE_9);
+ //map.put(??, KEYCODE_COLON);
+ map.put(KeyEvent.KEYCODE_SEMICOLON, KEYCODE_SEMICOLON);
+ //map.put(??, KEYCODE_LESS);
+ map.put(KeyEvent.KEYCODE_EQUALS, KEYCODE_EQUALS);
+ //map.put(??, KEYCODE_GREATER);
+ //map.put(??, KEYCODE_QUESTION);
+ map.put(KeyEvent.KEYCODE_AT, KEYCODE_AT);
+ map.put(KeyEvent.KEYCODE_LEFT_BRACKET, KEYCODE_LEFTBRACKET);
+ map.put(KeyEvent.KEYCODE_BACKSLASH, KEYCODE_BACKSLASH);
+ map.put(KeyEvent.KEYCODE_RIGHT_BRACKET, KEYCODE_RIGHTBRACKET);
+ //map.put(??, KEYCODE_CARET);
+ //map.put(??, KEYCODE_UNDERSCORE);
+ //map.put(??, KEYCODE_BACKQUOTE);
+ map.put(KeyEvent.KEYCODE_A, KEYCODE_a);
+ map.put(KeyEvent.KEYCODE_B, KEYCODE_b);
+ map.put(KeyEvent.KEYCODE_C, KEYCODE_c);
+ map.put(KeyEvent.KEYCODE_D, KEYCODE_d);
+ map.put(KeyEvent.KEYCODE_E, KEYCODE_e);
+ map.put(KeyEvent.KEYCODE_F, KEYCODE_f);
+ map.put(KeyEvent.KEYCODE_G, KEYCODE_g);
+ map.put(KeyEvent.KEYCODE_H, KEYCODE_h);
+ map.put(KeyEvent.KEYCODE_I, KEYCODE_i);
+ map.put(KeyEvent.KEYCODE_J, KEYCODE_j);
+ map.put(KeyEvent.KEYCODE_K, KEYCODE_k);
+ map.put(KeyEvent.KEYCODE_L, KEYCODE_l);
+ map.put(KeyEvent.KEYCODE_M, KEYCODE_m);
+ map.put(KeyEvent.KEYCODE_N, KEYCODE_n);
+ map.put(KeyEvent.KEYCODE_O, KEYCODE_o);
+ map.put(KeyEvent.KEYCODE_P, KEYCODE_p);
+ map.put(KeyEvent.KEYCODE_Q, KEYCODE_q);
+ map.put(KeyEvent.KEYCODE_R, KEYCODE_r);
+ map.put(KeyEvent.KEYCODE_S, KEYCODE_s);
+ map.put(KeyEvent.KEYCODE_T, KEYCODE_t);
+ map.put(KeyEvent.KEYCODE_U, KEYCODE_u);
+ map.put(KeyEvent.KEYCODE_V, KEYCODE_v);
+ map.put(KeyEvent.KEYCODE_W, KEYCODE_w);
+ map.put(KeyEvent.KEYCODE_X, KEYCODE_x);
+ map.put(KeyEvent.KEYCODE_Y, KEYCODE_y);
+ map.put(KeyEvent.KEYCODE_Z, KEYCODE_z);
+ //map.put(KeyEvent.KEYCODE_DEL, KEYCODE_DELETE); use BACKSPACE instead
+ //map.put(??, KEYCODE_KP_*);
+ map.put(KeyEvent.KEYCODE_DPAD_UP, KEYCODE_UP);
+ map.put(KeyEvent.KEYCODE_DPAD_DOWN, KEYCODE_DOWN);
+ map.put(KeyEvent.KEYCODE_DPAD_RIGHT, KEYCODE_RIGHT);
+ map.put(KeyEvent.KEYCODE_DPAD_LEFT, KEYCODE_LEFT);
+ //map.put(??, KEYCODE_INSERT);
+ //map.put(??, KEYCODE_HOME);
+ //map.put(??, KEYCODE_END);
+ //map.put(??, KEYCODE_PAGEUP);
+ //map.put(??, KEYCODE_PAGEDOWN);
+ //map.put(??, KEYCODE_F{1-15});
+ map.put(KeyEvent.KEYCODE_NUM, KEYCODE_NUMLOCK);
+ //map.put(??, KEYCODE_CAPSLOCK);
+ //map.put(??, KEYCODE_SCROLLLOCK);
+ map.put(KeyEvent.KEYCODE_SHIFT_RIGHT, KEYCODE_RSHIFT);
+ map.put(KeyEvent.KEYCODE_SHIFT_LEFT, KEYCODE_LSHIFT);
+ //map.put(??, KEYCODE_RCTRL);
+ //map.put(??, KEYCODE_LCTRL);
+ map.put(KeyEvent.KEYCODE_ALT_RIGHT, KEYCODE_RALT);
+ map.put(KeyEvent.KEYCODE_ALT_LEFT, KEYCODE_LALT);
+ // ?? META, SUPER
+ // ?? MODE, COMPOSE
+ // ?? HELP, PRINT, SYSREQ, BREAK, EURO, UNDO
+ map.put(KeyEvent.KEYCODE_MENU, KEYCODE_MENU);
+ map.put(KeyEvent.KEYCODE_POWER, KEYCODE_POWER);
+
+ androidKeyMap = Collections.unmodifiableMap(map);
+ }
+
+ public int type;
+ public boolean synthetic;
+ public int kbd_keycode;
+ public int kbd_ascii;
+ public int kbd_flags;
+ public int mouse_x;
+ public int mouse_y;
+ public boolean mouse_relative; // Used for trackball events
+
+ public Event() {
+ type = EVENT_INVALID;
+ synthetic = false;
+ }
+
+ public Event(int type) {
+ this.type = type;
+ synthetic = false;
+ }
+
+ public static Event KeyboardEvent(int type, int keycode, int ascii,
+ int flags) {
+ Event e = new Event();
+ e.type = type;
+ e.kbd_keycode = keycode;
+ e.kbd_ascii = ascii;
+ e.kbd_flags = flags;
+ return e;
+ }
+
+ public static Event MouseEvent(int type, int x, int y) {
+ Event e = new Event();
+ e.type = type;
+ e.mouse_x = x;
+ e.mouse_y = y;
+ return e;
+ }
+}
diff --git a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java
new file mode 100644
index 0000000000..840f3440d5
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java
@@ -0,0 +1,52 @@
+package org.inodes.gus.scummvm;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class PluginProvider extends BroadcastReceiver {
+ public final static String META_UNPACK_LIB =
+ "org.inodes.gus.scummvm.meta.UNPACK_LIB";
+
+ public void onReceive(Context context, Intent intent) {
+ if (!intent.getAction().equals(ScummVMApplication.ACTION_PLUGIN_QUERY))
+ return;
+
+ Bundle extras = getResultExtras(true);
+
+ final ActivityInfo info;
+ try {
+ info = context.getPackageManager()
+ .getReceiverInfo(new ComponentName(context, this.getClass()),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(this.toString(), "Error finding my own info?", e);
+ return;
+ }
+
+ String mylib = info.metaData.getString(META_UNPACK_LIB);
+ if (mylib != null) {
+ ArrayList<String> all_libs =
+ extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS);
+
+ all_libs.add(new Uri.Builder()
+ .scheme("plugin")
+ .authority(context.getPackageName())
+ .path(mylib)
+ .toString());
+
+ extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
+ all_libs);
+ }
+
+ setResultExtras(extras);
+ }
+}
diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java
new file mode 100644
index 0000000000..f4dca0e7e5
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java
@@ -0,0 +1,317 @@
+package org.inodes.gus.scummvm;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+
+import java.io.File;
+import java.util.concurrent.Semaphore;
+
+
+// At least in Android 2.1, eglCreateWindowSurface() requires an
+// EGLNativeWindowSurface object, which is hidden deep in the bowels
+// of libui. Until EGL is properly exposed, it's probably safer to
+// use the Java versions of most EGL functions :(
+
+public class ScummVM implements SurfaceHolder.Callback {
+ private final static String LOG_TAG = "ScummVM.java";
+
+ private final int AUDIO_FRAME_SIZE = 2 * 2; // bytes. 16bit audio * stereo
+ public static class AudioSetupException extends Exception {}
+
+ private long nativeScummVM; // native code hangs itself here
+ boolean scummVMRunning = false;
+
+ private native void create(AssetManager am);
+
+ public ScummVM(Context context) {
+ create(context.getAssets()); // Init C++ code, set nativeScummVM
+ }
+
+ private native void nativeDestroy();
+
+ public synchronized void destroy() {
+ if (nativeScummVM != 0) {
+ nativeDestroy();
+ nativeScummVM = 0;
+ }
+ }
+ protected void finalize() {
+ destroy();
+ }
+
+ // Surface creation:
+ // GUI thread: create surface, release lock
+ // ScummVM thread: acquire lock (block), read surface
+ //
+ // Surface deletion:
+ // GUI thread: post event, acquire lock (block), return
+ // ScummVM thread: read event, free surface, release lock
+ //
+ // In other words, ScummVM thread does this:
+ // acquire lock
+ // setup surface
+ // when SCREEN_CHANGED arrives:
+ // destroy surface
+ // release lock
+ // back to acquire lock
+ static final int configSpec[] = {
+ EGL10.EGL_RED_SIZE, 5,
+ EGL10.EGL_GREEN_SIZE, 5,
+ EGL10.EGL_BLUE_SIZE, 5,
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
+ EGL10.EGL_NONE,
+ };
+ EGL10 egl;
+ EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY;
+ EGLConfig eglConfig;
+ EGLContext eglContext = EGL10.EGL_NO_CONTEXT;
+ EGLSurface eglSurface = EGL10.EGL_NO_SURFACE;
+ Semaphore surfaceLock = new Semaphore(0, true);
+ SurfaceHolder nativeSurface;
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ nativeSurface = holder;
+ surfaceLock.release();
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format,
+ int width, int height) {
+ // Disabled while I debug GL problems
+ //pushEvent(new Event(Event.EVENT_SCREEN_CHANGED));
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ pushEvent(new Event(Event.EVENT_SCREEN_CHANGED));
+ try {
+ surfaceLock.acquire();
+ } catch (InterruptedException e) {
+ Log.e(this.toString(),
+ "Interrupted while waiting for surface lock", e);
+ }
+ }
+
+ // Called by ScummVM thread (from initBackend)
+ private void createScummVMGLContext() {
+ egl = (EGL10)EGLContext.getEGL();
+ eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ int[] version = new int[2];
+ egl.eglInitialize(eglDisplay, version);
+ int[] num_config = new int[1];
+ egl.eglChooseConfig(eglDisplay, configSpec, null, 0, num_config);
+
+ final int numConfigs = num_config[0];
+ if (numConfigs <= 0)
+ throw new IllegalArgumentException("No configs match configSpec");
+
+ EGLConfig[] configs = new EGLConfig[numConfigs];
+ egl.eglChooseConfig(eglDisplay, configSpec, configs, numConfigs,
+ num_config);
+ eglConfig = configs[0];
+
+ eglContext = egl.eglCreateContext(eglDisplay, eglConfig,
+ EGL10.EGL_NO_CONTEXT, null);
+ }
+
+ // Called by ScummVM thread
+ protected void setupScummVMSurface() {
+ try {
+ surfaceLock.acquire();
+ } catch (InterruptedException e) {
+ Log.e(this.toString(),
+ "Interrupted while waiting for surface lock", e);
+ return;
+ }
+ eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig,
+ nativeSurface, null);
+ egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
+ }
+
+ // Called by ScummVM thread
+ protected void destroyScummVMSurface() {
+ if (eglSurface != null) {
+ egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+ egl.eglDestroySurface(eglDisplay, eglSurface);
+ eglSurface = EGL10.EGL_NO_SURFACE;
+ }
+
+ surfaceLock.release();
+ }
+
+ public void setSurface(SurfaceHolder holder) {
+ holder.addCallback(this);
+ }
+
+ // Set scummvm config options
+ final public native static void loadConfigFile(String path);
+ final public native static void setConfMan(String key, int value);
+ final public native static void setConfMan(String key, String value);
+
+ // Feed an event to ScummVM. Safe to call from other threads.
+ final public native void pushEvent(Event e);
+
+ final private native void audioMixCallback(byte[] buf);
+
+ // Runs the actual ScummVM program and returns when it does.
+ // This should not be called from multiple threads simultaneously...
+ final public native int scummVMMain(String[] argv);
+
+ // Callbacks from C++ peer instance
+ //protected GraphicsMode[] getSupportedGraphicsModes() {}
+ protected void displayMessageOnOSD(String msg) {}
+ protected void setWindowCaption(String caption) {}
+ protected void showVirtualKeyboard(boolean enable) {}
+ protected String[] getSysArchives() { return new String[0]; }
+ protected String[] getPluginDirectories() { return new String[0]; }
+ protected void initBackend() throws AudioSetupException {
+ createScummVMGLContext();
+ initAudio();
+ }
+
+ private static class AudioThread extends Thread {
+ final private int buf_size;
+ private boolean is_paused = false;
+ final private ScummVM scummvm;
+ final private AudioTrack audio_track;
+
+ AudioThread(ScummVM scummvm, AudioTrack audio_track, int buf_size) {
+ super("AudioThread");
+ this.scummvm = scummvm;
+ this.audio_track = audio_track;
+ this.buf_size = buf_size;
+ setPriority(Thread.MAX_PRIORITY);
+ setDaemon(true);
+ }
+
+ public void pauseAudio() {
+ synchronized (this) {
+ is_paused = true;
+ }
+ audio_track.pause();
+ }
+
+ public void resumeAudio() {
+ synchronized (this) {
+ is_paused = false;
+ notifyAll();
+ }
+ audio_track.play();
+ }
+
+ public void run() {
+ byte[] buf = new byte[buf_size];
+ audio_track.play();
+ int offset = 0;
+ try {
+ while (true) {
+ synchronized (this) {
+ while (is_paused)
+ wait();
+ }
+
+ if (offset == buf.length) {
+ // Grab new audio data
+ scummvm.audioMixCallback(buf);
+ offset = 0;
+ }
+ int len = buf.length - offset;
+ int ret = audio_track.write(buf, offset, len);
+ if (ret < 0) {
+ Log.w(LOG_TAG, String.format(
+ "AudioTrack.write(%dB) returned error %d",
+ buf.length, ret));
+ break;
+ } else if (ret != len) {
+ Log.w(LOG_TAG, String.format(
+ "Short audio write. Wrote %dB, not %dB",
+ ret, buf.length));
+ // Buffer is full, so yield cpu for a while
+ Thread.sleep(100);
+ }
+ offset += ret;
+ }
+ } catch (InterruptedException e) {
+ Log.e(this.toString(), "Audio thread interrupted", e);
+ }
+ }
+ }
+ private AudioThread audio_thread;
+
+ final public int audioSampleRate() {
+ return AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
+ }
+
+ private void initAudio() throws AudioSetupException {
+ int sample_rate = audioSampleRate();
+ int buf_size =
+ AudioTrack.getMinBufferSize(sample_rate,
+ AudioFormat.CHANNEL_CONFIGURATION_STEREO,
+ AudioFormat.ENCODING_PCM_16BIT);
+ if (buf_size < 0) {
+ int guess = AUDIO_FRAME_SIZE * sample_rate / 100; // 10ms of audio
+ Log.w(LOG_TAG, String.format(
+ "Unable to get min audio buffer size (error %d). Guessing %dB.",
+ buf_size, guess));
+ buf_size = guess;
+ }
+ Log.d(LOG_TAG, String.format("Using %dB buffer for %dHZ audio",
+ buf_size, sample_rate));
+ AudioTrack audio_track =
+ new AudioTrack(AudioManager.STREAM_MUSIC,
+ sample_rate,
+ AudioFormat.CHANNEL_CONFIGURATION_STEREO,
+ AudioFormat.ENCODING_PCM_16BIT,
+ buf_size,
+ AudioTrack.MODE_STREAM);
+ if (audio_track.getState() != AudioTrack.STATE_INITIALIZED) {
+ Log.e(LOG_TAG, "Error initialising Android audio system.");
+ throw new AudioSetupException();
+ }
+
+ audio_thread = new AudioThread(this, audio_track, buf_size);
+ audio_thread.start();
+ }
+
+ public void pause() {
+ audio_thread.pauseAudio();
+ // TODO: need to pause engine too
+ }
+
+ public void resume() {
+ // TODO: need to resume engine too
+ audio_thread.resumeAudio();
+ }
+
+ static {
+ // For grabbing with gdb...
+ final boolean sleep_for_debugger = false;
+ if (sleep_for_debugger) {
+ try {
+ Thread.sleep(20*1000);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ //System.loadLibrary("scummvm");
+ File cache_dir = ScummVMApplication.getLastCacheDir();
+ String libname = System.mapLibraryName("scummvm");
+ File libpath = new File(cache_dir, libname);
+ System.load(libpath.getPath());
+ }
+}
diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java
new file mode 100644
index 0000000000..fb3cd6348f
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java
@@ -0,0 +1,446 @@
+package org.inodes.gus.scummvm;
+
+import android.app.AlertDialog;
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.Toast;
+
+import java.io.IOException;
+
+public class ScummVMActivity extends Activity {
+ private boolean _do_right_click;
+ private boolean _last_click_was_right;
+
+ // game pixels to move per trackball/dpad event.
+ // FIXME: replace this with proper mouse acceleration
+ private final static int TRACKBALL_SCALE = 2;
+
+ private class MyScummVM extends ScummVM {
+ private boolean scummvmRunning = false;
+
+ public MyScummVM() {
+ super(ScummVMActivity.this);
+ }
+
+ @Override
+ protected void initBackend() throws ScummVM.AudioSetupException {
+ synchronized (this) {
+ scummvmRunning = true;
+ notifyAll();
+ }
+ super.initBackend();
+ }
+
+ public void waitUntilRunning() throws InterruptedException {
+ synchronized (this) {
+ while (!scummvmRunning)
+ wait();
+ }
+ }
+
+ @Override
+ protected void displayMessageOnOSD(String msg) {
+ Log.i(this.toString(), "OSD: " + msg);
+ Toast.makeText(ScummVMActivity.this, msg, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ protected void setWindowCaption(final String caption) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ setTitle(caption);
+ }
+ });
+ }
+
+ @Override
+ protected String[] getPluginDirectories() {
+ String[] dirs = new String[1];
+ dirs[0] = ScummVMApplication.getLastCacheDir().getPath();
+ return dirs;
+ }
+
+ @Override
+ protected void showVirtualKeyboard(final boolean enable) {
+ if (getResources().getConfiguration().keyboard ==
+ Configuration.KEYBOARD_NOKEYS) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ showKeyboard(enable);
+ }
+ });
+ }
+ }
+ }
+ private MyScummVM scummvm;
+ private Thread scummvm_thread;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ _do_right_click = false;
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ setContentView(R.layout.main);
+ takeKeyEvents(true);
+
+ // This is a common enough error that we should warn about it
+ // explicitly.
+ if (!Environment.getExternalStorageDirectory().canRead()) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.no_sdcard_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_sdcard)
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int which) {
+ finish();
+ }
+ })
+ .show();
+ return;
+ }
+
+ SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
+ main_surface.setOnTouchListener(new View.OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ return onTouchEvent(event);
+ }
+ });
+ main_surface.setOnKeyListener(new View.OnKeyListener() {
+ public boolean onKey(View v, int code, KeyEvent ev) {
+ return onKeyDown(code, ev);
+ }
+ });
+ main_surface.requestFocus();
+
+ // Start ScummVM
+ scummvm = new MyScummVM();
+ scummvm_thread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ runScummVM();
+ } catch (Exception e) {
+ Log.e("ScummVM", "Fatal error in ScummVM thread", e);
+ new AlertDialog.Builder(ScummVMActivity.this)
+ .setTitle("Error")
+ .setMessage(e.toString())
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ finish();
+ }
+ }
+ }, "ScummVM");
+ scummvm_thread.start();
+
+ // Block UI thread until ScummVM has started. In particular,
+ // this means that surface and event callbacks should be safe
+ // after this point.
+ try {
+ scummvm.waitUntilRunning();
+ } catch (InterruptedException e) {
+ Log.e(this.toString(),
+ "Interrupted while waiting for ScummVM.initBackend", e);
+ finish();
+ }
+
+ scummvm.setSurface(main_surface.getHolder());
+ }
+
+ // Runs in another thread
+ private void runScummVM() throws IOException {
+ getFilesDir().mkdirs();
+ String[] args = {
+ "ScummVM-lib",
+ "--config=" + getFileStreamPath("scummvmrc").getPath(),
+ "--path=" + Environment.getExternalStorageDirectory().getPath(),
+ "--gui-theme=scummmodern",
+ "--savepath=" + getDir("saves", 0).getPath(),
+ };
+
+ int ret = scummvm.scummVMMain(args);
+
+ // On exit, tear everything down for a fresh
+ // restart next time.
+ System.exit(ret);
+ }
+
+ private boolean was_paused = false;
+
+ @Override
+ public void onPause() {
+ if (scummvm != null) {
+ was_paused = true;
+ scummvm.pause();
+ }
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (scummvm != null && was_paused)
+ scummvm.resume();
+ was_paused = false;
+ }
+
+ @Override
+ public void onStop() {
+ if (scummvm != null) {
+ scummvm.pushEvent(new Event(Event.EVENT_QUIT));
+ try {
+ scummvm_thread.join(1000); // 1s timeout
+ } catch (InterruptedException e) {
+ Log.i(this.toString(),
+ "Error while joining ScummVM thread", e);
+ }
+ }
+ super.onStop();
+ }
+
+ static final int MSG_MENU_LONG_PRESS = 1;
+ private final Handler keycodeMenuTimeoutHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_MENU_LONG_PRESS) {
+ InputMethodManager imm = (InputMethodManager)
+ getSystemService(INPUT_METHOD_SERVICE);
+ if (imm != null)
+ imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+ }
+ }
+ };
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent kevent) {
+ return onKeyDown(keyCode, kevent);
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount,
+ KeyEvent kevent) {
+ return onKeyDown(keyCode, kevent);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent kevent) {
+ // Filter out "special" keys
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ // Have to reimplement hold-down-menu-brings-up-softkeybd
+ // ourselves, since we are otherwise hijacking the menu
+ // key :(
+ // See com.android.internal.policy.impl.PhoneWindow.onKeyDownPanel()
+ // for the usual Android implementation of this feature.
+ if (kevent.getRepeatCount() > 0)
+ // Ignore keyrepeat for menu
+ return false;
+ boolean timeout_fired = false;
+ if (getResources().getConfiguration().keyboard ==
+ Configuration.KEYBOARD_NOKEYS) {
+ timeout_fired = !keycodeMenuTimeoutHandler.hasMessages(MSG_MENU_LONG_PRESS);
+ keycodeMenuTimeoutHandler.removeMessages(MSG_MENU_LONG_PRESS);
+ if (kevent.getAction() == KeyEvent.ACTION_DOWN) {
+ keycodeMenuTimeoutHandler.sendMessageDelayed(
+ keycodeMenuTimeoutHandler.obtainMessage(MSG_MENU_LONG_PRESS),
+ ViewConfiguration.getLongPressTimeout());
+ return true;
+ }
+ }
+ if (kevent.getAction() == KeyEvent.ACTION_UP) {
+ if (!timeout_fired)
+ scummvm.pushEvent(new Event(Event.EVENT_MAINMENU));
+ return true;
+ }
+ return false;
+ case KeyEvent.KEYCODE_CAMERA:
+ case KeyEvent.KEYCODE_SEARCH:
+ _do_right_click = (kevent.getAction() == KeyEvent.ACTION_DOWN);
+ return true;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT: {
+ // HTC Hero doesn't seem to generate
+ // MotionEvent.ACTION_DOWN events on trackball press :(
+ // We'll have to just fake one here.
+ // Some other handsets lack a trackball, so the DPAD is
+ // the only way of moving the cursor.
+ int motion_action;
+ // FIXME: this logic is a mess.
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ switch (kevent.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ motion_action = MotionEvent.ACTION_DOWN;
+ break;
+ case KeyEvent.ACTION_UP:
+ motion_action = MotionEvent.ACTION_UP;
+ break;
+ default: // ACTION_MULTIPLE
+ return false;
+ }
+ } else
+ motion_action = MotionEvent.ACTION_MOVE;
+
+ Event e = new Event(getEventType(motion_action));
+ e.mouse_x = 0;
+ e.mouse_y = 0;
+ e.mouse_relative = true;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ e.mouse_y = -TRACKBALL_SCALE;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ e.mouse_y = TRACKBALL_SCALE;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ e.mouse_x = -TRACKBALL_SCALE;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ e.mouse_x = TRACKBALL_SCALE;
+ break;
+ }
+ scummvm.pushEvent(e);
+ return true;
+ }
+ case KeyEvent.KEYCODE_BACK:
+ // skip isSystem() check and fall through to main code
+ break;
+ default:
+ if (kevent.isSystem())
+ return false;
+ }
+
+ // FIXME: what do I need to do for composed characters?
+
+ Event e = new Event();
+
+ switch (kevent.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ e.type = Event.EVENT_KEYDOWN;
+ e.synthetic = false;
+ break;
+ case KeyEvent.ACTION_UP:
+ e.type = Event.EVENT_KEYUP;
+ e.synthetic = false;
+ break;
+ case KeyEvent.ACTION_MULTIPLE:
+ // e.type is handled below
+ e.synthetic = true;
+ break;
+ default:
+ return false;
+ }
+
+ e.kbd_keycode = Event.androidKeyMap.containsKey(keyCode) ?
+ Event.androidKeyMap.get(keyCode) : Event.KEYCODE_INVALID;
+ e.kbd_ascii = kevent.getUnicodeChar();
+ if (e.kbd_ascii == 0)
+ e.kbd_ascii = e.kbd_keycode; // scummvm keycodes are mostly ascii
+
+
+ e.kbd_flags = 0;
+ if (kevent.isAltPressed())
+ e.kbd_flags |= Event.KBD_ALT;
+ if (kevent.isSymPressed()) // no ctrl key in android, so use sym (?)
+ e.kbd_flags |= Event.KBD_CTRL;
+ if (kevent.isShiftPressed()) {
+ if (keyCode >= KeyEvent.KEYCODE_0 &&
+ keyCode <= KeyEvent.KEYCODE_9) {
+ // Shift+number -> convert to F* key
+ int offset = keyCode == KeyEvent.KEYCODE_0 ?
+ 10 : keyCode - KeyEvent.KEYCODE_1; // turn 0 into 10
+ e.kbd_keycode = Event.KEYCODE_F1 + offset;
+ e.kbd_ascii = Event.ASCII_F1 + offset;
+ } else
+ e.kbd_flags |= Event.KBD_SHIFT;
+ }
+
+ if (kevent.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ for (int i = 0; i <= kevent.getRepeatCount(); i++) {
+ e.type = Event.EVENT_KEYDOWN;
+ scummvm.pushEvent(e);
+ e.type = Event.EVENT_KEYUP;
+ scummvm.pushEvent(e);
+ }
+ } else
+ scummvm.pushEvent(e);
+
+ return true;
+ }
+
+ private int getEventType(int action) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ _last_click_was_right = _do_right_click;
+ return _last_click_was_right ?
+ Event.EVENT_RBUTTONDOWN : Event.EVENT_LBUTTONDOWN;
+ case MotionEvent.ACTION_UP:
+ return _last_click_was_right ?
+ Event.EVENT_RBUTTONUP : Event.EVENT_LBUTTONUP;
+ case MotionEvent.ACTION_MOVE:
+ return Event.EVENT_MOUSEMOVE;
+ default:
+ return Event.EVENT_INVALID;
+ }
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ int type = getEventType(event.getAction());
+ if (type == Event.EVENT_INVALID)
+ return false;
+
+ Event e = new Event(type);
+ e.mouse_x =
+ (int)(event.getX() * event.getXPrecision()) * TRACKBALL_SCALE;
+ e.mouse_y =
+ (int)(event.getY() * event.getYPrecision()) * TRACKBALL_SCALE;
+ e.mouse_relative = true;
+ scummvm.pushEvent(e);
+
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int type = getEventType(event.getAction());
+ if (type == Event.EVENT_INVALID)
+ return false;
+
+ Event e = new Event(type);
+ e.mouse_x = (int)event.getX();
+ e.mouse_y = (int)event.getY();
+ e.mouse_relative = false;
+ scummvm.pushEvent(e);
+
+ return true;
+ }
+
+ private void showKeyboard(boolean show) {
+ SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
+ InputMethodManager imm = (InputMethodManager)
+ getSystemService(INPUT_METHOD_SERVICE);
+ if (show)
+ imm.showSoftInput(main_surface, InputMethodManager.SHOW_IMPLICIT);
+ else
+ imm.hideSoftInputFromWindow(main_surface.getWindowToken(),
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ }
+}
diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java
new file mode 100644
index 0000000000..37a9d09e1a
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java
@@ -0,0 +1,29 @@
+package org.inodes.gus.scummvm;
+
+import android.app.Application;
+
+import java.io.File;
+
+public class ScummVMApplication extends Application {
+ public final static String ACTION_PLUGIN_QUERY = "org.inodes.gus.scummvm.action.PLUGIN_QUERY";
+ public final static String EXTRA_UNPACK_LIBS = "org.inodes.gus.scummvm.extra.UNPACK_LIBS";
+
+ private static File cache_dir;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ // This is still on /data :(
+ cache_dir = getCacheDir();
+ // This is mounted noexec :(
+ //cache_dir = new File(Environment.getExternalStorageDirectory(),
+ // "/.ScummVM.tmp");
+ // This is owned by download manager and requires special
+ // permissions to access :(
+ //cache_dir = Environment.getDownloadCacheDirectory();
+ }
+
+ public static File getLastCacheDir() {
+ return cache_dir;
+ }
+}
diff --git a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
new file mode 100644
index 0000000000..efa3e1d2ef
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
@@ -0,0 +1,370 @@
+package org.inodes.gus.scummvm;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.ProgressBar;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipEntry;
+
+public class Unpacker extends Activity {
+ private final static String META_NEXT_ACTIVITY =
+ "org.inodes.gus.unpacker.nextActivity";
+ private ProgressBar mProgress;
+ private File mUnpackDest; // location to unpack into
+ private AsyncTask<String, Integer, Void> mUnpacker;
+ private final static int REQUEST_MARKET = 1;
+
+ private static class UnpackJob {
+ public ZipFile zipfile;
+ public Set<String> paths;
+
+ public UnpackJob(ZipFile zipfile, Set<String> paths) {
+ this.zipfile = zipfile;
+ this.paths = paths;
+ }
+
+ public long UnpackSize() {
+ long size = 0;
+ for (String path: paths) {
+ ZipEntry entry = zipfile.getEntry(path);
+ if (entry != null) size += entry.getSize();
+ }
+ return size;
+ }
+ }
+
+ private class UnpackTask extends AsyncTask<String, Integer, Void> {
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ mProgress.setIndeterminate(false);
+ mProgress.setMax(progress[1]);
+ mProgress.setProgress(progress[0]);
+ mProgress.postInvalidate();
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ Bundle md = getMetaData();
+ String nextActivity = md.getString(META_NEXT_ACTIVITY);
+ if (nextActivity != null) {
+ final ComponentName cn =
+ ComponentName.unflattenFromString(nextActivity);
+ if (cn != null) {
+ final Intent origIntent = getIntent();
+ Intent intent = new Intent();
+ intent.setPackage(origIntent.getPackage());
+ intent.setComponent(cn);
+ if (origIntent.getExtras() != null)
+ intent.putExtras(origIntent.getExtras());
+ intent.putExtra(Intent.EXTRA_INTENT, origIntent);
+ intent.setDataAndType(origIntent.getData(),
+ origIntent.getType());
+ //intent.fillIn(getIntent(), 0);
+ intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ Log.i(this.toString(),
+ "Starting next activity with intent " + intent);
+ startActivity(intent);
+ } else {
+ Log.w(this.toString(),
+ "Unable to extract a component name from " + nextActivity);
+ }
+ }
+
+ finish();
+ }
+
+ @Override
+ protected Void doInBackground(String... all_libs) {
+ // This will contain all unpack jobs
+ Map<String, UnpackJob> unpack_jobs =
+ new HashMap<String, UnpackJob>(all_libs.length);
+
+ // This will contain all unpack filenames (so we can
+ // detect stale files in the unpack directory)
+ Set<String> all_files = new HashSet<String>(all_libs.length);
+
+ for (String lib: all_libs) {
+ final Uri uri = Uri.parse(lib);
+ final String pkg = uri.getAuthority();
+ final String path = uri.getPath().substring(1); // skip first /
+
+ all_files.add(new File(path).getName());
+
+ UnpackJob job = unpack_jobs.get(pkg);
+ if (job == null) {
+ try {
+ // getPackageResourcePath is hidden in Context,
+ // but exposed in ContextWrapper...
+ ContextWrapper context =
+ new ContextWrapper(createPackageContext(pkg, 0));
+ ZipFile zipfile =
+ new ZipFile(context.getPackageResourcePath());
+ job = new UnpackJob(zipfile, new HashSet<String>(1));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(this.toString(), "Package " + pkg +
+ " not found", e);
+ continue;
+ } catch (IOException e) {
+ // FIXME: show some sort of GUI error dialog
+ Log.e(this.toString(),
+ "Error opening ZIP for package " + pkg, e);
+ continue;
+ }
+ unpack_jobs.put(pkg, job);
+ }
+ job.paths.add(path);
+ }
+
+ // Delete stale filenames from mUnpackDest
+ for (File file: mUnpackDest.listFiles()) {
+ if (!all_files.contains(file.getName())) {
+ Log.i(this.toString(),
+ "Deleting stale cached file " + file);
+ file.delete();
+ }
+ }
+
+ int total_size = 0;
+ for (UnpackJob job: unpack_jobs.values())
+ total_size += job.UnpackSize();
+
+ publishProgress(0, total_size);
+
+ mUnpackDest.mkdirs();
+
+ int progress = 0;
+
+ for (UnpackJob job: unpack_jobs.values()) {
+ try {
+ ZipFile zipfile = job.zipfile;
+ for (String path: job.paths) {
+ ZipEntry zipentry = zipfile.getEntry(path);
+ if (zipentry == null)
+ throw new FileNotFoundException(
+ "Couldn't find " + path + " in zip");
+ File dest = new File(mUnpackDest, new File(path).getName());
+ if (dest.exists() &&
+ dest.lastModified() == zipentry.getTime() &&
+ dest.length() == zipentry.getSize()) {
+ // Already unpacked
+ progress += zipentry.getSize();
+ } else {
+ if (dest.exists())
+ Log.d(this.toString(),
+ "Replacing " + dest.getPath() +
+ " old.mtime=" + dest.lastModified() +
+ " new.mtime=" + zipentry.getTime() +
+ " old.size=" + dest.length() +
+ " new.size=" + zipentry.getSize());
+ else
+ Log.i(this.toString(),
+ "Extracting " + zipentry.getName() +
+ " from " + zipfile.getName() +
+ " to " + dest.getPath());
+
+ long next_update = progress;
+
+ InputStream in = zipfile.getInputStream(zipentry);
+ OutputStream out = new FileOutputStream(dest);
+ int len;
+ byte[] buffer = new byte[4096];
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ progress += len;
+ if (progress >= next_update) {
+ publishProgress(progress, total_size);
+ // Arbitrary limit of 2% update steps
+ next_update += total_size / 50;
+ }
+ }
+
+ in.close();
+ out.close();
+ dest.setLastModified(zipentry.getTime());
+ }
+ publishProgress(progress, total_size);
+ }
+
+ zipfile.close();
+ } catch (IOException e) {
+ // FIXME: show some sort of GUI error dialog
+ Log.e(this.toString(), "Error unpacking plugin", e);
+ }
+ }
+
+ if (progress != total_size)
+ Log.d(this.toString(), "Ended with progress " + progress +
+ " != total size " + total_size);
+
+ setResult(RESULT_OK);
+
+ return null;
+ }
+ }
+
+ private class PluginBroadcastReciever extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!intent.getAction()
+ .equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) {
+ Log.e(this.toString(),
+ "Received unexpected action " + intent.getAction());
+ return;
+ }
+
+ Bundle extras = getResultExtras(false);
+ if (extras == null) {
+ // Nothing for us to do.
+ Unpacker.this.setResult(RESULT_OK);
+ finish();
+ }
+
+ ArrayList<String> unpack_libs =
+ extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS);
+
+ if (unpack_libs != null && !unpack_libs.isEmpty()) {
+ final String[] libs =
+ unpack_libs.toArray(new String[unpack_libs.size()]);
+ mUnpacker = new UnpackTask().execute(libs);
+ }
+ }
+ }
+
+ private void initPlugins() {
+ Bundle extras = new Bundle(1);
+
+ ArrayList<String> unpack_libs = new ArrayList<String>(1);
+ // This is the common ScummVM code (not really a "plugin" as such)
+ unpack_libs.add(new Uri.Builder()
+ .scheme("plugin")
+ .authority(getPackageName())
+ .path("mylib/armeabi/libscummvm.so")
+ .toString());
+ extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
+ unpack_libs);
+
+ Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
+ sendOrderedBroadcast(intent, Manifest.permission.SCUMMVM_PLUGIN,
+ new PluginBroadcastReciever(),
+ null, RESULT_OK, null, extras);
+ }
+
+ @Override
+ public void onCreate(Bundle b) {
+ super.onCreate(b);
+
+ mUnpackDest = ScummVMApplication.getLastCacheDir();
+
+ setContentView(R.layout.splash);
+ mProgress = (ProgressBar)findViewById(R.id.progress);
+
+ setResult(RESULT_CANCELED);
+
+ tryUnpack();
+ }
+
+ private void tryUnpack() {
+ Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
+ List<ResolveInfo> plugins = getPackageManager()
+ .queryBroadcastReceivers(intent, 0);
+ if (plugins.isEmpty()) {
+ // No plugins installed
+ AlertDialog.Builder alert = new AlertDialog.Builder(this)
+ .setTitle(R.string.no_plugins_title)
+ .setMessage(R.string.no_plugins_found)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ });
+
+ final Uri uri = Uri.parse("market://search?q=ScummVM plugin");
+ final Intent market_intent = new Intent(Intent.ACTION_VIEW, uri);
+ if (getPackageManager().resolveActivity(market_intent, 0) != null) {
+ alert.setPositiveButton(R.string.to_market,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ try {
+ startActivityForResult(market_intent,
+ REQUEST_MARKET);
+ } catch (ActivityNotFoundException e) {
+ Log.e(this.toString(),
+ "Error starting market", e);
+ }
+ }
+ });
+ }
+
+ alert.show();
+
+ } else {
+ // Already have at least one plugin installed
+ initPlugins();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mUnpacker != null)
+ mUnpacker.cancel(true);
+ super.onStop();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ switch (requestCode) {
+ case REQUEST_MARKET:
+ if (resultCode != RESULT_OK)
+ Log.w(this.toString(), "Market returned " + resultCode);
+ tryUnpack();
+ break;
+ }
+ }
+
+ private Bundle getMetaData() {
+ try {
+ ActivityInfo ai = getPackageManager()
+ .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
+ return ai.metaData;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(this.toString(), "Unable to find my own meta-data", e);
+ return new Bundle();
+ }
+ }
+}
diff --git a/backends/platform/android/scummvm-android-themeengine.patch b/backends/platform/android/scummvm-android-themeengine.patch
new file mode 100644
index 0000000000..1eafe7fb62
--- /dev/null
+++ b/backends/platform/android/scummvm-android-themeengine.patch
@@ -0,0 +1,135 @@
+diff -r 884e66fd1b9c gui/ThemeEngine.cpp
+--- a/gui/ThemeEngine.cpp Tue Apr 13 09:30:52 2010 +1000
++++ b/gui/ThemeEngine.cpp Fri May 28 23:24:43 2010 +1000
+@@ -390,21 +390,19 @@
+
+ // Try to create a Common::Archive with the files of the theme.
+ if (!_themeArchive && !_themeFile.empty()) {
+- Common::FSNode node(_themeFile);
+- if (node.getName().hasSuffix(".zip") && !node.isDirectory()) {
++ Common::ArchiveMemberPtr member = SearchMan.getMember(_themeFile);
++ if (member && member->getName().hasSuffix(".zip")) {
+ #ifdef USE_ZLIB
+- Common::Archive *zipArchive = Common::makeZipArchive(node);
++ Common::Archive *zipArchive = Common::makeZipArchive(member->createReadStream());
+
+ if (!zipArchive) {
+- warning("Failed to open Zip archive '%s'.", node.getPath().c_str());
++ warning("Failed to open Zip archive '%s'.", member->getDisplayName().c_str());
+ }
+ _themeArchive = zipArchive;
+ #else
+ warning("Trying to load theme '%s' in a Zip archive without zLib support", _themeFile.c_str());
+ return false;
+ #endif
+- } else if (node.isDirectory()) {
+- _themeArchive = new Common::FSDirectory(node);
+ }
+ }
+
+@@ -1436,6 +1434,30 @@
+ return tok.empty();
+ }
+
++bool ThemeEngine::themeConfigUsable(const Common::ArchiveMember &member, Common::String &themeName) {
++ Common::File stream;
++ bool foundHeader = false;
++
++ if (member.getName().hasSuffix(".zip")) {
++#ifdef USE_ZLIB
++ Common::Archive *zipArchive = Common::makeZipArchive(member.createReadStream());
++
++ if (zipArchive && zipArchive->hasFile("THEMERC")) {
++ stream.open("THEMERC", *zipArchive);
++ }
++
++ delete zipArchive;
++#endif
++ }
++
++ if (stream.isOpen()) {
++ Common::String stxHeader = stream.readLine();
++ foundHeader = themeConfigParseHeader(stxHeader, themeName);
++ }
++
++ return foundHeader;
++}
++
+ bool ThemeEngine::themeConfigUsable(const Common::FSNode &node, Common::String &themeName) {
+ Common::File stream;
+ bool foundHeader = false;
+@@ -1493,10 +1515,6 @@
+ if (ConfMan.hasKey("themepath"))
+ listUsableThemes(Common::FSNode(ConfMan.get("themepath")), list);
+
+-#ifdef DATA_PATH
+- listUsableThemes(Common::FSNode(DATA_PATH), list);
+-#endif
+-
+ #if defined(MACOSX) || defined(IPHONE)
+ CFURLRef resourceUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
+ if (resourceUrl) {
+@@ -1509,10 +1527,7 @@
+ }
+ #endif
+
+- if (ConfMan.hasKey("extrapath"))
+- listUsableThemes(Common::FSNode(ConfMan.get("extrapath")), list);
+-
+- listUsableThemes(Common::FSNode("."), list, 1);
++ listUsableThemes(SearchMan, list);
+
+ // Now we need to strip all duplicates
+ // TODO: It might not be the best idea to strip duplicates. The user might
+@@ -1531,6 +1546,34 @@
+ output.clear();
+ }
+
++void ThemeEngine::listUsableThemes(Common::Archive &archive, Common::List<ThemeDescriptor> &list) {
++ ThemeDescriptor td;
++
++#ifdef USE_ZLIB
++ Common::ArchiveMemberList fileList;
++ archive.listMatchingMembers(fileList, "*.zip");
++ for (Common::ArchiveMemberList::iterator i = fileList.begin();
++ i != fileList.end(); ++i) {
++ td.name.clear();
++ if (themeConfigUsable(**i, td.name)) {
++ td.filename = (*i)->getName();
++ td.id = (*i)->getDisplayName();
++
++ // If the name of the node object also contains
++ // the ".zip" suffix, we will strip it.
++ if (td.id.hasSuffix(".zip")) {
++ for (int j = 0; j < 4; ++j)
++ td.id.deleteLastChar();
++ }
++
++ list.push_back(td);
++ }
++ }
++
++ fileList.clear();
++#endif
++}
++
+ void ThemeEngine::listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth) {
+ if (!node.exists() || !node.isReadable() || !node.isDirectory())
+ return;
+diff -r 884e66fd1b9c gui/ThemeEngine.h
+--- a/gui/ThemeEngine.h Tue Apr 13 09:30:52 2010 +1000
++++ b/gui/ThemeEngine.h Fri May 28 23:24:43 2010 +1000
+@@ -560,11 +560,13 @@
+ static void listUsableThemes(Common::List<ThemeDescriptor> &list);
+ private:
+ static bool themeConfigUsable(const Common::FSNode &node, Common::String &themeName);
++ static bool themeConfigUsable(const Common::ArchiveMember &member, Common::String &themeName);
+ static bool themeConfigParseHeader(Common::String header, Common::String &themeName);
+
+ static Common::String getThemeFile(const Common::String &id);
+ static Common::String getThemeId(const Common::String &filename);
+ static void listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth = -1);
++ static void listUsableThemes(Common::Archive &archive, Common::List<ThemeDescriptor> &list);
+
+ protected:
+ OSystem *_system; /** Global system object. */
diff --git a/backends/platform/ds/arm7/source/main.cpp b/backends/platform/ds/arm7/source/main.cpp
index 7029d96405..a4cde02ba6 100644
--- a/backends/platform/ds/arm7/source/main.cpp
+++ b/backends/platform/ds/arm7/source/main.cpp
@@ -38,7 +38,7 @@
#include <system.h>
#include <stdlib.h>
#include <string.h>
-//#include <registers_alt.h> // not needed in current libnds
+#include <registers_alt.h> // Needed for SOUND_CR
#include <NDS/scummvm_ipc.h>
//////////////////////////////////////////////////////////////////////
#ifdef USE_DEBUGGER
@@ -590,7 +590,7 @@ int main(int argc, char ** argv) {
IPC->reset = false;
- fifoInit();
+ //fifoInit();
for (int r = 0; r < 8; r++) {
IPC->adpcm.arm7Buffer[r] = (u8 *) malloc(512);
diff --git a/backends/platform/ds/arm9/dist/readme_ds.txt b/backends/platform/ds/arm9/dist/readme_ds.txt
index 191b358f78..c4f1263452 100644
--- a/backends/platform/ds/arm9/dist/readme_ds.txt
+++ b/backends/platform/ds/arm9/dist/readme_ds.txt
@@ -41,6 +41,10 @@ Visit the main ScummVM website <http://www.scummvm.org>
What's New?
------------------------------------------------------------------------
+ScummVM DS 1.1.1
+
+ * Bugfix release: no new features
+
ScummVM DS 1.1.0
* New games are supported in this stable build: Return to Zork, Rodney's
@@ -319,7 +323,7 @@ CANNOT DO THIS.
------------------------------------------------------------------------
I'm glad you asked. Here is a list of the compatible games in version
-1.1.0. Demo versions of the games listed should work too.
+1.1.1. Demo versions of the games listed should work too.
Flight of the Amazon Queen, Beneath a Steel Sky, and Lure of the
Temptress have generously been released as freeware by the original
diff --git a/backends/platform/ds/arm9/makefile b/backends/platform/ds/arm9/makefile
index eca170ef96..7f03f4c310 100644
--- a/backends/platform/ds/arm9/makefile
+++ b/backends/platform/ds/arm9/makefile
@@ -75,7 +75,7 @@ else
ifdef DS_BUILD_K
else
- USE_MAD = 1
+ # USE_MAD = 1
endif
endif
endif
diff --git a/backends/platform/ds/arm9/source/dsmain.cpp b/backends/platform/ds/arm9/source/dsmain.cpp
index 1e9986e4e9..5a63e5f08f 100644
--- a/backends/platform/ds/arm9/source/dsmain.cpp
+++ b/backends/platform/ds/arm9/source/dsmain.cpp
@@ -103,6 +103,7 @@
#include "profiler/cyg-profile.h"
#endif
#include "backends/fs/ds/ds-fs.h"
+#include "base/version.h"
#include "engine.h"
extern "C" void OurIntrMain(void);
@@ -701,7 +702,7 @@ void displayMode8Bit() {
- consoleInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x256, 2, 0, true);
+ consoleInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x256, 2, 0, true, true);
// Set this again because consoleinit resets it
videoSetMode(MODE_5_2D | (consoleEnable? DISPLAY_BG0_ACTIVE: 0) | DISPLAY_BG3_ACTIVE | DISPLAY_SPR_ACTIVE | DISPLAY_SPR_1D | DISPLAY_SPR_1D_BMP);
@@ -939,7 +940,7 @@ void displayMode16Bit() {
SUB_BG0_CR = BG_MAP_BASE(4) | BG_TILE_BASE(0);
SUB_BG0_Y0 = 0;
- consoleInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x256, 4, 0, false);
+ consoleInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x256, 4, 0, false, true);
// consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(4), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);
for (int r = 0; r < 32 * 32; r++) {
@@ -3162,7 +3163,7 @@ int main(void) {
consolePrintf("-------------------------------\n");
consolePrintf("ScummVM DS\n");
consolePrintf("Ported by Neil Millstone\n");
- consolePrintf("Version 1.1.0 ");
+ consolePrintf("Version %s ", gScummVMVersion);
#if defined(DS_BUILD_A)
consolePrintf("build A\n");
consolePrintf("Lucasarts SCUMM games (SCUMM)\n");
diff --git a/backends/platform/ds/commoninclude/NDS/scummvm_ipc.h b/backends/platform/ds/commoninclude/NDS/scummvm_ipc.h
index 9344be68f9..f41548f400 100644
--- a/backends/platform/ds/commoninclude/NDS/scummvm_ipc.h
+++ b/backends/platform/ds/commoninclude/NDS/scummvm_ipc.h
@@ -33,6 +33,18 @@
//////////////////////////////////////////////////////////////////////
+typedef struct sTransferSoundData {
+//---------------------------------------------------------------------------------
+ const void *data;
+ u32 len;
+ u32 rate;
+ u8 vol;
+ u8 pan;
+ u8 format;
+ u8 PADDING;
+} TransferSoundData, * pTransferSoundData;
+
+
//---------------------------------------------------------------------------------
diff --git a/backends/platform/gp2x/gp2x-common.h b/backends/platform/gp2x/gp2x-common.h
index 79eb3f65cf..b54e2d4d4f 100644
--- a/backends/platform/gp2x/gp2x-common.h
+++ b/backends/platform/gp2x/gp2x-common.h
@@ -121,19 +121,6 @@ public:
virtual Audio::Mixer *getMixer();
- // Poll CD status
- // Returns true if cd audio is playing
- bool pollCD();
-
- // Play CD audio track
- void playCD(int track, int num_loops, int start_frame, int duration);
-
- // Stop CD audio track
- void stopCD();
-
- // Update CD audio status
- void updateCD();
-
// Quit
void quit();
@@ -163,8 +150,6 @@ public:
bool setGraphicsMode(int mode);
int getGraphicsMode() const;
- bool openCD(int drive);
-
bool hasFeature(Feature f);
void setFeatureState(Feature f, bool enable);
bool getFeatureState(Feature f);
@@ -206,15 +191,6 @@ protected:
bool _overlayVisible;
Graphics::PixelFormat _overlayFormat;
- // CD Audio
- SDL_CD *_cdrom;
- int _cdTrack, _cdNumLoops, _cdStartFrame, _cdDuration;
- uint32 _cdEndTime, _cdStopTime;
-
- enum {
- DF_WANT_RECT_OPTIM = 1 << 0
- };
-
enum {
kTransactionNone = 0,
kTransactionActive = 1,
@@ -255,7 +231,6 @@ protected:
Graphics::Surface _framebuffer;
/** Current video mode flags (see DF_* constants) */
- uint32 _modeFlags;
bool _modeChanged;
int _screenChangeCount;
@@ -272,9 +247,6 @@ protected:
// Dirty rect management
SDL_Rect _dirtyRectList[NUM_DIRTY_RECT];
int _numDirtyRects;
- uint32 *_dirtyChecksums;
- bool _cksumValid;
- int _cksumNum;
// Keyboard mouse emulation. Disabled by fingolfin 2004-12-18.
// I am keeping the rest of the code in for now, since the joystick
@@ -371,9 +343,6 @@ protected:
Common::TimerManager *_timer;
protected:
- void addDirtyRgnAuto(const byte *buf);
- void makeChecksums(const byte *buf);
-
virtual void addDirtyRect(int x, int y, int w, int h, bool realCoordinates = false);
void drawMouse();
diff --git a/backends/platform/gp2x/gp2x.cpp b/backends/platform/gp2x/gp2x.cpp
index 3d4ed51c1f..88d4f9d632 100644
--- a/backends/platform/gp2x/gp2x.cpp
+++ b/backends/platform/gp2x/gp2x.cpp
@@ -193,13 +193,11 @@ void OSystem_GP2X::initBackend() {
memset(&_videoMode, 0, sizeof(_videoMode));
memset(&_transactionDetails, 0, sizeof(_transactionDetails));
- _cksumValid = false;
_videoMode.mode = GFX_NORMAL;
_videoMode.scaleFactor = 1;
_scalerProc = Normal1x;
_videoMode.aspectRatioCorrection = ConfMan.getBool("aspect_ratio");
_scalerType = 0;
- _modeFlags = 0;
_adjustZoomOnMouse = false;
ConfMan.setBool("FM_low_quality", true);
@@ -246,7 +244,7 @@ OSystem_GP2X::OSystem_GP2X()
_hwscreen(0), _screen(0), _tmpscreen(0),
_overlayVisible(false),
_overlayscreen(0), _tmpscreen2(0),
- _cdrom(0), _scalerProc(0), _modeChanged(false), _screenChangeCount(0), _dirtyChecksums(0),
+ _scalerProc(0), _modeChanged(false), _screenChangeCount(0),
_mouseVisible(false), _mouseNeedsRedraw(false), _mouseData(0), _mouseSurface(0),
_mouseOrigSurface(0), _cursorTargetScale(1), _cursorPaletteDisabled(true),
_joystick(0),
@@ -281,7 +279,6 @@ OSystem_GP2X::~OSystem_GP2X() {
SDL_RemoveTimer(_timerID);
closeMixer();
- free(_dirtyChecksums);
free(_currentPalette);
free(_cursorPalette);
free(_mouseData);
@@ -380,7 +377,6 @@ bool OSystem_GP2X::hasFeature(Feature f) {
return
(f == kFeatureFullscreenMode) ||
(f == kFeatureAspectRatioCorrection) ||
- (f == kFeatureAutoComputeDirtyRects) ||
(f == kFeatureCursorHasPalette);
}
@@ -391,12 +387,6 @@ void OSystem_GP2X::setFeatureState(Feature f, bool enable) {
case kFeatureAspectRatioCorrection:
setAspectRatioCorrection(enable);
break;
- case kFeatureAutoComputeDirtyRects:
- if (enable)
- _modeFlags |= DF_WANT_RECT_OPTIM;
- else
- _modeFlags &= ~DF_WANT_RECT_OPTIM;
- break;
case kFeatureDisableKeyFiltering:
// TODO: Extend as more support for this is added to engines.
return;
@@ -413,8 +403,6 @@ bool OSystem_GP2X::getFeatureState(Feature f) {
return false;
case kFeatureAspectRatioCorrection:
return _videoMode.aspectRatioCorrection;
- case kFeatureAutoComputeDirtyRects:
- return _modeFlags & DF_WANT_RECT_OPTIM;
default:
return false;
}
@@ -431,7 +419,6 @@ void OSystem_GP2X::quit() {
SDL_RemoveTimer(_timerID);
closeMixer();
- free(_dirtyChecksums);
free(_currentPalette);
free(_cursorPalette);
free(_mouseData);
@@ -650,26 +637,3 @@ Audio::Mixer *OSystem_GP2X::getMixer() {
assert(_mixer);
return _mixer;
}
-
-#pragma mark -
-#pragma mark --- CD Audio ---
-#pragma mark -
-
-bool OSystem_GP2X::openCD(int drive) {
- return (_cdrom = NULL);
-}
-
-void OSystem_GP2X::stopCD() {
-}
-
-void OSystem_GP2X::playCD(int track, int num_loops, int start_frame, int duration) {
- return;
-}
-
-bool OSystem_GP2X::pollCD() {
- return false;
-}
-
-void OSystem_GP2X::updateCD() {
- return;
-}
diff --git a/backends/platform/gp2x/graphics.cpp b/backends/platform/gp2x/graphics.cpp
index 243c37dcb1..4a3c668c52 100644
--- a/backends/platform/gp2x/graphics.cpp
+++ b/backends/platform/gp2x/graphics.cpp
@@ -270,12 +270,7 @@ void OSystem_GP2X::initSize(uint w, uint h, const Graphics::PixelFormat *format)
_videoMode.screenWidth = w;
_videoMode.screenHeight = h;
- _cksumNum = (w * h / (8 * 8));
-
_transactionDetails.sizeChanged = true;
-
- free(_dirtyChecksums);
- _dirtyChecksums = (uint32 *)calloc(_cksumNum * 2, sizeof(uint32));
}
int OSystem_GP2X::effectiveScreenHeight() const {
@@ -724,40 +719,31 @@ void OSystem_GP2X::copyRectToScreen(const byte *src, int pitch, int x, int y, in
assert(h > 0 && y + h <= _videoMode.screenHeight);
assert(w > 0 && x + w <= _videoMode.screenWidth);
- if (IS_ALIGNED(src, 4) && pitch == _videoMode.screenWidth && x == 0 && y == 0 &&
- w == _videoMode.screenWidth && h == _videoMode.screenHeight && _modeFlags & DF_WANT_RECT_OPTIM) {
- /* Special, optimized case for full screen updates.
- * It tries to determine what areas were actually changed,
- * and just updates those, on the actual display. */
- addDirtyRgnAuto(src);
- } else {
- /* Clip the coordinates */
- if (x < 0) {
- w += x;
- src -= x;
- x = 0;
- }
+ /* Clip the coordinates */
+ if (x < 0) {
+ w += x;
+ src -= x;
+ x = 0;
+ }
- if (y < 0) {
- h += y;
- src -= y * pitch;
- y = 0;
- }
+ if (y < 0) {
+ h += y;
+ src -= y * pitch;
+ y = 0;
+ }
- if (w > _videoMode.screenWidth - x) {
- w = _videoMode.screenWidth - x;
- }
+ if (w > _videoMode.screenWidth - x) {
+ w = _videoMode.screenWidth - x;
+ }
- if (h > _videoMode.screenHeight - y) {
- h = _videoMode.screenHeight - y;
- }
+ if (h > _videoMode.screenHeight - y) {
+ h = _videoMode.screenHeight - y;
+ }
- if (w <= 0 || h <= 0)
- return;
+ if (w <= 0 || h <= 0)
+ return;
- _cksumValid = false;
- addDirtyRect(x, y, w, h);
- }
+ addDirtyRect(x, y, w, h);
// Try to lock the screen surface
if (SDL_LockSurface(_screen) == -1)
@@ -885,88 +871,6 @@ void OSystem_GP2X::addDirtyRect(int x, int y, int w, int h, bool realCoordinates
}
}
-void OSystem_GP2X::makeChecksums(const byte *buf) {
- assert(buf);
- uint32 *sums = _dirtyChecksums;
- uint x,y;
- const uint last_x = (uint)_videoMode.screenWidth / 8;
- const uint last_y = (uint)_videoMode.screenHeight / 8;
-
- const uint BASE = 65521; /* largest prime smaller than 65536 */
-
- /* the 8x8 blocks in buf are enumerated starting in the top left corner and
- * reading each line at a time from left to right */
- for (y = 0; y != last_y; y++, buf += _videoMode.screenWidth * (8 - 1))
- for (x = 0; x != last_x; x++, buf += 8) {
- // Adler32 checksum algorithm (from RFC1950, used by gzip and zlib).
- // This computes the Adler32 checksum of a 8x8 pixel block. Note
- // that we can do the modulo operation (which is the slowest part)
- // of the algorithm) at the end, instead of doing each iteration,
- // since we only have 64 iterations in total - and thus s1 and
- // s2 can't overflow anyway.
- uint32 s1 = 1;
- uint32 s2 = 0;
- const byte *ptr = buf;
- for (int subY = 0; subY < 8; subY++) {
- for (int subX = 0; subX < 8; subX++) {
- s1 += ptr[subX];
- s2 += s1;
- }
- ptr += _videoMode.screenWidth;
- }
-
- s1 %= BASE;
- s2 %= BASE;
-
- /* output the checksum for this block */
- *sums++ = (s2 << 16) + s1;
- }
-}
-
-void OSystem_GP2X::addDirtyRgnAuto(const byte *buf) {
- assert(buf);
- assert(IS_ALIGNED(buf, 4));
-
- /* generate a table of the checksums */
- makeChecksums(buf);
-
- if (!_cksumValid) {
- _forceFull = true;
- _cksumValid = true;
- }
-
- /* go through the checksum list, compare it with the previous checksums,
- and add all dirty rectangles to a list. try to combine small rectangles
- into bigger ones in a simple way */
- if (!_forceFull) {
- int x, y, w;
- uint32 *ck = _dirtyChecksums;
-
- for (y = 0; y != _videoMode.screenHeight / 8; y++) {
- for (x = 0; x != _videoMode.screenWidth / 8; x++, ck++) {
- if (ck[0] != ck[_cksumNum]) {
- /* found a dirty 8x8 block, now go as far to the right as possible,
- and at the same time, unmark the dirty status by setting old to new. */
- w=0;
- do {
- ck[w + _cksumNum] = ck[w];
- w++;
- } while (x + w != _videoMode.screenWidth / 8 && ck[w] != ck[w + _cksumNum]);
-
- addDirtyRect(x * 8, y * 8, w * 8, 8);
-
- if (_forceFull)
- goto get_out;
- }
- }
- }
- } else {
- get_out:;
- /* Copy old checksums to new */
- memcpy(_dirtyChecksums + _cksumNum, _dirtyChecksums, _cksumNum * sizeof(uint32));
- }
-}
-
int16 OSystem_GP2X::getHeight() {
return _videoMode.screenHeight;
}
@@ -1175,7 +1079,6 @@ void OSystem_GP2X::copyRectToOverlay(const OverlayColor *buf, int pitch, int x,
return;
// Mark the modified region as dirty
- _cksumValid = false;
addDirtyRect(x, y, w, h);
if (SDL_LockSurface(_overlayscreen) == -1)
@@ -1502,7 +1405,6 @@ void OSystem_GP2X::drawMouse() {
SDL_Rect zoomdst;
SDL_Rect dst;
int scale;
- int width, height;
int hotX, hotY;
int tmpScreenWidth, tmpScreenHeight;
@@ -1523,16 +1425,12 @@ void OSystem_GP2X::drawMouse() {
if (!_overlayVisible) {
scale = _videoMode.scaleFactor;
- width = _videoMode.screenWidth;
- height = _videoMode.screenHeight;
dst.w = _mouseCurState.vW;
dst.h = _mouseCurState.vH;
hotX = _mouseCurState.vHotX;
hotY = _mouseCurState.vHotY;
} else {
scale = 1;
- width = _videoMode.overlayWidth;
- height = _videoMode.overlayHeight;
dst.w = _mouseCurState.rW;
dst.h = _mouseCurState.rH;
hotX = _mouseCurState.rHotX;
diff --git a/backends/platform/gp2xwiz/build/build.sh b/backends/platform/gp2xwiz/build/build.sh
index 1bdc020f17..876c3e378a 100755
--- a/backends/platform/gp2xwiz/build/build.sh
+++ b/backends/platform/gp2xwiz/build/build.sh
@@ -13,7 +13,7 @@ export ASFLAGS=-mfloat-abi=soft
cd ../../../..
-echo Building ScummVM for GP2X.
+echo Building ScummVM for GP2X Wiz.
make
-echo Build for GP2X - SDL - complete - Please check build logs.
+echo Build for GP2X Wiz - complete - Please check build logs.
diff --git a/backends/platform/gp2xwiz/build/bundle.sh b/backends/platform/gp2xwiz/build/bundle.sh
index 492ba9e1c6..065bd7a685 100755
--- a/backends/platform/gp2xwiz/build/bundle.sh
+++ b/backends/platform/gp2xwiz/build/bundle.sh
@@ -35,10 +35,12 @@ loc=`dirname "$f"`
cp $loc/../lib/libz.so.1.2.3 ./scummvm-wiz-`date '+%Y-%m-%d'`/scummvm/lib/libz.so.1
cp $loc/../lib/libvorbisidec.so.1.0.2 ./scummvm-wiz-`date '+%Y-%m-%d'`/scummvm/lib/libvorbisidec.so.1
-
-echo Making Stripped exe.
+echo Making Stripped Binary.
arm-open2x-linux-strip ./scummvm-wiz-`date '+%Y-%m-%d'`/scummvm/scummvm.wiz
+echo Making Stripped Plugins.
+arm-open2x-linux-strip ./scummvm-wiz-`date '+%Y-%m-%d'`/scummvm/plugins/*
+
echo Building ZIP bundle.
if [ -f /usr/bin/zip ]
then
diff --git a/backends/platform/gp2xwiz/build/clean.sh b/backends/platform/gp2xwiz/build/clean.sh
index 2862887bb3..5ec1b9e62c 100755
--- a/backends/platform/gp2xwiz/build/clean.sh
+++ b/backends/platform/gp2xwiz/build/clean.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
echo Quick script to make building all the time less painful.
diff --git a/backends/platform/gp2xwiz/build/config.sh b/backends/platform/gp2xwiz/build/config.sh
index 27c1fbf0bf..54c4795298 100755
--- a/backends/platform/gp2xwiz/build/config.sh
+++ b/backends/platform/gp2xwiz/build/config.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
echo Quick script to make running configure all the time less painful
echo and let all the build work be done from the backend/build folder.
diff --git a/backends/platform/gp2xwiz/gp2xwiz-events.cpp b/backends/platform/gp2xwiz/gp2xwiz-events.cpp
index 2774efce1b..a69aa42967 100644
--- a/backends/platform/gp2xwiz/gp2xwiz-events.cpp
+++ b/backends/platform/gp2xwiz/gp2xwiz-events.cpp
@@ -30,6 +30,7 @@
#include "backends/platform/gp2xwiz/gp2xwiz-sdl.h"
#include "backends/platform/gp2xwiz/gp2xwiz-hw.h"
+#include "graphics/scaler/aspect.h"
#include "common/util.h"
#include "common/events.h"
diff --git a/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp
index b20087e6d9..6abddd52f3 100644
--- a/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp
+++ b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp
@@ -127,12 +127,7 @@ void OSystem_GP2XWIZ::initSize(uint w, uint h) {
toggleMouseGrab();
}
- _cksumNum = (w * h / (8 * 8));
-
_transactionDetails.sizeChanged = true;
-
- free(_dirtyChecksums);
- _dirtyChecksums = (uint32 *)calloc(_cksumNum * 2, sizeof(uint32));
}
bool OSystem_GP2XWIZ::loadGFXMode() {
@@ -154,7 +149,6 @@ void OSystem_GP2XWIZ::drawMouse() {
SDL_Rect dst;
int scale;
- int width, height;
int hotX, hotY;
if (_videoMode.mode == GFX_HALF && !_overlayVisible){
@@ -167,16 +161,12 @@ void OSystem_GP2XWIZ::drawMouse() {
if (!_overlayVisible) {
scale = _videoMode.scaleFactor;
- width = _videoMode.screenWidth;
- height = _videoMode.screenHeight;
dst.w = _mouseCurState.vW;
dst.h = _mouseCurState.vH;
hotX = _mouseCurState.vHotX;
hotY = _mouseCurState.vHotY;
} else {
scale = 1;
- width = _videoMode.overlayWidth;
- height = _videoMode.overlayHeight;
dst.w = _mouseCurState.rW;
dst.h = _mouseCurState.rH;
hotX = _mouseCurState.rHotX;
diff --git a/backends/platform/iphone/iphone_common.h b/backends/platform/iphone/iphone_common.h
index 9f7e3e808f..1f5ed01982 100644
--- a/backends/platform/iphone/iphone_common.h
+++ b/backends/platform/iphone/iphone_common.h
@@ -72,6 +72,7 @@ void iPhone_updateScreenRect(unsigned short* screen, int x1, int y1, int x2, int
void iPhone_initSurface(int width, int height);
bool iPhone_fetchEvent(int *outEvent, float *outX, float *outY);
const char* iPhone_getDocumentsDir();
+bool iPhone_isHighResDevice();
#ifdef __cplusplus
}
diff --git a/backends/platform/iphone/iphone_video.h b/backends/platform/iphone/iphone_video.h
index daa5e1d18e..1060a2a223 100644
--- a/backends/platform/iphone/iphone_video.h
+++ b/backends/platform/iphone/iphone_video.h
@@ -43,8 +43,6 @@
SoftKeyboard* _keyboardView;
CALayer* _screenLayer;
- int _fullWidth;
- int _fullHeight;
int _widthOffset;
int _heightOffset;
diff --git a/backends/platform/iphone/iphone_video.m b/backends/platform/iphone/iphone_video.m
index e8977be2f2..faa0719b6c 100644
--- a/backends/platform/iphone/iphone_video.m
+++ b/backends/platform/iphone/iphone_video.m
@@ -29,6 +29,8 @@
static iPhoneView *sharedInstance = nil;
static int _width = 0;
static int _height = 0;
+static int _fullWidth;
+static int _fullHeight;
static CGRect _screenRect;
static char* _textureBuffer = 0;
static int _textureWidth = 0;
@@ -42,6 +44,10 @@ static UITouch* _secondTouch = NULL;
// static long lastTick = 0;
// static int frames = 0;
+bool iPhone_isHighResDevice() {
+ return _fullHeight > 480;
+}
+
void iPhone_updateScreen() {
if (!_needsScreenUpdate) {
_needsScreenUpdate = 1;
diff --git a/backends/platform/iphone/osys_main.cpp b/backends/platform/iphone/osys_main.cpp
index b151688e4e..6c26b6ca8d 100644
--- a/backends/platform/iphone/osys_main.cpp
+++ b/backends/platform/iphone/osys_main.cpp
@@ -57,7 +57,7 @@ OSystem_IPHONE::OSystem_IPHONE() :
_overlayVisible(false), _overlayBuffer(NULL), _fullscreen(NULL),
_mouseHeight(0), _mouseWidth(0), _mouseBuf(NULL), _lastMouseTap(0),
_secondaryTapped(false), _lastSecondaryTap(0), _screenOrientation(kScreenOrientationFlippedLandscape),
- _needEventRestPeriod(false), _mouseClickAndDragEnabled(false), _touchpadModeEnabled(true),
+ _needEventRestPeriod(false), _mouseClickAndDragEnabled(false),
_gestureStartX(-1), _gestureStartY(-1), _fullScreenIsDirty(false), _fullScreenOverlayIsDirty(false),
_mouseDirty(false), _timeSuspended(0), _lastDragPosX(-1), _lastDragPosY(-1), _screenChangeCount(0)
@@ -65,6 +65,7 @@ OSystem_IPHONE::OSystem_IPHONE() :
_queuedInputEvent.type = (Common::EventType)0;
_lastDrawnMouseRect = Common::Rect(0, 0, 0, 0);
+ _touchpadModeEnabled = !iPhone_isHighResDevice();
_fsFactory = new POSIXFilesystemFactory();
}
diff --git a/backends/platform/iphone/osys_video.cpp b/backends/platform/iphone/osys_video.cpp
index 6cb5e18d95..76c2031758 100644
--- a/backends/platform/iphone/osys_video.cpp
+++ b/backends/platform/iphone/osys_video.cpp
@@ -86,7 +86,7 @@ int16 OSystem_IPHONE::getWidth() {
}
void OSystem_IPHONE::setPalette(const byte *colors, uint start, uint num) {
- //printf("setPalette()\n");
+ assert(start + num <= 256);
const byte *b = colors;
for (uint i = start; i < start + num; ++i) {
@@ -98,7 +98,14 @@ void OSystem_IPHONE::setPalette(const byte *colors, uint start, uint num) {
}
void OSystem_IPHONE::grabPalette(byte *colors, uint start, uint num) {
- //printf("grabPalette()\n");
+ assert(start + num <= 256);
+ byte *b = colors;
+
+ for (uint i = start; i < start + num; ++i) {
+ Graphics::colorToRGB<Graphics::ColorMasks<565> >(_palette[i], b[0], b[1], b[2]);
+ b[3] = 0xFF;
+ b += 4;
+ }
}
void OSystem_IPHONE::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) {
diff --git a/backends/platform/linuxmoto/linuxmoto-graphics.cpp b/backends/platform/linuxmoto/linuxmoto-graphics.cpp
index 8f718c82f6..a39416ebc4 100644
--- a/backends/platform/linuxmoto/linuxmoto-graphics.cpp
+++ b/backends/platform/linuxmoto/linuxmoto-graphics.cpp
@@ -128,12 +128,7 @@ void OSystem_LINUXMOTO::initSize(uint w, uint h) {
toggleMouseGrab();
}
- _cksumNum = (w * h / (8 * 8));
-
_transactionDetails.sizeChanged = true;
-
- free(_dirtyChecksums);
- _dirtyChecksums = (uint32 *)calloc(_cksumNum * 2, sizeof(uint32));
}
bool OSystem_LINUXMOTO::loadGFXMode() {
@@ -173,7 +168,6 @@ void OSystem_LINUXMOTO::drawMouse() {
SDL_Rect dst;
int scale;
- int width, height;
int hotX, hotY;
if (_videoMode.mode == GFX_HALF && !_overlayVisible) {
@@ -186,16 +180,12 @@ void OSystem_LINUXMOTO::drawMouse() {
if (!_overlayVisible) {
scale = _videoMode.scaleFactor;
- width = _videoMode.screenWidth;
- height = _videoMode.screenHeight;
dst.w = _mouseCurState.vW;
dst.h = _mouseCurState.vH;
hotX = _mouseCurState.vHotX;
hotY = _mouseCurState.vHotY;
} else {
scale = 1;
- width = _videoMode.overlayWidth;
- height = _videoMode.overlayHeight;
dst.w = _mouseCurState.rW;
dst.h = _mouseCurState.rH;
hotX = _mouseCurState.rHotX;
diff --git a/backends/platform/ps2/systemps2.cpp b/backends/platform/ps2/systemps2.cpp
index 49d583d1a1..7659d5194d 100644
--- a/backends/platform/ps2/systemps2.cpp
+++ b/backends/platform/ps2/systemps2.cpp
@@ -624,23 +624,6 @@ void OSystem_PS2::setMouseCursor(const byte *buf, uint w, uint h, int hotspot_x,
_screen->setMouseOverlay(buf, w, h, hotspot_x, hotspot_y, keycolor);
}
-bool OSystem_PS2::openCD(int drive) {
- return false;
-}
-
-bool OSystem_PS2::pollCD(void) {
- return false;
-}
-
-void OSystem_PS2::playCD(int track, int num_loops, int start_frame, int duration) {
-}
-
-void OSystem_PS2::stopCD(void) {
-}
-
-void OSystem_PS2::updateCD(void) {
-}
-
void OSystem_PS2::showOverlay(void) {
_screen->showOverlay();
}
diff --git a/backends/platform/ps2/systemps2.h b/backends/platform/ps2/systemps2.h
index 0068ffd93f..78973ed3f0 100644
--- a/backends/platform/ps2/systemps2.h
+++ b/backends/platform/ps2/systemps2.h
@@ -97,12 +97,6 @@ public:
virtual Audio::Mixer *getMixer();
- virtual bool openCD(int drive);
- virtual bool pollCD();
- virtual void playCD(int track, int num_loops, int start_frame, int duration);
- virtual void stopCD();
- virtual void updateCD();
-
virtual MutexRef createMutex(void);
virtual void lockMutex(MutexRef mutex);
virtual void unlockMutex(MutexRef mutex);
diff --git a/backends/platform/psp/Makefile b/backends/platform/psp/Makefile
index 7c33999b4d..7f8bb63b0a 100644
--- a/backends/platform/psp/Makefile
+++ b/backends/platform/psp/Makefile
@@ -129,7 +129,8 @@ SDLFLAGS := $(shell $(PSPBIN)/sdl-config --cflags)
SDLLIBS := $(shell $(PSPBIN)/sdl-config --libs)
# PSP LIBS
PSPLIBS = -lpspprof -lpspvfpu -lpspdebug -lpspgu -lpspge -lpspdisplay -lpspctrl -lpspsdk \
- -lpsputility -lpspuser -lpsppower -lpsphprm -lpspsdk -lpsprtc -lpspaudio -lpspkernel
+ -lpsputility -lpspuser -lpsppower -lpsphprm -lpspsdk -lpsprtc -lpspaudio -lpspaudiocodec \
+ -lpspkernel
# Add in PSPSDK includes and libraries.
CXXFLAGS += $(SDLFLAGS)
@@ -149,7 +150,8 @@ OBJS := powerman.o \
psploader.o \
pspkeyboard.o \
audio.o \
- thread.o
+ thread.o \
+ mp3.o
# Include common Scummvm makefile
include $(srcdir)/Makefile.common
diff --git a/backends/platform/psp/display_client.cpp b/backends/platform/psp/display_client.cpp
index 90c41e796d..c5a6250188 100644
--- a/backends/platform/psp/display_client.cpp
+++ b/backends/platform/psp/display_client.cpp
@@ -686,17 +686,18 @@ void GuRenderer::fillVertices(Vertex *vertices) {
uint32 gapX = _useGlobalScaler ? (PSP_SCREEN_WIDTH - outputWidth) >> 1 : 0;
uint32 gapY = _useGlobalScaler ? (PSP_SCREEN_HEIGHT - outputHeight) >> 1 : 0;
+ // Save scaled offset on screen
+ float scaledOffsetOnScreenX = scaleSourceToOutputX(_offsetOnScreen.x);
+ float scaledOffsetOnScreenY = scaleSourceToOutputY(_offsetOnScreen.y);
+
float imageStartX, imageStartY, imageEndX, imageEndY;
- imageStartX = gapX + (scaleSourceToOutputX(_maxTextureOffset.x));
- imageStartY = gapY;
-
- imageStartX += scaleSourceToOutputX(_offsetOnScreen.x);
- imageStartY += scaleSourceToOutputY(_offsetOnScreen.y);
+ imageStartX = gapX + scaledOffsetOnScreenX + (scaleSourceToOutputX(_maxTextureOffset.x));
+ imageStartY = gapY + scaledOffsetOnScreenY;
if (_fullScreen) { // shortcut
- imageEndX = PSP_SCREEN_WIDTH - gapX;
- imageEndY = PSP_SCREEN_HEIGHT - gapY;
+ imageEndX = PSP_SCREEN_WIDTH - gapX + scaledOffsetOnScreenX;
+ imageEndY = PSP_SCREEN_HEIGHT - gapY + scaledOffsetOnScreenY; // needed for screen shake
} else { /* !fullScreen */
imageEndX = imageStartX + scaleSourceToOutputX(_drawSize.width);
imageEndY = imageStartY + scaleSourceToOutputY(_drawSize.height);
diff --git a/backends/platform/psp/module.mk b/backends/platform/psp/module.mk
index 4d375bcef0..99170ce7fb 100644
--- a/backends/platform/psp/module.mk
+++ b/backends/platform/psp/module.mk
@@ -14,7 +14,8 @@ MODULE_OBJS := powerman.o \
psploader.o \
pspkeyboard.o \
audio.o \
- thread.o
+ thread.o \
+ mp3.o
MODULE_DIRS += \
backends/platform/psp/
diff --git a/backends/platform/psp/mp3.cpp b/backends/platform/psp/mp3.cpp
new file mode 100644
index 0000000000..972c5a8ba8
--- /dev/null
+++ b/backends/platform/psp/mp3.cpp
@@ -0,0 +1,487 @@
+/* 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$
+ *
+ */
+
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/singleton.h"
+#include "common/mutex.h"
+
+#include "sound/audiostream.h"
+
+#include <pspaudiocodec.h>
+#include <psputility_modules.h>
+#include <pspthreadman.h>
+#include <pspsysmem.h>
+#include <pspmodulemgr.h>
+#include <psputility_avmodules.h>
+#include <mad.h>
+#include "backends/platform/psp/mp3.h"
+
+//#define DISABLE_PSP_MP3 // to make us use the regular MAD decoder instead
+
+//#define __PSP_DEBUG_FUNCS__ /* For debugging the stack */
+//#define __PSP_DEBUG_PRINT__
+#include "backends/platform/psp/trace.h"
+
+//#define PRINT_BUFFERS /* to debug MP3 buffers */
+
+namespace Audio {
+
+class Mp3PspStream;
+
+bool Mp3PspStream::_decoderInit = false; // has the decoder been initialized
+#ifdef DISABLE_PSP_MP3
+bool Mp3PspStream::_decoderFail = true; // pretend the decoder failed
+#else
+bool Mp3PspStream::_decoderFail = false; // has the decoder failed to load
+#endif
+
+bool Mp3PspStream::initDecoder() {
+ DEBUG_ENTER_FUNC();
+
+ if (_decoderInit) {
+ PSP_ERROR("Already initialized!");
+ return true;
+ }
+
+ // Based on PSP firmware version, we need to do different things to do Media Engine processing
+ uint32 firmware = sceKernelDevkitVersion();
+ PSP_DEBUG_PRINT("Firmware version 0x%x\n", firmware);
+ if (firmware == 0x01050001){
+ if (!loadStartAudioModule((char *)(void *)"flash0:/kd/me_for_vsh.prx",
+ PSP_MEMORY_PARTITION_KERNEL)) {
+ PSP_ERROR("failed to load me_for_vsh.prx. ME cannot start.\n");
+ _decoderFail = true;
+ return false;
+ }
+ if (!loadStartAudioModule((char *)(void *)"flash0:/kd/audiocodec.prx", PSP_MEMORY_PARTITION_KERNEL)) {
+ PSP_ERROR("failed to load audiocodec.prx. ME cannot start.\n");
+ _decoderFail = true;
+ return false;
+ }
+ } else {
+ if (sceUtilityLoadAvModule(PSP_AV_MODULE_AVCODEC) < 0) {
+ PSP_ERROR("failed to load AVCODEC module.\n");
+ _decoderFail = true;
+ return false;
+ }
+ }
+
+ PSP_INFO_PRINT("Using PSP's ME for MP3\n"); // important to know this is happening
+
+ _decoderInit = true;
+ return true;
+}
+
+bool Mp3PspStream::stopDecoder() {
+ DEBUG_ENTER_FUNC();
+
+ if (!_decoderInit)
+ return true;
+
+ // Based on PSP firmware version, we need to do different things to do Media Engine processing
+ if (sceKernelDevkitVersion() == 0x01050001){
+/* if (!unloadAudioModule("flash0:/kd/me_for_vsh.prx", PSP_MEMORY_PARTITION_KERNEL) ||
+ !unloadAudioModule("flash0:/kd/audiocodec.prx", PSP_MEMORY_PARTITION_KERNEL) {
+ PSP_ERROR("failed to unload audio module\n");
+ return false;
+ }
+*/
+ }else{
+ if (sceUtilityUnloadModule(PSP_MODULE_AV_AVCODEC) < 0) {
+ PSP_ERROR("failed to unload avcodec module\n");
+ return false;
+ }
+ }
+
+ _decoderInit = false;
+ return true;
+}
+
+//Load a PSP audio module
+bool Mp3PspStream::loadStartAudioModule(const char *modname, int partition){
+ DEBUG_ENTER_FUNC();
+
+ SceKernelLMOption option;
+ SceUID modid;
+
+ memset(&option, 0, sizeof(option));
+ option.size = sizeof(option);
+ option.mpidtext = partition;
+ option.mpiddata = partition;
+ option.position = 0;
+ option.access = 1;
+
+ modid = sceKernelLoadModule(modname, 0, &option);
+ if (modid < 0) {
+ PSP_ERROR("Failed to load module %s. Got error 0x%x\n", modname, modid);
+ return false;
+ }
+
+ int ret = sceKernelStartModule(modid, 0, NULL, NULL, NULL);
+ if (ret < 0) {
+ PSP_ERROR("Failed to start module %s. Got error 0x%x\n", modname, ret);
+ return false;
+ }
+ return true;
+}
+
+// TODO: make parallel function for unloading the 1.50 modules
+
+Mp3PspStream::Mp3PspStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _pcmLength(0),
+ _posInFrame(0),
+ _state(MP3_STATE_INIT),
+ _length(0, 1000),
+ _sampleRate(0),
+ _totalTime(mad_timer_zero) {
+
+ DEBUG_ENTER_FUNC();
+
+ assert(_decoderInit); // must be initialized by now
+
+ // let's leave the buffer guard -- who knows, it may be good?
+ memset(_buf, 0, sizeof(_buf));
+ memset(_codecInBuffer, 0, sizeof(_codecInBuffer));
+
+ initStream(); // init needed stuff for the stream
+
+ while (_state != MP3_STATE_EOS)
+ findValidHeader(); // get a first header so we can read basic stuff
+
+ _sampleRate = _header.samplerate; // copy it before it gets destroyed
+
+ _length = Timestamp(mad_timer_count(_totalTime, MAD_UNITS_MILLISECONDS), getRate());
+
+ //initStreamME(); // init the stuff needed for the ME to work
+
+ deinitStream();
+ //releaseStreamME();
+
+ _state = MP3_STATE_INIT;
+}
+
+int Mp3PspStream::initStream() {
+ DEBUG_ENTER_FUNC();
+
+ if (_state != MP3_STATE_INIT)
+ deinitStream();
+
+ // Init MAD
+ mad_stream_init(&_stream);
+ mad_header_init(&_header);
+
+ // Reset the stream data
+ _inStream->seek(0, SEEK_SET);
+ _totalTime = mad_timer_zero;
+ _posInFrame = 0;
+
+ // Update state
+ _state = MP3_STATE_READY;
+
+ // Read the first few sample bytes into the buffer
+ readMP3DataIntoBuffer();
+
+ return true;
+}
+
+bool Mp3PspStream::initStreamME() {
+ // The following will eventually go into the thread
+ sceAudiocodecReleaseEDRAM(_codecParams); // do we need this?
+
+ memset(_codecParams, 0, sizeof(_codecParams));
+
+ // Init the MP3 hardware
+ int ret = 0;
+ ret = sceAudiocodecCheckNeedMem(_codecParams, 0x1002);
+ if (ret < 0) {
+ PSP_ERROR("failed to init MP3 ME module. sceAudiocodecCheckNeedMem returned 0x%x.\n", ret);
+ return false;
+ }
+ PSP_DEBUG_PRINT("sceAudiocodecCheckNeedMem returned %d\n", ret);
+ ret = sceAudiocodecGetEDRAM(_codecParams, 0x1002);
+ if (ret < 0) {
+ PSP_ERROR("failed to init MP3 ME module. sceAudiocodecGetEDRAM returned 0x%x.\n", ret);
+ return false;
+ }
+ PSP_DEBUG_PRINT("sceAudioCodecGetEDRAM returned %d\n", ret);
+
+ PSP_DEBUG_PRINT("samplerate[%d]\n", _sampleRate);
+ _codecParams[10] = _sampleRate;
+
+ ret = sceAudiocodecInit(_codecParams, 0x1002);
+ if (ret < 0) {
+ PSP_ERROR("failed to init MP3 ME module. sceAudiocodecInit returned 0x%x.\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+Mp3PspStream::~Mp3PspStream() {
+ DEBUG_ENTER_FUNC();
+
+ deinitStream();
+ releaseStreamME(); // free the memory used for this stream
+
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _inStream;
+}
+
+void Mp3PspStream::deinitStream() {
+ DEBUG_ENTER_FUNC();
+
+ if (_state == MP3_STATE_INIT)
+ return;
+
+ // Deinit MAD
+ mad_header_finish(&_header);
+ mad_stream_finish(&_stream);
+
+ _state = MP3_STATE_EOS;
+}
+
+void Mp3PspStream::releaseStreamME() {
+ sceAudiocodecReleaseEDRAM(_codecParams);
+}
+
+void Mp3PspStream::decodeMP3Data() {
+ DEBUG_ENTER_FUNC();
+
+ do {
+ if (_state == MP3_STATE_INIT) {
+ initStream();
+ initStreamME();
+ }
+
+ if (_state == MP3_STATE_EOS)
+ return;
+
+ findValidHeader(); // seach for next valid header
+
+ while (_state == MP3_STATE_READY) {
+ _stream.error = MAD_ERROR_NONE;
+
+ uint32 frame_size = _stream.next_frame - _stream.this_frame;
+ uint32 samplesPerFrame = _header.layer == MAD_LAYER_III ? 576 : 1152; // Varies by layer
+ // calculate frame size -- try
+ //uint32 calc_frame_size = ((144 * _header.bitrate) / 22050) + (_header.flags & MAD_FLAG_PADDING ? 1 : 0);
+
+ // Get stereo/mono
+ uint32 multFactor = 1;
+ if (_header.mode != MAD_MODE_SINGLE_CHANNEL) // mono - x2 for 16bit
+ multFactor *= 2; // stereo - x4 for 16bit
+
+ PSP_DEBUG_PRINT("MP3 frame size[%d]. Samples[%d]. Multfactor[%d] pad[%d]\n", frame_size, samplesPerFrame, multFactor, _header.flags & MAD_FLAG_PADDING);
+ memcpy(_codecInBuffer, _stream.this_frame, frame_size); // we need it aligned
+
+ // set up parameters for ME
+ _codecParams[6] = (unsigned long)_codecInBuffer;
+ _codecParams[8] = (unsigned long)_pcmSamples;
+ _codecParams[7] = frame_size;
+ _codecParams[9] = samplesPerFrame * multFactor; // x2 for stereo
+
+ // debug
+#ifdef PRINT_BUFFERS
+ PSP_DEBUG_PRINT("mp3 frame:\n");
+ for (int i=0; i < (int)frame_size; i++) {
+ PSP_DEBUG_PRINT_SAMELN("%x ", _codecInBuffer[i]);
+ }
+ PSP_DEBUG_PRINT("\n");
+#endif
+ // Decode the next frame
+ // This function blocks. We'll want to put it in a thread
+ int ret = sceAudiocodecDecode(_codecParams, 0x1002);
+ if (ret < 0) {
+ PSP_ERROR("failed to decode MP3 data in ME. sceAudiocodecDecode returned 0x%x\n", ret);
+ // handle error here
+ }
+
+#ifdef PRINT_BUFFERS
+ PSP_DEBUG_PRINT("PCM frame:\n");
+ for (int i=0; i < (int)_codecParams[9]; i+=2) { // changed from i+=2
+ PSP_DEBUG_PRINT_SAMELN("%d ", (int16)_pcmSamples[i]);
+ }
+ PSP_DEBUG_PRINT("\n");
+#endif
+ _pcmLength = samplesPerFrame;
+ _posInFrame = 0;
+ break;
+ }
+ } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN);
+
+ if (_stream.error != MAD_ERROR_NONE) // catch EOS
+ _state = MP3_STATE_EOS;
+}
+
+void Mp3PspStream::readMP3DataIntoBuffer() {
+ DEBUG_ENTER_FUNC();
+
+ uint32 remaining = 0;
+
+ // Give up immediately if we already used up all data in the stream
+ if (_inStream->eos()) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ if (_stream.next_frame) {
+ // If there is still data in the MAD stream, we need to preserve it.
+ // Note that we use memmove, as we are reusing the same buffer,
+ // and hence the data regions we copy from and to may overlap.
+ remaining = _stream.bufend - _stream.next_frame;
+ assert(remaining < BUFFER_SIZE); // Paranoia check
+ memmove(_buf, _stream.next_frame, remaining); // TODO: may want another buffer
+ }
+
+ // Try to read the next block
+ uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining);
+ if (size <= 0) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ // Feed the data we just read into the stream decoder
+ _stream.error = MAD_ERROR_NONE;
+ mad_stream_buffer(&_stream, _buf, size + remaining); // just setup the pointers
+}
+
+bool Mp3PspStream::seek(const Timestamp &where) {
+ DEBUG_ENTER_FUNC();
+
+ if (where == _length) {
+ _state = MP3_STATE_EOS;
+ return true;
+ } else if (where > _length) {
+ return false;
+ }
+
+ const uint32 time = where.msecs();
+
+ mad_timer_t destination;
+ mad_timer_set(&destination, time / 1000, time % 1000, 1000);
+
+ // Check if we need to rewind
+ if (_state != MP3_STATE_READY || mad_timer_compare(destination, _totalTime) < 0) {
+ initStream();
+ initStreamME();
+ }
+
+ // The ME will need clear data no matter what once we seek?
+ //if (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS)
+ // initStreamME();
+
+ // Skip ahead
+ while (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS)
+ findValidHeader();
+
+ return (_state != MP3_STATE_EOS);
+}
+
+// Seek in the stream, finding the next valid header
+void Mp3PspStream::findValidHeader() {
+ DEBUG_ENTER_FUNC();
+
+ if (_state != MP3_STATE_READY)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3DataIntoBuffer();
+
+ while (_state != MP3_STATE_EOS) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next header.
+ if (mad_header_decode(&_header, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ readMP3DataIntoBuffer(); // Read more data
+ continue;
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ debug(6, "MP3PSPStream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3PSPStream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Sum up the total playback time so far
+ mad_timer_add(&_totalTime, _header.duration);
+ break;
+ }
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+int Mp3PspStream::readBuffer(int16 *buffer, const int numSamples) {
+ DEBUG_ENTER_FUNC();
+
+ int samples = 0;
+#ifdef PRINT_BUFFERS
+ int16 *debugBuffer = buffer;
+#endif
+
+ // Keep going as long as we have input available
+ while (samples < numSamples && _state != MP3_STATE_EOS) {
+ const int len = MIN(numSamples, samples + (int)(_pcmLength - _posInFrame) * MAD_NCHANNELS(&_header));
+
+ while (samples < len) {
+ *buffer++ = _pcmSamples[_posInFrame << 1];
+ samples++;
+ if (MAD_NCHANNELS(&_header) == 2) {
+ *buffer++ = _pcmSamples[(_posInFrame << 1) + 1];
+ samples++;
+ }
+ _posInFrame++; // always skip an extra sample since ME always outputs stereo
+ }
+
+ //memcpy(buffer, &_pcmSamples[_posInFrame], len << 1); // 16 bits
+ //_posInFrame += len; // next time we start from the middle
+
+ if (_posInFrame >= _pcmLength) {
+ // We used up all PCM data in the current frame -- read & decode more
+ decodeMP3Data();
+ }
+ }
+
+#ifdef PRINT_BUFFERS
+ PSP_INFO_PRINT("buffer:\n");
+ for (int i = 0; i<numSamples; i++)
+ PSP_INFO_PRINT("%d ", debugBuffer[i]);
+ PSP_INFO_PRINT("\n\n");
+#endif
+
+ return samples;
+}
+
+} // End of namespace Audio
+
+
diff --git a/backends/platform/psp/mp3.h b/backends/platform/psp/mp3.h
new file mode 100644
index 0000000000..f8802f930c
--- /dev/null
+++ b/backends/platform/psp/mp3.h
@@ -0,0 +1,121 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_MP3_PSP_H
+#define SOUND_MP3_PSP_H
+
+#include "common/types.h"
+#include "common/scummsys.h"
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+class Mp3PspStream : public SeekableAudioStream {
+protected:
+ enum State {
+ MP3_STATE_INIT, // Need to init the decoder
+ MP3_STATE_READY, // ready for processing data
+ MP3_STATE_EOS // end of data reached (may need to loop)
+ };
+
+ #define MAX_SAMPLES_PER_FRAME 2048 * 2
+ int16 _pcmSamples[MAX_SAMPLES_PER_FRAME] __attribute__((aligned(64))); // samples to output PCM data into
+ byte _codecInBuffer[3072] __attribute__((aligned(64))); // the codec always needs alignment
+ unsigned long _codecParams[65]__attribute__((aligned(64))); // TODO: change to struct
+
+ Common::SeekableReadStream *_inStream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ uint32 _pcmLength; // how many pcm samples we have (/2 for mono)
+
+ uint _posInFrame; // position in frame
+ State _state; // what state the stream is in
+
+ Timestamp _length;
+ uint32 _sampleRate;
+
+ mad_timer_t _totalTime;
+ mad_stream _stream; //
+ mad_header _header; // This is all we need from libmad
+
+ static bool _decoderInit; // has the decoder been initialized
+ static bool _decoderFail; // has the decoder failed to load
+
+ enum {
+ BUFFER_SIZE = 5 * 8192
+ };
+
+ // This buffer contains a slab of input data
+ byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+ void decodeMP3Data();
+ void readMP3DataIntoBuffer();
+
+ static bool loadStartAudioModule(const char *modname, int partition);
+ int initStream();
+ void findValidHeader();
+ void deinitStream();
+
+ // to init and uninit ME decoder
+ static bool initDecoder();
+ static bool stopDecoder();
+
+ // ME functions for stream
+ bool initStreamME();
+ void releaseStreamME();
+
+public:
+ Mp3PspStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
+ ~Mp3PspStream();
+
+ // This function avoids having to create streams when it's not possible
+ static inline bool isOkToCreateStream() {
+ if (_decoderFail) // fatal failure
+ return false;
+ if (!_decoderInit) // if we're not initialized
+ if (!initDecoder()) // check if we failed init
+ return false;
+ return true;
+ }
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return _state == MP3_STATE_EOS; }
+ bool isStereo() const { return MAD_NCHANNELS(&_header) == 2; }
+ int getRate() const { return _header.samplerate; }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+};
+
+} // End of namespace Audio
+
+#endif // #ifndef SOUND_MP3_PSP_H
diff --git a/backends/platform/psp/osys_psp.cpp b/backends/platform/psp/osys_psp.cpp
index a36ae1847f..2043a4bef2 100644
--- a/backends/platform/psp/osys_psp.cpp
+++ b/backends/platform/psp/osys_psp.cpp
@@ -37,7 +37,6 @@
#include "backends/platform/psp/psppixelformat.h"
#include "backends/platform/psp/osys_psp.h"
#include "backends/platform/psp/powerman.h"
-#include "backends/platform/psp/thread.h"
#include "backends/saves/psp/psp-saves.h"
#include "backends/timer/default/default-timer.h"
@@ -300,7 +299,7 @@ bool OSystem_PSP::pollEvent(Common::Event &event) {
}
uint32 OSystem_PSP::getMillis() {
- return PspThread::getMillis();
+ return _pspRtc.getMillis();
}
void OSystem_PSP::delayMillis(uint msecs) {
diff --git a/backends/platform/psp/osys_psp.h b/backends/platform/psp/osys_psp.h
index 3f075d0139..a6c84ba39a 100644
--- a/backends/platform/psp/osys_psp.h
+++ b/backends/platform/psp/osys_psp.h
@@ -40,6 +40,7 @@
#include "backends/platform/psp/input.h"
#include "backends/platform/psp/audio.h"
#include "backends/timer/psp/timer.h"
+#include "backends/platform/psp/thread.h"
#include <SDL.h>
@@ -59,6 +60,7 @@ private:
InputHandler _inputHandler;
PspAudio _audio;
PspTimer _pspTimer;
+ PspRtc _pspRtc;
void initSDL();
diff --git a/backends/platform/psp/psp.spec b/backends/platform/psp/psp.spec
index debdab3208..ac325b7fd6 100644
--- a/backends/platform/psp/psp.spec
+++ b/backends/platform/psp/psp.spec
@@ -1,3 +1,3 @@
%rename lib old_lib
*lib:
-%(old_lib) -lz -lstdc++ -lc -lm -lpspprof -lpspvfpu -lpspdebug -lpspgu -lpspge -lpspdisplay -lpspctrl -lpspsdk -lpsputility -lpspuser -lpsppower -lpsphprm -lpsprtc -lpspaudio -lpspkernel
+%(old_lib) -lz -lstdc++ -lc -lm -lpspprof -lpspvfpu -lpspdebug -lpspgu -lpspge -lpspdisplay -lpspctrl -lpspsdk -lpsputility -lpspuser -lpsppower -lpsphprm -lpsprtc -lpspaudio -lpspaudiocodec -lpspkernel
diff --git a/backends/platform/psp/thread.cpp b/backends/platform/psp/thread.cpp
index 88e7b6fe38..4e7d5eada9 100644
--- a/backends/platform/psp/thread.cpp
+++ b/backends/platform/psp/thread.cpp
@@ -29,6 +29,7 @@
#include <pspthreadman.h>
#include "backends/platform/psp/thread.h"
+#include "backends/platform/psp/trace.h"
void PspThread::delayMillis(uint32 ms) {
sceKernelDelayThread(ms * 1000);
@@ -38,15 +39,49 @@ void PspThread::delayMicros(uint32 us) {
sceKernelDelayThread(us);
}
-uint32 PspThread::getMillis() {
+void PspRtc::init() { // init our starting ticks
uint32 ticks[2];
sceRtcGetCurrentTick((u64 *)ticks);
- return (ticks[0]/1000);
+
+ _startMillis = ticks[0]/1000;
+ _startMicros = ticks[0];
+ //_lastMillis = ticks[0]/1000; //debug - only when we don't subtract startMillis
+}
+
+#define MS_LOOP_AROUND 4294967 /* We loop every 2^32 / 1000 = 71 minutes */
+#define MS_LOOP_CHECK 60000 /* Threading can cause weird mixups without this */
+
+// Note that after we fill up 32 bits ie 50 days we'll loop back to 0, which may cause
+// unpredictable results
+uint32 PspRtc::getMillis() {
+ uint32 ticks[2];
+
+ sceRtcGetCurrentTick((u64 *)ticks); // can introduce weird thread delays
+
+ uint32 millis = ticks[0]/1000;
+ millis -= _startMillis; // get ms since start of program
+
+ if ((int)_lastMillis - (int)millis > MS_LOOP_CHECK) { // we must have looped around
+ if (_looped == false) { // check to make sure threads do this once
+ _looped = true;
+ _milliOffset += MS_LOOP_AROUND; // add the needed offset
+ PSP_DEBUG_PRINT("looping around. last ms[%d], curr ms[%d]\n", _lastMillis, millis);
+ }
+ } else {
+ _looped = false;
+ }
+
+ _lastMillis = millis;
+
+ return millis + _milliOffset;
}
-uint32 PspThread::getMicros() {
+uint32 PspRtc::getMicros() {
uint32 ticks[2];
+
sceRtcGetCurrentTick((u64 *)ticks);
+ ticks[0] -= _startMicros;
+
return ticks[0];
}
diff --git a/backends/platform/psp/thread.h b/backends/platform/psp/thread.h
index e83eead68e..380159fa2d 100644
--- a/backends/platform/psp/thread.h
+++ b/backends/platform/psp/thread.h
@@ -32,8 +32,20 @@ class PspThread {
public:
static void delayMillis(uint32 ms);
static void delayMicros(uint32 us);
- static uint32 getMillis();
- static uint32 getMicros();
+};
+
+class PspRtc {
+private:
+ uint32 _startMillis;
+ uint32 _startMicros;
+ uint32 _lastMillis;
+ uint32 _milliOffset; // to prevent looping around of millis
+ bool _looped; // make sure we only loop once
+public:
+ PspRtc() : _startMillis(0), _startMicros(0), _lastMillis(0), _milliOffset(0), _looped(false) { init(); }
+ void init();
+ uint32 getMillis();
+ uint32 getMicros();
};
enum ThreadPriority {
diff --git a/backends/platform/samsungtv/samsungtv.cpp b/backends/platform/samsungtv/samsungtv.cpp
index 4f0f3a1e3e..aa79b92558 100644
--- a/backends/platform/samsungtv/samsungtv.cpp
+++ b/backends/platform/samsungtv/samsungtv.cpp
@@ -30,7 +30,6 @@
bool OSystem_SDL_SamsungTV::hasFeature(Feature f) {
return
(f == kFeatureAspectRatioCorrection) ||
- (f == kFeatureAutoComputeDirtyRects) ||
(f == kFeatureCursorHasPalette);
}
@@ -39,12 +38,6 @@ void OSystem_SDL_SamsungTV::setFeatureState(Feature f, bool enable) {
case kFeatureAspectRatioCorrection:
setAspectRatioCorrection(enable);
break;
- case kFeatureAutoComputeDirtyRects:
- if (enable)
- _modeFlags |= DF_WANT_RECT_OPTIM;
- else
- _modeFlags &= ~DF_WANT_RECT_OPTIM;
- break;
default:
break;
}
@@ -56,8 +49,6 @@ bool OSystem_SDL_SamsungTV::getFeatureState(Feature f) {
switch (f) {
case kFeatureAspectRatioCorrection:
return _videoMode.aspectRatioCorrection;
- case kFeatureAutoComputeDirtyRects:
- return _modeFlags & DF_WANT_RECT_OPTIM;
default:
return false;
}
diff --git a/backends/platform/symbian/src/SymbianOS.cpp b/backends/platform/symbian/src/SymbianOS.cpp
index f8df2a5d5c..2ae47b07a8 100644
--- a/backends/platform/symbian/src/SymbianOS.cpp
+++ b/backends/platform/symbian/src/SymbianOS.cpp
@@ -83,7 +83,6 @@ bool OSystem_SDL_Symbian::hasFeature(Feature f) {
switch (f) {
case kFeatureFullscreenMode:
case kFeatureAspectRatioCorrection:
- case kFeatureAutoComputeDirtyRects:
case kFeatureCursorHasPalette:
#ifdef USE_VIBRA_SE_PXXX
case kFeatureVibration:
diff --git a/backends/platform/wince/wince-sdl.cpp b/backends/platform/wince/wince-sdl.cpp
index 7ce689fb63..b3480702b5 100644
--- a/backends/platform/wince/wince-sdl.cpp
+++ b/backends/platform/wince/wince-sdl.cpp
@@ -926,7 +926,7 @@ const OSystem::GraphicsMode *OSystem_WINCE3::getSupportedGraphicsModes() const {
}
bool OSystem_WINCE3::hasFeature(Feature f) {
- return (f == kFeatureAutoComputeDirtyRects || f == kFeatureVirtualKeyboard);
+ return (f == kFeatureVirtualKeyboard);
}
void OSystem_WINCE3::setFeatureState(Feature f, bool enable) {
@@ -1151,14 +1151,12 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 1;
_scaleFactorYd = 1;
_scalerProc = DownscaleHorizByThreeQuarters;
- _modeFlags = 0;
} else {
_scaleFactorXm = 1;
_scaleFactorXd = 1;
_scaleFactorYm = 1;
_scaleFactorYd = 1;
_scalerProc = Normal1x;
- _modeFlags = 0;
}
} else if ( _orientationLandscape && (_videoMode.screenWidth == 320 || !_videoMode.screenWidth)) {
if (!_panelVisible && !_hasSmartphoneResolution && !_overlayVisible && _canBeAspectScaled) {
@@ -1167,7 +1165,6 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 6;
_scaleFactorYd = 5;
_scalerProc = Normal1xAspect;
- _modeFlags = 0;
_videoMode.aspectRatioCorrection = true;
} else {
_scaleFactorXm = 1;
@@ -1175,7 +1172,6 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 1;
_scaleFactorYd = 1;
_scalerProc = Normal1x;
- _modeFlags = 0;
}
} else if (_videoMode.screenWidth == 640 && !(isOzone() && (getScreenWidth() >= 640 || getScreenHeight() >= 640))) {
_scaleFactorXm = 1;
@@ -1183,14 +1179,12 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 1;
_scaleFactorYd = 2;
_scalerProc = DownscaleAllByHalf;
- _modeFlags = 0;
} else if (_videoMode.screenWidth == 640 && (isOzone() && (getScreenWidth() >= 640 || getScreenHeight() >= 640))) {
_scaleFactorXm = 1;
_scaleFactorXd = 1;
_scaleFactorYm = 1;
_scaleFactorYd = 1;
_scalerProc = Normal1x;
- _modeFlags = 0;
}
return true;
@@ -1203,7 +1197,6 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 12;
_scaleFactorYd = 5;
_scalerProc = Normal2xAspect;
- _modeFlags = 0;
_videoMode.aspectRatioCorrection = true;
} else if ( (_panelVisible || _overlayVisible) && _canBeAspectScaled ) {
_scaleFactorXm = 2;
@@ -1211,7 +1204,6 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 2;
_scaleFactorYd = 1;
_scalerProc = Normal2x;
- _modeFlags = 0;
}
return true;
}
@@ -1232,7 +1224,6 @@ bool OSystem_WINCE3::update_scalers() {
_scaleFactorYm = 7;
_scaleFactorYd = 8;
_scalerProc = SmartphoneLandscape;
- _modeFlags = 0;
initZones();
return true;
}
@@ -1824,7 +1815,6 @@ void OSystem_WINCE3::copyRectToOverlay(const OverlayColor *buf, int pitch, int x
return;
// Mark the modified region as dirty
- _cksumValid = false;
addDirtyRect(x, y, w, h);
undrawMouse();
@@ -1851,40 +1841,31 @@ void OSystem_WINCE3::copyRectToScreen(const byte *src, int pitch, int x, int y,
Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
- if (((long)src & 3) == 0 && pitch == _videoMode.screenWidth && x == 0 && y == 0 &&
- w == _videoMode.screenWidth && h == _videoMode.screenHeight && _modeFlags & DF_WANT_RECT_OPTIM) {
- /* Special, optimized case for full screen updates.
- * It tries to determine what areas were actually changed,
- * and just updates those, on the actual display. */
- addDirtyRgnAuto(src);
- } else {
- /* Clip the coordinates */
- if (x < 0) {
- w += x;
- src -= x;
- x = 0;
- }
+ /* Clip the coordinates */
+ if (x < 0) {
+ w += x;
+ src -= x;
+ x = 0;
+ }
- if (y < 0) {
- h += y;
- src -= y * pitch;
- y = 0;
- }
+ if (y < 0) {
+ h += y;
+ src -= y * pitch;
+ y = 0;
+ }
- if (w > _videoMode.screenWidth - x) {
- w = _videoMode.screenWidth - x;
- }
+ if (w > _videoMode.screenWidth - x) {
+ w = _videoMode.screenWidth - x;
+ }
- if (h > _videoMode.screenHeight - y) {
- h = _videoMode.screenHeight - y;
- }
+ if (h > _videoMode.screenHeight - y) {
+ h = _videoMode.screenHeight - y;
+ }
- if (w <= 0 || h <= 0)
- return;
+ if (w <= 0 || h <= 0)
+ return;
- _cksumValid = false;
- addDirtyRect(x, y, w, h);
- }
+ addDirtyRect(x, y, w, h);
undrawMouse();