From 8b54efd8a62a68a0544dfc5438c4efe42c0f79f1 Mon Sep 17 00:00:00 2001 From: Yotam Barnoy Date: Wed, 26 May 2010 14:43:25 +0000 Subject: PSP: implemented basic file cache. Turns out the PSP reads 1 byte as fast as it reads 1 KB. svn-id: r49243 --- backends/fs/psp/psp-stream.cpp | 249 ++++++++++++++++++++++++++++------------- backends/fs/psp/psp-stream.h | 31 +++-- 2 files changed, 196 insertions(+), 84 deletions(-) (limited to 'backends') diff --git a/backends/fs/psp/psp-stream.cpp b/backends/fs/psp/psp-stream.cpp index 8cb7dfea17..d3d6311836 100644 --- a/backends/fs/psp/psp-stream.cpp +++ b/backends/fs/psp/psp-stream.cpp @@ -32,28 +32,26 @@ #include +#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__ //#define __PSP_DEBUG_FUNCS__ /* For debugging function calls */ //#define __PSP_DEBUG_PRINT__ /* For debug printouts */ #include "backends/platform/psp/trace.h" 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), + _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 +61,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 +83,16 @@ void *PSPIoStream::open() { _handle = fopen(_path.c_str(), _writeMode ? "wb" : "rb"); // open + if (_handle) { + // Get the file size + fseek((FILE *)_handle, 0, SEEK_END); // go to the end + _fileSize = ftell((FILE *)_handle); + fseek((FILE *)_handle, 0, SEEK_SET); // back to the beginning + + // Allocate the cache + _cache = (char *)memalign(64, CACHE_SIZE); + } + PowerMan.registerSuspend(this); // Register with the powermanager to be suspended PowerMan.endCriticalSection(); @@ -91,100 +102,177 @@ 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 (_pos >= _fileSize); } 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[%d], whence[%d], _pos[%d], _physPos[%d]\n", offs, whence, _pos, _physicalPos); + bool success = true; + + 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 || posToSearchFor > _fileSize) { _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); + + // See if we can find it in cache + if (isOffsetInCache(posToSearchFor)) { + PSP_DEBUG_PRINT("seek offset[%d] found in cache. Cache starts[%d]\n", posToSearchFor, _cacheStartOffset); + _inCache = true; + } else { // not in cache + _inCache = false; + } + _pos = posToSearchFor; + return success; } +// for debugging +/* +void printBuffer(byte *ptr, uint32 len) { + for (int i = 0; i < len; i++) { + PSP_INFO_PRINT("%x ", *ptr); + ptr++; + } + PSP_INFO_PRINT("\n"); +}*/ + uint32 PSPIoStream::read(void *ptr, uint32 len) { DEBUG_ENTER_FUNC(); - // Check if we can access the file + PSP_DEBUG_PRINT_FUNC("filename[%s], len[%d], ptr[%p]\n", _path.c_str(), len, ptr); + + 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; + + // 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 %d bytes from cache to %p. pos[%d] physPos[%d] cacheStart[%d]\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 + // debug + //if (len < 10) printBuffer((byte *)ptr, len); + 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 %d bytes from physicalPos[%d]. cacheStart[%d], pos[%d], fileSize[%d]\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 %d bytes. Only got %d\n", lenToCopyToCache, ret); + _ferror = true; + clearerr((FILE *)_handle); + } + _cacheStartOffset = _physicalPos; + _inCache = true; + + _physicalPos += ret; + + PSP_DEBUG_PRINT("copying %d 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 %d bytes from file to %p. Pos[%d], physPos[%d]\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 [%d] instead of len[%d]\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; + // debug + //if (len < 10) printBuffer((byte *)ptr, len); + 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) { @@ -195,9 +283,17 @@ uint32 PSPIoStream::write(const void *ptr, uint32 len) { PSP_DEBUG_PRINT_FUNC("filename[%s], len[%d]\n", _path.c_str(), len); + 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; @@ -286,6 +382,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..10d5016852 100644 --- a/backends/fs/psp/psp-stream.h +++ b/backends/fs/psp/psp-stream.h @@ -35,25 +35,38 @@ */ 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 + 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: /** -- cgit v1.2.3 From f7ba7b4684c86546151928cf5de669af04d237c1 Mon Sep 17 00:00:00 2001 From: Yotam Barnoy Date: Thu, 27 May 2010 06:11:50 +0000 Subject: PSP: fixed issue with handling of EOS. Caused crashes. svn-id: r49257 --- backends/fs/psp/psp-stream.cpp | 100 +++++++++++++++++++++++++++-------------- backends/fs/psp/psp-stream.h | 1 + 2 files changed, 67 insertions(+), 34 deletions(-) (limited to 'backends') diff --git a/backends/fs/psp/psp-stream.cpp b/backends/fs/psp/psp-stream.cpp index d3d6311836..9bcbe9d7cf 100644 --- a/backends/fs/psp/psp-stream.cpp +++ b/backends/fs/psp/psp-stream.cpp @@ -35,15 +35,37 @@ #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__ -//#define __PSP_DEBUG_FUNCS__ /* For debugging function calls */ +//#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), _ferror(false), _pos(0), - _physicalPos(0), _fileSize(0), _inCache(false), + _physicalPos(0), _fileSize(0), _inCache(false), _eos(false), _cacheStartOffset(-1), _cache(0), _errorSuspend(0), _errorSource(0), _errorPos(0), _errorHandle(0), _suspendCount(0) { @@ -102,6 +124,7 @@ void *PSPIoStream::open() { bool PSPIoStream::err() const { DEBUG_ENTER_FUNC(); + 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", @@ -116,7 +139,7 @@ void PSPIoStream::clearErr() { } bool PSPIoStream::eos() const { - return (_pos >= _fileSize); + return _eos; } int32 PSPIoStream::pos() const { @@ -129,8 +152,8 @@ int32 PSPIoStream::size() const { bool PSPIoStream::seek(int32 offs, int whence) { DEBUG_ENTER_FUNC(); - PSP_DEBUG_PRINT_FUNC("offset[%d], whence[%d], _pos[%d], _physPos[%d]\n", offs, whence, _pos, _physicalPos); - bool success = true; + 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) { @@ -144,43 +167,44 @@ bool PSPIoStream::seek(int32 offs, int whence) { posToSearchFor += offs; // Check for bad values - if (posToSearchFor < 0 || posToSearchFor > _fileSize) { + if (posToSearchFor < 0) { _ferror = true; return false; } + 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[%d] found in cache. Cache starts[%d]\n", posToSearchFor, _cacheStartOffset); + 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 success; + return true; } -// for debugging -/* -void printBuffer(byte *ptr, uint32 len) { - for (int i = 0; i < len; i++) { - PSP_INFO_PRINT("%x ", *ptr); - ptr++; - } - PSP_INFO_PRINT("\n"); -}*/ - uint32 PSPIoStream::read(void *ptr, uint32 len) { DEBUG_ENTER_FUNC(); - PSP_DEBUG_PRINT_FUNC("filename[%s], len[%d], ptr[%p]\n", _path.c_str(), len, ptr); + 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) + if (lenFromFile > lenRemainingInFile) { lenFromFile = lenRemainingInFile; + _eos = true; + } // Are we in cache? if (_inCache && isCacheValid()) { @@ -188,7 +212,7 @@ uint32 PSPIoStream::read(void *ptr, uint32 len) { // 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 %d bytes from cache to %p. pos[%d] physPos[%d] cacheStart[%d]\n", lenFromCache, destPtr, _pos, _physicalPos, _cacheStartOffset); + 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; @@ -198,8 +222,10 @@ uint32 PSPIoStream::read(void *ptr, uint32 len) { lenRemainingInFile -= lenFromCache; // since we moved pos destPtr += lenFromCache; } else { // we're done - // debug - //if (len < 10) printBuffer((byte *)ptr, len); +#ifdef DEBUG_BUFFERS + printBuffer((byte *)ptr, len); +#endif + return lenFromCache; // how much we actually read } } @@ -214,11 +240,11 @@ uint32 PSPIoStream::read(void *ptr, uint32 len) { // 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 %d bytes from physicalPos[%d]. cacheStart[%d], pos[%d], fileSize[%d]\n", lenToCopyToCache, _physicalPos, _cacheStartOffset, _pos, _fileSize); + 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 %d bytes. Only got %d\n", lenToCopyToCache, ret); + PSP_ERROR("in filling cache, failed to get 0x%x bytes. Only got 0x%x\n", lenToCopyToCache, ret); _ferror = true; clearerr((FILE *)_handle); } @@ -227,21 +253,21 @@ uint32 PSPIoStream::read(void *ptr, uint32 len) { _physicalPos += ret; - PSP_DEBUG_PRINT("copying %d bytes from cache to %p\n", lenFromFile, destPtr); + 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 %d bytes from file to %p. Pos[%d], physPos[%d]\n", lenFromFile, destPtr, _pos, _physicalPos); + 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 [%d] instead of len[%d]\n", ret, lenFromFile); + PSP_ERROR("fread returned [0x%x] instead of len[0x%x]\n", ret, lenFromFile); _ferror = true; clearerr((FILE *)_handle); _errorSource = 4; @@ -251,8 +277,10 @@ uint32 PSPIoStream::read(void *ptr, uint32 len) { PowerMan.endCriticalSection(); - // debug - //if (len < 10) printBuffer((byte *)ptr, len); +#ifdef DEBUG_BUFFERS + printBuffer((byte *)ptr, len); +#endif + return lenFromCache + lenFromFile; // total of what was copied } @@ -281,8 +309,12 @@ 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); @@ -300,7 +332,7 @@ uint32 PSPIoStream::write(const void *ptr, uint32 len) { 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(); @@ -320,7 +352,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(); diff --git a/backends/fs/psp/psp-stream.h b/backends/fs/psp/psp-stream.h index 10d5016852..9fd1ad0470 100644 --- a/backends/fs/psp/psp-stream.h +++ b/backends/fs/psp/psp-stream.h @@ -41,6 +41,7 @@ protected: 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, -- cgit v1.2.3 From 5f53683cab48becc1d790132cc46ff29a1a3ae26 Mon Sep 17 00:00:00 2001 From: Yotam Barnoy Date: Thu, 27 May 2010 06:21:37 +0000 Subject: PSP: fixed a screen shake bug svn-id: r49258 --- backends/platform/psp/display_client.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'backends') 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); -- cgit v1.2.3 From 1c32db1e084698f6594a66e142c217e83cab60d9 Mon Sep 17 00:00:00 2001 From: Torbjörn Andersson Date: Sat, 29 May 2010 08:09:47 +0000 Subject: Removed some unused variables, found by cppcheck. svn-id: r49306 --- backends/platform/gp2x/graphics.cpp | 5 ----- backends/platform/gp2xwiz/gp2xwiz-graphics.cpp | 5 ----- backends/platform/linuxmoto/linuxmoto-graphics.cpp | 5 ----- backends/platform/sdl/graphics.cpp | 5 ----- 4 files changed, 20 deletions(-) (limited to 'backends') diff --git a/backends/platform/gp2x/graphics.cpp b/backends/platform/gp2x/graphics.cpp index 243c37dcb1..449e744e8b 100644 --- a/backends/platform/gp2x/graphics.cpp +++ b/backends/platform/gp2x/graphics.cpp @@ -1502,7 +1502,6 @@ void OSystem_GP2X::drawMouse() { SDL_Rect zoomdst; SDL_Rect dst; int scale; - int width, height; int hotX, hotY; int tmpScreenWidth, tmpScreenHeight; @@ -1523,16 +1522,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/gp2xwiz-graphics.cpp b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp index b20087e6d9..28c03db4b0 100644 --- a/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp +++ b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp @@ -154,7 +154,6 @@ void OSystem_GP2XWIZ::drawMouse() { SDL_Rect dst; int scale; - int width, height; int hotX, hotY; if (_videoMode.mode == GFX_HALF && !_overlayVisible){ @@ -167,16 +166,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/linuxmoto/linuxmoto-graphics.cpp b/backends/platform/linuxmoto/linuxmoto-graphics.cpp index 8f718c82f6..482ed772c7 100644 --- a/backends/platform/linuxmoto/linuxmoto-graphics.cpp +++ b/backends/platform/linuxmoto/linuxmoto-graphics.cpp @@ -173,7 +173,6 @@ void OSystem_LINUXMOTO::drawMouse() { SDL_Rect dst; int scale; - int width, height; int hotX, hotY; if (_videoMode.mode == GFX_HALF && !_overlayVisible) { @@ -186,16 +185,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/sdl/graphics.cpp b/backends/platform/sdl/graphics.cpp index b3ef101e9e..e0bf7565fb 100644 --- a/backends/platform/sdl/graphics.cpp +++ b/backends/platform/sdl/graphics.cpp @@ -1771,7 +1771,6 @@ void OSystem_SDL::drawMouse() { SDL_Rect dst; int scale; - int width, height; int hotX, hotY; dst.x = _mouseCurState.x; @@ -1779,16 +1778,12 @@ void OSystem_SDL::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; -- cgit v1.2.3 From 989c1fddddbe63075a9d4b9860476a080711164c Mon Sep 17 00:00:00 2001 From: Neil Millstone Date: Sat, 29 May 2010 20:30:54 +0000 Subject: DS: Various fixes to allow the DS port to compile on DevkitARM r30 and libnds 1.4.3. svn-id: r49313 --- backends/platform/ds/arm7/source/main.cpp | 4 ++-- backends/platform/ds/arm9/makefile | 2 +- backends/platform/ds/arm9/source/dsmain.cpp | 4 ++-- backends/platform/ds/commoninclude/NDS/scummvm_ipc.h | 12 ++++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) (limited to 'backends') 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 #include #include -//#include // not needed in current libnds +#include // Needed for SOUND_CR #include ////////////////////////////////////////////////////////////////////// #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/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..4828ae2192 100644 --- a/backends/platform/ds/arm9/source/dsmain.cpp +++ b/backends/platform/ds/arm9/source/dsmain.cpp @@ -701,7 +701,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 +939,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++) { 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; + + //--------------------------------------------------------------------------------- -- cgit v1.2.3 From 6884ffc2918d192c9b091d4836f8c566ef3a7fcb Mon Sep 17 00:00:00 2001 From: Yotam Barnoy Date: Sun, 30 May 2010 09:47:00 +0000 Subject: PSP: added preliminary support for using ME hardware to play MP3 files. If the decoder fails to load, MAD is used instead. Disable with DISABLE_PSP_MP3. svn-id: r49319 --- backends/platform/psp/Makefile | 6 +- backends/platform/psp/module.mk | 3 +- backends/platform/psp/mp3.cpp | 487 ++++++++++++++++++++++++++++++++++++++++ backends/platform/psp/mp3.h | 121 ++++++++++ backends/platform/psp/psp.spec | 2 +- 5 files changed, 615 insertions(+), 4 deletions(-) create mode 100644 backends/platform/psp/mp3.cpp create mode 100644 backends/platform/psp/mp3.h (limited to 'backends') 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/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 +#include +#include +#include +#include +#include +#include +#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 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/source/dsmain.cpp b/backends/platform/ds/arm9/source/dsmain.cpp index 4828ae2192..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); @@ -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"); -- cgit v1.2.3 From c9a38abf605197c4641b74fcef06d44d8d87b2d7 Mon Sep 17 00:00:00 2001 From: Tony Puccinelli Date: Mon, 31 May 2010 22:41:46 +0000 Subject: modifications to get the ps2 to use the ps2loader svn-id: r49363 --- backends/module.mk | 1 + backends/platform/ps2/module.mk | 3 ++- backends/platform/ps2/systemps2.cpp | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) (limited to 'backends') diff --git a/backends/module.mk b/backends/module.mk index 59df56b468..46c9e166a6 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -35,6 +35,7 @@ MODULE_OBJS := \ plugins/sdl/sdl-provider.o \ plugins/win32/win32-provider.o \ plugins/psp/psp-provider.o \ + plugins/ps2/ps2-provider.o \ saves/savefile.o \ saves/default/default-saves.o \ saves/posix/posix-saves.o \ diff --git a/backends/platform/ps2/module.mk b/backends/platform/ps2/module.mk index 86b12cb668..69a28b93c4 100644 --- a/backends/platform/ps2/module.mk +++ b/backends/platform/ps2/module.mk @@ -16,7 +16,8 @@ MODULE_OBJS := \ systemps2.o \ ps2mutex.o \ ps2time.o \ - ps2debug.o + ps2debug.o \ + ps2loader.o MODULE_DIRS += \ backends/platform/ps2/ diff --git a/backends/platform/ps2/systemps2.cpp b/backends/platform/ps2/systemps2.cpp index 49d583d1a1..fe8cc6b29c 100644 --- a/backends/platform/ps2/systemps2.cpp +++ b/backends/platform/ps2/systemps2.cpp @@ -59,6 +59,7 @@ #include "backends/platform/ps2/ps2debug.h" #include "backends/fs/ps2/ps2-fs-factory.h" +#include "backends/plugins/ps2/ps2-provider.h" #include "backends/saves/default/default-saves.h" #include "common/config-manager.h" @@ -132,6 +133,10 @@ extern "C" int main(int argc, char *argv[]) { g_systemPs2->init(); +#ifdef DYNAMIC_MODULES + PluginManager::instance().addPluginProvider(new PS2PluginProvider()); +#endif + sioprintf("init done. starting ScummVM.\n"); int res = scummvm_main(argc, argv); sioprintf("scummvm_main terminated: %d\n", res); -- cgit v1.2.3 From 9d72dde5e7d3e92e7e870350c0b24bf556e425f6 Mon Sep 17 00:00:00 2001 From: Jordi Vilalta Prat Date: Mon, 31 May 2010 22:58:22 +0000 Subject: Fix trunk compilation (don't try to compile non-existing files) svn-id: r49365 --- backends/module.mk | 1 - 1 file changed, 1 deletion(-) (limited to 'backends') diff --git a/backends/module.mk b/backends/module.mk index 46c9e166a6..59df56b468 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -35,7 +35,6 @@ MODULE_OBJS := \ plugins/sdl/sdl-provider.o \ plugins/win32/win32-provider.o \ plugins/psp/psp-provider.o \ - plugins/ps2/ps2-provider.o \ saves/savefile.o \ saves/default/default-saves.o \ saves/posix/posix-saves.o \ -- cgit v1.2.3 From 400542a1fe688bb702a89333c833bc1d89dd1ed4 Mon Sep 17 00:00:00 2001 From: Jordi Vilalta Prat Date: Mon, 31 May 2010 23:04:30 +0000 Subject: Revert the rest of 49362, which wasn't meant for trunk svn-id: r49366 --- backends/platform/ps2/module.mk | 3 +-- backends/platform/ps2/systemps2.cpp | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'backends') diff --git a/backends/platform/ps2/module.mk b/backends/platform/ps2/module.mk index 69a28b93c4..86b12cb668 100644 --- a/backends/platform/ps2/module.mk +++ b/backends/platform/ps2/module.mk @@ -16,8 +16,7 @@ MODULE_OBJS := \ systemps2.o \ ps2mutex.o \ ps2time.o \ - ps2debug.o \ - ps2loader.o + ps2debug.o MODULE_DIRS += \ backends/platform/ps2/ diff --git a/backends/platform/ps2/systemps2.cpp b/backends/platform/ps2/systemps2.cpp index fe8cc6b29c..49d583d1a1 100644 --- a/backends/platform/ps2/systemps2.cpp +++ b/backends/platform/ps2/systemps2.cpp @@ -59,7 +59,6 @@ #include "backends/platform/ps2/ps2debug.h" #include "backends/fs/ps2/ps2-fs-factory.h" -#include "backends/plugins/ps2/ps2-provider.h" #include "backends/saves/default/default-saves.h" #include "common/config-manager.h" @@ -133,10 +132,6 @@ extern "C" int main(int argc, char *argv[]) { g_systemPs2->init(); -#ifdef DYNAMIC_MODULES - PluginManager::instance().addPluginProvider(new PS2PluginProvider()); -#endif - sioprintf("init done. starting ScummVM.\n"); int res = scummvm_main(argc, argv); sioprintf("scummvm_main terminated: %d\n", res); -- cgit v1.2.3 From caf1a6bee4bb60014290a2208ec0daa0c68496cd Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 1 Jun 2010 15:38:09 +0000 Subject: Remove dead code from PS2 and GP2x backends svn-id: r49374 --- backends/platform/gp2x/gp2x-common.h | 18 ------------------ backends/platform/gp2x/gp2x.cpp | 25 +------------------------ backends/platform/ps2/systemps2.cpp | 17 ----------------- backends/platform/ps2/systemps2.h | 6 ------ 4 files changed, 1 insertion(+), 65 deletions(-) (limited to 'backends') diff --git a/backends/platform/gp2x/gp2x-common.h b/backends/platform/gp2x/gp2x-common.h index 79eb3f65cf..9f3a79ff97 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(); @@ -206,11 +193,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 }; diff --git a/backends/platform/gp2x/gp2x.cpp b/backends/platform/gp2x/gp2x.cpp index 3d4ed51c1f..8669671c4e 100644 --- a/backends/platform/gp2x/gp2x.cpp +++ b/backends/platform/gp2x/gp2x.cpp @@ -246,7 +246,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), _dirtyChecksums(0), _mouseVisible(false), _mouseNeedsRedraw(false), _mouseData(0), _mouseSurface(0), _mouseOrigSurface(0), _cursorTargetScale(1), _cursorPaletteDisabled(true), _joystick(0), @@ -650,26 +650,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/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); -- cgit v1.2.3 From 4cb3a2b41b5f92fda50f6db15e4e69be7dd0129a Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Tue, 1 Jun 2010 17:03:43 +0000 Subject: This hopefully fixes the compilation of the GP2x backend. svn-id: r49378 --- backends/platform/gp2x/gp2x-common.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'backends') diff --git a/backends/platform/gp2x/gp2x-common.h b/backends/platform/gp2x/gp2x-common.h index 9f3a79ff97..8183a87300 100644 --- a/backends/platform/gp2x/gp2x-common.h +++ b/backends/platform/gp2x/gp2x-common.h @@ -150,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); -- cgit v1.2.3 From 93867d567fd76f909bfbb64f8300d6841af584e2 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 1 Jun 2010 22:28:24 +0000 Subject: Inline palettes in SDL backend into class OSystem_SDL svn-id: r49383 --- backends/platform/sdl/sdl.cpp | 10 +++------- backends/platform/sdl/sdl.h | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) (limited to 'backends') diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index 9fa43cb389..c947e32cd8 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -251,9 +251,9 @@ OSystem_SDL::OSystem_SDL() _screenIsLocked(false), _graphicsMutex(0), _transactionMode(kTransactionNone) { - // allocate palette storage - _currentPalette = (SDL_Color *)calloc(sizeof(SDL_Color), 256); - _cursorPalette = (SDL_Color *)calloc(sizeof(SDL_Color), 256); + // clear palette storage + memset(_currentPalette, 0, sizeof(_currentPalette)); + memset(_cursorPalette, 0, sizeof(_cursorPalette)); _mouseBackup.x = _mouseBackup.y = _mouseBackup.w = _mouseBackup.h = 0; @@ -282,8 +282,6 @@ OSystem_SDL::~OSystem_SDL() { closeMixer(); free(_dirtyChecksums); - free(_currentPalette); - free(_cursorPalette); free(_mouseData); delete _savefile; @@ -513,8 +511,6 @@ void OSystem_SDL::deinit() { closeMixer(); free(_dirtyChecksums); - free(_currentPalette); - free(_cursorPalette); free(_mouseData); delete _timer; diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index c850ec5ab1..bfaabab80a 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -422,11 +422,11 @@ protected: int _newShakePos; // Palette data - SDL_Color *_currentPalette; + SDL_Color _currentPalette[256]; uint _paletteDirtyStart, _paletteDirtyEnd; // Cursor palette data - SDL_Color *_cursorPalette; + SDL_Color _cursorPalette[256]; /** * Mutex which prevents multiple threads from interfering with each other -- cgit v1.2.3 From 9f14e43d16a45c37b041ed2e3041fd8e7690121d Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 1 Jun 2010 22:29:55 +0000 Subject: Fix bug #2999153: "IPHONE: No grabPalette() implementation" svn-id: r49384 --- backends/platform/iphone/osys_video.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'backends') 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 >(_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) { -- cgit v1.2.3 From 767edc91faebdc1a60e5c8b3764b169dc9807ea5 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 2 Jun 2010 00:52:57 +0000 Subject: OSYSTEM: Get rid of kFeatureAutoComputeDirtyRects svn-id: r49388 --- backends/platform/gp2x/gp2x-common.h | 11 -- backends/platform/gp2x/gp2x.cpp | 15 +-- backends/platform/gp2x/graphics.cpp | 137 +++------------------ backends/platform/gp2xwiz/gp2xwiz-graphics.cpp | 5 - backends/platform/linuxmoto/linuxmoto-graphics.cpp | 5 - backends/platform/samsungtv/samsungtv.cpp | 9 -- backends/platform/sdl/graphics.cpp | 100 +-------------- backends/platform/sdl/sdl.cpp | 15 +-- backends/platform/sdl/sdl.h | 11 -- backends/platform/symbian/src/SymbianOS.cpp | 1 - backends/platform/wince/wince-sdl.cpp | 61 ++++----- 11 files changed, 44 insertions(+), 326 deletions(-) (limited to 'backends') diff --git a/backends/platform/gp2x/gp2x-common.h b/backends/platform/gp2x/gp2x-common.h index 8183a87300..b54e2d4d4f 100644 --- a/backends/platform/gp2x/gp2x-common.h +++ b/backends/platform/gp2x/gp2x-common.h @@ -191,10 +191,6 @@ protected: bool _overlayVisible; Graphics::PixelFormat _overlayFormat; - enum { - DF_WANT_RECT_OPTIM = 1 << 0 - }; - enum { kTransactionNone = 0, kTransactionActive = 1, @@ -235,7 +231,6 @@ protected: Graphics::Surface _framebuffer; /** Current video mode flags (see DF_* constants) */ - uint32 _modeFlags; bool _modeChanged; int _screenChangeCount; @@ -252,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 @@ -351,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 8669671c4e..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), - _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); diff --git a/backends/platform/gp2x/graphics.cpp b/backends/platform/gp2x/graphics.cpp index 449e744e8b..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) diff --git a/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp b/backends/platform/gp2xwiz/gp2xwiz-graphics.cpp index 28c03db4b0..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() { diff --git a/backends/platform/linuxmoto/linuxmoto-graphics.cpp b/backends/platform/linuxmoto/linuxmoto-graphics.cpp index 482ed772c7..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() { 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/sdl/graphics.cpp b/backends/platform/sdl/graphics.cpp index e0bf7565fb..82670cfcb7 100644 --- a/backends/platform/sdl/graphics.cpp +++ b/backends/platform/sdl/graphics.cpp @@ -469,12 +469,7 @@ void OSystem_SDL::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_SDL::effectiveScreenHeight() const { @@ -976,16 +971,7 @@ void OSystem_SDL::copyRectToScreen(const byte *src, int pitch, int x, int y, int 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 { - _cksumValid = false; - addDirtyRect(x, y, w, h); - } + addDirtyRect(x, y, w, h); // Try to lock the screen surface if (SDL_LockSurface(_screen) == -1) @@ -1131,89 +1117,6 @@ void OSystem_SDL::addDirtyRect(int x, int y, int w, int h, bool realCoordinates) } } - -void OSystem_SDL::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_SDL::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_SDL::getHeight() { return _videoMode.screenHeight; } @@ -1432,7 +1335,6 @@ void OSystem_SDL::copyRectToOverlay(const OverlayColor *buf, int pitch, int x, i return; // Mark the modified region as dirty - _cksumValid = false; addDirtyRect(x, y, w, h); if (SDL_LockSurface(_overlayscreen) == -1) diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index c947e32cd8..f8ae824acf 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -149,7 +149,6 @@ void OSystem_SDL::initBackend() { memset(&_videoMode, 0, sizeof(_videoMode)); memset(&_transactionDetails, 0, sizeof(_transactionDetails)); - _cksumValid = false; #if !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) && defined(USE_SCALERS) _videoMode.mode = GFX_DOUBLESIZE; _videoMode.scaleFactor = 2; @@ -163,7 +162,6 @@ void OSystem_SDL::initBackend() { _scalerProc = Normal1x; #endif _scalerType = 0; - _modeFlags = 0; #if !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) _videoMode.fullscreen = ConfMan.getBool("fullscreen"); @@ -233,7 +231,7 @@ OSystem_SDL::OSystem_SDL() #endif _overlayVisible(false), _overlayscreen(0), _tmpscreen2(0), - _cdrom(0), _scalerProc(0), _modeChanged(false), _screenChangeCount(0), _dirtyChecksums(0), + _cdrom(0), _scalerProc(0), _modeChanged(false), _screenChangeCount(0), _scrollLock(false), _mouseVisible(false), _mouseNeedsRedraw(false), _mouseData(0), _mouseSurface(0), _mouseOrigSurface(0), _cursorTargetScale(1), _cursorPaletteDisabled(true), @@ -281,7 +279,6 @@ OSystem_SDL::~OSystem_SDL() { SDL_RemoveTimer(_timerID); closeMixer(); - free(_dirtyChecksums); free(_mouseData); delete _savefile; @@ -451,7 +448,6 @@ bool OSystem_SDL::hasFeature(Feature f) { return (f == kFeatureFullscreenMode) || (f == kFeatureAspectRatioCorrection) || - (f == kFeatureAutoComputeDirtyRects) || (f == kFeatureCursorHasPalette) || (f == kFeatureIconifyWindow); } @@ -464,12 +460,6 @@ void OSystem_SDL::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 kFeatureIconifyWindow: if (enable) SDL_WM_IconifyWindow(); @@ -487,8 +477,6 @@ bool OSystem_SDL::getFeatureState(Feature f) { return _videoMode.fullscreen; case kFeatureAspectRatioCorrection: return _videoMode.aspectRatioCorrection; - case kFeatureAutoComputeDirtyRects: - return _modeFlags & DF_WANT_RECT_OPTIM; default: return false; } @@ -510,7 +498,6 @@ void OSystem_SDL::deinit() { SDL_RemoveTimer(_timerID); closeMixer(); - free(_dirtyChecksums); free(_mouseData); delete _timer; diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index bfaabab80a..341a59c8cf 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -290,10 +290,6 @@ protected: int _cdTrack, _cdNumLoops, _cdStartFrame, _cdDuration; uint32 _cdEndTime, _cdStopTime; - enum { - DF_WANT_RECT_OPTIM = 1 << 0 - }; - enum { kTransactionNone = 0, kTransactionActive = 1, @@ -342,7 +338,6 @@ protected: Graphics::Surface _framebuffer; /** Current video mode flags (see DF_* constants) */ - uint32 _modeFlags; bool _modeChanged; int _screenChangeCount; @@ -354,9 +349,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 @@ -459,9 +451,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); // overloaded by CE backend virtual void drawMouse(); // overloaded by CE backend 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(); -- cgit v1.2.3 From 90649938d2d70115967c45cdd1cbb8dab1635794 Mon Sep 17 00:00:00 2001 From: Oystein Eftevaag Date: Thu, 3 Jun 2010 03:22:10 +0000 Subject: We'll now default to non-touchpad mode on the iPad svn-id: r49403 --- backends/platform/iphone/iphone_common.h | 1 + backends/platform/iphone/iphone_video.h | 2 -- backends/platform/iphone/iphone_video.m | 6 ++++++ backends/platform/iphone/osys_main.cpp | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) (limited to 'backends') 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(); } -- cgit v1.2.3 From 6a796c2bb0b2a6d46546a97c24320bdceae2c69d Mon Sep 17 00:00:00 2001 From: John Willis Date: Sat, 5 Jun 2010 18:24:28 +0000 Subject: GP2X Wiz: Update handy shell scripts. svn-id: r49441 --- backends/platform/gp2xwiz/build/build.sh | 4 ++-- backends/platform/gp2xwiz/build/bundle.sh | 6 ++++-- backends/platform/gp2xwiz/build/config.sh | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) (limited to 'backends') 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/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. -- cgit v1.2.3 From 7b56f39985a62852478ccb8ad01a85e9f028097c Mon Sep 17 00:00:00 2001 From: John Willis Date: Sat, 5 Jun 2010 18:26:53 +0000 Subject: GP2X Wiz: Add missing "#include "graphics/scaler/aspect.h"". svn-id: r49442 --- backends/platform/gp2xwiz/build/clean.sh | 2 +- backends/platform/gp2xwiz/gp2xwiz-events.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'backends') 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/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" -- cgit v1.2.3 From 46155b2c3678784f6333eed1d65a35eefdcb2001 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 6 Jun 2010 09:34:36 +0000 Subject: Add Android backend from patch #2603856 svn-id: r49449 --- backends/platform/android/README.build | 84 ++ backends/platform/android/android.cpp | 1413 ++++++++++++++++++++ backends/platform/android/android.mk | 52 + backends/platform/android/asset-archive.cpp | 414 ++++++ backends/platform/android/asset-archive.h | 53 + backends/platform/android/module.mk | 85 ++ .../inodes/gus/scummvm/EditableSurfaceView.java | 59 + .../android/org/inodes/gus/scummvm/Event.java | 330 +++++ .../org/inodes/gus/scummvm/PluginProvider.java | 52 + .../android/org/inodes/gus/scummvm/ScummVM.java | 317 +++++ .../org/inodes/gus/scummvm/ScummVMActivity.java | 446 ++++++ .../org/inodes/gus/scummvm/ScummVMApplication.java | 29 + .../android/org/inodes/gus/scummvm/Unpacker.java | 370 +++++ .../android/scummvm-android-themeengine.patch | 135 ++ 14 files changed, 3839 insertions(+) create mode 100644 backends/platform/android/README.build create mode 100644 backends/platform/android/android.cpp create mode 100644 backends/platform/android/android.mk create mode 100644 backends/platform/android/asset-archive.cpp create mode 100644 backends/platform/android/asset-archive.h create mode 100644 backends/platform/android/module.mk create mode 100644 backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java create mode 100644 backends/platform/android/org/inodes/gus/scummvm/Event.java create mode 100644 backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java create mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVM.java create mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java create mode 100644 backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java create mode 100644 backends/platform/android/org/inodes/gus/scummvm/Unpacker.java create mode 100644 backends/platform/android/scummvm-android-themeengine.patch (limited to 'backends') 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= + + 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= + + EGL_INC="-I" + EGL_LIBS="-L" + + 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..76590ec823 --- /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 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 +static inline T scalef(T in, float numerator, float denominator) { + return static_cast(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 _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 _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(cpp_obj->getMixer()); + assert(mixer); + mixer->mixCallback(reinterpret_cast(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(_overlay_texture) + : static_cast(_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(env->GetIntField( + java_event, FID_Event_kbd_ascii)); + event.kbd.flags = static_cast(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(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 + +#include +#include + +#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(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(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(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(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(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 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(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 + +#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 androidKeyMap; + static { + Map map = new HashMap(); + + 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..b4035a296b --- /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 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..bc0c5ef408 --- /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..29e1eba3d3 --- /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 mUnpacker; + private final static int REQUEST_MARKET = 1; + + private static class UnpackJob { + public ZipFile zipfile; + public Set paths; + + public UnpackJob(ZipFile zipfile, Set 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 { + @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 unpack_jobs = + new HashMap(all_libs.length); + + // This will contain all unpack filenames (so we can + // detect stale files in the unpack directory) + Set all_files = new HashSet(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(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 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 unpack_libs = new ArrayList(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 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 &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 &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 &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 &list, int depth = -1); ++ static void listUsableThemes(Common::Archive &archive, Common::List &list); + + protected: + OSystem *_system; /** Global system object. */ -- cgit v1.2.3 From 4667907b5cf9da3336352053b16b84484adf0393 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 6 Jun 2010 13:35:08 +0000 Subject: whitespace corrections svn-id: r49453 --- backends/platform/android/android.cpp | 6 +++--- .../platform/android/org/inodes/gus/scummvm/PluginProvider.java | 2 +- backends/platform/android/org/inodes/gus/scummvm/ScummVM.java | 2 +- .../platform/android/org/inodes/gus/scummvm/ScummVMActivity.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'backends') diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index 76590ec823..a6258df554 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -703,10 +703,10 @@ void OSystem_Android::setPalette(const byte* colors, uint start, uint num) { palette += 3; colors += 4; } while (--num); -} - +} + void OSystem_Android::grabPalette(byte *colors, uint start, uint num) { - ENTER("grabPalette(%p, %u, %u)", colors, start, 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) diff --git a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java index b4035a296b..840f3440d5 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java +++ b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java @@ -42,7 +42,7 @@ public class PluginProvider extends BroadcastReceiver { .authority(context.getPackageName()) .path(mylib) .toString()); - + extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS, all_libs); } diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java index bc0c5ef408..f4dca0e7e5 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java @@ -42,7 +42,7 @@ public class ScummVM implements SurfaceHolder.Callback { create(context.getAssets()); // Init C++ code, set nativeScummVM } - private native void nativeDestroy(); + private native void nativeDestroy(); public synchronized void destroy() { if (nativeScummVM != 0) { diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java index 29e1eba3d3..fb3cd6348f 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java @@ -83,7 +83,7 @@ public class ScummVMActivity extends Activity { } }); } - } + } } private MyScummVM scummvm; private Thread scummvm_thread; @@ -353,7 +353,7 @@ public class ScummVMActivity extends Activity { 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()) -- cgit v1.2.3 From 857f3ab550e015f9560bafa6bf080619216140fa Mon Sep 17 00:00:00 2001 From: Yotam Barnoy Date: Sun, 6 Jun 2010 14:17:37 +0000 Subject: PSP: faster way of getting file size svn-id: r49457 --- backends/fs/psp/psp-stream.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'backends') diff --git a/backends/fs/psp/psp-stream.cpp b/backends/fs/psp/psp-stream.cpp index 9bcbe9d7cf..83ff095aa8 100644 --- a/backends/fs/psp/psp-stream.cpp +++ b/backends/fs/psp/psp-stream.cpp @@ -24,6 +24,8 @@ */ #ifdef __PSP__ +#include +#include #include #include @@ -106,10 +108,11 @@ void *PSPIoStream::open() { _handle = fopen(_path.c_str(), _writeMode ? "wb" : "rb"); // open if (_handle) { - // Get the file size - fseek((FILE *)_handle, 0, SEEK_END); // go to the end - _fileSize = ftell((FILE *)_handle); - fseek((FILE *)_handle, 0, SEEK_SET); // back to the beginning + // 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); -- cgit v1.2.3 From 356728dab7f2c4cedf73684d7fe3b968be7396fd Mon Sep 17 00:00:00 2001 From: Yotam Barnoy Date: Mon, 7 Jun 2010 13:47:27 +0000 Subject: PSP: found bug in fast getMillis() implementation. Fixed it by adding a fixed amount to the time counter. svn-id: r49485 --- backends/platform/psp/osys_psp.cpp | 3 +-- backends/platform/psp/osys_psp.h | 2 ++ backends/platform/psp/thread.cpp | 41 +++++++++++++++++++++++++++++++++++--- backends/platform/psp/thread.h | 16 +++++++++++++-- 4 files changed, 55 insertions(+), 7 deletions(-) (limited to 'backends') 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 @@ -59,6 +60,7 @@ private: InputHandler _inputHandler; PspAudio _audio; PspTimer _pspTimer; + PspRtc _pspRtc; void initSDL(); 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 #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 { -- cgit v1.2.3