diff options
180 files changed, 5096 insertions, 3667 deletions
diff --git a/.gitignore b/.gitignore index 6e3e61134c..29a14c5459 100644 --- a/.gitignore +++ b/.gitignore @@ -66,17 +66,12 @@ lib*.a /dists/iphone/scummvm.xcodeproj/*.mode1v3 /dists/iphone/scummvm.xcodeproj/*.pbxuser -/dists/msvc8/[Dd]ebug*/ -/dists/msvc8/[Rr]elease*/ -/dists/msvc8/*.lib - -/dists/msvc9/[Dd]ebug*/ -/dists/msvc9/[Rr]elease*/ -/dists/msvc9/*.lib - -/dists/msvc10/[Dd]ebug*/ -/dists/msvc10/[Rr]elease*/ -/dists/msvc10/*.lib +/dists/msvc*/[Dd]ebug*/ +/dists/msvc*/[Rr]elease*/ +/dists/msvc*/*.lib +/dists/msvc*/*.SAV +/dists/msvc*/*.dat +/dists/msvc*/*.dll /doc/*.aux /doc/*.dvi @@ -405,7 +405,7 @@ Other contributions engine) Paolo Costabel - PSP port contributions Martin Doucha - CinE engine objectification - Thomas Fach-Pedersen - ProTracker module player + Thomas Fach-Pedersen - ProTracker module player, Smacker video decoder Tobias Gunkel - Sound support for C64 version of MM/Zak, Loom PCE support Janne Huttunen - V3 actor mask support, Dig/FT SMUSH audio @@ -1,5 +1,6 @@ -For a more comprehensive changelog for the latest experimental SVN code, see: - http://scummvm.svn.sourceforge.net/viewvc/scummvm/?view=log +For a more comprehensive changelog of the latest experimental code, see: + https://github.com/scummvm/scummvm/commits/ + 1.3.0 (????-??-??) New Games: - Added support for Backyard Baseball. @@ -24,14 +25,14 @@ For a more comprehensive changelog for the latest experimental SVN code, see: Cine: - Corrected memory leaks and invalid memory accesses. Future Wars should be more stable. - - Operation Stealth is now completable, though significant - graphical glitches remain so not official supported. + - Made Operation Stealth completable, though significant graphical + glitches remain so not official supported. Drascula: - Added German and French subtitles in the Von Braun cutscene (#3069981: no subtitles in scene with "von Braun"). - Improved French translation of the game. - - Return To Launcher now supported. + - Added support for "Return To Launcher". Gob: - Fixed "Goblin Stuck On Reload" bugs affecting Gobliiins. @@ -41,7 +42,7 @@ For a more comprehensive changelog for the latest experimental SVN code, see: Parallaction: - Corrected issue which could cause crash at engine exit. - - Leak fixes in Nippon Safes Amiga. + - Closed memory leaks in Nippon Safes Amiga. SCI: - Added a CMS music driver for SCI1 - SCI1.1 games. @@ -78,14 +79,15 @@ For a more comprehensive changelog for the latest experimental SVN code, see: - Improved support for FM-TOWNS versions of games. Sky: - - Fixed crashes on sequences for several backends - (e.g. OpenGL, including Android) + - Fixed crashes on sequences for several ports (Android, OpenGL, ...) Teenagent: - Closed memory leaks. Tinsel: - Closed memory leaks in Coroutines. + - Added enhanced music support for the German CD "Neon Edition" re-release + of Discworld 1. Touche: - Corrected memory leaks and minor issues. @@ -94,6 +96,14 @@ For a more comprehensive changelog for the latest experimental SVN code, see: - Added support for OpenGL. (GSoC Task) - Closed memory leaks in Mouse Surfaces. + Android port: + - Switched to the official NDK toolchain for building. + - Fixed GFX output for various devices. + - Fixed various crashes. + + Nintendo DS port: + - Added support for loadable modules. + PSP port: - Added support for loadable modules. - Added image viewer. @@ -101,22 +111,14 @@ For a more comprehensive changelog for the latest experimental SVN code, see: PS2 port: - Added support for loadable modules. - Nintendo DS port: - - Added support for loadable modules. - Wii/GameCube port: - Added support for loadable modules. - Fixed 16bit mouse cursors on HE games. - Android port: - - Switched to the official NDK toolchain for building. - - Fixed GFX output for various devices. - - Fixed various crashes. - 1.2.1 (2010-12-19) General - - Add Hungarian translation. - - Add Brazilian Portuguese translation. + - Added Hungarian translation. + - Added Brazilian Portuguese translation. Cruise: - Fixed a problem with Raoul appearing when examining the Book. diff --git a/audio/mididrv.h b/audio/mididrv.h index 9e649cba3d..eed8c419f3 100644 --- a/audio/mididrv.h +++ b/audio/mididrv.h @@ -71,9 +71,14 @@ enum MusicType { * A set of flags to be passed to detectDevice() which can be used to * specify what kind of music driver is preferred / accepted. * - * The flags (except for MDT_PREFER_MT32 and MDT_PREFER_GM) indicate whether a given driver - * type is acceptable. E.g. the TOWNS music driver could be returned by - * detectDevice if and only if MDT_TOWNS is specified. + * The flags (except for MDT_PREFER_MT32 and MDT_PREFER_GM) indicate whether a + * given driver type is acceptable. E.g. the TOWNS music driver could be + * returned by detectDevice if and only if MDT_TOWNS is specified. + * + * MDT_PREFER_MT32 and MDT_PREFER_GM indicate the MIDI device type to use when + * no device is selected in the music options, or when the MIDI device selected + * does not match the requirements of a game engine. With these flags, more + * priority is given to an MT-32 device, or a GM device respectively. * * @todo Rename MidiDriverFlags to MusicDriverFlags */ diff --git a/audio/mixer.cpp b/audio/mixer.cpp index c2271b1059..dc0287e3fb 100644 --- a/audio/mixer.cpp +++ b/audio/mixer.cpp @@ -54,8 +54,9 @@ public: * @param len number of sample *pairs*. So a value of * 10 means that the buffer contains twice 10 sample, each * 16 bits, for a total of 40 bytes. + * @return number of sample pairs processed (which can still be silence!) */ - void mix(int16 *data, uint len); + int mix(int16 *data, uint len); /** * Queries whether the channel is still playing or not. @@ -257,7 +258,7 @@ void MixerImpl::playStream( insertChannel(handle, chan); } -void MixerImpl::mixCallback(byte *samples, uint len) { +int MixerImpl::mixCallback(byte *samples, uint len) { assert(samples); Common::StackLock lock(_mutex); @@ -272,14 +273,21 @@ void MixerImpl::mixCallback(byte *samples, uint len) { memset(buf, 0, 2 * len * sizeof(int16)); // mix all channels + int res = 0, tmp; for (int i = 0; i != NUM_CHANNELS; i++) if (_channels[i]) { if (_channels[i]->isFinished()) { delete _channels[i]; _channels[i] = 0; - } else if (!_channels[i]->isPaused()) - _channels[i]->mix(buf, len); + } else if (!_channels[i]->isPaused()) { + tmp = _channels[i]->mix(buf, len); + + if (tmp > res) + res = tmp; + } } + + return res; } void MixerImpl::stopAll() { @@ -538,19 +546,23 @@ Timestamp Channel::getElapsedTime() { return ts; } -void Channel::mix(int16 *data, uint len) { +int Channel::mix(int16 *data, uint len) { assert(_stream); + int res = 0; + if (_stream->endOfData()) { // TODO: call drain method } else { assert(_converter); - _samplesConsumed = _samplesDecoded; _mixerTimeStamp = g_system->getMillis(); _pauseTime = 0; - _samplesDecoded += _converter->flow(*_stream, data, len, _volL, _volR); + res = _converter->flow(*_stream, data, len, _volL, _volR); + _samplesDecoded += res; } + + return res; } } // End of namespace Audio diff --git a/audio/mixer_intern.h b/audio/mixer_intern.h index c8df9a594d..dd2746e9ea 100644 --- a/audio/mixer_intern.h +++ b/audio/mixer_intern.h @@ -118,8 +118,10 @@ public: * The mixer callback function, to be called at regular intervals by * the backend (e.g. from an audio mixing thread). All the actual mixing * work is done from here. + * + * @return number of sample pairs processed (which can still be silence!) */ - void mixCallback(byte *samples, uint len); + int mixCallback(byte *samples, uint len); /** * Set the internal 'is ready' flag of the mixer. diff --git a/backends/base-backend.cpp b/backends/base-backend.cpp index 42ab7b887a..f349cc8005 100644 --- a/backends/base-backend.cpp +++ b/backends/base-backend.cpp @@ -101,7 +101,4 @@ AudioCDManager *BaseBackend::getAudioCDManager() { } void BaseBackend::resetGraphicsScale() { - // As a hack, we use 0 here. Backends should override this method - // and provide their own. - setGraphicsMode(0); } diff --git a/backends/graphics/opengl/gltexture.cpp b/backends/graphics/opengl/gltexture.cpp index cd9e23cb71..7b7d40f174 100644 --- a/backends/graphics/opengl/gltexture.cpp +++ b/backends/graphics/opengl/gltexture.cpp @@ -149,7 +149,7 @@ void GLTexture::updateBuffer(const void *buf, int pitch, GLuint x, GLuint y, GLu glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR(); // Check if the buffer has its data contiguously - if (static_cast<int>(w) * _bytesPerPixel == pitch && w == _textureWidth) { + if (static_cast<int>(w) * _bytesPerPixel == pitch) { glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, _glFormat, _glType, buf); CHECK_GL_ERROR(); } else { diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp index beac2f6d3e..9a2efe3eec 100644 --- a/backends/graphics/opengl/opengl-graphics.cpp +++ b/backends/graphics/opengl/opengl-graphics.cpp @@ -136,6 +136,8 @@ int OpenGLGraphicsManager::getDefaultGraphicsMode() const { bool OpenGLGraphicsManager::setGraphicsMode(int mode) { assert(_transactionMode == kTransactionActive); + setScale(2); + if (_oldVideoMode.setup && _oldVideoMode.mode == mode) return true; @@ -166,11 +168,9 @@ void OpenGLGraphicsManager::resetGraphicsScale() { } #ifdef USE_RGB_COLOR - Graphics::PixelFormat OpenGLGraphicsManager::getScreenFormat() const { return _screenFormat; } - #endif void OpenGLGraphicsManager::initSize(uint width, uint height, const Graphics::PixelFormat *format) { @@ -308,7 +308,7 @@ int16 OpenGLGraphicsManager::getWidth() { void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) { assert(colors); - + #ifdef USE_RGB_COLOR assert(_screenFormat.bytesPerPixel == 1); #endif @@ -324,7 +324,7 @@ void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) void OpenGLGraphicsManager::grabPalette(byte *colors, uint start, uint num) { assert(colors); - + #ifdef USE_RGB_COLOR assert(_screenFormat.bytesPerPixel == 1); #endif @@ -341,9 +341,9 @@ void OpenGLGraphicsManager::copyRectToScreen(const byte *buf, int pitch, int x, // Copy buffer data to game screen internal buffer const byte *src = buf; - byte *dst = (byte *)_screenData.pixels + y * _screenData.pitch; + byte *dst = (byte *)_screenData.pixels + y * _screenData.pitch + x * _screenData.bytesPerPixel; for (int i = 0; i < h; i++) { - memcpy(dst + x * _screenData.bytesPerPixel, src, w * _screenData.bytesPerPixel); + memcpy(dst, src, w * _screenData.bytesPerPixel); src += pitch; dst += _screenData.pitch; } @@ -367,6 +367,7 @@ void OpenGLGraphicsManager::fillScreen(uint32 col) { if (_gameTexture == NULL) return; +#ifdef USE_RGB_COLOR if (_screenFormat.bytesPerPixel == 1) { memset(_screenData.pixels, col, _screenData.h * _screenData.pitch); } else if (_screenFormat.bytesPerPixel == 2) { @@ -392,7 +393,9 @@ void OpenGLGraphicsManager::fillScreen(uint32 col) { pixels[i] = col; } } - +#else + memset(_screenData.pixels, col, _screenData.h * _screenData.pitch); +#endif _screenNeedsRedraw = true; } @@ -558,7 +561,8 @@ void OpenGLGraphicsManager::setMouseCursor(const byte *buf, uint w, uint h, int #endif // Allocate space for cursor data - if (_cursorData.w != w || _cursorData.h != h) + if (_cursorData.w != w || _cursorData.h != h || + _cursorData.bytesPerPixel != _cursorFormat.bytesPerPixel) _cursorData.create(w, h, _cursorFormat.bytesPerPixel); // Save cursor data @@ -754,11 +758,17 @@ void OpenGLGraphicsManager::refreshOverlay() { void OpenGLGraphicsManager::refreshCursor() { _cursorNeedsRedraw = false; - if (_cursorFormat.bytesPerPixel == 1) { - // Create a temporary RGBA8888 surface - byte *surface = new byte[_cursorState.w * _cursorState.h * 4]; - memset(surface, 0, _cursorState.w * _cursorState.h * 4); + // Allocate a texture big enough for cursor + _cursorTexture->allocBuffer(_cursorState.w, _cursorState.h); + // Create a temporary RGBA8888 surface + byte *surface = new byte[_cursorState.w * _cursorState.h * 4]; + memset(surface, 0, _cursorState.w * _cursorState.h * 4); + + byte *dst = surface; + + // Convert the paletted cursor to RGBA8888 + if (_cursorFormat.bytesPerPixel == 1) { // Select palette byte *palette; if (_cursorPaletteDisabled) @@ -768,7 +778,6 @@ void OpenGLGraphicsManager::refreshCursor() { // Convert the paletted cursor to RGBA8888 const byte *src = (byte *)_cursorData.pixels; - byte *dst = surface; for (int i = 0; i < _cursorState.w * _cursorState.h; i++) { // Check for keycolor if (src[i] != _cursorKeyColor) { @@ -779,16 +788,42 @@ void OpenGLGraphicsManager::refreshCursor() { } dst += 4; } + } else { + const bool gotNoAlpha = (_cursorFormat.aLoss == 8); + + // Convert the RGB cursor to RGBA8888 + if (_cursorFormat.bytesPerPixel == 2) { + const uint16 *src = (uint16 *)_cursorData.pixels; + for (int i = 0; i < _cursorState.w * _cursorState.h; i++) { + // Check for keycolor + if (src[i] != _cursorKeyColor) { + _cursorFormat.colorToARGB(src[i], dst[3], dst[0], dst[1], dst[2]); + + if (gotNoAlpha) + dst[3] = 255; + } + dst += 4; + } + } else if (_cursorFormat.bytesPerPixel == 4) { + const uint32 *src = (uint32 *)_cursorData.pixels; + for (int i = 0; i < _cursorState.w * _cursorState.h; i++) { + // Check for keycolor + if (src[i] != _cursorKeyColor) { + _cursorFormat.colorToARGB(src[i], dst[3], dst[0], dst[1], dst[2]); + + if (gotNoAlpha) + dst[3] = 255; + } + dst += 4; + } + } + } - // Allocate a texture big enough for cursor - _cursorTexture->allocBuffer(_cursorState.w, _cursorState.h); - - // Update the texture with new cursor - _cursorTexture->updateBuffer(surface, _cursorState.w * 4, 0, 0, _cursorState.w, _cursorState.h); + // Update the texture with new cursor + _cursorTexture->updateBuffer(surface, _cursorState.w * 4, 0, 0, _cursorState.w, _cursorState.h); - // Free the temp surface - delete[] surface; - } + // Free the temp surface + delete[] surface; } void OpenGLGraphicsManager::refreshCursorScale() { @@ -811,7 +846,8 @@ void OpenGLGraphicsManager::refreshCursorScale() { } else { // Otherwise, scale the cursor for the overlay int targetScaleFactor = MIN(_cursorTargetScale, _videoMode.scaleFactor); - int actualFactor = screenScaleFactor - (targetScaleFactor - 1) * 10000; + // We limit the maximum scale to 3 here to avoid too big cursors, for large overlay resolutions + int actualFactor = MIN<uint>(3, screenScaleFactor - (targetScaleFactor - 1)) * 10000; _cursorState.rW = (int16)(_cursorState.w * actualFactor / 10000); _cursorState.rH = (int16)(_cursorState.h * actualFactor / 10000); _cursorState.rHotX = (int16)(_cursorState.hotX * actualFactor / 10000); @@ -873,6 +909,11 @@ void OpenGLGraphicsManager::getGLPixelFormat(Graphics::PixelFormat pixelFormat, intFormat = GL_RGBA; glFormat = GL_RGBA; gltype = GL_UNSIGNED_SHORT_5_5_5_1; + } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555 + bpp = 2; + intFormat = GL_RGB; + glFormat = GL_BGRA; + gltype = GL_UNSIGNED_SHORT_1_5_5_5_REV; } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)) { // RGBA4444 bpp = 2; intFormat = GL_RGBA; @@ -963,7 +1004,7 @@ void OpenGLGraphicsManager::internUpdateScreen() { refreshOverlay(); // Draw the overlay - _overlayTexture->drawTexture(_displayX, _displayY, _displayWidth, _displayHeight); + _overlayTexture->drawTexture(0, 0, _videoMode.overlayWidth, _videoMode.overlayHeight); } if (_cursorVisible) { @@ -1059,10 +1100,14 @@ void OpenGLGraphicsManager::initGL() { void OpenGLGraphicsManager::loadTextures() { #ifdef USE_RGB_COLOR - if (_transactionDetails.formatChanged && _gameTexture) + if (_transactionDetails.formatChanged && _gameTexture) { delete _gameTexture; + _gameTexture = 0; + } #endif + uint gameScreenBPP = 0; + if (!_gameTexture) { byte bpp; GLenum intformat; @@ -1073,6 +1118,7 @@ void OpenGLGraphicsManager::loadTextures() { #else getGLPixelFormat(Graphics::PixelFormat::createFormatCLUT8(), bpp, intformat, format, type); #endif + gameScreenBPP = bpp; _gameTexture = new GLTexture(bpp, intformat, format, type); } else _gameTexture->refresh(); @@ -1093,7 +1139,7 @@ void OpenGLGraphicsManager::loadTextures() { _cursorTexture = new GLTexture(4, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE); else _cursorTexture->refresh(); - + GLint filter = _videoMode.antialiasing ? GL_LINEAR : GL_NEAREST; _gameTexture->setFilter(filter); _overlayTexture->setFilter(filter); @@ -1104,21 +1150,38 @@ void OpenGLGraphicsManager::loadTextures() { _overlayTexture->allocBuffer(_videoMode.overlayWidth, _videoMode.overlayHeight); _cursorTexture->allocBuffer(_cursorState.w, _cursorState.h); - if (_transactionDetails.formatChanged || + if ( +#ifdef USE_RGB_COLOR + _transactionDetails.formatChanged || +#endif _oldVideoMode.screenWidth != _videoMode.screenWidth || _oldVideoMode.screenHeight != _videoMode.screenHeight) _screenData.create(_videoMode.screenWidth, _videoMode.screenHeight, - _screenFormat.bytesPerPixel); +#ifdef USE_RGB_COLOR + _screenFormat.bytesPerPixel +#else + 1 +#endif + ); + if (_oldVideoMode.overlayWidth != _videoMode.overlayWidth || _oldVideoMode.overlayHeight != _videoMode.overlayHeight) _overlayData.create(_videoMode.overlayWidth, _videoMode.overlayHeight, _overlayFormat.bytesPerPixel); - + _screenNeedsRedraw = true; _overlayNeedsRedraw = true; _cursorNeedsRedraw = true; + // We need to setup a proper unpack alignment value here, else we will + // get problems with the texture updates, in case the surface data is + // not properly aligned. + // For now we use the gcd of the game screen format and 2, since 2 is + // the BPP value for the overlay and the OSD. + if (gameScreenBPP) + glPixelStorei(GL_UNPACK_ALIGNMENT, Common::gcd<uint>(gameScreenBPP, 2)); + #ifdef USE_OSD if (!_osdTexture) _osdTexture = new GLTexture(2, GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1); @@ -1166,32 +1229,20 @@ uint OpenGLGraphicsManager::getAspectRatio() { } void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) { - if (_videoMode.mode == OpenGL::GFX_NORMAL) { - if (_videoMode.hardwareWidth != _videoMode.overlayWidth) - x = x * _videoMode.overlayWidth / _videoMode.hardwareWidth; - if (_videoMode.hardwareHeight != _videoMode.overlayHeight) - y = y * _videoMode.overlayHeight / _videoMode.hardwareHeight; - - if (!_overlayVisible) { - x /= _videoMode.scaleFactor; - y /= _videoMode.scaleFactor; - } + if (_overlayVisible) + return; - } else { + if (_videoMode.mode == OpenGL::GFX_NORMAL) { + x /= _videoMode.scaleFactor; + y /= _videoMode.scaleFactor; + } else if (!_overlayVisible) { x -= _displayX; y -= _displayY; - if (_overlayVisible) { - if (_displayWidth != _videoMode.overlayWidth) - x = x * _videoMode.overlayWidth / _displayWidth; - if (_displayHeight != _videoMode.overlayHeight) - y = y * _videoMode.overlayHeight / _displayHeight; - } else { - if (_displayWidth != _videoMode.screenWidth) - x = x * _videoMode.screenWidth / _displayWidth; - if (_displayHeight != _videoMode.screenHeight) - y = y * _videoMode.screenHeight / _displayHeight; - } + if (_displayWidth != _videoMode.screenWidth) + x = x * _videoMode.screenWidth / _displayWidth; + if (_displayHeight != _videoMode.screenHeight) + y = y * _videoMode.screenHeight / _displayHeight; } } diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp index c7ce0aa7de..cbc152a4a3 100644 --- a/backends/graphics/openglsdl/openglsdl-graphics.cpp +++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp @@ -121,6 +121,7 @@ void OpenGLSdlGraphicsManager::detectSupportedFormats() { #endif Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), // RGB565 Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0), // RGB5551 + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0), // RGB555 Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0), // RGBA4444 #ifndef USE_GLES Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12) // ARGB4444 @@ -344,13 +345,10 @@ bool OpenGLSdlGraphicsManager::loadGFXMode() { if (_aspectRatioCorrection) _videoMode.mode = OpenGL::GFX_4_3; - _videoMode.overlayWidth = _videoMode.screenWidth * _videoMode.scaleFactor; - _videoMode.overlayHeight = _videoMode.screenHeight * _videoMode.scaleFactor; - // If the screen was resized, do not change its size if (!_screenResized) { - _videoMode.hardwareWidth = _videoMode.overlayWidth; - _videoMode.hardwareHeight = _videoMode.overlayHeight; + _videoMode.overlayWidth = _videoMode.hardwareWidth = _videoMode.screenWidth * _videoMode.scaleFactor; + _videoMode.overlayHeight = _videoMode.hardwareHeight = _videoMode.screenHeight * _videoMode.scaleFactor; int screenAspectRatio = _videoMode.screenWidth * 10000 / _videoMode.screenHeight; int desiredAspectRatio = getAspectRatio(); @@ -365,6 +363,9 @@ bool OpenGLSdlGraphicsManager::loadGFXMode() { // the width is modified it can break the overlay. if (_videoMode.hardwareHeight > _videoMode.overlayHeight) _videoMode.overlayHeight = _videoMode.hardwareHeight; + } else { + _videoMode.overlayWidth = _videoMode.hardwareWidth; + _videoMode.overlayHeight = _videoMode.hardwareHeight; } _screenResized = false; @@ -376,11 +377,15 @@ bool OpenGLSdlGraphicsManager::loadGFXMode() { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - if (_videoMode.fullscreen) + if (_videoMode.fullscreen) { if (!setupFullscreenMode()) // Failed setuping a fullscreen mode return false; + _videoMode.overlayWidth = _videoMode.hardwareWidth; + _videoMode.overlayHeight = _videoMode.hardwareHeight; + } + uint32 flags = SDL_OPENGL; if (_videoMode.fullscreen) diff --git a/backends/graphics/sdl/sdl-graphics.cpp b/backends/graphics/sdl/sdl-graphics.cpp index 15d896c57a..d8b686e61f 100644 --- a/backends/graphics/sdl/sdl-graphics.cpp +++ b/backends/graphics/sdl/sdl-graphics.cpp @@ -1166,25 +1166,25 @@ void SdlGraphicsManager::copyRectToScreen(const byte *src, int pitch, int x, int error("SDL_LockSurface failed: %s", SDL_GetError()); #ifdef USE_RGB_COLOR - byte *dst = (byte *)_screen->pixels + y * _videoMode.screenWidth * _screenFormat.bytesPerPixel + x * _screenFormat.bytesPerPixel; - if (_videoMode.screenWidth == w && pitch == w * _screenFormat.bytesPerPixel) { - memcpy(dst, src, h*w*_screenFormat.bytesPerPixel); + byte *dst = (byte *)_screen->pixels + y * _screen->pitch + x * _screenFormat.bytesPerPixel; + if (_videoMode.screenWidth == w && pitch == _screen->pitch) { + memcpy(dst, src, h*pitch); } else { do { memcpy(dst, src, w * _screenFormat.bytesPerPixel); src += pitch; - dst += _videoMode.screenWidth * _screenFormat.bytesPerPixel; + dst += _screen->pitch; } while (--h); } #else - byte *dst = (byte *)_screen->pixels + y * _videoMode.screenWidth + x; - if (_videoMode.screenWidth == pitch && pitch == w) { + byte *dst = (byte *)_screen->pixels + y * _screen->pitch + x; + if (_screen->pitch == pitch && pitch == w) { memcpy(dst, src, h*w); } else { do { memcpy(dst, src, w); src += pitch; - dst += _videoMode.screenWidth; + dst += _screen->pitch; } while (--h); } #endif diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index c49745f8bd..0cfe7c9a22 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -25,22 +25,11 @@ #if defined(__ANDROID__) -#include "backends/base-backend.h" -#include "base/main.h" -#include "graphics/surface.h" - -#include "backends/platform/android/android.h" -#include "backends/platform/android/video.h" - -#include <jni.h> - -#include <string.h> -#include <unistd.h> -#include <pthread.h> #include <sys/time.h> +#include <sys/resource.h> #include <time.h> +#include <unistd.h> -#include "common/archive.h" #include "common/util.h" #include "common/rect.h" #include "common/queue.h" @@ -48,14 +37,12 @@ #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 "audio/mixer_intern.h" -#include "backends/platform/android/asset-archive.h" +#include "backends/platform/android/jni.h" +#include "backends/platform/android/android.h" const char *android_log_tag = "ScummVM"; @@ -68,7 +55,8 @@ extern "C" { expr, file, line); } - void __assert2(const char *file, int line, const char *func, const char *expr) { + void __assert2(const char *file, int line, const char *func, + const char *expr) { __android_log_assert(expr, android_log_tag, "Assertion failure: '%s' in %s:%d (%s)", expr, file, line, func); @@ -106,234 +94,24 @@ void checkGlError(const char *expr, const char *file, int line) { } #endif -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 = 0; - - jint res = cached_jvm->GetEnv((void **)&env, JNI_VERSION_1_2); - - if (res != JNI_OK) { - LOGE("GetEnv() failed: %d", res); - abort(); - } - - return env; -} - -static void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) { - jclass cls = env->FindClass(name); - - // if cls is 0, an exception has already been thrown - if (cls != 0) - env->ThrowNew(cls, msg); - - env->DeleteLocalRef(cls); -} - // floating point. use sparingly template <class T> static inline T scalef(T in, float numerator, float denominator) { return static_cast<float>(in) * numerator / denominator; } -static inline GLfixed xdiv(int numerator, int denominator) { - assert(numerator < (1 << 16)); - return (numerator << 16) / denominator; -} - -#ifdef DYNAMIC_MODULES -class AndroidPluginProvider : public POSIXPluginProvider { -protected: - virtual void addCustomDirectories(Common::FSList &dirs) const; -}; -#endif - -class OSystem_Android : public BaseBackend, public PaletteManager { -private: - // back pointer to (java) peer instance - jobject _back_ptr; - - 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; - jmethodID MID_swapBuffers; - - int _screen_changeid; - int _egl_surface_width; - int _egl_surface_height; - - bool _force_redraw; - - // Game layer - GLESPaletteTexture *_game_texture; - int _shake_offset; - Common::Rect _focus_rect; - - // Overlay layer - GLES4444Texture *_overlay_texture; - bool _show_overlay; - - // Mouse layer - GLESPaletteATexture *_mouse_texture; - Common::Point _mouse_hotspot; - int _mouse_targetscale; - bool _show_mouse; - bool _use_mouse_palette; - - Common::Queue<Common::Event> _event_queue; - MutexRef _event_queue_lock; - - bool _timer_thread_exit; - pthread_t _timer_thread; - static void *timerThreadFunc(void *arg); - - bool _enable_zoning; - 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; - void enableZoning(bool enable) { _enable_zoning = enable; } - void setSurfaceSize(int width, int height) { - _egl_surface_width = width; - _egl_surface_height = height; - } - - 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 PaletteManager *getPaletteManager() { - return this; - } - -protected: - // PaletteManager API - virtual void setPalette(const byte *colors, uint start, uint num); - virtual void grabPalette(byte *colors, uint start, uint num); - -public: - 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(); - - // RGBA 4444 - virtual Graphics::PixelFormat getOverlayFormat() const { - 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 logMessage(LogMessageType::Type type, const char *message); - virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); -}; - -OSystem_Android::OSystem_Android(jobject am) : - _back_ptr(0), +OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) : + _audio_sample_rate(audio_sample_rate), + _audio_buffer_size(audio_buffer_size), _screen_changeid(0), + _egl_surface_width(0), + _egl_surface_height(0), _force_redraw(false), _game_texture(0), _overlay_texture(0), _mouse_texture(0), + _mouse_texture_palette(0), + _mouse_texture_rgb(0), _use_mouse_palette(false), _show_mouse(false), _show_overlay(false), @@ -342,7 +120,6 @@ OSystem_Android::OSystem_Android(jobject am) : _mixer(0), _timer(0), _fsFactory(new POSIXFilesystemFactory()), - _asset_archive(new AndroidAssetArchive(am)), _shake_offset(0), _event_queue_lock(createMutex()) { } @@ -350,172 +127,182 @@ OSystem_Android::OSystem_Android(jobject am) : OSystem_Android::~OSystem_Android() { ENTER(); - 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 _mixer; 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; -} +void *OSystem_Android::timerThreadFunc(void *arg) { + OSystem_Android *system = (OSystem_Android *)arg; + DefaultTimerManager *timer = (DefaultTimerManager *)(system->_timer); -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 == 0) \ - 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"); - FIND_METHOD(swapBuffers, "()Z"); - -#undef FIND_METHOD + // renice this thread to boost the audio thread + if (setpriority(PRIO_PROCESS, 0, 19) < 0) + LOGW("couldn't renice the timer thread"); - return true; -} + JNI::attachThread(); -static void ScummVM_create(JNIEnv *env, jobject self, jobject am) { - OSystem_Android *cpp_obj = new OSystem_Android(am); + struct timespec tv; + tv.tv_sec = 0; + tv.tv_nsec = 100 * 1000 * 1000; // 100ms - // Exception already thrown by initJavaHooks? - if (!cpp_obj->initJavaHooks(env, self)) - return; + while (!system->_timer_thread_exit) { + if (JNI::pause) { + LOGD("timer thread going to sleep"); + sem_wait(&JNI::pause_sem); + LOGD("timer thread woke up"); + } - env->SetLongField(self, FID_ScummVM_nativeScummVM, (jlong)cpp_obj); + timer->handler(); + nanosleep(&tv, 0); + } -#ifdef DYNAMIC_MODULES - PluginManager::instance().addPluginProvider(new AndroidPluginProvider()); -#endif -} + JNI::detachThread(); -static void ScummVM_nativeDestroy(JNIEnv *env, jobject self) { - OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); - delete cpp_obj; + return 0; } -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, 0); +void *OSystem_Android::audioThreadFunc(void *arg) { + JNI::attachThread(); - if (buf == 0) { - warning("Unable to get Java audio byte array. Skipping"); - return; - } + OSystem_Android *system = (OSystem_Android *)arg; + Audio::MixerImpl *mixer = system->_mixer; - Audio::MixerImpl *mixer = - static_cast<Audio::MixerImpl *>(cpp_obj->getMixer()); - assert(mixer); - mixer->mixCallback(reinterpret_cast<byte *>(buf), len); + uint buf_size = system->_audio_buffer_size; - env->ReleaseByteArrayElements(jbuf, buf, 0); -} + JNIEnv *env = JNI::getEnv(); -static void ScummVM_setConfManInt(JNIEnv *env, jclass cls, - jstring key_obj, jint value) { - ENTER("%p, %d", key_obj, (int)value); + jbyteArray bufa = env->NewByteArray(buf_size); - const char *key = env->GetStringUTFChars(key_obj, 0); + bool paused = true; - if (key == 0) - return; + byte *buf; + int offset, left, written; + int samples, i; - ConfMan.setInt(key, value); + struct timespec tv_delay; + tv_delay.tv_sec = 0; + tv_delay.tv_nsec = 20 * 1000 * 1000; - env->ReleaseStringUTFChars(key_obj, key); -} + uint msecs_full = buf_size * 1000 / (mixer->getOutputRate() * 2 * 2); -static void ScummVM_setConfManString(JNIEnv *env, jclass cls, jstring key_obj, - jstring value_obj) { - ENTER("%p, %p", key_obj, value_obj); + struct timespec tv_full; + tv_full.tv_sec = 0; + tv_full.tv_nsec = msecs_full * 1000 * 1000; - const char *key = env->GetStringUTFChars(key_obj, 0); + bool silence; + uint silence_count = 33; - if (key == 0) - return; + while (!system->_audio_thread_exit) { + if (JNI::pause) { + JNI::setAudioStop(); - const char *value = env->GetStringUTFChars(value_obj, 0); + paused = true; + silence_count = 33; - if (value == 0) { - env->ReleaseStringUTFChars(key_obj, key); - return; - } + LOGD("audio thread going to sleep"); + sem_wait(&JNI::pause_sem); + LOGD("audio thread woke up"); + } - ConfMan.set(key, value); + buf = (byte *)env->GetPrimitiveArrayCritical(bufa, 0); + assert(buf); - env->ReleaseStringUTFChars(value_obj, value); - env->ReleaseStringUTFChars(key_obj, key); -} + samples = mixer->mixCallback(buf, buf_size); -void *OSystem_Android::timerThreadFunc(void *arg) { - OSystem_Android *system = (OSystem_Android *)arg; - DefaultTimerManager *timer = (DefaultTimerManager *)(system->_timer); + silence = samples < 1; - JNIEnv *env = 0; - jint res = cached_jvm->AttachCurrentThread(&env, 0); + // looks stupid, and it is, but currently there's no way to detect + // silence-only buffers from the mixer + if (!silence) { + silence = true; - if (res != JNI_OK) { - LOGE("AttachCurrentThread() failed: %d", res); - abort(); - } + for (i = 0; i < samples; i += 2) + // SID streams constant crap + if (READ_UINT16(buf + i) > 32) { + silence = false; + break; + } + } - struct timespec tv; - tv.tv_sec = 0; - tv.tv_nsec = 100 * 1000 * 1000; // 100ms + env->ReleasePrimitiveArrayCritical(bufa, buf, 0); - while (!system->_timer_thread_exit) { - timer->handler(); - nanosleep(&tv, 0); - } + if (silence) { + if (!paused) + silence_count++; + + // only pause after a while to prevent toggle mania + if (silence_count > 32) { + if (!paused) { + LOGD("AudioTrack pause"); + + JNI::setAudioPause(); + paused = true; + } + + nanosleep(&tv_full, 0); + + continue; + } + } + + if (paused) { + LOGD("AudioTrack play"); + + JNI::setAudioPlay(); + paused = false; + + silence_count = 0; + } + + offset = 0; + left = buf_size; + written = 0; - res = cached_jvm->DetachCurrentThread(); + while (left > 0) { + written = JNI::writeAudio(env, bufa, offset, left); - if (res != JNI_OK) { - LOGE("DetachCurrentThread() failed: %d", res); - abort(); + if (written < 0) { + LOGE("AudioTrack error: %d", written); + break; + } + + // buffer full + if (written < left) + nanosleep(&tv_delay, 0); + + offset += written; + left -= written; + } + + if (written < 0) + break; + + // sleep a little, prepare the next buffer, and run into the + // blocking AudioTrack.write + nanosleep(&tv_delay, 0); } + JNI::setAudioStop(); + + env->DeleteLocalRef(bufa); + + JNI::detachThread(); + return 0; } void OSystem_Android::initBackend() { ENTER(); - JNIEnv *env = JNU_GetEnv(); + _main_thread = pthread_self(); ConfMan.setInt("autosave_period", 0); - ConfMan.setInt("FM_medium_quality", true); + ConfMan.setBool("FM_high_quality", false); + ConfMan.setBool("FM_medium_quality", true); // must happen before creating TimerManager to avoid race in // creating EventManager @@ -529,74 +316,34 @@ void OSystem_Android::initBackend() { gettimeofday(&_startTime, 0); - 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 = new Audio::MixerImpl(this, _audio_sample_rate); _mixer->setReady(true); - env->CallVoidMethod(_back_ptr, MID_initBackend); + _timer_thread_exit = false; + pthread_create(&_timer_thread, 0, timerThreadFunc, this); - if (env->ExceptionCheck()) { - error("Error in Java initBackend"); + _audio_thread_exit = false; + pthread_create(&_audio_thread, 0, audioThreadFunc, this); - env->ExceptionDescribe(); - env->ExceptionClear(); - } + initSurface(); + initViewport(); - _timer_thread_exit = false; - pthread_create(&_timer_thread, 0, timerThreadFunc, this); + _game_texture = new GLESPalette888Texture(); + _overlay_texture = new GLES4444Texture(); + _mouse_texture_palette = new GLESPalette8888Texture(); + _mouse_texture = _mouse_texture_palette; - OSystem::initBackend(); + // renice this thread to boost the audio thread + if (setpriority(PRIO_PROCESS, 0, 19) < 0) + warning("couldn't renice the main thread"); - setupScummVMSurface(); + JNI::setReadyForEvents(true); } void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const { ENTER(); - 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 == 0) - continue; - - const char *path = env->GetStringUTFChars(path_obj, 0); - if (path == 0) { - 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); - } + JNI::getPluginDirectories(dirs); } bool OSystem_Android::hasFeature(Feature f) { @@ -627,430 +374,6 @@ bool OSystem_Android::getFeatureState(Feature f) { } } -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("%s", mode); - return true; -} - -bool OSystem_Android::setGraphicsMode(int mode) { - ENTER("%d", mode); - return true; -} - -int OSystem_Android::getGraphicsMode() const { - return 1; -} - -void OSystem_Android::setupScummVMSurface() { - ENTER(); - - JNIEnv *env = JNU_GetEnv(); - env->CallVoidMethod(_back_ptr, MID_setupScummVMSurface); - - if (env->ExceptionCheck()) - return; - - // EGL set up with a new surface. Initialise OpenGLES context. - GLESTexture::initGLExtensions(); - - // Turn off anything that looks like 3D ;) - GLCALL(glDisable(GL_CULL_FACE)); - GLCALL(glDisable(GL_DEPTH_TEST)); - GLCALL(glDisable(GL_LIGHTING)); - GLCALL(glDisable(GL_FOG)); - GLCALL(glDisable(GL_DITHER)); - - GLCALL(glShadeModel(GL_FLAT)); - GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)); - - GLCALL(glEnable(GL_BLEND)); - GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - GLCALL(glEnableClientState(GL_VERTEX_ARRAY)); - GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); - - GLCALL(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(); - - GLCALL(glViewport(0, 0, _egl_surface_width, _egl_surface_height)); - - GLCALL(glMatrixMode(GL_PROJECTION)); - GLCALL(glLoadIdentity()); - GLCALL(glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1)); - GLCALL(glMatrixMode(GL_MODELVIEW)); - GLCALL(glLoadIdentity()); - - clearFocusRectangle(); -} - -void OSystem_Android::destroyScummVMSurface() { - 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("%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("%p, %u, %u", colors, start, num); - - if (!_use_mouse_palette) - _setCursorPalette(colors, start, num); - - memcpy(_game_texture->palette() + start * 3, colors, num * 3); -} - -void OSystem_Android::grabPalette(byte *colors, uint start, uint num) { - ENTER("%p, %u, %u", colors, start, num); - memcpy(colors, _game_texture->palette_const() + start * 3, num * 3); -} - -void OSystem_Android::copyRectToScreen(const byte *buf, int pitch, - int x, int y, int w, int h) { - ENTER("%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(); - - if (!_force_redraw && - !_game_texture->dirty() && - !_overlay_texture->dirty() && - !_mouse_texture->dirty()) - return; - - _force_redraw = false; - - GLCALL(glPushMatrix()); - - if (_shake_offset != 0 || - (!_focus_rect.isEmpty() && - !Common::Rect(_game_texture->width(), - _game_texture->height()).contains(_focus_rect))) { - // These are the only cases where _game_texture doesn't - // cover the entire screen. - GLCALL(glClearColorx(0, 0, 0, 1 << 16)); - GLCALL(glClear(GL_COLOR_BUFFER_BIT)); - - // Move everything up by _shake_offset (game) pixels - GLCALL(glTranslatex(0, -_shake_offset << 16, 0)); - } - - if (_focus_rect.isEmpty()) { - _game_texture->drawTexture(0, 0, - _egl_surface_width, _egl_surface_height); - } else { - GLCALL(glPushMatrix()); - GLCALL(glScalex(xdiv(_egl_surface_width, _focus_rect.width()), - xdiv(_egl_surface_height, _focus_rect.height()), - 1 << 16)); - GLCALL(glTranslatex(-_focus_rect.left << 16, - -_focus_rect.top << 16, 0)); - GLCALL(glScalex(xdiv(_game_texture->width(), _egl_surface_width), - xdiv(_game_texture->height(), _egl_surface_height), - 1 << 16)); - - _game_texture->drawTexture(0, 0, - _egl_surface_width, _egl_surface_height); - GLCALL(glPopMatrix()); - } - - int cs = _mouse_targetscale; - - if (_show_overlay) { - // ugly, but the modern theme sets a wacko factor, only god knows why - cs = 1; - - GLCALL(_overlay_texture->drawTexture(0, 0, - _egl_surface_width, - _egl_surface_height)); - } - - if (_show_mouse) { - GLCALL(glPushMatrix()); - - // Scale up ScummVM -> OpenGL (pixel) coordinates - int texwidth, texheight; - - if (_show_overlay) { - texwidth = getOverlayWidth(); - texheight = getOverlayHeight(); - } else { - texwidth = getWidth(); - texheight = getHeight(); - } - - GLCALL(glScalex(xdiv(_egl_surface_width, texwidth), - xdiv(_egl_surface_height, texheight), - 1 << 16)); - - GLCALL(glTranslatex((-_mouse_hotspot.x * cs) << 16, - (-_mouse_hotspot.y * cs) << 16, - 0)); - - // Note the extra half texel to position the mouse in - // the middle of the x,y square: - const Common::Point& mouse = getEventManager()->getMousePos(); - GLCALL(glTranslatex((mouse.x << 16) | 1 << 15, - (mouse.y << 16) | 1 << 15, 0)); - - GLCALL(glScalex(cs << 16, cs << 16, 1 << 16)); - - _mouse_texture->drawTexture(); - - GLCALL(glPopMatrix()); - } - - GLCALL(glPopMatrix()); - - JNIEnv *env = JNU_GetEnv(); - if (!env->CallBooleanMethod(_back_ptr, MID_swapBuffers)) { - // Context lost -> need to reinit GL - destroyScummVMSurface(); - setupScummVMSurface(); - } -} - -Graphics::Surface *OSystem_Android::lockScreen() { - ENTER(); - - Graphics::Surface *surface = _game_texture->surface(); - assert(surface->pixels); - - return surface; -} - -void OSystem_Android::unlockScreen() { - ENTER(); - - assert(_game_texture->dirty()); -} - -void OSystem_Android::setShakePos(int shake_offset) { - ENTER("%d", shake_offset); - - if (_shake_offset != shake_offset) { - _shake_offset = shake_offset; - _force_redraw = true; - } -} - -void OSystem_Android::fillScreen(uint32 col) { - ENTER("%u", col); - - assert(col < 256); - _game_texture->fillBuffer(col); -} - -void OSystem_Android::setFocusRectangle(const Common::Rect& rect) { - ENTER("%d, %d, %d, %d", rect.left, rect.top, rect.right, rect.bottom); - - if (_enable_zoning) { - _focus_rect = rect; - _force_redraw = true; - } -} - -void OSystem_Android::clearFocusRectangle() { - ENTER(); - - if (_enable_zoning) { - _focus_rect = Common::Rect(); - _force_redraw = true; - } -} - -void OSystem_Android::showOverlay() { - ENTER(); - - _show_overlay = true; - _force_redraw = true; -} - -void OSystem_Android::hideOverlay() { - ENTER(); - - _show_overlay = false; - _force_redraw = true; -} - -void OSystem_Android::clearOverlay() { - ENTER(); - - _overlay_texture->fillBuffer(0); - - // Shouldn't need this, but works around a 'blank screen' bug on Nexus1 - updateScreen(); -} - -void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) { - ENTER("%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])); - - // This 'pitch' is pixels not bytes - buf += pitch; - } while (--h); -} - -void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch, - int x, int y, int w, int h) { - ENTER("%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])); - - // Shouldn't need this, but works around a 'blank screen' bug on Nexus1? - updateScreen(); -} - -int16 OSystem_Android::getOverlayHeight() { - return _overlay_texture->height(); -} - -int16 OSystem_Android::getOverlayWidth() { - return _overlay_texture->width(); -} - -bool OSystem_Android::showMouse(bool visible) { - ENTER("%d", visible); - - _show_mouse = visible; - - return true; -} - -void OSystem_Android::warpMouse(int x, int y) { - ENTER("%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("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY, - 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 += 3; - } while (--num); -} - -void OSystem_Android::setCursorPalette(const byte *colors, - uint start, uint num) { - ENTER("%p, %u, %u", colors, start, num); - - _setCursorPalette(colors, start, num); - _use_mouse_palette = true; -} - -void OSystem_Android::disableCursorPalette(bool disable) { - ENTER("%d", disable); - - _use_mouse_palette = !disable; -} - void OSystem_Android::setupKeymapper() { #ifdef ENABLE_KEYMAPPER using namespace Common; @@ -1082,6 +405,48 @@ void OSystem_Android::setupKeymapper() { bool OSystem_Android::pollEvent(Common::Event &event) { //ENTER(); + if (pthread_self() == _main_thread) { + if (_screen_changeid != JNI::surface_changeid) { + if (JNI::egl_surface_width > 0 && JNI::egl_surface_height > 0) { + if (_egl_surface_width > 0 && _egl_surface_height > 0) { + // surface still alive but changed + _screen_changeid = JNI::surface_changeid; + _egl_surface_width = JNI::egl_surface_width; + _egl_surface_height = JNI::egl_surface_height; + + initViewport(); + // double buffered, flip twice + _force_redraw = true; + updateScreen(); + _force_redraw = true; + + event.type = Common::EVENT_SCREEN_CHANGED; + + return true; + } else { + // new surface + initSurface(); + _force_redraw = true; + + event.type = Common::EVENT_SCREEN_CHANGED; + + return true; + } + } else { + // surface lost + deinitSurface(); + } + } + + if (JNI::pause) { + deinitSurface(); + + LOGD("main thread going to sleep"); + sem_wait(&JNI::pause_sem); + LOGD("main thread woke up"); + } + } + lockMutex(_event_queue_lock); if (_event_queue.empty()) { @@ -1119,9 +484,12 @@ bool OSystem_Android::pollEvent(Common::Event &event) { } else { // Touchscreen events need to be converted // from device to game coords first. - const GLESTexture *tex = _show_overlay - ? static_cast<GLESTexture *>(_overlay_texture) - : static_cast<GLESTexture *>(_game_texture); + const GLESTexture *tex; + if (_show_overlay) + tex = _overlay_texture; + else + tex = _game_texture; + event.mouse.x = scalef(event.mouse.x, tex->width(), _egl_surface_width); event.mouse.y = scalef(event.mouse.y, tex->height(), @@ -1130,12 +498,6 @@ bool OSystem_Android::pollEvent(Common::Event &event) { } break; } - case Common::EVENT_SCREEN_CHANGED: - debug("EVENT_SCREEN_CHANGED"); - _screen_changeid++; - destroyScummVMSurface(); - setupScummVMSurface(); - break; default: break; } @@ -1168,52 +530,6 @@ void OSystem_Android::pushEvent(const Common::Event& event) { unlockMutex(_event_queue_lock); } -static void ScummVM_pushEvent(JNIEnv *env, jobject self, jobject java_event) { - OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); - - Common::Event event; - event.type = (Common::EventType)env->GetIntField(java_event, - FID_Event_type); - - event.synthetic = - env->GetBooleanField(java_event, FID_Event_synthetic); - - switch (event.type) { - case Common::EVENT_KEYDOWN: - case Common::EVENT_KEYUP: - event.kbd.keycode = (Common::KeyCode)env->GetIntField( - java_event, FID_Event_kbd_keycode); - event.kbd.ascii = static_cast<int>(env->GetIntField( - java_event, FID_Event_kbd_ascii)); - event.kbd.flags = static_cast<int>(env->GetIntField( - java_event, FID_Event_kbd_flags)); - break; - case Common::EVENT_MOUSEMOVE: - case Common::EVENT_LBUTTONDOWN: - case Common::EVENT_LBUTTONUP: - case Common::EVENT_RBUTTONDOWN: - case Common::EVENT_RBUTTONUP: - case Common::EVENT_WHEELUP: - case Common::EVENT_WHEELDOWN: - case Common::EVENT_MBUTTONDOWN: - case Common::EVENT_MBUTTONUP: - event.mouse.x = - env->GetIntField(java_event, FID_Event_mouse_x); - event.mouse.y = - env->GetIntField(java_event, FID_Event_mouse_y); - // This is a terrible hack. We stash "relativeness" - // in the kbd.flags field until pollEvent() can work - // it out. - event.kbd.flags = env->GetBooleanField( - java_event, FID_Event_mouse_relative) ? 1 : 0; - break; - default: - break; - } - - cpp_obj->pushEvent(event); -} - uint32 OSystem_Android::getMillis() { timeval curTime; @@ -1229,6 +545,7 @@ void OSystem_Android::delayMillis(uint msecs) { OSystem::MutexRef OSystem_Android::createMutex() { pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); @@ -1267,58 +584,38 @@ void OSystem_Android::deleteMutex(MutexRef mutex) { void OSystem_Android::quit() { ENTER(); + JNI::setReadyForEvents(false); + + _audio_thread_exit = true; + pthread_join(_audio_thread, 0); + _timer_thread_exit = true; pthread_join(_timer_thread, 0); + + delete _game_texture; + delete _overlay_texture; + delete _mouse_texture_palette; + delete _mouse_texture_rgb; + + deinitSurface(); } void OSystem_Android::setWindowCaption(const char *caption) { ENTER("%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); + JNI::setWindowCaption(caption); } void OSystem_Android::displayMessageOnOSD(const char *msg) { ENTER("%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); + JNI::displayMessageOnOSD(msg); } void OSystem_Android::showVirtualKeyboard(bool enable) { ENTER("%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(); - } + JNI::showVirtualKeyboard(enable); } Common::SaveFileManager *OSystem_Android::getSavefileManager() { @@ -1355,37 +652,13 @@ FilesystemFactory *OSystem_Android::getFilesystemFactory() { 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(); + ENTER(""); - 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, 0); - - if (path != 0) { - s.addDirectory(path, path, priority); - env->ReleaseStringUTFChars(path_obj, path); - } - - env->DeleteLocalRef(path_obj); - } + JNI::addSysArchivesToSearchSet(s, priority); } -void OSystem_Android::logMessage(LogMessageType::Type type, const char *message) { +void OSystem_Android::logMessage(LogMessageType::Type type, + const char *message) { switch (type) { case LogMessageType::kDebug: __android_log_write(ANDROID_LOG_DEBUG, android_log_tag, message); @@ -1401,177 +674,11 @@ void OSystem_Android::logMessage(LogMessageType::Type type, const char *message) } } -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]; - - // note use in cleanup loop below - int nargs; - - for (nargs = 0; nargs < argc; ++nargs) { - jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); - - if (arg == 0) { - argv[nargs] = 0; - } else { - const char *cstr = env->GetStringUTFChars(arg, 0); - - argv[nargs] = const_cast<char *>(cstr); - - // exception already thrown? - if (cstr == 0) - goto cleanup; - } - - env->DeleteLocalRef(arg); - } - - g_system = cpp_obj; - assert(g_system); - - LOGI("Entering scummvm_main with %d args", argc); - - res = scummvm_main(argc, argv); - - LOGI("Exiting scummvm_main"); - - g_system->quit(); - -cleanup: - nargs--; - - for (int i = 0; i < nargs; ++i) { - if (argv[i] == 0) - continue; - - jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); - - // Exception already thrown? - if (arg == 0) - 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); + ((OSystem_Android *)g_system)->addPluginDirectories(dirs); } #endif -static void ScummVM_enableZoning(JNIEnv *env, jobject self, jboolean enable) { - OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); - cpp_obj->enableZoning(enable); -} - -static void ScummVM_setSurfaceSize(JNIEnv *env, jobject self, - jint width, jint height) { - OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); - cpp_obj->setSurfaceSize(width, height); -} - -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 }, - { "enableZoning", "(Z)V", - (void *)ScummVM_enableZoning }, - { "setSurfaceSize", "(II)V", - (void *)ScummVM_setSurfaceSize }, -}; - -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 == 0) - 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 == 0) - return JNI_ERR; - - jclass event = env->FindClass("org/inodes/gus/scummvm/Event"); - if (event == 0) - return JNI_ERR; - - FID_Event_type = env->GetFieldID(event, "type", "I"); - if (FID_Event_type == 0) - return JNI_ERR; - - FID_Event_synthetic = env->GetFieldID(event, "synthetic", "Z"); - if (FID_Event_synthetic == 0) - return JNI_ERR; - - FID_Event_kbd_keycode = env->GetFieldID(event, "kbd_keycode", "I"); - if (FID_Event_kbd_keycode == 0) - return JNI_ERR; - - FID_Event_kbd_ascii = env->GetFieldID(event, "kbd_ascii", "I"); - if (FID_Event_kbd_ascii == 0) - return JNI_ERR; - - FID_Event_kbd_flags = env->GetFieldID(event, "kbd_flags", "I"); - if (FID_Event_kbd_flags == 0) - return JNI_ERR; - - FID_Event_mouse_x = env->GetFieldID(event, "mouse_x", "I"); - if (FID_Event_mouse_x == 0) - return JNI_ERR; - - FID_Event_mouse_y = env->GetFieldID(event, "mouse_y", "I"); - if (FID_Event_mouse_y == 0) - return JNI_ERR; - - FID_Event_mouse_relative = env->GetFieldID(event, "mouse_relative", "Z"); - if (FID_Event_mouse_relative == 0) - return JNI_ERR; - - cls = env->FindClass("java/lang/Object"); - if (cls == 0) - return JNI_ERR; - - MID_Object_wait = env->GetMethodID(cls, "wait", "()V"); - if (MID_Object_wait == 0) - return JNI_ERR; - - return JNI_VERSION_1_2; -} - #endif + diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h index 855fb04b5d..5c7154a8e4 100644 --- a/backends/platform/android/android.h +++ b/backends/platform/android/android.h @@ -23,8 +23,23 @@ * */ +#ifndef _ANDROID_H_ +#define _ANDROID_H_ + #if defined(__ANDROID__) +#include "common/fs.h" +#include "common/archive.h" +#include "audio/mixer_intern.h" +#include "graphics/surface.h" +#include "backends/base-backend.h" +#include "backends/plugins/posix/posix-provider.h" +#include "backends/fs/posix/posix-fs-factory.h" + +#include "backends/platform/android/texture.h" + +#include <pthread.h> + #include <android/log.h> #include <GLES/gl.h> @@ -46,7 +61,7 @@ extern const char *android_log_tag; #ifdef ANDROID_DEBUG_ENTER #define ENTER(fmt, args...) LOGD("%s(" fmt ")", __FUNCTION__, ##args) #else -#define ENTER(fmt, args...) /**/ +#define ENTER(fmt, args...) do { } while (false) #endif #ifdef ANDROID_DEBUG_GL @@ -58,13 +73,200 @@ extern void checkGlError(const char *expr, const char *file, int line); checkGlError(#x, __FILE__, __LINE__); \ } while (false) +#define GLTHREADCHECK \ + do { \ + assert(pthread_self() == _main_thread); \ + } while (false) + #else #define GLCALL(x) do { (x); } while (false) +#define GLTHREADCHECK do { } while (false) +#endif + +#ifdef DYNAMIC_MODULES +class AndroidPluginProvider : public POSIXPluginProvider { +protected: + virtual void addCustomDirectories(Common::FSList &dirs) const; +}; +#endif + +class OSystem_Android : public BaseBackend, public PaletteManager { +private: + // passed from the dark side + int _audio_sample_rate; + int _audio_buffer_size; + + int _screen_changeid; + int _egl_surface_width; + int _egl_surface_height; + + bool _force_redraw; + + // Game layer + GLESTexture *_game_texture; + int _shake_offset; + Common::Rect _focus_rect; + + // Overlay layer + GLES4444Texture *_overlay_texture; + bool _show_overlay; + + // Mouse layer + GLESTexture *_mouse_texture; + GLESPaletteTexture *_mouse_texture_palette; + GLES5551Texture *_mouse_texture_rgb; + Common::Point _mouse_hotspot; + int _mouse_targetscale; + bool _show_mouse; + bool _use_mouse_palette; + + Common::Queue<Common::Event> _event_queue; + MutexRef _event_queue_lock; + + pthread_t _main_thread; + + bool _timer_thread_exit; + pthread_t _timer_thread; + static void *timerThreadFunc(void *arg); + + bool _audio_thread_exit; + pthread_t _audio_thread; + static void *audioThreadFunc(void *arg); + + bool _enable_zoning; + bool _virtkeybd_on; + + Common::SaveFileManager *_savefile; + Audio::MixerImpl *_mixer; + Common::TimerManager *_timer; + FilesystemFactory *_fsFactory; + timeval _startTime; + + void initSurface(); + void deinitSurface(); + void initViewport(); + +#ifdef USE_RGB_COLOR + Common::String getPixelFormatName(const Graphics::PixelFormat &format) const; + void initTexture(GLESTexture **texture, uint width, uint height, + const Graphics::PixelFormat *format, bool alphaPalette); #endif -// Fix JNIEXPORT declaration to actually do something useful -#undef JNIEXPORT -#define JNIEXPORT __attribute__ ((visibility("default"))) + void setupKeymapper(); + void setCursorPaletteInternal(const byte *colors, uint start, uint num); +public: + OSystem_Android(int audio_sample_rate, int audio_buffer_size); + virtual ~OSystem_Android(); + + virtual void initBackend(); + void addPluginDirectories(Common::FSList &dirs) const; + void enableZoning(bool enable) { _enable_zoning = enable; } + + 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; + +#ifdef USE_RGB_COLOR + virtual Graphics::PixelFormat getScreenFormat() const; + virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const; +#endif + + virtual void initSize(uint width, uint height, + const Graphics::PixelFormat *format); + virtual int getScreenChangeID() const; + + virtual int16 getHeight(); + virtual int16 getWidth(); + + virtual PaletteManager *getPaletteManager() { + return this; + } + +protected: + // PaletteManager API + virtual void setPalette(const byte *colors, uint start, uint num); + virtual void grabPalette(byte *colors, uint start, uint num); + +public: + 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(); + + // RGBA 4444 + virtual Graphics::PixelFormat getOverlayFormat() const { + 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 logMessage(LogMessageType::Type type, const char *message); + virtual void addSysArchivesToSearchSet(Common::SearchSet &s, + int priority = 0); +}; + +#endif #endif diff --git a/backends/platform/android/asset-archive.cpp b/backends/platform/android/asset-archive.cpp index 71ce25aa72..26b1a6ad39 100644 --- a/backends/platform/android/asset-archive.cpp +++ b/backends/platform/android/asset-archive.cpp @@ -36,10 +36,9 @@ #include "common/archive.h" #include "common/debug.h" +#include "backends/platform/android/jni.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; @@ -100,7 +99,7 @@ JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) : { _input_stream = env->NewGlobalRef(is); _buflen = 8192; - _buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen))); + _buf = (jbyteArray)env->NewGlobalRef(env->NewByteArray(_buflen)); jclass cls = env->GetObjectClass(_input_stream); MID_mark = env->GetMethodID(cls, "mark", "(I)V"); @@ -124,7 +123,7 @@ JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) : } JavaInputStream::~JavaInputStream() { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); close(env); env->DeleteGlobalRef(_buf); @@ -139,11 +138,11 @@ void JavaInputStream::close(JNIEnv *env) { } uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); if (_buflen < jint(dataSize)) { _buflen = dataSize; - + env->DeleteGlobalRef(_buf); _buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen))); } @@ -171,7 +170,7 @@ uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) { } bool JavaInputStream::seek(int32 offset, int whence) { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); uint32 newpos; switch (whence) { @@ -305,7 +304,8 @@ AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) : _declared_len = env->CallLongMethod(_assetfd, MID_getDeclaredLength); jmethodID MID_getFileDescriptor = - env->GetMethodID(cls, "getFileDescriptor", "()Ljava/io/FileDescriptor;"); + env->GetMethodID(cls, "getFileDescriptor", + "()Ljava/io/FileDescriptor;"); assert(MID_getFileDescriptor); jobject javafd = env->CallObjectMethod(_assetfd, MID_getFileDescriptor); assert(javafd); @@ -318,7 +318,7 @@ AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) : } AssetFdReadStream::~AssetFdReadStream() { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); env->CallVoidMethod(_assetfd, MID_close); if (env->ExceptionCheck()) @@ -369,7 +369,7 @@ bool AssetFdReadStream::seek(int32 offset, int whence) { } AndroidAssetArchive::AndroidAssetArchive(jobject am) { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); _am = env->NewGlobalRef(am); jclass cls = env->GetObjectClass(_am); @@ -377,8 +377,8 @@ AndroidAssetArchive::AndroidAssetArchive(jobject am) { "(Ljava/lang/String;I)Ljava/io/InputStream;"); assert(MID_open); - MID_openFd = env->GetMethodID(cls, "openFd", - "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"); + MID_openFd = env->GetMethodID(cls, "openFd", "(Ljava/lang/String;)" + "Landroid/content/res/AssetFileDescriptor;"); assert(MID_openFd); MID_list = env->GetMethodID(cls, "list", @@ -387,12 +387,12 @@ AndroidAssetArchive::AndroidAssetArchive(jobject am) { } AndroidAssetArchive::~AndroidAssetArchive() { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); env->DeleteGlobalRef(_am); } bool AndroidAssetArchive::hasFile(const Common::String &name) { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); jstring path = env->NewStringUTF(name.c_str()); jobject result = env->CallObjectMethod(_am, MID_open, path, ACCESS_UNKNOWN); if (env->ExceptionCheck()) { @@ -412,7 +412,7 @@ bool AndroidAssetArchive::hasFile(const Common::String &name) { } int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); Common::List<Common::String> dirlist; dirlist.push_back(""); @@ -422,7 +422,8 @@ int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) { dirlist.pop_back(); jstring jpath = env->NewStringUTF(dir.c_str()); - jobjectArray jpathlist = static_cast<jobjectArray>(env->CallObjectMethod(_am, MID_list, jpath)); + jobjectArray jpathlist = + (jobjectArray)env->CallObjectMethod(_am, MID_list, jpath); if (env->ExceptionCheck()) { warning("Error while calling AssetManager->list(%s). Ignoring.", @@ -469,7 +470,7 @@ Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::String &na } Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const { - JNIEnv *env = JNU_GetEnv(); + JNIEnv *env = JNI::getEnv(); jstring jpath = env->NewStringUTF(path.c_str()); // Try openFd() first ... diff --git a/backends/platform/android/asset-archive.h b/backends/platform/android/asset-archive.h index 28e48426e9..6ec86e4cd0 100644 --- a/backends/platform/android/asset-archive.h +++ b/backends/platform/android/asset-archive.h @@ -23,6 +23,9 @@ * */ +#ifndef _ANDROID_ASSET_H_ +#define _ANDROID_ASSET_H_ + #if defined(__ANDROID__) #include <jni.h> @@ -51,3 +54,5 @@ private: }; #endif +#endif + diff --git a/backends/platform/android/gfx.cpp b/backends/platform/android/gfx.cpp new file mode 100644 index 0000000000..90b1c3ec57 --- /dev/null +++ b/backends/platform/android/gfx.cpp @@ -0,0 +1,699 @@ +/* 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 "graphics/conversion.h" + +#include "backends/platform/android/android.h" +#include "backends/platform/android/jni.h" + +static inline GLfixed xdiv(int numerator, int denominator) { + assert(numerator < (1 << 16)); + return (numerator << 16) / denominator; +} + +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("%s", mode); + + return true; +} + +bool OSystem_Android::setGraphicsMode(int mode) { + ENTER("%d", mode); + + return true; +} + +int OSystem_Android::getGraphicsMode() const { + return 1; +} + +#ifdef USE_RGB_COLOR +Graphics::PixelFormat OSystem_Android::getScreenFormat() const { + return _game_texture->getPixelFormat(); +} + +Common::List<Graphics::PixelFormat> OSystem_Android::getSupportedFormats() const { + Common::List<Graphics::PixelFormat> res; + res.push_back(GLES565Texture::getPixelFormat()); + res.push_back(GLES5551Texture::getPixelFormat()); + res.push_back(GLES4444Texture::getPixelFormat()); + res.push_back(Graphics::PixelFormat::createFormatCLUT8()); + + return res; +} + +Common::String OSystem_Android::getPixelFormatName(const Graphics::PixelFormat &format) const { + if (format.bytesPerPixel == 1) + return "CLUT8"; + + if (format.aLoss == 8) + return Common::String::format("RGB%u%u%u", + 8 - format.rLoss, + 8 - format.gLoss, + 8 - format.bLoss); + + return Common::String::format("RGBA%u%u%u%u", + 8 - format.rLoss, + 8 - format.gLoss, + 8 - format.bLoss, + 8 - format.aLoss); +} + +void OSystem_Android::initTexture(GLESTexture **texture, + uint width, uint height, + const Graphics::PixelFormat *format, + bool alphaPalette) { + assert(texture); + Graphics::PixelFormat format_clut8 = + Graphics::PixelFormat::createFormatCLUT8(); + Graphics::PixelFormat format_current; + Graphics::PixelFormat format_new; + + if (*texture) + format_current = (*texture)->getPixelFormat(); + else + format_current = Graphics::PixelFormat(); + + if (format) + format_new = *format; + else + format_new = format_clut8; + + if (format_current != format_new) { + if (*texture) + LOGD("switching pixel format from: %s", + getPixelFormatName((*texture)->getPixelFormat()).c_str()); + + delete *texture; + + if (format_new == GLES565Texture::getPixelFormat()) + *texture = new GLES565Texture(); + else if (format_new == GLES5551Texture::getPixelFormat()) + *texture = new GLES5551Texture(); + else if (format_new == GLES4444Texture::getPixelFormat()) + *texture = new GLES4444Texture(); + else { + // TODO what now? + if (format_new != format_clut8) + LOGE("unsupported pixel format: %s", + getPixelFormatName(format_new).c_str()); + + if (alphaPalette) + *texture = new GLESPalette8888Texture; + else + *texture = new GLESPalette888Texture; + } + + LOGD("new pixel format: %s", + getPixelFormatName((*texture)->getPixelFormat()).c_str()); + } + + (*texture)->allocBuffer(width, height); + (*texture)->fillBuffer(0); +} +#endif + +void OSystem_Android::initSurface() { + LOGD("initializing surface"); + + assert(!JNI::haveSurface()); + + _screen_changeid = JNI::surface_changeid; + _egl_surface_width = JNI::egl_surface_width; + _egl_surface_height = JNI::egl_surface_height; + + assert(_egl_surface_width > 0 && _egl_surface_height > 0); + + JNI::initSurface(); + + // Initialise OpenGLES context. + GLESTexture::initGLExtensions(); + + if (_game_texture) + _game_texture->reinit(); + + if (_overlay_texture) + _overlay_texture->reinit(); + + if (_mouse_texture) + _mouse_texture->reinit(); +} + +void OSystem_Android::deinitSurface() { + if (!JNI::haveSurface()) + return; + + LOGD("deinitializing surface"); + + _screen_changeid = JNI::surface_changeid; + _egl_surface_width = 0; + _egl_surface_height = 0; + + // release texture resources + if (_game_texture) + _game_texture->release(); + + if (_overlay_texture) + _overlay_texture->release(); + + if (_mouse_texture) + _mouse_texture->release(); + + JNI::deinitSurface(); +} + +void OSystem_Android::initViewport() { + LOGD("initializing viewport"); + + assert(JNI::haveSurface()); + + // Turn off anything that looks like 3D ;) + GLCALL(glDisable(GL_CULL_FACE)); + GLCALL(glDisable(GL_DEPTH_TEST)); + GLCALL(glDisable(GL_LIGHTING)); + GLCALL(glDisable(GL_FOG)); + GLCALL(glDisable(GL_DITHER)); + + GLCALL(glShadeModel(GL_FLAT)); + GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)); + + GLCALL(glEnable(GL_BLEND)); + GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + GLCALL(glEnableClientState(GL_VERTEX_ARRAY)); + GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + + GLCALL(glEnable(GL_TEXTURE_2D)); + + GLCALL(glViewport(0, 0, _egl_surface_width, _egl_surface_height)); + + GLCALL(glMatrixMode(GL_PROJECTION)); + GLCALL(glLoadIdentity()); + GLCALL(glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1)); + GLCALL(glMatrixMode(GL_MODELVIEW)); + GLCALL(glLoadIdentity()); + + clearFocusRectangle(); +} + +void OSystem_Android::initSize(uint width, uint height, + const Graphics::PixelFormat *format) { + ENTER("%d, %d, %p", width, height, format); + + GLTHREADCHECK; + + int overlay_width = _egl_surface_width; + int overlay_height = _egl_surface_height; + + // the 'normal' theme layout uses a max height of 400 pixels. if the + // surface is too big we use only a quarter of the size so that the widgets + // don't get too small. if the surface height has less than 800 pixels, this + // enforces the 'lowres' layout, which will be scaled back up by factor 2x, + // but this looks way better than the 'normal' layout scaled by some + // calculated factors + if (overlay_height > 480) { + overlay_width /= 2; + overlay_height /= 2; + } + + LOGI("overlay size is %ux%u", overlay_width, overlay_height); + + _overlay_texture->allocBuffer(overlay_width, overlay_height); + +#ifdef USE_RGB_COLOR + initTexture(&_game_texture, width, height, format, false); +#else + _game_texture->allocBuffer(width, height); + _game_texture->fillBuffer(0); +#endif + // 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_palette->allocBuffer(20, 20); + + // clear screen + GLCALL(glClearColorx(0, 0, 0, 1 << 16)); + GLCALL(glClear(GL_COLOR_BUFFER_BIT)); + JNI::swapBuffers(); +} + +int OSystem_Android::getScreenChangeID() const { + return _screen_changeid; +} + +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("%p, %u, %u", colors, start, num); + +#ifdef USE_RGB_COLOR + assert(_game_texture->getPixelFormat().bytesPerPixel == 1); +#endif + + GLTHREADCHECK; + + memcpy(((GLESPaletteTexture *)_game_texture)->palette() + start * 3, + colors, num * 3); + + if (!_use_mouse_palette) + setCursorPaletteInternal(colors, start, num); +} + +void OSystem_Android::grabPalette(byte *colors, uint start, uint num) { + ENTER("%p, %u, %u", colors, start, num); + +#ifdef USE_RGB_COLOR + assert(_game_texture->getPixelFormat().bytesPerPixel == 1); +#endif + + GLTHREADCHECK; + + memcpy(colors, ((GLESPaletteTexture *)_game_texture)->palette() + start * 3, + num * 3); +} + +void OSystem_Android::copyRectToScreen(const byte *buf, int pitch, + int x, int y, int w, int h) { + ENTER("%p, %d, %d, %d, %d, %d", buf, pitch, x, y, w, h); + + GLTHREADCHECK; + + _game_texture->updateBuffer(x, y, w, h, buf, pitch); +} + +void OSystem_Android::updateScreen() { + //ENTER(); + + GLTHREADCHECK; + + if (!JNI::haveSurface()) + return; + + if (!_force_redraw && + !_game_texture->dirty() && + !_overlay_texture->dirty() && + !_mouse_texture->dirty()) + return; + + _force_redraw = false; + + GLCALL(glPushMatrix()); + + if (_shake_offset != 0 || + (!_focus_rect.isEmpty() && + !Common::Rect(_game_texture->width(), + _game_texture->height()).contains(_focus_rect))) { + // These are the only cases where _game_texture doesn't + // cover the entire screen. + GLCALL(glClearColorx(0, 0, 0, 1 << 16)); + GLCALL(glClear(GL_COLOR_BUFFER_BIT)); + + // Move everything up by _shake_offset (game) pixels + GLCALL(glTranslatex(0, -_shake_offset << 16, 0)); + } + + if (_focus_rect.isEmpty()) { + _game_texture->drawTexture(0, 0, _egl_surface_width, + _egl_surface_height); + } else { + GLCALL(glPushMatrix()); + GLCALL(glScalex(xdiv(_egl_surface_width, _focus_rect.width()), + xdiv(_egl_surface_height, _focus_rect.height()), + 1 << 16)); + GLCALL(glTranslatex(-_focus_rect.left << 16, + -_focus_rect.top << 16, 0)); + GLCALL(glScalex(xdiv(_game_texture->width(), _egl_surface_width), + xdiv(_game_texture->height(), _egl_surface_height), + 1 << 16)); + + _game_texture->drawTexture(0, 0, _egl_surface_width, + _egl_surface_height); + GLCALL(glPopMatrix()); + } + + int cs = _mouse_targetscale; + + if (_show_overlay) { + // ugly, but the modern theme sets a wacko factor, only god knows why + cs = 1; + + GLCALL(_overlay_texture->drawTexture(0, 0, _egl_surface_width, + _egl_surface_height)); + } + + if (_show_mouse) { + GLCALL(glPushMatrix()); + + // Scale up ScummVM -> OpenGL (pixel) coordinates + int texwidth, texheight; + + if (_show_overlay) { + texwidth = getOverlayWidth(); + texheight = getOverlayHeight(); + } else { + texwidth = getWidth(); + texheight = getHeight(); + } + + GLCALL(glScalex(xdiv(_egl_surface_width, texwidth), + xdiv(_egl_surface_height, texheight), + 1 << 16)); + + GLCALL(glTranslatex((-_mouse_hotspot.x * cs) << 16, + (-_mouse_hotspot.y * cs) << 16, + 0)); + + // Note the extra half texel to position the mouse in + // the middle of the x,y square: + const Common::Point& mouse = getEventManager()->getMousePos(); + GLCALL(glTranslatex((mouse.x << 16) | 1 << 15, + (mouse.y << 16) | 1 << 15, 0)); + + GLCALL(glScalex(cs << 16, cs << 16, 1 << 16)); + + _mouse_texture->drawTexture(); + + GLCALL(glPopMatrix()); + } + + GLCALL(glPopMatrix()); + + if (!JNI::swapBuffers()) + LOGW("swapBuffers failed: 0x%x", glGetError()); +} + +Graphics::Surface *OSystem_Android::lockScreen() { + ENTER(); + + GLTHREADCHECK; + + // TODO this doesn't return any pixel data for non CLUT8 + Graphics::Surface *surface = _game_texture->surface(); + assert(surface->pixels); + + return surface; +} + +void OSystem_Android::unlockScreen() { + ENTER(); + + GLTHREADCHECK; + + assert(_game_texture->dirty()); +} + +void OSystem_Android::setShakePos(int shake_offset) { + ENTER("%d", shake_offset); + + if (_shake_offset != shake_offset) { + _shake_offset = shake_offset; + _force_redraw = true; + } +} + +void OSystem_Android::fillScreen(uint32 col) { + ENTER("%u", col); + + GLTHREADCHECK; + + _game_texture->fillBuffer(col); +} + +void OSystem_Android::setFocusRectangle(const Common::Rect& rect) { + ENTER("%d, %d, %d, %d", rect.left, rect.top, rect.right, rect.bottom); + + if (_enable_zoning) { + _focus_rect = rect; + _force_redraw = true; + } +} + +void OSystem_Android::clearFocusRectangle() { + ENTER(); + + if (_enable_zoning) { + _focus_rect = Common::Rect(); + _force_redraw = true; + } +} + +void OSystem_Android::showOverlay() { + ENTER(); + + _show_overlay = true; + _force_redraw = true; +} + +void OSystem_Android::hideOverlay() { + ENTER(); + + _show_overlay = false; + _force_redraw = true; +} + +void OSystem_Android::clearOverlay() { + ENTER(); + + GLTHREADCHECK; + + _overlay_texture->fillBuffer(0); + + // breaks more than it fixes, disabled for now + // Shouldn't need this, but works around a 'blank screen' bug on Nexus1 + //updateScreen(); +} + +void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) { + ENTER("%p, %d", buf, pitch); + + GLTHREADCHECK; + + // 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])); + + uint h = surface->h; + + do { + memset(buf, 0, surface->w * sizeof(buf[0])); + + // This 'pitch' is pixels not bytes + buf += pitch; + } while (--h); +} + +void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch, + int x, int y, int w, int h) { + ENTER("%p, %d, %d, %d, %d, %d", buf, pitch, x, y, w, h); + + GLTHREADCHECK; + + // This 'pitch' is pixels not bytes + _overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0])); + + // Shouldn't need this, but works around a 'blank screen' bug on Nexus1? + //updateScreen(); +} + +int16 OSystem_Android::getOverlayHeight() { + return _overlay_texture->height(); +} + +int16 OSystem_Android::getOverlayWidth() { + return _overlay_texture->width(); +} + +bool OSystem_Android::showMouse(bool visible) { + ENTER("%d", visible); + + _show_mouse = visible; + + return true; +} + +void OSystem_Android::warpMouse(int x, int y) { + ENTER("%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("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY, + keycolor, cursorTargetScale, format); + + GLTHREADCHECK; + + assert(keycolor < 256); + +#ifdef USE_RGB_COLOR + if (format && format->bytesPerPixel > 1) { + if (_mouse_texture != _mouse_texture_rgb) + LOGD("switching to rgb mouse cursor"); + + _mouse_texture_rgb = new GLES5551Texture(); + _mouse_texture = _mouse_texture_rgb; + } else { + if (_mouse_texture != _mouse_texture_palette) + LOGD("switching to paletted mouse cursor"); + + _mouse_texture = _mouse_texture_palette; + + delete _mouse_texture_rgb; + _mouse_texture_rgb = 0; + } +#endif + + _mouse_texture->allocBuffer(w, h); + + if (_mouse_texture == _mouse_texture_palette) { + // Update palette alpha based on keycolor + byte *palette = _mouse_texture_palette->palette(); + + for (uint i = 0; i < 256; ++i, palette += 4) + palette[3] = 0xff; + + _mouse_texture_palette->palette()[keycolor * 4 + 3] = 0; + } + + if (w == 0 || h == 0) + return; + + if (_mouse_texture == _mouse_texture_palette) { + _mouse_texture->updateBuffer(0, 0, w, h, buf, w); + } else { + uint16 pitch = _mouse_texture->pitch(); + + byte *tmp = new byte[pitch * h]; + + // meh, a 16bit cursor without alpha bits... this is so silly + if (!crossBlit(tmp, buf, pitch, w * 2, w, h, + _mouse_texture->getPixelFormat(), + *format)) { + LOGE("crossblit failed"); + + delete[] tmp; + + _mouse_texture->fillBuffer(0); + + return; + } + + uint16 *s = (uint16 *)buf; + uint16 *d = (uint16 *)tmp; + for (uint16 y = 0; y < h; ++y, d += pitch / 2 - w) + for (uint16 x = 0; x < w; ++x, d++) + if (*s++ != (keycolor & 0xffff)) + *d |= 1; + + _mouse_texture->updateBuffer(0, 0, w, h, tmp, pitch); + + delete[] tmp; + } + + _mouse_hotspot = Common::Point(hotspotX, hotspotY); + _mouse_targetscale = cursorTargetScale; +} + +void OSystem_Android::setCursorPaletteInternal(const byte *colors, + uint start, uint num) { + byte *palette = _mouse_texture_palette->palette() + start * 4; + + for (uint i = 0; i < num; ++i, palette += 4, colors += 3) { + palette[0] = colors[0]; + palette[1] = colors[1]; + palette[2] = colors[2]; + // Leave alpha untouched to preserve keycolor + } +} + +void OSystem_Android::setCursorPalette(const byte *colors, + uint start, uint num) { + ENTER("%p, %u, %u", colors, start, num); + + GLTHREADCHECK; + + if (_mouse_texture->getPixelFormat().bytesPerPixel != 1) { + LOGD("switching to paletted mouse cursor"); + + _mouse_texture = _mouse_texture_palette; + + delete _mouse_texture_rgb; + _mouse_texture_rgb = 0; + } + + setCursorPaletteInternal(colors, start, num); + _use_mouse_palette = true; +} + +void OSystem_Android::disableCursorPalette(bool disable) { + ENTER("%d", disable); + + // when disabling the cursor palette, and we're running a clut8 game, + // it expects the game palette to be used for the cursor + if (disable && _game_texture->getPixelFormat().bytesPerPixel == 1) { + byte *src = ((GLESPaletteTexture *)_game_texture)->palette(); + byte *dst = _mouse_texture_palette->palette(); + + for (uint i = 0; i < 256; ++i, src += 3, dst += 4) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + // Leave alpha untouched to preserve keycolor + } + } + + _use_mouse_palette = !disable; +} + +#endif + diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp new file mode 100644 index 0000000000..6bfe9c2095 --- /dev/null +++ b/backends/platform/android/jni.cpp @@ -0,0 +1,653 @@ +/* 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 "base/main.h" +#include "common/config-manager.h" +#include "engines/engine.h" + +#include "backends/platform/android/android.h" +#include "backends/platform/android/asset-archive.h" +#include "backends/platform/android/jni.h" + +__attribute__ ((visibility("default"))) +jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return JNI::onLoad(vm); +} + +JavaVM *JNI::_vm = 0; +jobject JNI::_jobj = 0; +jobject JNI::_jobj_audio_track = 0; +jobject JNI::_jobj_egl = 0; +jobject JNI::_jobj_egl_display = 0; +jobject JNI::_jobj_egl_surface = 0; + +Common::Archive *JNI::_asset_archive = 0; +OSystem_Android *JNI::_system = 0; + +bool JNI::pause = false; +sem_t JNI::pause_sem = { 0 }; + +int JNI::surface_changeid = 0; +int JNI::egl_surface_width = 0; +int JNI::egl_surface_height = 0; +bool JNI::_ready_for_events = 0; + +jfieldID JNI::_FID_Event_type = 0; +jfieldID JNI::_FID_Event_synthetic = 0; +jfieldID JNI::_FID_Event_kbd_keycode = 0; +jfieldID JNI::_FID_Event_kbd_ascii = 0; +jfieldID JNI::_FID_Event_kbd_flags = 0; +jfieldID JNI::_FID_Event_mouse_x = 0; +jfieldID JNI::_FID_Event_mouse_y = 0; +jfieldID JNI::_FID_Event_mouse_relative = 0; + +jmethodID JNI::_MID_displayMessageOnOSD = 0; +jmethodID JNI::_MID_setWindowCaption = 0; +jmethodID JNI::_MID_showVirtualKeyboard = 0; +jmethodID JNI::_MID_getSysArchives = 0; +jmethodID JNI::_MID_getPluginDirectories = 0; +jmethodID JNI::_MID_initSurface = 0; +jmethodID JNI::_MID_deinitSurface = 0; + +jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0; + +jmethodID JNI::_MID_AudioTrack_flush = 0; +jmethodID JNI::_MID_AudioTrack_pause = 0; +jmethodID JNI::_MID_AudioTrack_play = 0; +jmethodID JNI::_MID_AudioTrack_stop = 0; +jmethodID JNI::_MID_AudioTrack_write = 0; + +const JNINativeMethod JNI::_natives[] = { + { "create", "(Landroid/content/res/AssetManager;" + "Ljavax/microedition/khronos/egl/EGL10;" + "Ljavax/microedition/khronos/egl/EGLDisplay;" + "Landroid/media/AudioTrack;II)V", + (void *)JNI::create }, + { "destroy", "()V", + (void *)JNI::destroy }, + { "setSurface", "(II)V", + (void *)JNI::setSurface }, + { "main", "([Ljava/lang/String;)I", + (void *)JNI::main }, + { "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V", + (void *)JNI::pushEvent }, + { "enableZoning", "(Z)V", + (void *)JNI::enableZoning }, + { "setPause", "(Z)V", + (void *)JNI::setPause } +}; + +JNI::JNI() { +} + +JNI::~JNI() { +} + +jint JNI::onLoad(JavaVM *vm) { + _vm = vm; + + JNIEnv *env; + + if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2)) + return JNI_ERR; + + jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM"); + if (cls == 0) + return JNI_ERR; + + if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0) + return JNI_ERR; + + jclass event = env->FindClass("org/inodes/gus/scummvm/Event"); + if (event == 0) + return JNI_ERR; + + _FID_Event_type = env->GetFieldID(event, "type", "I"); + if (_FID_Event_type == 0) + return JNI_ERR; + + _FID_Event_synthetic = env->GetFieldID(event, "synthetic", "Z"); + if (_FID_Event_synthetic == 0) + return JNI_ERR; + + _FID_Event_kbd_keycode = env->GetFieldID(event, "kbd_keycode", "I"); + if (_FID_Event_kbd_keycode == 0) + return JNI_ERR; + + _FID_Event_kbd_ascii = env->GetFieldID(event, "kbd_ascii", "I"); + if (_FID_Event_kbd_ascii == 0) + return JNI_ERR; + + _FID_Event_kbd_flags = env->GetFieldID(event, "kbd_flags", "I"); + if (_FID_Event_kbd_flags == 0) + return JNI_ERR; + + _FID_Event_mouse_x = env->GetFieldID(event, "mouse_x", "I"); + if (_FID_Event_mouse_x == 0) + return JNI_ERR; + + _FID_Event_mouse_y = env->GetFieldID(event, "mouse_y", "I"); + if (_FID_Event_mouse_y == 0) + return JNI_ERR; + + _FID_Event_mouse_relative = env->GetFieldID(event, "mouse_relative", "Z"); + if (_FID_Event_mouse_relative == 0) + return JNI_ERR; + + return JNI_VERSION_1_2; +} + +JNIEnv *JNI::getEnv() { + JNIEnv *env = 0; + + jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2); + + if (res != JNI_OK) { + LOGE("GetEnv() failed: %d", res); + abort(); + } + + return env; +} + +void JNI::attachThread() { + JNIEnv *env = 0; + + jint res = _vm->AttachCurrentThread(&env, 0); + + if (res != JNI_OK) { + LOGE("AttachCurrentThread() failed: %d", res); + abort(); + } +} + +void JNI::detachThread() { + jint res = _vm->DetachCurrentThread(); + + if (res != JNI_OK) { + LOGE("DetachCurrentThread() failed: %d", res); + abort(); + } +} + +void JNI::setReadyForEvents(bool ready) { + _ready_for_events = ready; +} + +void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { + jclass cls = env->FindClass(name); + + // if cls is 0, an exception has already been thrown + if (cls != 0) + env->ThrowNew(cls, msg); + + env->DeleteLocalRef(cls); +} + +void JNI::throwRuntimeException(JNIEnv *env, const char *msg) { + throwByName(env, "java/lang/RuntimeException", msg); +} + +// calls to the dark side + +void JNI::displayMessageOnOSD(const char *msg) { + JNIEnv *env = JNI::getEnv(); + jstring java_msg = env->NewStringUTF(msg); + + env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg); + + if (env->ExceptionCheck()) { + LOGE("Failed to display OSD message"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(java_msg); +} + +void JNI::setWindowCaption(const char *caption) { + JNIEnv *env = JNI::getEnv(); + jstring java_caption = env->NewStringUTF(caption); + + env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption); + + if (env->ExceptionCheck()) { + LOGE("Failed to set window caption"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(java_caption); +} + +void JNI::showVirtualKeyboard(bool enable) { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable); + + if (env->ExceptionCheck()) { + LOGE("Error trying to show virtual keyboard"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { + JNIEnv *env = JNI::getEnv(); + + s.add("ASSET", _asset_archive, priority, false); + + jobjectArray array = + (jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives); + + if (env->ExceptionCheck()) { + LOGE("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, 0); + + if (path != 0) { + s.addDirectory(path, path, priority); + env->ReleaseStringUTFChars(path_obj, path); + } + + env->DeleteLocalRef(path_obj); + } +} + +void JNI::getPluginDirectories(Common::FSList &dirs) { + JNIEnv *env = JNI::getEnv(); + + jobjectArray array = + (jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories); + + if (env->ExceptionCheck()) { + LOGE("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 == 0) + continue; + + const char *path = env->GetStringUTFChars(path_obj, 0); + + if (path == 0) { + LOGE("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 JNI::initSurface() { + JNIEnv *env = JNI::getEnv(); + + jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface); + + if (!obj || env->ExceptionCheck()) { + LOGE("initSurface failed"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + + return false; + } + + _jobj_egl_surface = env->NewGlobalRef(obj); + + return true; +} + +void JNI::deinitSurface() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_deinitSurface); + + if (env->ExceptionCheck()) { + LOGE("deinitSurface failed"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteGlobalRef(_jobj_egl_surface); + _jobj_egl_surface = 0; +} + +void JNI::setAudioPause() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush); + + if (env->ExceptionCheck()) { + LOGE("Error flushing AudioTrack"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause); + + if (env->ExceptionCheck()) { + LOGE("Error setting AudioTrack: pause"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::setAudioPlay() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play); + + if (env->ExceptionCheck()) { + LOGE("Error setting AudioTrack: play"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::setAudioStop() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop); + + if (env->ExceptionCheck()) { + LOGE("Error setting AudioTrack: stop"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +// natives for the dark side + +void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, + jobject egl, jobject egl_display, + jobject at, jint audio_sample_rate, jint audio_buffer_size) { + assert(!_system); + + pause = false; + // initial value of zero! + sem_init(&pause_sem, 0, 0); + + _asset_archive = new AndroidAssetArchive(asset_manager); + assert(_asset_archive); + + _system = new OSystem_Android(audio_sample_rate, audio_buffer_size); + assert(_system); + + // weak global ref to allow class to be unloaded + // ... except dalvik implements NewWeakGlobalRef only on froyo + //_jobj = env->NewWeakGlobalRef(self); + + _jobj = env->NewGlobalRef(self); + + jclass cls = env->GetObjectClass(_jobj); + +#define FIND_METHOD(prefix, name, signature) do { \ + _MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \ + if (_MID_ ## prefix ## name == 0) \ + return; \ + } while (0) + + FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V"); + FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V"); + FIND_METHOD(, showVirtualKeyboard, "(Z)V"); + FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;"); + FIND_METHOD(, getPluginDirectories, "()[Ljava/lang/String;"); + FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;"); + FIND_METHOD(, deinitSurface, "()V"); + + _jobj_egl = env->NewGlobalRef(egl); + _jobj_egl_display = env->NewGlobalRef(egl_display); + + cls = env->GetObjectClass(_jobj_egl); + + FIND_METHOD(EGL10_, eglSwapBuffers, + "(Ljavax/microedition/khronos/egl/EGLDisplay;" + "Ljavax/microedition/khronos/egl/EGLSurface;)Z"); + + _jobj_audio_track = env->NewGlobalRef(at); + + cls = env->GetObjectClass(_jobj_audio_track); + + FIND_METHOD(AudioTrack_, flush, "()V"); + FIND_METHOD(AudioTrack_, pause, "()V"); + FIND_METHOD(AudioTrack_, play, "()V"); + FIND_METHOD(AudioTrack_, stop, "()V"); + FIND_METHOD(AudioTrack_, write, "([BII)I"); + +#undef FIND_METHOD + + g_system = _system; +} + +void JNI::destroy(JNIEnv *env, jobject self) { + delete _asset_archive; + _asset_archive = 0; + + delete _system; + g_system = 0; + _system = 0; + + sem_destroy(&pause_sem); + + // see above + //JNI::getEnv()->DeleteWeakGlobalRef(_jobj); + + JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display); + JNI::getEnv()->DeleteGlobalRef(_jobj_egl); + JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track); + JNI::getEnv()->DeleteGlobalRef(_jobj); +} + +void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) { + egl_surface_width = width; + egl_surface_height = height; + surface_changeid++; +} + +jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { + assert(_system); + + const int MAX_NARGS = 32; + int res = -1; + + int argc = env->GetArrayLength(args); + if (argc > MAX_NARGS) { + throwByName(env, "java/lang/IllegalArgumentException", + "too many arguments"); + return 0; + } + + char *argv[MAX_NARGS]; + + // note use in cleanup loop below + int nargs; + + for (nargs = 0; nargs < argc; ++nargs) { + jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); + + if (arg == 0) { + argv[nargs] = 0; + } else { + const char *cstr = env->GetStringUTFChars(arg, 0); + + argv[nargs] = const_cast<char *>(cstr); + + // exception already thrown? + if (cstr == 0) + goto cleanup; + } + + env->DeleteLocalRef(arg); + } + +#ifdef DYNAMIC_MODULES + PluginManager::instance().addPluginProvider(new AndroidPluginProvider()); +#endif + + LOGI("Entering scummvm_main with %d args", argc); + + res = scummvm_main(argc, argv); + + LOGI("scummvm_main exited with code %d", res); + + _system->quit(); + +cleanup: + nargs--; + + for (int i = 0; i < nargs; ++i) { + if (argv[i] == 0) + continue; + + jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); + + // Exception already thrown? + if (arg == 0) + return res; + + env->ReleaseStringUTFChars(arg, argv[i]); + env->DeleteLocalRef(arg); + } + + return res; +} + +void JNI::pushEvent(JNIEnv *env, jobject self, jobject java_event) { + // drop events until we're ready and after we quit + if (!_ready_for_events) + return; + + assert(_system); + + Common::Event event; + event.type = (Common::EventType)env->GetIntField(java_event, + _FID_Event_type); + + event.synthetic = + env->GetBooleanField(java_event, _FID_Event_synthetic); + + switch (event.type) { + case Common::EVENT_KEYDOWN: + case Common::EVENT_KEYUP: + event.kbd.keycode = (Common::KeyCode)env->GetIntField( + java_event, _FID_Event_kbd_keycode); + event.kbd.ascii = static_cast<int>(env->GetIntField( + java_event, _FID_Event_kbd_ascii)); + event.kbd.flags = static_cast<int>(env->GetIntField( + java_event, _FID_Event_kbd_flags)); + break; + case Common::EVENT_MOUSEMOVE: + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + case Common::EVENT_WHEELUP: + case Common::EVENT_WHEELDOWN: + case Common::EVENT_MBUTTONDOWN: + case Common::EVENT_MBUTTONUP: + event.mouse.x = + env->GetIntField(java_event, _FID_Event_mouse_x); + event.mouse.y = + env->GetIntField(java_event, _FID_Event_mouse_y); + // This is a terrible hack. We stash "relativeness" + // in the kbd.flags field until pollEvent() can work + // it out. + event.kbd.flags = env->GetBooleanField( + java_event, _FID_Event_mouse_relative) ? 1 : 0; + break; + default: + break; + } + + _system->pushEvent(event); +} + +void JNI::enableZoning(JNIEnv *env, jobject self, jboolean enable) { + assert(_system); + + _system->enableZoning(enable); +} + +void JNI::setPause(JNIEnv *env, jobject self, jboolean value) { + if (!_system) + return; + + if (g_engine) { + LOGD("pauseEngine: %d", value); + + g_engine->pauseEngine(value); + + if (value && + g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) && + g_engine->canSaveGameStateCurrently()) + g_engine->saveGameState(0, "Android parachute"); + } + + pause = value; + + if (!pause) { + // wake up all threads + for (uint i = 0; i < 3; ++i) + sem_post(&pause_sem); + } +} + +#endif + diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h new file mode 100644 index 0000000000..146938636d --- /dev/null +++ b/backends/platform/android/jni.h @@ -0,0 +1,158 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef _ANDROID_JNI_H_ +#define _ANDROID_JNI_H_ + +#if defined(__ANDROID__) + +#include <jni.h> +#include <semaphore.h> + +#include "common/fs.h" +#include "common/archive.h" + +class OSystem_Android; + +class JNI { +private: + JNI(); + virtual ~JNI(); + +public: + static bool pause; + static sem_t pause_sem; + + static int surface_changeid; + static int egl_surface_width; + static int egl_surface_height; + + static jint onLoad(JavaVM *vm); + + static JNIEnv *getEnv(); + + static void attachThread(); + static void detachThread(); + + static void setReadyForEvents(bool ready); + + static void getPluginDirectories(Common::FSList &dirs); + static void setWindowCaption(const char *caption); + static void displayMessageOnOSD(const char *msg); + static void showVirtualKeyboard(bool enable); + static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); + + static inline bool haveSurface(); + static inline bool swapBuffers(); + static bool initSurface(); + static void deinitSurface(); + + static void setAudioPause(); + static void setAudioPlay(); + static void setAudioStop(); + + static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset, + int size); + +private: + static JavaVM *_vm; + // back pointer to (java) peer instance + static jobject _jobj; + static jobject _jobj_audio_track; + static jobject _jobj_egl; + static jobject _jobj_egl_display; + static jobject _jobj_egl_surface; + + static Common::Archive *_asset_archive; + static OSystem_Android *_system; + + static bool _ready_for_events; + + 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_displayMessageOnOSD; + static jmethodID _MID_setWindowCaption; + static jmethodID _MID_showVirtualKeyboard; + static jmethodID _MID_getSysArchives; + static jmethodID _MID_getPluginDirectories; + static jmethodID _MID_initSurface; + static jmethodID _MID_deinitSurface; + + static jmethodID _MID_EGL10_eglSwapBuffers; + + static jmethodID _MID_AudioTrack_flush; + static jmethodID _MID_AudioTrack_pause; + static jmethodID _MID_AudioTrack_play; + static jmethodID _MID_AudioTrack_stop; + static jmethodID _MID_AudioTrack_write; + + static const JNINativeMethod _natives[]; + + static void throwByName(JNIEnv *env, const char *name, const char *msg); + static void throwRuntimeException(JNIEnv *env, const char *msg); + + // natives for the dark side + static void create(JNIEnv *env, jobject self, jobject asset_manager, + jobject egl, jobject egl_display, + jobject at, jint audio_sample_rate, + jint audio_buffer_size); + static void destroy(JNIEnv *env, jobject self); + + static void setSurface(JNIEnv *env, jobject self, jint width, jint height); + static jint main(JNIEnv *env, jobject self, jobjectArray args); + + static void pushEvent(JNIEnv *env, jobject self, jobject java_event); + static void enableZoning(JNIEnv *env, jobject self, jboolean enable); + + static void setPause(JNIEnv *env, jobject self, jboolean value); +}; + +inline bool JNI::haveSurface() { + return _jobj_egl_surface != 0; +} + +inline bool JNI::swapBuffers() { + JNIEnv *env = JNI::getEnv(); + + return env->CallBooleanMethod(_jobj_egl, _MID_EGL10_eglSwapBuffers, + _jobj_egl_display, _jobj_egl_surface); +} + +inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) { + return env->CallIntMethod(_jobj_audio_track, _MID_AudioTrack_write, data, + offset, size); +} + +#endif +#endif + diff --git a/backends/platform/android/module.mk b/backends/platform/android/module.mk index 8b120b21ff..3bcfa7ad87 100644 --- a/backends/platform/android/module.mk +++ b/backends/platform/android/module.mk @@ -1,9 +1,11 @@ MODULE := backends/platform/android MODULE_OBJS := \ - android.o \ + jni.o \ + texture.o \ asset-archive.o \ - video.o + android.o \ + gfx.o # We don't use rules.mk but rather manually update OBJS and MODULE_DIRS. MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) diff --git a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java index 5b71d4a3a5..cede7eedd4 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java +++ b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java @@ -19,13 +19,13 @@ public class EditableSurfaceView extends SurfaceView { } public EditableSurfaceView(Context context, AttributeSet attrs, - int defStyle) { + int defStyle) { super(context, attrs, defStyle); } @Override public boolean onCheckIsTextEditor() { - return true; + return false; } private class MyInputConnection extends BaseInputConnection { @@ -40,7 +40,9 @@ public class EditableSurfaceView extends SurfaceView { getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getWindowToken(), 0); } - return super.performEditorAction(actionCode); // Sends enter key + + // Sends enter key + return super.performEditorAction(actionCode); } } @@ -49,11 +51,12 @@ public class EditableSurfaceView extends SurfaceView { 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); + InputType.TYPE_TEXT_VARIATION_NORMAL | + InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); outAttrs.imeOptions = (EditorInfo.IME_ACTION_DONE | - EditorInfo.IME_FLAG_NO_EXTRACT_UI); + EditorInfo.IME_FLAG_NO_EXTRACT_UI); return new MyInputConnection(); } } + diff --git a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java index c94ab0a3ff..3c91d9f5dc 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java +++ b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java @@ -28,7 +28,7 @@ public class PluginProvider extends BroadcastReceiver { try { info = context.getPackageManager() .getReceiverInfo(new ComponentName(context, this.getClass()), - PackageManager.GET_META_DATA); + PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { Log.e(LOG_TAG, "Error finding my own info?", e); return; @@ -38,17 +38,17 @@ public class PluginProvider extends BroadcastReceiver { if (mylib != null) { ArrayList<String> all_libs = extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS); - all_libs.add(new Uri.Builder() - .scheme("plugin") - .authority(context.getPackageName()) - .path(mylib) - .toString()); + .scheme("plugin") + .authority(context.getPackageName()) + .path(mylib) + .toString()); extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS, - all_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 index 0e905f43a5..f263b89015 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java @@ -1,18 +1,12 @@ package org.inodes.gus.scummvm; -import android.content.Context; +import android.util.Log; import android.content.res.AssetManager; +import android.view.SurfaceHolder; 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.opengles.GL; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; @@ -22,58 +16,269 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import java.io.File; -import java.util.concurrent.Semaphore; import java.util.Map; import java.util.LinkedHashMap; +public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { + final protected static String LOG_TAG = "ScummVM"; + final private AssetManager _asset_manager; + final private Object _sem_surface; + + private EGL10 _egl; + private EGLDisplay _egl_display = EGL10.EGL_NO_DISPLAY; + private EGLConfig _egl_config; + private EGLContext _egl_context = EGL10.EGL_NO_CONTEXT; + private EGLSurface _egl_surface = EGL10.EGL_NO_SURFACE; + + private SurfaceHolder _surface_holder; + private AudioTrack _audio_track; + private int _sample_rate = 0; + private int _buffer_size = 0; + + private String[] _args; + + final private native void create(AssetManager _asset_manager, + EGL10 egl, EGLDisplay egl_display, + AudioTrack audio_track, + int sample_rate, int buffer_size); + final private native void destroy(); + final private native void setSurface(int width, int height); + final private native int main(String[] args); + + // pause the engine and all native threads + final public native void setPause(boolean pause); + final public native void enableZoning(boolean enable); + // Feed an event to ScummVM. Safe to call from other threads. + final public native void pushEvent(Event e); + + // Callbacks from C++ peer instance + abstract protected void displayMessageOnOSD(String msg); + abstract protected void setWindowCaption(String caption); + abstract protected String[] getPluginDirectories(); + abstract protected void showVirtualKeyboard(boolean enable); + abstract protected String[] getSysArchives(); -// 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 ScummVM(AssetManager asset_manager, SurfaceHolder holder) { + _asset_manager = asset_manager; + _sem_surface = new Object(); -public class ScummVM implements SurfaceHolder.Callback { - protected final static String LOG_TAG = "ScummVM"; + holder.addCallback(this); + } - private final int AUDIO_FRAME_SIZE = 2 * 2; // bytes. 16bit audio * stereo - public static class AudioSetupException extends Exception {} + // SurfaceHolder callback + final public void surfaceCreated(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceCreated"); - private long nativeScummVM; // native code hangs itself here - boolean scummVMRunning = false; + // no need to do anything, surfaceChanged() will be called in any case + } - private native void create(AssetManager am); + // SurfaceHolder callback + final public void surfaceChanged(SurfaceHolder holder, int format, + int width, int height) { + Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)", + width, height, format)); - public ScummVM(Context context) { - create(context.getAssets()); // Init C++ code, set nativeScummVM + synchronized(_sem_surface) { + _surface_holder = holder; + _sem_surface.notifyAll(); + } + + // store values for the native code + setSurface(width, height); } - private native void nativeDestroy(); + // SurfaceHolder callback + final public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceDestroyed"); - public synchronized void destroy() { - if (nativeScummVM != 0) { - nativeDestroy(); - nativeScummVM = 0; + synchronized(_sem_surface) { + _surface_holder = null; + _sem_surface.notifyAll(); } + + // clear values for the native code + setSurface(0, 0); } - protected void finalize() { + + final public void setArgs(String[] args) { + _args = args; + } + + final public void run() { + try { + initAudio(); + initEGL(); + + // wait for the surfaceChanged callback + synchronized(_sem_surface) { + while (_surface_holder == null) + _sem_surface.wait(); + } + } catch (Exception e) { + deinitEGL(); + deinitAudio(); + + throw new RuntimeException("Error preparing the ScummVM thread", e); + } + + create(_asset_manager, _egl, _egl_display, + _audio_track, _sample_rate, _buffer_size); + + int res = main(_args); + destroy(); + + deinitEGL(); + deinitAudio(); + + // On exit, tear everything down for a fresh restart next time. + System.exit(res); + } + + final private void initEGL() throws Exception { + _egl = (EGL10)EGLContext.getEGL(); + _egl_display = _egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + int[] version = new int[2]; + _egl.eglInitialize(_egl_display, version); + + int[] num_config = new int[1]; + _egl.eglChooseConfig(_egl_display, 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(_egl_display, configSpec, configs, numConfigs, + num_config); + + if (false) { + Log.d(LOG_TAG, String.format("Found %d EGL configurations.", + numConfigs)); + for (EGLConfig config : configs) + dumpEglConfig(config); + } + + // Android's eglChooseConfig is busted in several versions and + // devices so we have to filter/rank the configs again ourselves. + _egl_config = chooseEglConfig(configs); + + if (false) { + Log.d(LOG_TAG, String.format("Chose from %d EGL configs", + numConfigs)); + dumpEglConfig(_egl_config); + } + + _egl_context = _egl.eglCreateContext(_egl_display, _egl_config, + EGL10.EGL_NO_CONTEXT, null); + + if (_egl_context == EGL10.EGL_NO_CONTEXT) + throw new Exception(String.format("Failed to create context: 0x%x", + _egl.eglGetError())); + } + + // Callback from C++ peer instance + final protected EGLSurface initSurface() throws Exception { + _egl_surface = _egl.eglCreateWindowSurface(_egl_display, _egl_config, + _surface_holder, null); + + if (_egl_surface == EGL10.EGL_NO_SURFACE) + throw new Exception(String.format( + "eglCreateWindowSurface failed: 0x%x", _egl.eglGetError())); + + _egl.eglMakeCurrent(_egl_display, _egl_surface, _egl_surface, + _egl_context); + + GL10 gl = (GL10)_egl_context.getGL(); + + Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", + _egl.eglQueryString(_egl_display, EGL10.EGL_VERSION), + _egl.eglQueryString(_egl_display, EGL10.EGL_VENDOR), + gl.glGetString(GL10.GL_VERSION), + gl.glGetString(GL10.GL_RENDERER), + gl.glGetString(GL10.GL_VENDOR))); + + return _egl_surface; + } + + // Callback from C++ peer instance + final protected void deinitSurface() { + if (_egl_display != EGL10.EGL_NO_DISPLAY) { + _egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + + if (_egl_surface != EGL10.EGL_NO_SURFACE) + _egl.eglDestroySurface(_egl_display, _egl_surface); + } + + _egl_surface = EGL10.EGL_NO_SURFACE; + } + + final private void deinitEGL() { + if (_egl_display != EGL10.EGL_NO_DISPLAY) { + _egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + + if (_egl_surface != EGL10.EGL_NO_SURFACE) + _egl.eglDestroySurface(_egl_display, _egl_surface); + + if (_egl_context != EGL10.EGL_NO_CONTEXT) + _egl.eglDestroyContext(_egl_display, _egl_context); + + _egl.eglTerminate(_egl_display); + } + + _egl_surface = EGL10.EGL_NO_SURFACE; + _egl_context = EGL10.EGL_NO_CONTEXT; + _egl_config = null; + _egl_display = EGL10.EGL_NO_DISPLAY; + _egl = null; + } + + final private void initAudio() throws Exception { + _sample_rate = AudioTrack.getNativeOutputSampleRate( + AudioManager.STREAM_MUSIC); + _buffer_size = AudioTrack.getMinBufferSize(_sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT); + + // ~100ms + int buffer_size_want = (_sample_rate * 2 * 2 / 10) & ~1023; + + if (_buffer_size < buffer_size_want) { + Log.w(LOG_TAG, String.format( + "adjusting audio buffer size (was: %d)", _buffer_size)); + + _buffer_size = buffer_size_want; + } + + Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio", + _buffer_size, _sample_rate)); + + _audio_track = new AudioTrack(AudioManager.STREAM_MUSIC, + _sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + _buffer_size, + AudioTrack.MODE_STREAM); + + if (_audio_track.getState() != AudioTrack.STATE_INITIALIZED) + throw new Exception( + String.format("Error initialising AudioTrack: %d", + _audio_track.getState())); + } + + final private void deinitAudio() { + if (_audio_track != null) + _audio_track.stop(); + + _audio_track = null; + _buffer_size = 0; + _sample_rate = 0; } - // 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, @@ -82,35 +287,10 @@ public class ScummVM implements SurfaceHolder.Callback { 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) { - try { - surfaceLock.acquire(); - } catch (InterruptedException e) { - Log.e(LOG_TAG, "Interrupted while waiting for surface lock", e); - } - } // For debugging private static final Map<String, Integer> attribs; + static { attribs = new LinkedHashMap<String, Integer>(); attribs.put("CONFIG_ID", EGL10.EGL_CONFIG_ID); @@ -141,11 +321,14 @@ public class ScummVM implements SurfaceHolder.Callback { attribs.put("TRANSPARENT_GREEN_VALUE", EGL10.EGL_TRANSPARENT_GREEN_VALUE); attribs.put("TRANSPARENT_BLUE_VALUE", EGL10.EGL_TRANSPARENT_BLUE_VALUE); } - private void dumpEglConfig(EGLConfig config) { + + final private void dumpEglConfig(EGLConfig config) { int[] value = new int[1]; + for (Map.Entry<String, Integer> entry : attribs.entrySet()) { - egl.eglGetConfigAttrib(eglDisplay, config, - entry.getValue(), value); + _egl.eglGetConfigAttrib(_egl_display, config, + entry.getValue(), value); + if (value[0] == EGL10.EGL_NONE) Log.d(LOG_TAG, entry.getKey() + ": NONE"); else @@ -153,44 +336,7 @@ public class ScummVM implements SurfaceHolder.Callback { } } - // 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); - - if (false) { - Log.d(LOG_TAG, String.format("Found %d EGL configurations.", numConfigs)); - for (EGLConfig config : configs) - dumpEglConfig(config); - } - - // Android's eglChooseConfig is busted in several versions and - // devices so we have to filter/rank the configs again ourselves. - eglConfig = chooseEglConfig(configs); - if (false) { - Log.d(LOG_TAG, String.format("Chose EGL config from %d possibilities.", numConfigs)); - dumpEglConfig(eglConfig); - } - - eglContext = egl.eglCreateContext(eglDisplay, eglConfig, - EGL10.EGL_NO_CONTEXT, null); - if (eglContext == EGL10.EGL_NO_CONTEXT) - throw new RuntimeException("Failed to create context"); - } - - private EGLConfig chooseEglConfig(EGLConfig[] configs) { + final private EGLConfig chooseEglConfig(EGLConfig[] configs) { int best = 0; int bestScore = -1; int[] value = new int[1]; @@ -198,32 +344,43 @@ public class ScummVM implements SurfaceHolder.Callback { for (int i = 0; i < configs.length; i++) { EGLConfig config = configs[i]; int score = 10000; - egl.eglGetConfigAttrib(eglDisplay, config, - EGL10.EGL_SURFACE_TYPE, value); + + _egl.eglGetConfigAttrib(_egl_display, config, + EGL10.EGL_SURFACE_TYPE, value); + + // must have if ((value[0] & EGL10.EGL_WINDOW_BIT) == 0) - continue; // must have + continue; + + _egl.eglGetConfigAttrib(_egl_display, config, + EGL10.EGL_CONFIG_CAVEAT, value); - egl.eglGetConfigAttrib(eglDisplay, config, - EGL10.EGL_CONFIG_CAVEAT, value); if (value[0] != EGL10.EGL_NONE) score -= 1000; // Must be at least 555, but then smaller is better - final int[] colorBits = {EGL10.EGL_RED_SIZE, - EGL10.EGL_GREEN_SIZE, - EGL10.EGL_BLUE_SIZE, - EGL10.EGL_ALPHA_SIZE}; + final int[] colorBits = { EGL10.EGL_RED_SIZE, + EGL10.EGL_GREEN_SIZE, + EGL10.EGL_BLUE_SIZE, + EGL10.EGL_ALPHA_SIZE + }; + for (int component : colorBits) { - egl.eglGetConfigAttrib(eglDisplay, config, - component, value); + _egl.eglGetConfigAttrib(_egl_display, config, component, value); + + // boost if >5 bits accuracy if (value[0] >= 5) - score += 10; // boost if >5 bits accuracy - score -= value[0]; // penalize for wasted bits + score += 10; + + // penalize for wasted bits + score -= value[0]; } - egl.eglGetConfigAttrib(eglDisplay, config, - EGL10.EGL_DEPTH_SIZE, value); - score -= value[0]; // penalize for wasted bits + _egl.eglGetConfigAttrib(_egl_display, config, + EGL10.EGL_DEPTH_SIZE, value); + + // penalize for wasted bits + score -= value[0]; if (score > bestScore) { best = i; @@ -239,224 +396,21 @@ public class ScummVM implements SurfaceHolder.Callback { return configs[best]; } - // Called by ScummVM thread - static private boolean _log_version = true; - protected void setupScummVMSurface() { - try { - surfaceLock.acquire(); - } catch (InterruptedException e) { - Log.e(LOG_TAG, "Interrupted while waiting for surface lock", e); - return; - } - eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, - nativeSurface, null); - if (eglSurface == EGL10.EGL_NO_SURFACE) - Log.e(LOG_TAG, "CreateWindowSurface failed!"); - egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); - - GL10 gl = (GL10)eglContext.getGL(); - - if (_log_version) { - Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", - egl.eglQueryString(eglDisplay, EGL10.EGL_VERSION), - egl.eglQueryString(eglDisplay, EGL10.EGL_VENDOR), - gl.glGetString(GL10.GL_VERSION), - gl.glGetString(GL10.GL_RENDERER), - gl.glGetString(GL10.GL_VENDOR))); - _log_version = false; // only log this once - } - - int[] value = new int[1]; - egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_WIDTH, value); - int width = value[0]; - egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_HEIGHT, value); - int height = value[0]; - Log.i(LOG_TAG, String.format("New surface is %dx%d", width, height)); - setSurfaceSize(width, height); - } - - // 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); - } - - final public boolean swapBuffers() { - if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) { - int error = egl.eglGetError(); - Log.w(LOG_TAG, String.format("eglSwapBuffers exited with error 0x%x", error)); - if (error == EGL11.EGL_CONTEXT_LOST) - return false; - } - return true; - } - - // 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); - final public native void enableZoning(boolean enable); - final public native void setSurfaceSize(int width, int height); - - // 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(LOG_TAG, "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); + 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 index fae35b6695..8cb3d80063 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java @@ -3,6 +3,7 @@ package org.inodes.gus.scummvm; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; +import android.content.res.AssetManager; import android.content.res.Configuration; import android.media.AudioManager; import android.os.Bundle; @@ -14,6 +15,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceView; +import android.view.SurfaceHolder; import android.view.View; import android.view.ViewConfiguration; import android.view.inputmethod.InputMethodManager; @@ -30,14 +32,13 @@ public class ScummVMActivity extends Activity { private final static int TRACKBALL_SCALE = 2; private class MyScummVM extends ScummVM { - private boolean scummvmRunning = false; - private boolean usingSmallScreen() { // Multiple screen sizes came in with Android 1.6. Have // to use reflection in order to continue supporting 1.5 // devices :( DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); + try { // This 'density' term is very confusing. int DENSITY_LOW = metrics.getClass().getField("DENSITY_LOW").getInt(null); @@ -48,27 +49,13 @@ public class ScummVMActivity extends Activity { } } - public MyScummVM() { - super(ScummVMActivity.this); + public MyScummVM(SurfaceHolder holder) { + super(ScummVMActivity.this.getAssets(), holder); // Enable ScummVM zoning on 'small' screens. - enableZoning(usingSmallScreen()); - } - - @Override - protected void initBackend() throws ScummVM.AudioSetupException { - synchronized (this) { - scummvmRunning = true; - notifyAll(); - } - super.initBackend(); - } - - public void waitUntilRunning() throws InterruptedException { - synchronized (this) { - while (!scummvmRunning) - wait(); - } + // FIXME make this optional for the user + // disabled for now since it crops too much + //enableZoning(usingSmallScreen()); } @Override @@ -95,18 +82,22 @@ public class ScummVMActivity extends Activity { @Override protected void showVirtualKeyboard(final boolean enable) { - if (getResources().getConfiguration().keyboard == - Configuration.KEYBOARD_NOKEYS) { - runOnUiThread(new Runnable() { - public void run() { - showKeyboard(enable); - } - }); - } + runOnUiThread(new Runnable() { + public void run() { + showKeyboard(enable); + } + }); + } + + @Override + protected String[] getSysArchives() { + return new String[0]; } + } - private MyScummVM scummvm; - private Thread scummvm_thread; + + private MyScummVM _scummvm; + private Thread _scummvm_thread; @Override public void onCreate(Bundle savedInstanceState) { @@ -126,113 +117,106 @@ public class ScummVMActivity extends Activity { .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(); - } - }) + 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.LOG_TAG, "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(ScummVM.LOG_TAG, "Interrupted while waiting for ScummVM.initBackend", e); - finish(); - } + getFilesDir().mkdirs(); - scummvm.setSurface(main_surface.getHolder()); - } + // Start ScummVM + _scummvm = new MyScummVM(main_surface.getHolder()); - // Runs in another thread - private void runScummVM() throws IOException { - getFilesDir().mkdirs(); - String[] args = { - "ScummVM-lib", + _scummvm.setArgs(new String[] { + "ScummVM", "--config=" + getFileStreamPath("scummvmrc").getPath(), "--path=" + Environment.getExternalStorageDirectory().getPath(), "--gui-theme=scummmodern", "--savepath=" + getDir("saves", 0).getPath() - }; + }); - int ret = scummvm.scummVMMain(args); + _scummvm_thread = new Thread(_scummvm, "ScummVM"); + _scummvm_thread.start(); + } + + @Override + public void onStart() { + Log.d(ScummVM.LOG_TAG, "onStart"); - // On exit, tear everything down for a fresh - // restart next time. - System.exit(ret); + super.onStart(); } - private boolean was_paused = false; + @Override + public void onResume() { + Log.d(ScummVM.LOG_TAG, "onResume"); + + super.onResume(); + + if (_scummvm != null) + _scummvm.setPause(false); + } @Override public void onPause() { - if (scummvm != null) { - was_paused = true; - scummvm.pause(); - } + Log.d(ScummVM.LOG_TAG, "onPause"); + super.onPause(); + + if (_scummvm != null) + _scummvm.setPause(true); } @Override - public void onResume() { - super.onResume(); - if (scummvm != null && was_paused) - scummvm.resume(); - was_paused = false; + public void onStop() { + Log.d(ScummVM.LOG_TAG, "onStop"); + + super.onStop(); } @Override - public void onStop() { - if (scummvm != null) { - scummvm.pushEvent(new Event(Event.EVENT_QUIT)); + public void onDestroy() { + Log.d(ScummVM.LOG_TAG, "onDestroy"); + + super.onDestroy(); + + if (_scummvm != null) { + _scummvm.pushEvent(new Event(Event.EVENT_QUIT)); + try { - scummvm_thread.join(1000); // 1s timeout + // 1s timeout + _scummvm_thread.join(1000); } catch (InterruptedException e) { Log.i(ScummVM.LOG_TAG, "Error while joining ScummVM thread", e); } + + _scummvm = null; } - super.onStop(); } static final int MSG_MENU_LONG_PRESS = 1; + private final Handler keycodeMenuTimeoutHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -252,7 +236,7 @@ public class ScummVMActivity extends Activity { @Override public boolean onKeyMultiple(int keyCode, int repeatCount, - KeyEvent kevent) { + KeyEvent kevent) { return onKeyDown(keyCode, kevent); } @@ -262,35 +246,37 @@ public class ScummVMActivity extends Activity { switch (keyCode) { case KeyEvent.KEYCODE_MENU: // Have to reimplement hold-down-menu-brings-up-softkeybd - // ourselves, since we are otherwise hijacking the menu - // key :( + // 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. + + // Ignore keyrepeat for menu 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; - } + + boolean 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)); + _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: @@ -302,45 +288,59 @@ public class ScummVMActivity extends Activity { // 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 + + // ACTION_MULTIPLE + default: return false; } - } else + } 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); + + _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; @@ -355,51 +355,63 @@ public class ScummVMActivity extends Activity { 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 (?) + + // no ctrl key in android, so use sym (?) + if (kevent.isSymPressed()) e.kbd_flags |= Event.KBD_CTRL; + if (kevent.isShiftPressed()) { if (keyCode >= KeyEvent.KEYCODE_0 && - keyCode <= KeyEvent.KEYCODE_9) { + 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 + } 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); + _scummvm.pushEvent(e); + e.type = Event.EVENT_KEYUP; - scummvm.pushEvent(e); + _scummvm.pushEvent(e); } - } else - scummvm.pushEvent(e); + } else { + _scummvm.pushEvent(e); + } return true; } @@ -410,11 +422,14 @@ public class ScummVMActivity extends Activity { _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; } @@ -432,7 +447,8 @@ public class ScummVMActivity extends Activity { e.mouse_y = (int)(event.getY() * event.getYPrecision()) * TRACKBALL_SCALE; e.mouse_relative = true; - scummvm.pushEvent(e); + + _scummvm.pushEvent(e); return true; } @@ -440,6 +456,7 @@ public class ScummVMActivity extends Activity { @Override public boolean onTouchEvent(MotionEvent event) { int type = getEventType(event.getAction()); + if (type == Event.EVENT_INVALID) return false; @@ -447,7 +464,8 @@ public class ScummVMActivity extends Activity { e.mouse_x = (int)event.getX(); e.mouse_y = (int)event.getY(); e.mouse_relative = false; - scummvm.pushEvent(e); + + _scummvm.pushEvent(e); return true; } @@ -456,6 +474,7 @@ public class ScummVMActivity extends Activity { 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 @@ -463,3 +482,4 @@ public class ScummVMActivity extends Activity { 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 index 37a9d09e1a..f9eec72eac 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java @@ -8,22 +8,24 @@ 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; + private static File _cache_dir; @Override public void onCreate() { super.onCreate(); + // This is still on /data :( - cache_dir = getCacheDir(); + _cache_dir = getCacheDir(); // This is mounted noexec :( //cache_dir = new File(Environment.getExternalStorageDirectory(), - // "/.ScummVM.tmp"); + // "/.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; + 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 index 8811b1f3ae..c4b2ad7f5d 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java +++ b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java @@ -370,3 +370,4 @@ public class Unpacker extends Activity { } } } + diff --git a/backends/platform/android/video.cpp b/backends/platform/android/texture.cpp index f8427c2ac8..24e6549b1a 100644 --- a/backends/platform/android/video.cpp +++ b/backends/platform/android/texture.cpp @@ -33,8 +33,8 @@ #include "common/util.h" #include "common/tokenizer.h" +#include "backends/platform/android/texture.h" #include "backends/platform/android/android.h" -#include "backends/platform/android/video.h" // Unfortunately, Android devices are too varied to make broad assumptions :/ #define TEXSUBIMAGE_IS_EXPENSIVE 0 @@ -82,43 +82,67 @@ void GLESTexture::initGLExtensions() { } } -GLESTexture::GLESTexture() : +GLESTexture::GLESTexture(byte bytesPerPixel, GLenum glFormat, GLenum glType, + size_t paletteSize, Graphics::PixelFormat pixelFormat) : + _bytesPerPixel(bytesPerPixel), + _glFormat(glFormat), + _glType(glType), + _paletteSize(paletteSize), + _texture_name(0), + _surface(), _texture_width(0), _texture_height(0), - _all_dirty(true) + _all_dirty(false), + _dirty_rect(), + _pixelFormat(pixelFormat) { GLCALL(glGenTextures(1, &_texture_name)); - - // This all gets reset later in allocBuffer: - _surface.w = 0; - _surface.h = 0; - _surface.pitch = _texture_width; - _surface.pixels = 0; - _surface.bytesPerPixel = 0; } GLESTexture::~GLESTexture() { + release(); +} + +void GLESTexture::release() { debug("Destroying texture %u", _texture_name); GLCALL(glDeleteTextures(1, &_texture_name)); } -void GLESTexture::reinitGL() { - GLCALL(glDeleteTextures(1, &_texture_name)); +void GLESTexture::reinit() { GLCALL(glGenTextures(1, &_texture_name)); - // bypass allocBuffer() shortcut to reinit the texture properly - _texture_width = 0; - _texture_height = 0; + if (_paletteSize) { + // paletted textures are in a local buffer, don't wipe it + initSize(); + } else { + // bypass allocBuffer() shortcut to reinit the texture properly + _texture_width = 0; + _texture_height = 0; + + allocBuffer(_surface.w, _surface.h); + } - allocBuffer(_surface.w, _surface.h); setDirty(); } +void GLESTexture::initSize() { + // Allocate room for the texture now, but pixel data gets uploaded + // later (perhaps with multiple TexSubImage2D operations). + GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, _glFormat, + _texture_width, _texture_height, + 0, _glFormat, _glType, 0)); +} + void GLESTexture::allocBuffer(GLuint w, GLuint h) { - int bpp = bytesPerPixel(); _surface.w = w; _surface.h = h; - _surface.bytesPerPixel = bpp; + _surface.bytesPerPixel = _bytesPerPixel; // Already allocated a sufficiently large buffer? if (w <= _texture_width && h <= _texture_height) @@ -132,72 +156,81 @@ void GLESTexture::allocBuffer(GLuint w, GLuint h) { _texture_height = nextHigher2(_surface.h); } - _surface.pitch = _texture_width * bpp; + _surface.pitch = _texture_width * _bytesPerPixel; - // Allocate room for the texture now, but pixel data gets uploaded - // later (perhaps with multiple TexSubImage2D operations). - GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); - GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, glFormat(), - _texture_width, _texture_height, - 0, glFormat(), glType(), 0)); + initSize(); } void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, - const void *buf, int pitch) { - ENTER("%u, %u, %u, %u, %p, %d", x, y, w, h, buf, pitch); + const void *buf, int pitch_buf) { + ENTER("%u, %u, %u, %u, %p, %d", x, y, w, h, buf, pitch_buf); GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - setDirtyRect(Common::Rect(x, y, x+w, y+h)); + setDirtyRect(Common::Rect(x, y, x + w, y + h)); - if (static_cast<int>(w) * bytesPerPixel() == pitch) { + if (static_cast<int>(w) * _bytesPerPixel == pitch_buf) { GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, - glFormat(), glType(), buf)); + _glFormat, _glType, buf)); } else { // GLES removed the ability to specify pitch, so we // have to do this ourselves. - if (h == 0) - return; - #if TEXSUBIMAGE_IS_EXPENSIVE - byte tmpbuf[w * h * bytesPerPixel()]; + byte *tmp = new byte[w * h * _bytesPerPixel]; + assert(tmp); + const byte *src = static_cast<const byte *>(buf); - byte *dst = tmpbuf; + byte *dst = tmp; GLuint count = h; do { - memcpy(dst, src, w * bytesPerPixel()); - dst += w * bytesPerPixel(); - src += pitch; + memcpy(dst, src, w * _bytesPerPixel); + dst += w * _bytesPerPixel; + src += pitch_buf; } while (--count); GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, - glFormat(), glType(), tmpbuf)); + _glFormat, _glType, tmp)); + + delete[] tmp; #else // This version avoids the intermediate copy at the expense of // repeat glTexSubImage2D calls. On some devices this is worse. const byte *src = static_cast<const byte *>(buf); do { GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, - w, 1, glFormat(), glType(), src)); + w, 1, _glFormat, _glType, src)); ++y; - src += pitch; + src += pitch_buf; } while (--h); #endif } } -void GLESTexture::fillBuffer(byte x) { - int rowbytes = _surface.w * bytesPerPixel(); - byte tmpbuf[_surface.h * rowbytes]; - memset(tmpbuf, x, _surface.h * rowbytes); - updateBuffer(0, 0, _surface.w, _surface.h, tmpbuf, rowbytes); +void GLESTexture::fillBuffer(uint32 color) { + uint rowbytes = _surface.w * _bytesPerPixel; + + byte *tmp = new byte[rowbytes]; + assert(tmp); + + if (_bytesPerPixel == 1 || ((color & 0xff) == ((color >> 8) & 0xff))) { + memset(tmp, color & 0xff, rowbytes); + } else { + uint16 *p = (uint16 *)tmp; + Common::set_to(p, p + _surface.w, (uint16)color); + } + + GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + + for (GLuint y = 0; y < _surface.h; ++y) + GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, y, _surface.w, 1, + _glFormat, _glType, tmp)); + + delete[] tmp; + + setDirty(); } void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { @@ -206,7 +239,7 @@ void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { #ifdef GL_OES_draw_texture // Great extension, but only works under specific conditions. // Still a work-in-progress - disabled for now. - if (false && draw_tex_supported && paletteSize() == 0) { + if (false && draw_tex_supported && _paletteSize == 0) { //GLCALL(glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); const GLint crop[4] = { 0, _surface.h, _surface.w, -_surface.h }; @@ -243,12 +276,34 @@ void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, ARRAYSIZE(vertices) / 2)); } - _all_dirty = false; - _dirty_rect = Common::Rect(); + clearDirty(); +} + +GLES4444Texture::GLES4444Texture() : + GLESTexture(2, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0, getPixelFormat()) { +} + +GLES4444Texture::~GLES4444Texture() { +} + +GLES5551Texture::GLES5551Texture() : + GLESTexture(2, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 0, getPixelFormat()) { +} + +GLES5551Texture::~GLES5551Texture() { +} + +GLES565Texture::GLES565Texture() : + GLESTexture(2, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0, getPixelFormat()) { +} + +GLES565Texture::~GLES565Texture() { } -GLESPaletteTexture::GLESPaletteTexture() : - GLESTexture(), +GLESPaletteTexture::GLESPaletteTexture(byte bytesPerPixel, GLenum glFormat, + GLenum glType, size_t paletteSize) : + GLESTexture(bytesPerPixel, glFormat, glType, paletteSize, + Graphics::PixelFormat::createFormatCLUT8()), _texture(0) { } @@ -258,85 +313,73 @@ GLESPaletteTexture::~GLESPaletteTexture() { } void GLESPaletteTexture::allocBuffer(GLuint w, GLuint h) { - int bpp = bytesPerPixel(); - _surface.w = w; - _surface.h = h; - _surface.bytesPerPixel = bpp; - - // Already allocated a sufficiently large buffer? - if (w <= _texture_width && h <= _texture_height) - return; - - if (npot_supported) { - _texture_width = _surface.w; - _texture_height = _surface.h; - } else { - _texture_width = nextHigher2(_surface.w); - _texture_height = nextHigher2(_surface.h); - } - _surface.pitch = _texture_width * bpp; + GLESTexture::allocBuffer(w, h); // Texture gets uploaded later (from drawTexture()) - byte *new_buffer = new byte[paletteSize() + - _texture_width * _texture_height * bytesPerPixel()]; + byte *new_buffer = new byte[_paletteSize + + _texture_width * _texture_height * _bytesPerPixel]; + assert(new_buffer); + if (_texture) { // preserve palette - memcpy(new_buffer, _texture, paletteSize()); + memcpy(new_buffer, _texture, _paletteSize); delete[] _texture; } _texture = new_buffer; - _surface.pixels = _texture + paletteSize(); + _surface.pixels = _texture + _paletteSize; } -void GLESPaletteTexture::fillBuffer(byte x) { +void GLESPaletteTexture::fillBuffer(uint32 color) { assert(_surface.pixels); - memset(_surface.pixels, x, _surface.pitch * _surface.h); + memset(_surface.pixels, color & 0xff, _surface.pitch * _surface.h); setDirty(); } -void GLESPaletteTexture::updateBuffer(GLuint x, GLuint y, - GLuint w, GLuint h, - const void *buf, int pitch) { - _all_dirty = true; +void GLESPaletteTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, + const void *buf, int pitch_buf) { + setDirtyRect(Common::Rect(x, y, x + w, y + h)); const byte * src = static_cast<const byte *>(buf); byte *dst = static_cast<byte *>(_surface.getBasePtr(x, y)); do { - memcpy(dst, src, w * bytesPerPixel()); + memcpy(dst, src, w * _bytesPerPixel); dst += _surface.pitch; - src += pitch; + src += pitch_buf; } while (--h); } -void GLESPaletteTexture::uploadTexture() const { - const size_t texture_size = - paletteSize() + _texture_width * _texture_height * bytesPerPixel(); +void GLESPaletteTexture::drawTexture(GLshort x, GLshort y, GLshort w, + GLshort h) { + if (dirty()) { + GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); - GLCALL(glCompressedTexImage2D(GL_TEXTURE_2D, 0, glType(), - _texture_width, _texture_height, - 0, texture_size, _texture)); -} + const size_t texture_size = + _paletteSize + _texture_width * _texture_height * _bytesPerPixel; -void GLESPaletteTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { - if (_all_dirty) { - GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, - GL_NEAREST)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_NEAREST)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, - GL_CLAMP_TO_EDGE)); - GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, - GL_CLAMP_TO_EDGE)); - uploadTexture(); - _all_dirty = false; + GLCALL(glCompressedTexImage2D(GL_TEXTURE_2D, 0, _glType, + _texture_width, _texture_height, + 0, texture_size, _texture)); } GLESTexture::drawTexture(x, y, w, h); } +GLESPalette888Texture::GLESPalette888Texture() : + GLESPaletteTexture(1, GL_RGB, GL_PALETTE8_RGB8_OES, 256 * 3) { +} + +GLESPalette888Texture::~GLESPalette888Texture() { +} + +GLESPalette8888Texture::GLESPalette8888Texture() : + GLESPaletteTexture(1, GL_RGBA, GL_PALETTE8_RGBA8_OES, 256 * 4) { +} + +GLESPalette8888Texture::~GLESPalette8888Texture() { +} + #endif diff --git a/backends/platform/android/video.h b/backends/platform/android/texture.h index da42ea876d..78df43aea9 100644 --- a/backends/platform/android/video.h +++ b/backends/platform/android/texture.h @@ -23,11 +23,15 @@ * */ +#ifndef _ANDROID_TEXTURE_H_ +#define _ANDROID_TEXTURE_H_ + #if defined(__ANDROID__) #include <GLES/gl.h> #include "graphics/surface.h" +#include "graphics/pixelformat.h" #include "common/rect.h" #include "common/array.h" @@ -36,18 +40,29 @@ class GLESTexture { public: static void initGLExtensions(); - GLESTexture(); +protected: + GLESTexture(byte bytesPerPixel, GLenum glFormat, GLenum glType, + size_t paletteSize, Graphics::PixelFormat pixelFormat); + +public: virtual ~GLESTexture(); - virtual void reinitGL(); + void release(); + void reinit(); + void initSize(); + virtual void allocBuffer(GLuint width, GLuint height); virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, - const void *buf, int pitch); - virtual void fillBuffer(byte x); + const void *buf, int pitch_buf); + virtual void fillBuffer(uint32 color); virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); + inline void drawTexture() { + drawTexture(0, 0, _surface.w, _surface.h); + } + inline GLuint width() const { return _surface.w; } @@ -56,8 +71,8 @@ public: return _surface.h; } - inline GLuint texture_name() const { - return _texture_name; + inline uint16 pitch() const { + return _surface.pitch; } inline const Graphics::Surface *surface_const() const { @@ -73,22 +88,21 @@ public: return _all_dirty || !_dirty_rect.isEmpty(); } - inline void drawTexture() { - drawTexture(0, 0, _surface.w, _surface.h); + inline const Graphics::PixelFormat &getPixelFormat() const { + return _pixelFormat; } protected: - virtual byte bytesPerPixel() const = 0; - virtual GLenum glFormat() const = 0; - virtual GLenum glType() const = 0; - - virtual size_t paletteSize() const { - return 0; - } - inline void setDirty() { _all_dirty = true; - _dirty_rect = Common::Rect(); + } + + inline void clearDirty() { + _all_dirty = false; + _dirty_rect.top = 0; + _dirty_rect.left = 0; + _dirty_rect.bottom = 0; + _dirty_rect.right = 0; } inline void setDirtyRect(const Common::Rect& r) { @@ -100,58 +114,68 @@ protected: } } + byte _bytesPerPixel; + GLenum _glFormat; + GLenum _glType; + size_t _paletteSize; + GLuint _texture_name; Graphics::Surface _surface; GLuint _texture_width; GLuint _texture_height; - bool _all_dirty; // Covers dirty area + bool _all_dirty; Common::Rect _dirty_rect; + + Graphics::PixelFormat _pixelFormat; }; // RGBA4444 texture class GLES4444Texture : public GLESTexture { -protected: - virtual byte bytesPerPixel() const { - return 2; - } +public: + GLES4444Texture(); + virtual ~GLES4444Texture(); - virtual GLenum glFormat() const { - return GL_RGBA; + static inline Graphics::PixelFormat getPixelFormat() { + return Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0); } +}; - virtual GLenum glType() const { - return GL_UNSIGNED_SHORT_4_4_4_4; +// RGBA5551 texture +class GLES5551Texture : public GLESTexture { +public: + GLES5551Texture(); + virtual ~GLES5551Texture(); + + static inline Graphics::PixelFormat getPixelFormat() { + return Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0); } }; // RGB565 texture class GLES565Texture : public GLESTexture { -protected: - virtual byte bytesPerPixel() const { - return 2; - } - - virtual GLenum glFormat() const { - return GL_RGB; - } +public: + GLES565Texture(); + virtual ~GLES565Texture(); - virtual GLenum glType() const { - return GL_UNSIGNED_SHORT_5_6_5; + static inline Graphics::PixelFormat getPixelFormat() { + return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); } }; -// RGB888 256-entry paletted texture class GLESPaletteTexture : public GLESTexture { +protected: + GLESPaletteTexture(byte bytesPerPixel, GLenum glFormat, GLenum glType, + size_t paletteSize); + public: - GLESPaletteTexture(); virtual ~GLESPaletteTexture(); virtual void allocBuffer(GLuint width, GLuint height); virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, - const void *buf, int pitch); - virtual void fillBuffer(byte x); + const void *buf, int pitch_buf); + virtual void fillBuffer(uint32 color); virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); @@ -169,41 +193,23 @@ public: }; protected: - virtual byte bytesPerPixel() const { - return 1; - } - - virtual GLenum glFormat() const { - return GL_RGB; - } - - virtual GLenum glType() const { - return GL_PALETTE8_RGB8_OES; - } - - virtual size_t paletteSize() const { - return 256 * 3; - } - - void uploadTexture() const; - byte *_texture; }; -// RGBA8888 256-entry paletted texture -class GLESPaletteATexture : public GLESPaletteTexture { -protected: - virtual GLenum glFormat() const { - return GL_RGBA; - } - - virtual GLenum glType() const { - return GL_PALETTE8_RGBA8_OES; - } +// RGB888 256-entry paletted texture +class GLESPalette888Texture : public GLESPaletteTexture { +public: + GLESPalette888Texture(); + virtual ~GLESPalette888Texture(); +}; - virtual size_t paletteSize() const { - return 256 * 4; - } +// RGBA8888 256-entry paletted texture +class GLESPalette8888Texture : public GLESPaletteTexture { +public: + GLESPalette8888Texture(); + virtual ~GLESPalette8888Texture(); }; #endif +#endif + diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index dc91bd9fe7..bddb48ca95 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -125,10 +125,10 @@ void OSystem_SDL::init() { if (_timerManager == 0) _timerManager = new SdlTimerManager(); - #ifdef USE_OPENGL - // Setup a list with both SDL and OpenGL graphics modes - setupGraphicsModes(); - #endif +#ifdef USE_OPENGL + // Setup a list with both SDL and OpenGL graphics modes + setupGraphicsModes(); +#endif } void OSystem_SDL::initBackend() { @@ -148,11 +148,15 @@ void OSystem_SDL::initBackend() { Common::String gfxMode(ConfMan.get("gfx_mode")); bool use_opengl = false; const OSystem::GraphicsMode *mode = OpenGLSdlGraphicsManager::supportedGraphicsModes(); + int i = 0; while (mode->name) { - if (scumm_stricmp(mode->name, gfxMode.c_str()) == 0) + if (scumm_stricmp(mode->name, gfxMode.c_str()) == 0) { + _graphicsMode = i + _sdlModesCount; use_opengl = true; + } mode++; + ++i; } // If the gfx_mode is from OpenGL, create the OpenGL graphics manager @@ -464,6 +468,7 @@ int OSystem_SDL::getDefaultGraphicsMode() const { bool OSystem_SDL::setGraphicsMode(int mode) { const OSystem::GraphicsMode *srcMode; int i; + // Check if mode is from SDL or OpenGL if (mode < _sdlModesCount) { srcMode = SdlGraphicsManager::supportedGraphicsModes(); @@ -472,28 +477,34 @@ bool OSystem_SDL::setGraphicsMode(int mode) { srcMode = OpenGLSdlGraphicsManager::supportedGraphicsModes(); i = _sdlModesCount; } + // Loop through modes while (srcMode->name) { if (i == mode) { // If the new mode and the current mode are not from the same graphics // manager, delete and create the new mode graphics manager if (_graphicsMode >= _sdlModesCount && mode < _sdlModesCount) { + debug(1, "switching to plain SDL graphics"); delete _graphicsManager; _graphicsManager = new SdlGraphicsManager(_eventSource); ((SdlGraphicsManager *)_graphicsManager)->initEventObserver(); _graphicsManager->beginGFXTransaction(); } else if (_graphicsMode < _sdlModesCount && mode >= _sdlModesCount) { + debug(1, "switching to OpenGL graphics"); delete _graphicsManager; _graphicsManager = new OpenGLSdlGraphicsManager(); ((OpenGLSdlGraphicsManager *)_graphicsManager)->initEventObserver(); _graphicsManager->beginGFXTransaction(); } + _graphicsMode = mode; return _graphicsManager->setGraphicsMode(srcMode->id); } + i++; srcMode++; } + return false; } diff --git a/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in index c7623d09a8..12b4cb3610 100644 --- a/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in +++ b/backends/platform/symbian/S60v3/ScummVM_A0000658_S60v3.mmp.in @@ -91,7 +91,7 @@ STATICLIBRARY esdl.lib // *** Include paths USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\engines -USERINCLUDE ..\..\..\..\backends\fs ..\src ..\..\..\..\backends\platform\sdl ..\..\..\..\sound +USERINCLUDE ..\..\..\..\backends\fs ..\src ..\..\..\..\backends\platform\sdl ..\..\..\..\audio SYSTEMINCLUDE \epoc32\include\ESDL SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version diff --git a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in index 6f14e858b0..9af6535b22 100644 --- a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in +++ b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in @@ -91,7 +91,7 @@ STATICLIBRARY esdl.lib // *** Include paths USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\engines -USERINCLUDE ..\..\..\..\backends\fs ..\src ..\..\..\..\backends\platform\sdl ..\..\..\..\sound +USERINCLUDE ..\..\..\..\backends\fs ..\src ..\..\..\..\backends\platform\sdl ..\..\..\..\audio SYSTEMINCLUDE \epoc32\include\ESDL SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version diff --git a/backends/platform/symbian/mmp/scummvm_agi.mmp.in b/backends/platform/symbian/mmp/scummvm_agi.mmp.in index 044326405d..e2d98b9ae4 100644 --- a/backends/platform/symbian/mmp/scummvm_agi.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_agi.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\agi // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_agos.mmp.in b/backends/platform/symbian/mmp/scummvm_agos.mmp.in index e2d1e5eff4..077111d783 100644 --- a/backends/platform/symbian/mmp/scummvm_agos.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_agos.mmp.in @@ -65,6 +65,6 @@ SOURCEPATH ..\..\..\..\engines\agos // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_base.mmp.in b/backends/platform/symbian/mmp/scummvm_base.mmp.in index d25cef4ffe..b3bfbab530 100644 --- a/backends/platform/symbian/mmp/scummvm_base.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_base.mmp.in @@ -51,7 +51,7 @@ ALWAYS_BUILD_AS_ARM // *** Include paths -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio USERINCLUDE ..\..\..\..\backends\fs ..\src ..\..\..\..\backends\platform\sdl SYSTEMINCLUDE \epoc32\include\ESDL @@ -95,7 +95,7 @@ SOURCEPATH ..\..\..\..\gui //SOURCE KeysDialog.cpp //SOURCE Actions.cpp -SOURCEPATH ..\..\..\..\sound +SOURCEPATH ..\..\..\..\audio //START_AUTO_OBJECTS_SOUND_// // empty base file, will be updated by Perl build scripts @@ -110,6 +110,12 @@ SOURCE rate_arm.cpp // ARM version: add ASM .cpp wrapper SOURCE rate_arm_asm.s // ARM version: add ASM routines #endif +SOURCEPATH ..\..\..\..\video +//START_AUTO_OBJECTS_VIDEO_// + + // empty base file, will be updated by Perl build scripts + +//STOP_AUTO_OBJECTS_VIDEO_// // add a few files manually, since they are not parsed from modules.mk files SOURCEPATH ..\..\..\.. diff --git a/backends/platform/symbian/mmp/scummvm_cine.mmp.in b/backends/platform/symbian/mmp/scummvm_cine.mmp.in index d114ec554b..2c8118ef13 100644 --- a/backends/platform/symbian/mmp/scummvm_cine.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cine.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\cine // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_cruise.mmp.in b/backends/platform/symbian/mmp/scummvm_cruise.mmp.in index e4c836a68b..b43a867da3 100644 --- a/backends/platform/symbian/mmp/scummvm_cruise.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cruise.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\cruise // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_draci.mmp.in b/backends/platform/symbian/mmp/scummvm_draci.mmp.in index b10eb0b5a8..9f24927f27 100644 --- a/backends/platform/symbian/mmp/scummvm_draci.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_draci.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\draci // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_drascula.mmp.in b/backends/platform/symbian/mmp/scummvm_drascula.mmp.in index 529eeb9e0b..d8cc6da6ae 100644 --- a/backends/platform/symbian/mmp/scummvm_drascula.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_drascula.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\drascula // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_gob.mmp.in b/backends/platform/symbian/mmp/scummvm_gob.mmp.in index fa2ce0dedf..ce94f85283 100644 --- a/backends/platform/symbian/mmp/scummvm_gob.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_gob.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\gob // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_groovie.mmp.in b/backends/platform/symbian/mmp/scummvm_groovie.mmp.in index 7a03cc1745..7229edf4aa 100644 --- a/backends/platform/symbian/mmp/scummvm_groovie.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_groovie.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\groovie // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_hugo.mmp.in b/backends/platform/symbian/mmp/scummvm_hugo.mmp.in index 301d23cab4..b3f9edd1e6 100644 --- a/backends/platform/symbian/mmp/scummvm_hugo.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_hugo.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\hugo // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_kyra.mmp.in b/backends/platform/symbian/mmp/scummvm_kyra.mmp.in index 5fa5fdac6a..654632c229 100644 --- a/backends/platform/symbian/mmp/scummvm_kyra.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_kyra.mmp.in @@ -65,5 +65,5 @@ SOURCEPATH ..\..\..\..\engines\kyra // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in b/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in index a297137f02..418730c064 100644 --- a/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_lastexpress.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\lastexpress // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_lure.mmp.in b/backends/platform/symbian/mmp/scummvm_lure.mmp.in index c4a3900a05..e1a63b602b 100644 --- a/backends/platform/symbian/mmp/scummvm_lure.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_lure.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\lure // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_m4.mmp.in b/backends/platform/symbian/mmp/scummvm_m4.mmp.in index f4430f2e58..c2e1ce4e8b 100644 --- a/backends/platform/symbian/mmp/scummvm_m4.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_m4.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\m4 // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_made.mmp.in b/backends/platform/symbian/mmp/scummvm_made.mmp.in index bf50157224..91b9ca756d 100644 --- a/backends/platform/symbian/mmp/scummvm_made.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_made.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\made // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in b/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in index 78e931b06f..0edba5eb4d 100644 --- a/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_mohawk.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\mohawk // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in b/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in index e96ab907cd..744a756f4e 100644 --- a/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\parallaction // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_queen.mmp.in b/backends/platform/symbian/mmp/scummvm_queen.mmp.in index 4f9d9319e0..bf88744701 100644 --- a/backends/platform/symbian/mmp/scummvm_queen.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_queen.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\queen // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_saga.mmp.in b/backends/platform/symbian/mmp/scummvm_saga.mmp.in index e5beefc0c5..a95ee1e7fd 100644 --- a/backends/platform/symbian/mmp/scummvm_saga.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_saga.mmp.in @@ -71,5 +71,5 @@ SOURCEPATH ..\..\..\..\engines\saga // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_sci.mmp.in b/backends/platform/symbian/mmp/scummvm_sci.mmp.in index eb0d1b020f..ca9e712f3f 100644 --- a/backends/platform/symbian/mmp/scummvm_sci.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sci.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\sci // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_scumm.mmp.in b/backends/platform/symbian/mmp/scummvm_scumm.mmp.in index 6b5d249422..0a3cd9bb7d 100644 --- a/backends/platform/symbian/mmp/scummvm_scumm.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_scumm.mmp.in @@ -82,7 +82,7 @@ SOURCE smush/codec47ARM.s // ARM version: add ASM routines // *** Include paths USERINCLUDE ..\..\..\..\engines ..\..\..\..\engines\scumm\smush ..\..\..\..\engines\scumm\insane -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include\libc diff --git a/backends/platform/symbian/mmp/scummvm_sky.mmp.in b/backends/platform/symbian/mmp/scummvm_sky.mmp.in index d1d222b1fa..1bc2301745 100644 --- a/backends/platform/symbian/mmp/scummvm_sky.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sky.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\sky // *** Include paths USERINCLUDE ..\..\..\..\engines ..\..\..\..\engines\sky\music -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_sword1.mmp.in b/backends/platform/symbian/mmp/scummvm_sword1.mmp.in index 0aaf9d504b..6bf543070b 100644 --- a/backends/platform/symbian/mmp/scummvm_sword1.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword1.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\sword1 // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_sword2.mmp.in b/backends/platform/symbian/mmp/scummvm_sword2.mmp.in index 4a7181709f..cee4143f94 100644 --- a/backends/platform/symbian/mmp/scummvm_sword2.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword2.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\sword2 // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in b/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in index 7832fc4880..fa4ef79692 100644 --- a/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_teenagent.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\teenagent // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in b/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in index 6190ec8152..569b79ba3c 100644 --- a/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_tinsel.mmp.in @@ -59,5 +59,5 @@ SOURCEPATH ..\..\..\..\engines\tinsel // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_toon.mmp.in b/backends/platform/symbian/mmp/scummvm_toon.mmp.in index f2301c4ae2..f309e6d0fa 100644 --- a/backends/platform/symbian/mmp/scummvm_toon.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_toon.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\toon // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_touche.mmp.in b/backends/platform/symbian/mmp/scummvm_touche.mmp.in index 9e4c3d0496..ab42afe304 100644 --- a/backends/platform/symbian/mmp/scummvm_touche.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_touche.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\touche // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/backends/platform/symbian/mmp/scummvm_tucker.mmp.in b/backends/platform/symbian/mmp/scummvm_tucker.mmp.in index d193ac49a9..434072bc96 100644 --- a/backends/platform/symbian/mmp/scummvm_tucker.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_tucker.mmp.in @@ -59,6 +59,6 @@ SOURCEPATH ..\..\..\..\engines\tucker // *** Include paths USERINCLUDE ..\..\..\..\engines -USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\sound ..\src +USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\audio ..\src SYSTEMINCLUDE \epoc32\include\ZLIB // before \epoc32\include because symbian already has older version SYSTEMINCLUDE \epoc32\include \epoc32\include\libc ..\src diff --git a/base/commandLine.cpp b/base/commandLine.cpp index 36218376c8..f920dd0170 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -178,6 +178,9 @@ void registerDefaults() { ConfMan.registerDefault("midi_gain", 100); // ConfMan.registerDefault("music_driver", ???); + ConfMan.registerDefault("mt32_device", "null"); + ConfMan.registerDefault("gm_device", "null"); + ConfMan.registerDefault("cdrom", 0); // Game specific diff --git a/common/config-manager.cpp b/common/config-manager.cpp index 0561f390a4..c8c0999a25 100644 --- a/common/config-manager.cpp +++ b/common/config-manager.cpp @@ -103,9 +103,9 @@ void ConfigManager::loadConfigFile(const String &filename) { FSNode node(filename); File cfg_file; if (!cfg_file.open(node)) { - debug("Creating configuration file: %s\n", filename.c_str()); + debug("Creating configuration file: %s", filename.c_str()); } else { - debug("Using configuration file: %s\n", _filename.c_str()); + debug("Using configuration file: %s", _filename.c_str()); loadFromStream(cfg_file); } } @@ -1505,12 +1505,21 @@ case $_host_os in ;; android) CXXFLAGS="$CXXFLAGS --sysroot=$ANDROID_NDK/platforms/android-4/arch-arm" - CXXFLAGS="$CXXFLAGS -Os -mandroid -msoft-float -mthumb-interwork" - CXXFLAGS="$CXXFLAGS -march=armv5te -mtune=xscale" + CXXFLAGS="$CXXFLAGS -march=armv5te -mtune=xscale -msoft-float" + CXXFLAGS="$CXXFLAGS -fpic -ffunction-sections -funwind-tables" + if test "$_release_build" = yes; then + CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -fstrict-aliasing" + else + CXXFLAGS="$CXXFLAGS -fno-omit-frame-pointer -fno-strict-aliasing" + fi + CXXFLAGS="$CXXFLAGS -finline-limit=300" + CXXFLAGS="$CXXFLAGS -Os -mthumb-interwork" + CXXFLAGS="$CXXFLAGS -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__" + CXXFLAGS="$CXXFLAGS -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__" # supress 'mangling of 'va_list' has changed in GCC 4.4' CXXFLAGS="$CXXFLAGS -Wno-psabi" LDFLAGS="$LDFLAGS --sysroot=$ANDROID_NDK/platforms/android-4/arch-arm" - LDFLAGS="$LDFLAGS -mandroid -mthumb-interwork" + LDFLAGS="$LDFLAGS -mthumb-interwork" add_line_to_config_mk "ANDROID_SDK = $ANDROID_SDK" _unix=yes _seq_midi=no @@ -1580,7 +1589,6 @@ case $_host_os in DEFINES="$DEFINES -DSYSTEM_NOT_SUPPORTING_D_TYPE" # Needs -lnetwork for the timidity MIDI driver LIBS="$LIBS -lnetwork" - CXXFLAGS="$CXXFLAGS -fhuge-objects" _unix=yes _seq_midi=no ;; @@ -2096,7 +2104,7 @@ fi # Enable 16bit support only for backends which support it # case $_backend in - dingux | dreamcast | gph | openpandora | psp | samsungtv | sdl | wii) + android | dingux | dreamcast | gph | openpandora | psp | samsungtv | sdl | wii) if test "$_16bit" = auto ; then _16bit=yes else @@ -2956,6 +2964,10 @@ fi # case $_backend in android) + # ssp at this point so the cxxtests link + CXXFLAGS="$CXXFLAGS -fstack-protector -Wa,--noexecstack" + LDFLAGS="$LDFLAGS -Wl,-z,noexecstack" + static_libs='' system_libs='' for lib in $LIBS; do @@ -2974,7 +2986,7 @@ case $_backend in # than pick up anything unhygenic from the Android libs. LIBS="-Wl,-Bstatic $static_libs" LIBS="$LIBS -Wl,-Bdynamic -lgcc $system_libs -llog -lGLESv1_CM" - DEFINES="$DEFINES -D__ANDROID__ -DREDUCE_MEMORY_USAGE" + DEFINES="$DEFINES -DREDUCE_MEMORY_USAGE" ;; dc) INCLUDES="$INCLUDES "'-I$(srcdir)/backends/platform/dc -isystem $(ronindir)/include' diff --git a/dists/android/AndroidManifest.xml b/dists/android/AndroidManifest.xml index 2ea2b484ac..68c58d9aea 100644 --- a/dists/android/AndroidManifest.xml +++ b/dists/android/AndroidManifest.xml @@ -1,58 +1,61 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -*- xml -*- --> +<?xml version="1.0" encoding="utf-8"?> <!-- NB: android:versionCode needs to be bumped for formal releases --> + <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.inodes.gus.scummvm" - android:versionCode="6" android:versionName="1.3.0git" - android:installLocation="preferExternal"> - - <!-- This version works on Android 1.5 (SDK 3) and newer, but we - want Android 2.2 (SDK 8) defaults and features. - --> - <uses-sdk android:minSdkVersion="3" - android:targetSdkVersion="8" /> - - <application android:name=".ScummVMApplication" - android:label="@string/app_name" - android:description="@string/app_desc" - android:icon="@drawable/scummvm" - android:persistent="true"> - <activity android:name=".ScummVMActivity" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen" - android:screenOrientation="landscape" - android:configChanges="orientation|keyboardHidden" - android:windowSoftInputMode="adjustResize"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - </intent-filter> - </activity> - <activity android:name=".Unpacker" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen" - android:screenOrientation="landscape" - android:configChanges="orientation|keyboardHidden"> - <meta-data android:name="org.inodes.gus.unpacker.nextActivity" - android:value="org.inodes.gus.scummvm/.ScummVMActivity" /> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> - - <permission android:name="org.inodes.gus.scummvm.permission.SCUMMVM_PLUGIN" - android:label="@string/scummvm_perm_plugin_label" - android:description="@string/scummvm_perm_plugin_desc" - android:protectionLevel="signature" /> - - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - - - <!-- Always needs some sort of qwerty keyboard. - Can work with a D-pad / trackball --> - <uses-configuration android:reqFiveWayNav="true" - android:reqKeyboardType="qwerty"/> - <!-- .. or touchscreen --> - <uses-configuration android:reqTouchScreen="finger" - android:reqKeyboardType="qwerty"/> - <uses-configuration android:reqTouchScreen="stylus" - android:reqKeyboardType="qwerty"/> + package="org.inodes.gus.scummvm" + android:versionCode="6" + android:versionName="1.3.0git" + android:installLocation="preferExternal"> + + <!-- This version works on Android 1.5 (SDK 3) and newer, but we + want Android 2.2 (SDK 8) defaults and features. --> + <uses-sdk android:minSdkVersion="3" + android:targetSdkVersion="8"/> + + <application android:name=".ScummVMApplication" + android:label="@string/app_name" + android:description="@string/app_desc" + android:icon="@drawable/scummvm"> + <activity android:name=".ScummVMActivity" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" + android:screenOrientation="landscape" + android:configChanges="orientation|keyboardHidden" + android:windowSoftInputMode="adjustResize"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + </intent-filter> + </activity> + + <activity android:name=".Unpacker" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" + android:screenOrientation="landscape" + android:configChanges="orientation|keyboardHidden"> + <meta-data android:name="org.inodes.gus.unpacker.nextActivity" + android:value="org.inodes.gus.scummvm/.ScummVMActivity"/> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + + <permission android:name="org.inodes.gus.scummvm.permission.SCUMMVM_PLUGIN" + android:label="@string/scummvm_perm_plugin_label" + android:description="@string/scummvm_perm_plugin_desc" + android:protectionLevel="signature"/> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + + <!-- Always needs some sort of qwerty keyboard. + Can work with a D-pad / trackball --> + <uses-configuration android:reqFiveWayNav="true" + android:reqKeyboardType="qwerty"/> + + <!-- .. or touchscreen --> + <uses-configuration android:reqTouchScreen="finger" + android:reqKeyboardType="qwerty"/> + + <uses-configuration android:reqTouchScreen="stylus" + android:reqKeyboardType="qwerty"/> </manifest> + diff --git a/dists/android/AndroidManifest.xml.in b/dists/android/AndroidManifest.xml.in index 26a94f957b..7a75b6fd0b 100644 --- a/dists/android/AndroidManifest.xml.in +++ b/dists/android/AndroidManifest.xml.in @@ -1,58 +1,61 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -*- xml -*- --> +<?xml version="1.0" encoding="utf-8"?> <!-- NB: android:versionCode needs to be bumped for formal releases --> + <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.inodes.gus.scummvm" - android:versionCode="6" android:versionName="@VERSION@" - android:installLocation="preferExternal"> - - <!-- This version works on Android 1.5 (SDK 3) and newer, but we - want Android 2.2 (SDK 8) defaults and features. - --> - <uses-sdk android:minSdkVersion="3" - android:targetSdkVersion="8" /> - - <application android:name=".ScummVMApplication" - android:label="@string/app_name" - android:description="@string/app_desc" - android:icon="@drawable/scummvm" - android:persistent="true"> - <activity android:name=".ScummVMActivity" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen" - android:screenOrientation="landscape" - android:configChanges="orientation|keyboardHidden" - android:windowSoftInputMode="adjustResize"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - </intent-filter> - </activity> - <activity android:name=".Unpacker" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen" - android:screenOrientation="landscape" - android:configChanges="orientation|keyboardHidden"> - <meta-data android:name="org.inodes.gus.unpacker.nextActivity" - android:value="org.inodes.gus.scummvm/.ScummVMActivity" /> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> - - <permission android:name="org.inodes.gus.scummvm.permission.SCUMMVM_PLUGIN" - android:label="@string/scummvm_perm_plugin_label" - android:description="@string/scummvm_perm_plugin_desc" - android:protectionLevel="signature" /> - - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - - - <!-- Always needs some sort of qwerty keyboard. - Can work with a D-pad / trackball --> - <uses-configuration android:reqFiveWayNav="true" - android:reqKeyboardType="qwerty"/> - <!-- .. or touchscreen --> - <uses-configuration android:reqTouchScreen="finger" - android:reqKeyboardType="qwerty"/> - <uses-configuration android:reqTouchScreen="stylus" - android:reqKeyboardType="qwerty"/> + package="org.inodes.gus.scummvm" + android:versionCode="6" + android:versionName="@VERSION@" + android:installLocation="preferExternal"> + + <!-- This version works on Android 1.5 (SDK 3) and newer, but we + want Android 2.2 (SDK 8) defaults and features. --> + <uses-sdk android:minSdkVersion="3" + android:targetSdkVersion="8"/> + + <application android:name=".ScummVMApplication" + android:label="@string/app_name" + android:description="@string/app_desc" + android:icon="@drawable/scummvm"> + <activity android:name=".ScummVMActivity" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" + android:screenOrientation="landscape" + android:configChanges="orientation|keyboardHidden" + android:windowSoftInputMode="adjustResize"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + </intent-filter> + </activity> + + <activity android:name=".Unpacker" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" + android:screenOrientation="landscape" + android:configChanges="orientation|keyboardHidden"> + <meta-data android:name="org.inodes.gus.unpacker.nextActivity" + android:value="org.inodes.gus.scummvm/.ScummVMActivity"/> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + + <permission android:name="org.inodes.gus.scummvm.permission.SCUMMVM_PLUGIN" + android:label="@string/scummvm_perm_plugin_label" + android:description="@string/scummvm_perm_plugin_desc" + android:protectionLevel="signature"/> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + + <!-- Always needs some sort of qwerty keyboard. + Can work with a D-pad / trackball --> + <uses-configuration android:reqFiveWayNav="true" + android:reqKeyboardType="qwerty"/> + + <!-- .. or touchscreen --> + <uses-configuration android:reqTouchScreen="finger" + android:reqKeyboardType="qwerty"/> + + <uses-configuration android:reqTouchScreen="stylus" + android:reqKeyboardType="qwerty"/> </manifest> + diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index fdc05f0ba9..c5a1f81af6 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -273,23 +273,16 @@ void AgiEngine::processEvents() { } void AgiEngine::pollTimer() { - uint32 dm; + _lastTick += 50; - if (_tickTimer < _lastTickTimer) - _lastTickTimer = 0; - - while ((dm = _tickTimer - _lastTickTimer) < 5) { + while (_system->getMillis() < _lastTick) { processEvents(); _console->onFrame(); _system->delayMillis(10); _system->updateScreen(); } - _lastTickTimer = _tickTimer; -} -void AgiEngine::agiTimerFunctionLow(void *refCon) { - AgiEngine *self = (AgiEngine *)refCon; - self->_tickTimer++; + _lastTick = _system->getMillis(); } void AgiEngine::pause(uint32 msec) { @@ -532,9 +525,6 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas _allowSynthetic = false; - _tickTimer = 0; - _lastTickTimer = 0; - _intobj = NULL; _menu = NULL; @@ -646,11 +636,10 @@ void AgiEngine::initialize() { _lastSaveTime = 0; - _timer->installTimerProc(agiTimerFunctionLow, 10 * 1000, this); + _lastTick = _system->getMillis(); debugC(2, kDebugLevelMain, "Detect game"); - if (agiDetectGame() == errOK) { _game.state = STATE_LOADED; debugC(2, kDebugLevelMain, "game loaded"); @@ -662,8 +651,6 @@ void AgiEngine::initialize() { } AgiEngine::~AgiEngine() { - _timer->removeTimerProc(agiTimerFunctionLow); - // If the engine hasn't been initialized yet via AgiEngine::initialize(), don't attempt to free any resources, // as they haven't been allocated. Fixes bug #1742432 - AGI: Engine crashes if no game is detected if (_game.state == STATE_INIT) { @@ -719,7 +706,7 @@ void AgiEngine::parseFeatures() { /* FIXME: Seems this method doesn't really do anything. It might be a leftover that could be removed, except that some of its intended purpose may still need to be reimplemented. - + [0:29] <Fingolfin> can you tell me what the point behind AgiEngine::parseFeatures() is? [0:30] <_sev> when games are created with WAGI studio [0:31] <_sev> it creates .wag site with game-specific features such as full game title, whether to use AGIMOUSE etc diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 89b116daec..df19f55b52 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -810,9 +810,7 @@ public: Common::Error saveGameState(int slot, const char *desc); private: - - uint32 _tickTimer; - uint32 _lastTickTimer; + uint32 _lastTick; int _keyQueue[KEY_QUEUE_SIZE]; int _keyQueueStart; @@ -883,7 +881,6 @@ public: virtual bool isKeypress(); virtual void clearKeyQueue(); - static void agiTimerFunctionLow(void *refCon); void initPriTable(); void newInputMode(InputMode mode); diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h index 711701f55a..fb1b830e24 100644 --- a/engines/agi/detection_tables.h +++ b/engines/agi/detection_tables.h @@ -569,6 +569,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME_PS("xmascard", "", "25ad35e9628fc77e5e0dd35852a272b6", 768, 0x2440, GID_XMASCARD, Common::kPlatformCoCo3), FANMADE_F("2 Player Demo", "4279f46b3cebd855132496476b1d2cca", GF_AGIMOUSE), + FANMADE("AGI Combat", "0be6a8a9e19203dcca0067d280798871"), FANMADE("AGI Contest 1 Template", "d879aed25da6fc655564b29567358ae2"), FANMADE("AGI Contest 2 Template", "5a2fb2894207eff36c72f5c1b08bcc07"), FANMADE("AGI Mouse Demo 0.60 demo 1", "c07e2519de674c67386cb2cc6f2e3904"), diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index fe864d7659..1aa6ef5cc4 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -122,9 +122,6 @@ void PreAgiEngine::initialize() { _mixer->playStream(Audio::Mixer::kSFXSoundType, &_speakerHandle, _speakerStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - - //_timer->installTimerProc(agiTimerFunctionLow, 10 * 1000, NULL); - debugC(2, kDebugLevelMain, "Detect game"); // clear all resources and events diff --git a/engines/agos/agos.h b/engines/agos/agos.h index 7201dfd9d3..9c2a2929e3 100644 --- a/engines/agos/agos.h +++ b/engines/agos/agos.h @@ -2018,7 +2018,7 @@ protected: void scrollOracleUp(); void scrollOracleDown(); - void listSaveGames(int n); + void listSaveGamesFeeble(); void saveUserGame(int slot); void windowBackSpace(WindowBlock *window); diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp index d39ca377dc..10d3d7f1ff 100644 --- a/engines/agos/animation.cpp +++ b/engines/agos/animation.cpp @@ -368,7 +368,7 @@ void MoviePlayerDXA::handleNextFrame() { bool MoviePlayerDXA::processFrame() { Graphics::Surface *screen = _vm->_system->lockScreen(); - copyFrameToBuffer((byte *)screen->pixels, (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, _vm->_screenWidth); + copyFrameToBuffer((byte *)screen->pixels, (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, screen->pitch); _vm->_system->unlockScreen(); Common::Rational soundTime(_mixer->getSoundElapsedTime(_bgSound), 1000); @@ -482,7 +482,7 @@ void MoviePlayerSMK::nextFrame() { bool MoviePlayerSMK::processFrame() { Graphics::Surface *screen = _vm->_system->lockScreen(); - copyFrameToBuffer((byte *)screen->pixels, (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, _vm->_screenWidth); + copyFrameToBuffer((byte *)screen->pixels, (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, screen->pitch); _vm->_system->unlockScreen(); uint32 waitTime = getTimeToNextFrame(); diff --git a/engines/agos/oracle.cpp b/engines/agos/oracle.cpp index 0a4ab8246f..8ff79965e4 100644 --- a/engines/agos/oracle.cpp +++ b/engines/agos/oracle.cpp @@ -369,7 +369,7 @@ void AGOSEngine_Feeble::swapCharacterLogo() { } } -void AGOSEngine_Feeble::listSaveGames(int n) { +void AGOSEngine_Feeble::listSaveGamesFeeble() { char b[108]; Common::InSaveFile *in; uint16 j, k, z, maxFiles; @@ -377,8 +377,8 @@ void AGOSEngine_Feeble::listSaveGames(int n) { memset(b, 0, 108); maxFiles = countSaveGames() - 1; - j = maxFiles - n + 1; - k = maxFiles - j + 1; + j = maxFiles; + k = 1; z = maxFiles; if (getBitFlag(95)) { j++; diff --git a/engines/agos/script_ff.cpp b/engines/agos/script_ff.cpp index 3198b1b499..dbd89cebf5 100644 --- a/engines/agos/script_ff.cpp +++ b/engines/agos/script_ff.cpp @@ -430,8 +430,7 @@ void AGOSEngine_Feeble::off_loadUserGame() { } void AGOSEngine_Feeble::off_listSaveGames() { - // 134: dummy opcode? - listSaveGames(1); + listSaveGamesFeeble(); } void AGOSEngine_Feeble::off_checkCD() { diff --git a/engines/cruise/cruise_main.cpp b/engines/cruise/cruise_main.cpp index 6b487fadc9..99de66deb6 100644 --- a/engines/cruise/cruise_main.cpp +++ b/engines/cruise/cruise_main.cpp @@ -1889,7 +1889,7 @@ void CruiseEngine::mainLoop() { // FIXME: I suspect that the original game does multiple script executions between game frames; the bug with // Raoul appearing when looking at the book is being there are 3 script iterations separation between the // scene being changed to the book, and the Raoul actor being frozen/disabled. This loop is a hack to ensure - // that when a background changes, a few extra script executions are done + // that does a few extra script executions for that scene bool bgChanged; int numIterations = 1; @@ -1902,7 +1902,8 @@ void CruiseEngine::mainLoop() { removeFinishedScripts(&relHead); removeFinishedScripts(&procHead); - if (!bgChanged && backgroundChanged[masterScreen]) { + if (!bgChanged && backgroundChanged[masterScreen] && + !strcmp(backgroundTable[0].name, "S06B.PI1")) { bgChanged = true; numIterations += 2; } diff --git a/engines/cruise/script.cpp b/engines/cruise/script.cpp index 317dcfbbe6..aae4dba475 100644 --- a/engines/cruise/script.cpp +++ b/engines/cruise/script.cpp @@ -619,13 +619,13 @@ int executeScripts(scriptInstanceStruct *ptr) { positionInStack = 0; do { -//#ifdef SKIP_INTRO +#ifdef SKIP_INTRO if (currentScriptPtr->scriptOffset == 290 && currentScriptPtr->overlayNumber == 4 && currentScriptPtr->scriptNumber == 0) { currentScriptPtr->scriptOffset = 923; } -//#endif +#endif opcodeType = getByteFromScript(); debugC(5, kCruiseDebugScript, "Script %s/%d ip=%d opcode=%d", diff --git a/engines/engine.cpp b/engines/engine.cpp index 0d92c1aef1..d773f370f5 100644 --- a/engines/engine.cpp +++ b/engines/engine.cpp @@ -145,7 +145,11 @@ void initCommonGFX(bool defaultTo1XScaler) { assert(transientDomain); const bool useDefaultGraphicsMode = - !transientDomain->contains("gfx_mode") && + (!transientDomain->contains("gfx_mode") || + !scumm_stricmp(transientDomain->getVal("gfx_mode").c_str(), "normal") || + !scumm_stricmp(transientDomain->getVal("gfx_mode").c_str(), "default") + ) + && ( !gameDomain || !gameDomain->contains("gfx_mode") || @@ -155,10 +159,7 @@ void initCommonGFX(bool defaultTo1XScaler) { // See if the game should default to 1x scaler if (useDefaultGraphicsMode && defaultTo1XScaler) { - // FIXME: As a hack, we use "1x" here. Would be nicer to use - // getDefaultGraphicsMode() instead, but right now, we do not specify - // whether that is a 1x scaler or not... - g_system->setGraphicsMode("1x"); + g_system->resetGraphicsScale(); } else { // Override global scaler with any game-specific define if (ConfMan.hasKey("gfx_mode")) { diff --git a/engines/gob/save/saveload.h b/engines/gob/save/saveload.h index c231c1dbbb..dc1c184504 100644 --- a/engines/gob/save/saveload.h +++ b/engines/gob/save/saveload.h @@ -305,7 +305,7 @@ protected: int getSlot(int32 offset) const; int getSlotRemainder(int32 offset) const; - void buildIndex(byte *buffer) const; + void buildScreenshotIndex(byte *buffer) const; protected: uint32 _shotSize; @@ -430,7 +430,7 @@ protected: int getSlot(int32 offset) const; int getSlotRemainder(int32 offset) const; - void buildIndex(byte *buffer) const; + void buildScreenshotIndex(byte *buffer) const; }; File *_file; diff --git a/engines/gob/save/saveload_inca2.cpp b/engines/gob/save/saveload_inca2.cpp index 68c76c3f2b..5fa1b69fa7 100644 --- a/engines/gob/save/saveload_inca2.cpp +++ b/engines/gob/save/saveload_inca2.cpp @@ -260,7 +260,7 @@ int SaveLoad_Inca2::ScreenshotHandler::File::getSlotRemainder(int32 offset) cons return (offset - 80) % 15168; } -void SaveLoad_Inca2::ScreenshotHandler::File::buildIndex(byte *buffer) const { +void SaveLoad_Inca2::ScreenshotHandler::File::buildScreenshotIndex(byte *buffer) const { Common::SaveFileManager *saveMan = g_system->getSavefileManager(); Common::InSaveFile *in; @@ -306,7 +306,7 @@ bool SaveLoad_Inca2::ScreenshotHandler::load(int16 dataVar, int32 size, int32 of } // Create/Fake the index - _file->buildIndex(_index + 40); + _file->buildScreenshotIndex(_index + 40); _vm->_inter->_variables->copyFrom(dataVar, _index + offset, size); diff --git a/engines/gob/save/saveload_v3.cpp b/engines/gob/save/saveload_v3.cpp index 098b8e1160..39edddb66f 100644 --- a/engines/gob/save/saveload_v3.cpp +++ b/engines/gob/save/saveload_v3.cpp @@ -367,7 +367,7 @@ int SaveLoad_v3::ScreenshotHandler::File::getSlotRemainder(int32 offset) const { return ((offset - _shotIndexSize) % _shotSize); } -void SaveLoad_v3::ScreenshotHandler::File::buildIndex(byte *buffer) const { +void SaveLoad_v3::ScreenshotHandler::File::buildScreenshotIndex(byte *buffer) const { Common::SaveFileManager *saveMan = g_system->getSavefileManager(); Common::InSaveFile *in; @@ -418,12 +418,12 @@ bool SaveLoad_v3::ScreenshotHandler::load(int16 dataVar, int32 size, int32 offse if (_sShotType == kScreenshotTypeGob3) { // Create/Fake the index - _file->buildIndex(_index + 40); + _file->buildScreenshotIndex(_index + 40); // The last 10 bytes are 0 memset(_index + 70, 0, 10); } else if (_sShotType == kScreenshotTypeLost) { // Create/Fake the index - _file->buildIndex(_index); + _file->buildScreenshotIndex(_index); // The last byte is 0 _index[30] = 0; } diff --git a/engines/gob/sound/bgatmosphere.cpp b/engines/gob/sound/bgatmosphere.cpp index f0977aa45b..8850a727d3 100644 --- a/engines/gob/sound/bgatmosphere.cpp +++ b/engines/gob/sound/bgatmosphere.cpp @@ -47,7 +47,7 @@ BackgroundAtmosphere::~BackgroundAtmosphere() { queueClear(); } -void BackgroundAtmosphere::play() { +void BackgroundAtmosphere::playBA() { Common::StackLock slock(_mutex); _queuePos = -1; @@ -59,7 +59,7 @@ void BackgroundAtmosphere::play() { SoundMixer::play(*_queue[_queuePos], 1, 0); } -void BackgroundAtmosphere::stop() { +void BackgroundAtmosphere::stopBA() { SoundMixer::stop(0); } diff --git a/engines/gob/sound/bgatmosphere.h b/engines/gob/sound/bgatmosphere.h index 71a2263341..7e58c0b4e9 100644 --- a/engines/gob/sound/bgatmosphere.h +++ b/engines/gob/sound/bgatmosphere.h @@ -46,8 +46,8 @@ public: BackgroundAtmosphere(Audio::Mixer &mixer); ~BackgroundAtmosphere(); - void play(); - void stop(); + void playBA(); + void stopBA(); void setPlayMode(PlayMode mode); diff --git a/engines/gob/sound/sound.cpp b/engines/gob/sound/sound.cpp index 5950388c28..1aa63eb940 100644 --- a/engines/gob/sound/sound.cpp +++ b/engines/gob/sound/sound.cpp @@ -648,7 +648,7 @@ void Sound::bgPlay(const char *file, SoundType type) { debugC(1, kDebugSound, "BackgroundAtmosphere: Playing \"%s\"", file); - _bgatmos->stop(); + _bgatmos->stopBA(); _bgatmos->queueClear(); SoundDesc *sndDesc = new SoundDesc; @@ -658,7 +658,7 @@ void Sound::bgPlay(const char *file, SoundType type) { } _bgatmos->queueSample(*sndDesc); - _bgatmos->play(); + _bgatmos->playBA(); } void Sound::bgPlay(const char *base, const char *ext, SoundType type, int count) { @@ -667,7 +667,7 @@ void Sound::bgPlay(const char *base, const char *ext, SoundType type, int count) debugC(1, kDebugSound, "BackgroundAtmosphere: Playing \"%s\" (%d)", base, count); - _bgatmos->stop(); + _bgatmos->stopBA(); _bgatmos->queueClear(); int length = strlen(base) + 7; @@ -684,7 +684,7 @@ void Sound::bgPlay(const char *base, const char *ext, SoundType type, int count) delete sndDesc; } - _bgatmos->play(); + _bgatmos->playBA(); } void Sound::bgStop() { @@ -693,7 +693,7 @@ void Sound::bgStop() { debugC(1, kDebugSound, "BackgroundAtmosphere: Stopping playback"); - _bgatmos->stop(); + _bgatmos->stopBA(); _bgatmos->queueClear(); } diff --git a/engines/groovie/music.h b/engines/groovie/music.h index 74bb6f98e5..5974559c53 100644 --- a/engines/groovie/music.h +++ b/engines/groovie/music.h @@ -94,14 +94,14 @@ public: ~MusicPlayerMidi(); // MidiDriver interface - int open(); - void close(); - void send(uint32 b); - void metaEvent(byte type, byte *data, uint16 length); - void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); - uint32 getBaseTempo(); - MidiChannel *allocateChannel(); - MidiChannel *getPercussionChannel(); + virtual int open(); + virtual void close(); + virtual void send(uint32 b); + virtual void metaEvent(byte type, byte *data, uint16 length); + virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + virtual uint32 getBaseTempo(); + virtual MidiChannel *allocateChannel(); + virtual MidiChannel *getPercussionChannel(); private: // Channel volumes @@ -115,7 +115,7 @@ protected: MidiParser *_midiParser; MidiDriver *_driver; - void onTimerInternal(); + virtual void onTimerInternal(); void updateVolume(); void unload(); diff --git a/engines/hugo/display.cpp b/engines/hugo/display.cpp index 0210eb8f04..aa4d4384c1 100644 --- a/engines/hugo/display.cpp +++ b/engines/hugo/display.cpp @@ -189,13 +189,13 @@ void Screen::displayRect(const int16 x, const int16 y, const int16 dx, const int * Alse save the new color in the current palette. */ void Screen::remapPal(const uint16 oldIndex, const uint16 newIndex) { - debugC(1, kDebugDisplay, "Remap_pal(%d, %d)", oldIndex, newIndex); + debugC(1, kDebugDisplay, "RemapPal(%d, %d)", oldIndex, newIndex); _curPalette[3 * oldIndex + 0] = _mainPalette[newIndex * 3 + 0]; _curPalette[3 * oldIndex + 1] = _mainPalette[newIndex * 3 + 1]; _curPalette[3 * oldIndex + 2] = _mainPalette[newIndex * 3 + 2]; - g_system->getPaletteManager()->setPalette(_curPalette, oldIndex, 1); + g_system->getPaletteManager()->setPalette(_curPalette, 0, _paletteSize / 3); } /** @@ -214,10 +214,10 @@ void Screen::savePal(Common::WriteStream *f) const { void Screen::restorePal(Common::ReadStream *f) { debugC(1, kDebugDisplay, "restorePal()"); - for (int i = 0; i < _paletteSize; i++) { + for (int i = 0; i < _paletteSize; i++) _curPalette[i] = f->readByte(); - g_system->getPaletteManager()->setPalette(_curPalette, i, 1); - } + + g_system->getPaletteManager()->setPalette(_curPalette, 0, _paletteSize / 3); } @@ -233,24 +233,6 @@ void Screen::setBackgroundColor(const uint16 color) { } /** - * Return the overlay state (Foreground/Background) of the currently - * processed object by looking down the current column for an overlay - * base bit set (in which case the object is foreground). - */ -overlayState_t Screen::findOvl(seq_t *seq_p, image_pt dst_p, uint16 y) { - debugC(4, kDebugDisplay, "findOvl()"); - - for (; y < seq_p->lines; y++) { // Each line in object - byte ovb = _vm->_object->getBaseBoundary((uint16)(dst_p - _frontBuffer) >> 3); // Ptr into overlay bits - if (ovb & (0x80 >> ((uint16)(dst_p - _frontBuffer) & 7))) // Overlay bit is set - return kOvlForeground; // Found a bit - must be foreground - dst_p += kXPix; - } - - return kOvlBackground; // No bits set, must be background -} - -/** * Merge an object frame into _frontBuffer at sx, sy and update rectangle list. * If fore TRUE, force object above any overlay */ @@ -261,7 +243,7 @@ void Screen::displayFrame(const int sx, const int sy, seq_t *seq, const bool for image_pt subFrontBuffer = &_frontBuffer[sy * kXPix + sx]; // Ptr to offset in _frontBuffer int16 frontBufferwrap = kXPix - seq->x2 - 1; // Wraps dest_p after each line int16 imageWrap = seq->bytesPerLine8 - seq->x2 - 1; - overlayState_t overlayState = kOvlUndef; // Overlay state of object + overlayState_t overlayState = (foreFl) ? kOvlForeground : kOvlUndef; // Overlay state of object for (uint16 y = 0; y < seq->lines; y++) { // Each line in object for (uint16 x = 0; x <= seq->x2; x++) { if (*image) { // Non-transparent @@ -269,7 +251,7 @@ void Screen::displayFrame(const int sx, const int sy, seq_t *seq, const bool for if (ovlBound & (0x80 >> ((uint16)(subFrontBuffer - _frontBuffer) & 7))) { // Overlay bit is set if (overlayState == kOvlUndef) // Overlay defined yet? overlayState = findOvl(seq, subFrontBuffer, y);// No, find it. - if (foreFl || overlayState == kOvlForeground) // Object foreground + if (overlayState == kOvlForeground) // Object foreground *subFrontBuffer = *image; // Copy pixel } else { // No overlay *subFrontBuffer = *image; // Copy pixel @@ -740,6 +722,25 @@ void Screen_v1d::loadFontArr(Common::ReadStream &in) { } } +/** + * Return the overlay state (Foreground/Background) of the currently + * processed object by looking down the current column for an overlay + * base byte set (in which case the object is foreground). + */ +overlayState_t Screen_v1d::findOvl(seq_t *seq_p, image_pt dst_p, uint16 y) { + debugC(4, kDebugDisplay, "findOvl()"); + + uint16 index = (uint16)(dst_p - _frontBuffer) >> 3; + + for (int i = 0; i < seq_p->lines-y; i++) { // Each line in object + if (_vm->_object->getBaseBoundary(index)) // If any overlay base byte is non-zero then the object is foreground, else back. + return kOvlForeground; + index += kCompLineSize; + } + + return kOvlBackground; // No bits set, must be background +} + Screen_v1w::Screen_v1w(HugoEngine *vm) : Screen(vm) { } @@ -790,5 +791,23 @@ void Screen_v1w::loadFontArr(Common::ReadStream &in) { } } +/** + * Return the overlay state (Foreground/Background) of the currently + * processed object by looking down the current column for an overlay + * base bit set (in which case the object is foreground). + */ +overlayState_t Screen_v1w::findOvl(seq_t *seq_p, image_pt dst_p, uint16 y) { + debugC(4, kDebugDisplay, "findOvl()"); + + for (; y < seq_p->lines; y++) { // Each line in object + byte ovb = _vm->_object->getBaseBoundary((uint16)(dst_p - _frontBuffer) >> 3); // Ptr into overlay bits + if (ovb & (0x80 >> ((uint16)(dst_p - _frontBuffer) & 7))) // Overlay bit is set + return kOvlForeground; // Found a bit - must be foreground + dst_p += kXPix; + } + + return kOvlBackground; // No bits set, must be background +} + } // End of namespace Hugo diff --git a/engines/hugo/display.h b/engines/hugo/display.h index f234f76019..91e1752df0 100644 --- a/engines/hugo/display.h +++ b/engines/hugo/display.h @@ -101,10 +101,6 @@ protected: static const byte stdMouseCursorHeight = 20; static const byte stdMouseCursorWidth = 12; - inline bool isInX(const int16 x, const rect_t *rect) const; - inline bool isInY(const int16 y, const rect_t *rect) const; - inline bool isOverlapping(const rect_t *rectA, const rect_t *rectB) const; - bool fontLoadedFl[kNumFonts]; // Fonts used in dib (non-GDI) @@ -115,6 +111,14 @@ protected: byte *_mainPalette; int16 _arrayFontSize[kNumFonts]; + viewdib_t _frontBuffer; + + inline bool isInX(const int16 x, const rect_t *rect) const; + inline bool isInY(const int16 y, const rect_t *rect) const; + inline bool isOverlapping(const rect_t *rectA, const rect_t *rectB) const; + + virtual overlayState_t findOvl(seq_t *seq_p, image_pt dst_p, uint16 y) = 0; + private: byte *_curPalette; byte _iconImage[kInvDx * kInvDy]; @@ -125,9 +129,6 @@ private: int16 mergeLists(rect_t *list, rect_t *blist, const int16 len, int16 blen); int16 center(const char *s) const; - overlayState_t findOvl(seq_t *seq_p, image_pt dst_p, uint16 y); - - viewdib_t _frontBuffer; viewdib_t _backBuffer; viewdib_t _GUIBuffer; // User interface images viewdib_t _backBufferBackup; // Backup _backBuffer during inventory @@ -151,6 +152,8 @@ public: void loadFont(int16 fontId); void loadFontArr(Common::ReadStream &in); +protected: + overlayState_t findOvl(seq_t *seq_p, image_pt dst_p, uint16 y); }; class Screen_v1w : public Screen { @@ -160,6 +163,8 @@ public: void loadFont(int16 fontId); void loadFontArr(Common::ReadStream &in); +protected: + overlayState_t findOvl(seq_t *seq_p, image_pt dst_p, uint16 y); }; } // End of namespace Hugo diff --git a/engines/hugo/file.cpp b/engines/hugo/file.cpp index 740513d7b2..c287ebf6e8 100644 --- a/engines/hugo/file.cpp +++ b/engines/hugo/file.cpp @@ -563,6 +563,8 @@ void FileManager::readBootFile() { if (_vm->_gameVariant == kGameVariantH1Dos) { //TODO initialize properly _boot structure warning("readBootFile - Skipping as H1 Dos may be a freeware"); + memset(_vm->_boot.distrib, '\0', sizeof(_vm->_boot.distrib)); + _vm->_boot.registered = kRegFreeware; return; } else { error("Missing startup file"); diff --git a/engines/hugo/hugo.cpp b/engines/hugo/hugo.cpp index 2143b8cfde..9a622290f9 100644 --- a/engines/hugo/hugo.cpp +++ b/engines/hugo/hugo.cpp @@ -28,6 +28,7 @@ #include "common/events.h" #include "common/EventRecorder.h" #include "common/debug-channels.h" +#include "common/config-manager.h" #include "hugo/hugo.h" #include "hugo/game.h" @@ -253,14 +254,20 @@ Common::Error HugoEngine::run() { // Start the state machine _status.viewState = kViewIntroInit; - _status.doQuitFl = false; + int16 loadSlot = Common::ConfigManager::instance().getInt("save_slot"); + if (loadSlot >= 0) { + _status.skipIntroFl = true; + _file->restoreGame(loadSlot); + _scheduler->restoreScreen(*_screen_p); + _status.viewState = kViewPlay; + } while (!_status.doQuitFl) { _screen->drawBoundaries(); - g_system->updateScreen(); runMachine(); + // Handle input Common::Event event; while (_eventMan->pollEvent(event)) { @@ -285,6 +292,7 @@ Common::Error HugoEngine::run() { break; } } + _mouse->mouseHandler(); // Mouse activity - adds to display list _screen->displayList(kDisplayDisplay); // Blit the display list to screen _status.doQuitFl |= shouldQuit(); // update game quit flag @@ -623,6 +631,13 @@ void HugoEngine::readScreenFiles(const int screenNum) { _file->readBackground(screenNum); // Scenery file memcpy(_screen->getBackBuffer(), _screen->getFrontBuffer(), sizeof(_screen->getFrontBuffer())); // Make a copy + + // Workaround for graphic glitches in DOS versions. Cleaning the overlays fix the problem + memset(_object->_objBound, '\0', sizeof(overlay_t)); + memset(_object->_boundary, '\0', sizeof(overlay_t)); + memset(_object->_overlay, '\0', sizeof(overlay_t)); + memset(_object->_ovlBase, '\0', sizeof(overlay_t)); + _file->readOverlay(screenNum, _object->_boundary, kOvlBoundary); // Boundary file _file->readOverlay(screenNum, _object->_overlay, kOvlOverlay); // Overlay file _file->readOverlay(screenNum, _object->_ovlBase, kOvlBase); // Overlay base file @@ -658,7 +673,7 @@ void HugoEngine::calcMaxScore() { void HugoEngine::endGame() { debugC(1, kDebugEngine, "endGame"); - if (!_boot.registered) + if (_boot.registered != kRegRegistered) Utils::Box(kBoxAny, "%s", _text->getTextEngine(kEsAdvertise)); Utils::Box(kBoxAny, "%s\n%s", _episode, getCopyrightString()); _status.viewState = kViewExit; diff --git a/engines/hugo/hugo.h b/engines/hugo/hugo.h index b2739c829e..2de442e0f7 100644 --- a/engines/hugo/hugo.h +++ b/engines/hugo/hugo.h @@ -58,7 +58,7 @@ class RandomSource; */ namespace Hugo { -static const int kSavegameVersion = 3; +static const int kSavegameVersion = 5; static const int kInvDx = 32; // Width of an inventory icon static const int kInvDy = 32; // Height of inventory icon static const int kMaxTunes = 16; // Max number of tunes @@ -125,6 +125,12 @@ enum HugoDebugChannels { kDebugMusic = 1 << 9 }; +enum HugoRegistered { + kRegShareware = 0, + kRegRegistered, + kRegFreeware +}; + /** * Ways to dismiss a text/prompt box */ @@ -242,7 +248,6 @@ public: object_t *_hero; byte *_screen_p; byte _heroImage; - byte *_screenStates; command_t _line; // Line of user text input config_t _config; // User's config diff --git a/engines/hugo/intro.cpp b/engines/hugo/intro.cpp index 689dfbfd7c..7551476300 100644 --- a/engines/hugo/intro.cpp +++ b/engines/hugo/intro.cpp @@ -119,15 +119,19 @@ bool intro_v1d::introPlay() { error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 8"); char buffer[80]; - if (_vm->_boot.registered) + if (_vm->_boot.registered == kRegRegistered) strcpy(buffer, "Registered Version"); - else + else if (_vm->_boot.registered == kRegShareware) strcpy(buffer, "Shareware Version"); + else if (_vm->_boot.registered == kRegFreeware) + strcpy(buffer, "Freeware Version"); + else + error("Unknown registration flag in hugo.bsf: %d", _vm->_boot.registered); font.drawString(&surf, buffer, 0, 163, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter); font.drawString(&surf, _vm->getCopyrightString(), 0, 176, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter); - if (scumm_stricmp(_vm->_boot.distrib, "David P. Gray")) { + if ((*_vm->_boot.distrib != '\0') && (scumm_stricmp(_vm->_boot.distrib, "David P. Gray"))) { sprintf(buffer, "Distributed by %s.", _vm->_boot.distrib); font.drawString(&surf, buffer, 0, 75, 320, _TMAGENTA, Graphics::kTextAlignCenter); } diff --git a/engines/hugo/object.cpp b/engines/hugo/object.cpp index 04e3449cbb..f82a6a53c6 100644 --- a/engines/hugo/object.cpp +++ b/engines/hugo/object.cpp @@ -418,20 +418,25 @@ void ObjectHandler::readUse(Common::ReadStream &in, uses_t &curUse) { */ void ObjectHandler::loadObjectUses(Common::ReadStream &in) { uses_t tmpUse; + tmpUse.targets = 0; + //Read _uses for (int varnt = 0; varnt < _vm->_numVariant; varnt++) { - tmpUse.targets = 0; uint16 numElem = in.readUint16BE(); if (varnt == _vm->_gameVariant) { _usesSize = numElem; _uses = (uses_t *)malloc(sizeof(uses_t) * numElem); } - for (int i = 0; i < numElem; i++) - readUse(in, (varnt == _vm->_gameVariant) ? _uses[i] : tmpUse); - - if (tmpUse.targets) - free(tmpUse.targets); + for (int i = 0; i < numElem; i++) { + if (varnt == _vm->_gameVariant) + readUse(in, _uses[i]); + else { + readUse(in, tmpUse); + free(tmpUse.targets); + tmpUse.targets = 0; + } + } } } @@ -497,20 +502,26 @@ void ObjectHandler::readObject(Common::ReadStream &in, object_t &curObject) { void ObjectHandler::loadObjectArr(Common::ReadStream &in) { debugC(6, kDebugObject, "loadObject(&in)"); object_t tmpObject; + tmpObject.stateDataIndex = 0; for (int varnt = 0; varnt < _vm->_numVariant; varnt++) { uint16 numElem = in.readUint16BE(); - tmpObject.stateDataIndex = 0; + if (varnt == _vm->_gameVariant) { _objCount = numElem; _objects = (object_t *)malloc(sizeof(object_t) * numElem); } - for (int i = 0; i < numElem; i++) - readObject(in, (varnt == _vm->_gameVariant) ? _objects[i] : tmpObject); - - if (tmpObject.stateDataIndex) - free(tmpObject.stateDataIndex); + for (int i = 0; i < numElem; i++) { + if (varnt == _vm->_gameVariant) + readObject(in, _objects[i]); + else { + // Skip over uneeded objects. + readObject(in, tmpObject); + free(tmpObject.stateDataIndex); + tmpObject.stateDataIndex = 0; + } + } } } diff --git a/engines/hugo/schedule.cpp b/engines/hugo/schedule.cpp index 5843c2317e..1556f3a154 100644 --- a/engines/hugo/schedule.cpp +++ b/engines/hugo/schedule.cpp @@ -708,21 +708,19 @@ void Scheduler::saveEvents(Common::WriteStream *f) { f->writeSint16BE(tailIndex); // Convert event ptrs to indexes - event_t saveEventArr[kMaxEvents]; // Convert event ptrs to indexes for (int16 i = 0; i < kMaxEvents; i++) { event_t *wrkEvent = &_events[i]; - saveEventArr[i] = *wrkEvent; - // fix up action pointer (to do better) + // fix up action pointer (to do better) int16 index, subElem; - findAction(saveEventArr[i].action, &index, &subElem); - saveEventArr[i].action = (act*)((index << 16)| subElem); - - saveEventArr[i].prevEvent = (wrkEvent->prevEvent == 0) ? (event_t *) - 1 : (event_t *)(wrkEvent->prevEvent - _events); - saveEventArr[i].nextEvent = (wrkEvent->nextEvent == 0) ? (event_t *) - 1 : (event_t *)(wrkEvent->nextEvent - _events); + findAction(wrkEvent->action, &index, &subElem); + f->writeSint16BE(index); + f->writeSint16BE(subElem); + f->writeByte((wrkEvent->localActionFl) ? 1 : 0); + f->writeUint32BE(wrkEvent->time); + f->writeSint16BE((wrkEvent->prevEvent == 0) ? -1 : (wrkEvent->prevEvent - _events)); + f->writeSint16BE((wrkEvent->nextEvent == 0) ? -1 : (wrkEvent->nextEvent - _events)); } - - f->write(saveEventArr, sizeof(saveEventArr)); } /** @@ -730,27 +728,11 @@ void Scheduler::saveEvents(Common::WriteStream *f) { */ void Scheduler::restoreActions(Common::ReadStream *f) { - for (int i = 0; i < _actListArrSize; i++) { - - // read all the sub elems - int j = 0; - do { - - // handle special case for a3, keep list pointer - int* responsePtr = 0; - if (_actListArr[i][j].a3.actType == PROMPT) { - responsePtr = _actListArr[i][j].a3.responsePtr; - } - - f->read(&_actListArr[i][j], sizeof(act)); - - // handle special case for a3, reset list pointer - if (_actListArr[i][j].a3.actType == PROMPT) { - _actListArr[i][j].a3.responsePtr = responsePtr; - } - j++; - } while (_actListArr[i][j-1].a0.actType != ANULL); + uint16 numSubElem = f->readUint16BE(); + for (int j = 0; j < numSubElem; j++) { + readAct(*f, _actListArr[i][j]); + } } } @@ -764,23 +746,301 @@ int16 Scheduler::calcMaxPoints() const { /* * Save the action data in the file with handle f */ -void Scheduler::saveActions(Common::WriteStream* f) const { +void Scheduler::saveActions(Common::WriteStream *f) const { + byte subElemType; + int16 nbrCpt; + uint16 nbrSubElem; + for (int i = 0; i < _actListArrSize; i++) { // write all the sub elems data - - int j = 0; - do { - f->write(&_actListArr[i][j], sizeof(act)); - j++; - } while (_actListArr[i][j-1].a0.actType != ANULL); + for (nbrSubElem = 1; _actListArr[i][nbrSubElem - 1].a0.actType != ANULL; nbrSubElem++) + ; + + f->writeUint16BE(nbrSubElem); + for (int j = 0; j < nbrSubElem; j++) { + subElemType = _actListArr[i][j].a0.actType; + f->writeByte(subElemType); + switch (subElemType) { + case ANULL: // -1 + break; + case ASCHEDULE: // 0 + f->writeSint16BE(_actListArr[i][j].a0.timer); + f->writeUint16BE(_actListArr[i][j].a0.actIndex); + break; + case START_OBJ: // 1 + f->writeSint16BE(_actListArr[i][j].a1.timer); + f->writeSint16BE(_actListArr[i][j].a1.objIndex); + f->writeSint16BE(_actListArr[i][j].a1.cycleNumb); + f->writeByte(_actListArr[i][j].a1.cycle); + break; + case INIT_OBJXY: // 2 + f->writeSint16BE(_actListArr[i][j].a2.timer); + f->writeSint16BE(_actListArr[i][j].a2.objIndex); + f->writeSint16BE(_actListArr[i][j].a2.x); + f->writeSint16BE(_actListArr[i][j].a2.y); + break; + case PROMPT: // 3 + f->writeSint16BE(_actListArr[i][j].a3.timer); + f->writeSint16BE(_actListArr[i][j].a3.promptIndex); + for (nbrCpt = 0; _actListArr[i][j].a3.responsePtr[nbrCpt] != -1; nbrCpt++) + ; + nbrCpt++; + f->writeUint16BE(nbrCpt); + for (int k = 0; k < nbrCpt; k++) + f->writeSint16BE(_actListArr[i][j].a3.responsePtr[k]); + f->writeUint16BE(_actListArr[i][j].a3.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a3.actFailIndex); + f->writeByte((_actListArr[i][j].a3.encodedFl) ? 1 : 0); + break; + case BKGD_COLOR: // 4 + f->writeSint16BE(_actListArr[i][j].a4.timer); + f->writeUint32BE(_actListArr[i][j].a4.newBackgroundColor); + break; + case INIT_OBJVXY: // 5 + f->writeSint16BE(_actListArr[i][j].a5.timer); + f->writeSint16BE(_actListArr[i][j].a5.objIndex); + f->writeSint16BE(_actListArr[i][j].a5.vx); + f->writeSint16BE(_actListArr[i][j].a5.vy); + break; + case INIT_CARRY: // 6 + f->writeSint16BE(_actListArr[i][j].a6.timer); + f->writeSint16BE(_actListArr[i][j].a6.objIndex); + f->writeByte((_actListArr[i][j].a6.carriedFl) ? 1 : 0); + break; + case INIT_HF_COORD: // 7 + f->writeSint16BE(_actListArr[i][j].a7.timer); + f->writeSint16BE(_actListArr[i][j].a7.objIndex); + break; + case NEW_SCREEN: // 8 + f->writeSint16BE(_actListArr[i][j].a8.timer); + f->writeSint16BE(_actListArr[i][j].a8.screenIndex); + break; + case INIT_OBJSTATE: // 9 + f->writeSint16BE(_actListArr[i][j].a9.timer); + f->writeSint16BE(_actListArr[i][j].a9.objIndex); + f->writeByte(_actListArr[i][j].a9.newState); + break; + case INIT_PATH: // 10 + f->writeSint16BE(_actListArr[i][j].a10.timer); + f->writeSint16BE(_actListArr[i][j].a10.objIndex); + f->writeSint16BE(_actListArr[i][j].a10.newPathType); + f->writeByte(_actListArr[i][j].a10.vxPath); + f->writeByte(_actListArr[i][j].a10.vyPath); + break; + case COND_R: // 11 + f->writeSint16BE(_actListArr[i][j].a11.timer); + f->writeSint16BE(_actListArr[i][j].a11.objIndex); + f->writeByte(_actListArr[i][j].a11.stateReq); + f->writeUint16BE(_actListArr[i][j].a11.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a11.actFailIndex); + break; + case TEXT: // 12 + f->writeSint16BE(_actListArr[i][j].a12.timer); + f->writeSint16BE(_actListArr[i][j].a12.stringIndex); + break; + case SWAP_IMAGES: // 13 + f->writeSint16BE(_actListArr[i][j].a13.timer); + f->writeSint16BE(_actListArr[i][j].a13.objIndex1); + f->writeSint16BE(_actListArr[i][j].a13.objIndex2); + break; + case COND_SCR: // 14 + f->writeSint16BE(_actListArr[i][j].a14.timer); + f->writeSint16BE(_actListArr[i][j].a14.objIndex); + f->writeSint16BE(_actListArr[i][j].a14.screenReq); + f->writeUint16BE(_actListArr[i][j].a14.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a14.actFailIndex); + break; + case AUTOPILOT: // 15 + f->writeSint16BE(_actListArr[i][j].a15.timer); + f->writeSint16BE(_actListArr[i][j].a15.objIndex1); + f->writeSint16BE(_actListArr[i][j].a15.objIndex2); + f->writeByte(_actListArr[i][j].a15.dx); + f->writeByte(_actListArr[i][j].a15.dy); + break; + case INIT_OBJ_SEQ: // 16 + f->writeSint16BE(_actListArr[i][j].a16.timer); + f->writeSint16BE(_actListArr[i][j].a16.objIndex); + f->writeSint16BE(_actListArr[i][j].a16.seqIndex); + break; + case SET_STATE_BITS: // 17 + f->writeSint16BE(_actListArr[i][j].a17.timer); + f->writeSint16BE(_actListArr[i][j].a17.objIndex); + f->writeSint16BE(_actListArr[i][j].a17.stateMask); + break; + case CLEAR_STATE_BITS: // 18 + f->writeSint16BE(_actListArr[i][j].a18.timer); + f->writeSint16BE(_actListArr[i][j].a18.objIndex); + f->writeSint16BE(_actListArr[i][j].a18.stateMask); + break; + case TEST_STATE_BITS: // 19 + f->writeSint16BE(_actListArr[i][j].a19.timer); + f->writeSint16BE(_actListArr[i][j].a19.objIndex); + f->writeSint16BE(_actListArr[i][j].a19.stateMask); + f->writeUint16BE(_actListArr[i][j].a19.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a19.actFailIndex); + break; + case DEL_EVENTS: // 20 + f->writeSint16BE(_actListArr[i][j].a20.timer); + f->writeByte(_actListArr[i][j].a20.actTypeDel); + break; + case GAMEOVER: // 21 + f->writeSint16BE(_actListArr[i][j].a21.timer); + break; + case INIT_HH_COORD: // 22 + f->writeSint16BE(_actListArr[i][j].a22.timer); + f->writeSint16BE(_actListArr[i][j].a22.objIndex); + break; + case EXIT: // 23 + f->writeSint16BE(_actListArr[i][j].a23.timer); + break; + case BONUS: // 24 + f->writeSint16BE(_actListArr[i][j].a24.timer); + f->writeSint16BE(_actListArr[i][j].a24.pointIndex); + break; + case COND_BOX: // 25 + f->writeSint16BE(_actListArr[i][j].a25.timer); + f->writeSint16BE(_actListArr[i][j].a25.objIndex); + f->writeSint16BE(_actListArr[i][j].a25.x1); + f->writeSint16BE(_actListArr[i][j].a25.y1); + f->writeSint16BE(_actListArr[i][j].a25.x2); + f->writeSint16BE(_actListArr[i][j].a25.y2); + f->writeUint16BE(_actListArr[i][j].a25.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a25.actFailIndex); + break; + case SOUND: // 26 + f->writeSint16BE(_actListArr[i][j].a26.timer); + f->writeSint16BE(_actListArr[i][j].a26.soundIndex); + break; + case ADD_SCORE: // 27 + f->writeSint16BE(_actListArr[i][j].a27.timer); + f->writeSint16BE(_actListArr[i][j].a27.objIndex); + break; + case SUB_SCORE: // 28 + f->writeSint16BE(_actListArr[i][j].a28.timer); + f->writeSint16BE(_actListArr[i][j].a28.objIndex); + break; + case COND_CARRY: // 29 + f->writeSint16BE(_actListArr[i][j].a29.timer); + f->writeSint16BE(_actListArr[i][j].a29.objIndex); + f->writeUint16BE(_actListArr[i][j].a29.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a29.actFailIndex); + break; + case INIT_MAZE: // 30 + f->writeSint16BE(_actListArr[i][j].a30.timer); + f->writeByte(_actListArr[i][j].a30.mazeSize); + f->writeSint16BE(_actListArr[i][j].a30.x1); + f->writeSint16BE(_actListArr[i][j].a30.y1); + f->writeSint16BE(_actListArr[i][j].a30.x2); + f->writeSint16BE(_actListArr[i][j].a30.y2); + f->writeSint16BE(_actListArr[i][j].a30.x3); + f->writeSint16BE(_actListArr[i][j].a30.x4); + f->writeByte(_actListArr[i][j].a30.firstScreenIndex); + break; + case EXIT_MAZE: // 31 + f->writeSint16BE(_actListArr[i][j].a31.timer); + break; + case INIT_PRIORITY: // 32 + f->writeSint16BE(_actListArr[i][j].a32.timer); + f->writeSint16BE(_actListArr[i][j].a32.objIndex); + f->writeByte(_actListArr[i][j].a32.priority); + break; + case INIT_SCREEN: // 33 + f->writeSint16BE(_actListArr[i][j].a33.timer); + f->writeSint16BE(_actListArr[i][j].a33.objIndex); + f->writeSint16BE(_actListArr[i][j].a33.screenIndex); + break; + case AGSCHEDULE: // 34 + f->writeSint16BE(_actListArr[i][j].a34.timer); + f->writeUint16BE(_actListArr[i][j].a34.actIndex); + break; + case REMAPPAL: // 35 + f->writeSint16BE(_actListArr[i][j].a35.timer); + f->writeSint16BE(_actListArr[i][j].a35.oldColorIndex); + f->writeSint16BE(_actListArr[i][j].a35.newColorIndex); + break; + case COND_NOUN: // 36 + f->writeSint16BE(_actListArr[i][j].a36.timer); + f->writeUint16BE(_actListArr[i][j].a36.nounIndex); + f->writeUint16BE(_actListArr[i][j].a36.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a36.actFailIndex); + break; + case SCREEN_STATE: // 37 + f->writeSint16BE(_actListArr[i][j].a37.timer); + f->writeSint16BE(_actListArr[i][j].a37.screenIndex); + f->writeByte(_actListArr[i][j].a37.newState); + break; + case INIT_LIPS: // 38 + f->writeSint16BE(_actListArr[i][j].a38.timer); + f->writeSint16BE(_actListArr[i][j].a38.lipsObjIndex); + f->writeSint16BE(_actListArr[i][j].a38.objIndex); + f->writeByte(_actListArr[i][j].a38.dxLips); + f->writeByte(_actListArr[i][j].a38.dyLips); + break; + case INIT_STORY_MODE: // 39 + f->writeSint16BE(_actListArr[i][j].a39.timer); + f->writeByte((_actListArr[i][j].a39.storyModeFl) ? 1 : 0); + break; + case WARN: // 40 + f->writeSint16BE(_actListArr[i][j].a40.timer); + f->writeSint16BE(_actListArr[i][j].a40.stringIndex); + break; + case COND_BONUS: // 41 + f->writeSint16BE(_actListArr[i][j].a41.timer); + f->writeSint16BE(_actListArr[i][j].a41.BonusIndex); + f->writeUint16BE(_actListArr[i][j].a41.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a41.actFailIndex); + break; + case TEXT_TAKE: // 42 + f->writeSint16BE(_actListArr[i][j].a42.timer); + f->writeSint16BE(_actListArr[i][j].a42.objIndex); + break; + case YESNO: // 43 + f->writeSint16BE(_actListArr[i][j].a43.timer); + f->writeSint16BE(_actListArr[i][j].a43.promptIndex); + f->writeUint16BE(_actListArr[i][j].a43.actYesIndex); + f->writeUint16BE(_actListArr[i][j].a43.actNoIndex); + break; + case STOP_ROUTE: // 44 + f->writeSint16BE(_actListArr[i][j].a44.timer); + break; + case COND_ROUTE: // 45 + f->writeSint16BE(_actListArr[i][j].a45.timer); + f->writeSint16BE(_actListArr[i][j].a45.routeIndex); + f->writeUint16BE(_actListArr[i][j].a45.actPassIndex); + f->writeUint16BE(_actListArr[i][j].a45.actFailIndex); + break; + case INIT_JUMPEXIT: // 46 + f->writeSint16BE(_actListArr[i][j].a46.timer); + f->writeByte((_actListArr[i][j].a46.jumpExitFl) ? 1 : 0); + break; + case INIT_VIEW: // 47 + f->writeSint16BE(_actListArr[i][j].a47.timer); + f->writeSint16BE(_actListArr[i][j].a47.objIndex); + f->writeSint16BE(_actListArr[i][j].a47.viewx); + f->writeSint16BE(_actListArr[i][j].a47.viewy); + f->writeSint16BE(_actListArr[i][j].a47.direction); + break; + case INIT_OBJ_FRAME: // 48 + f->writeSint16BE(_actListArr[i][j].a48.timer); + f->writeSint16BE(_actListArr[i][j].a48.objIndex); + f->writeSint16BE(_actListArr[i][j].a48.seqIndex); + f->writeSint16BE(_actListArr[i][j].a48.frameIndex); + break; + case OLD_SONG: // 49, Added by Strangerke for DOS versions + f->writeSint16BE(_actListArr[i][j].a49.timer); + f->writeUint16BE(_actListArr[i][j].a49.songIndex); + break; + default: + error("Unknown action %d", subElemType); + } + } } } /* * Find the index in the action list to be able to serialize the action to save game */ - -void Scheduler::findAction(act* action, int16* index, int16* subElem) { +void Scheduler::findAction(const act* action, int16* index, int16* subElem) { assert(index && subElem); if (!action) { @@ -831,28 +1091,30 @@ void Scheduler::restoreSchedulerData(Common::ReadStream *in) { void Scheduler::restoreEvents(Common::ReadStream *f) { debugC(1, kDebugSchedule, "restoreEvents"); - event_t savedEvents[kMaxEvents]; // Convert event ptrs to indexes - uint32 saveTime = f->readUint32BE(); // time of save int16 freeIndex = f->readSint16BE(); // Free list index int16 headIndex = f->readSint16BE(); // Head of list index int16 tailIndex = f->readSint16BE(); // Tail of list index - f->read(savedEvents, sizeof(savedEvents)); - event_t *wrkEvent; // Restore events indexes to pointers for (int i = 0; i < kMaxEvents; i++) { - wrkEvent = &savedEvents[i]; - _events[i] = *wrkEvent; + int16 index = f->readSint16BE(); + int16 subElem = f->readSint16BE(); + // fix up action pointer (to do better) - int32 val = (size_t)_events[i].action; - if ((val & 0xffff) == 0xffff) { + if ((index == -1) && (subElem == -1)) _events[i].action = 0; - } else { - _events[i].action = (act*)&_actListArr[val >> 16][val & 0xffff]; - } - _events[i].prevEvent = (wrkEvent->prevEvent == (event_t *) - 1) ? (event_t *)0 : &_events[(size_t)wrkEvent->prevEvent ]; - _events[i].nextEvent = (wrkEvent->nextEvent == (event_t *) - 1) ? (event_t *)0 : &_events[(size_t)wrkEvent->nextEvent ]; + else + _events[i].action = (act*)&_actListArr[index][subElem]; + + _events[i].localActionFl = (f->readByte() == 1) ? true : false; + _events[i].time = f->readUint32BE(); + + int16 prevIndex = f->readSint16BE(); + int16 nextIndex = f->readSint16BE(); + + _events[i].prevEvent = (prevIndex == -1) ? (event_t *)0 : &_events[prevIndex]; + _events[i].nextEvent = (nextIndex == -1) ? (event_t *)0 : &_events[nextIndex]; } _freeEvent = (freeIndex == -1) ? 0 : &_events[freeIndex]; _headEvent = (headIndex == -1) ? 0 : &_events[headIndex]; @@ -860,7 +1122,7 @@ void Scheduler::restoreEvents(Common::ReadStream *f) { // Adjust times to fit our time uint32 curTime = getTicks(); - wrkEvent = _headEvent; // The earliest event + event_t *wrkEvent = _headEvent; // The earliest event while (wrkEvent) { // While mature events found wrkEvent->time = wrkEvent->time - saveTime + curTime; wrkEvent = wrkEvent->nextEvent; diff --git a/engines/hugo/schedule.h b/engines/hugo/schedule.h index 953e9affea..a066fc63c4 100644 --- a/engines/hugo/schedule.h +++ b/engines/hugo/schedule.h @@ -583,7 +583,7 @@ protected: void delEventType(const action_t actTypeDel); void delQueue(event_t *curEvent); - void findAction(act* action, int16* index, int16* subElem); + void findAction(const act* action, int16* index, int16* subElem); void insertAction(act *action); void readAct(Common::ReadStream &in, act &curAct); void restoreActions(Common::ReadStream *f); diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index f842269893..1aba820fed 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -1143,20 +1143,6 @@ void MohawkEngine_Myst::loadResources() { delete rlstStream; } -void MohawkEngine_Myst::runLoadDialog() { - const Common::String gameId = ConfMan.get("gameid"); - - const EnginePlugin *plugin = 0; - EngineMan.findGame(gameId, &plugin); - - pauseEngine(true); - int slot = _loadDialog->runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName()); - if (slot >= 0) { - // TODO - } - pauseEngine(false); -} - Common::Error MohawkEngine_Myst::loadGameState(int slot) { if (_gameState->load(_gameState->generateSaveGameList()[slot])) return Common::kNoError; diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h index 919509384b..47e8a6562c 100644 --- a/engines/mohawk/myst.h +++ b/engines/mohawk/myst.h @@ -155,8 +155,6 @@ public: Common::String wrapMovieFilename(const Common::String &movieName, uint16 stack); void reloadSaveList(); - void runLoadDialog(); - void runSaveDialog(); void changeToStack(uint16 stack, uint16 card, uint16 linkSrcSound, uint16 linkDstSound); void changeToCard(uint16 card, bool updateScreen); diff --git a/engines/mohawk/myst_areas.h b/engines/mohawk/myst_areas.h index 66430f2068..aa06d1a5b4 100644 --- a/engines/mohawk/myst_areas.h +++ b/engines/mohawk/myst_areas.h @@ -233,6 +233,7 @@ public: void drawFrame(uint16 frame); bool pullLeverV(); void releaseLeverV(); + uint16 getNumFrames() { return _numFrames; } protected: uint16 _numFrames; diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp index 0ae9078974..3dab2f7939 100644 --- a/engines/mohawk/myst_stacks/mechanical.cpp +++ b/engines/mohawk/myst_stacks/mechanical.cpp @@ -23,6 +23,7 @@ * */ +#include "mohawk/cursors.h" #include "mohawk/myst.h" #include "mohawk/graphics.h" #include "mohawk/myst_areas.h" @@ -54,9 +55,12 @@ void Mechanical::setupOpcodes() { OPCODE(100, o_throneEnablePassage); OPCODE(104, o_snakeBoxTrigger); OPCODE(105, o_fortressStaircaseMovie); - OPCODE(121, opcode_121); - OPCODE(122, opcode_122); - OPCODE(123, opcode_123); + OPCODE(106, o_elevatorRotationStart); + OPCODE(107, o_elevatorRotationMove); + OPCODE(108, o_elevatorRotationStop); + OPCODE(121, o_elevatorWindowMovie); + OPCODE(122, o_elevatorGoMiddle); + OPCODE(123, o_elevatorTopMovie); OPCODE(124, opcode_124); OPCODE(125, o_mystStaircaseMovie); OPCODE(126, opcode_126); @@ -72,28 +76,34 @@ void Mechanical::setupOpcodes() { OPCODE(201, o_fortressStaircase_init); OPCODE(202, opcode_202); OPCODE(203, o_snakeBox_init); - OPCODE(204, opcode_204); + OPCODE(204, o_elevatorRotation_init); OPCODE(205, opcode_205); OPCODE(206, opcode_206); OPCODE(209, opcode_209); // "Exit" Opcodes - OPCODE(300, opcode_300); + OPCODE(300, NOP); } #undef OPCODE void Mechanical::disablePersistentScripts() { opcode_202_disable(); - opcode_204_disable(); opcode_205_disable(); opcode_206_disable(); opcode_209_disable(); + _elevatorGoingMiddle = false; } void Mechanical::runPersistentScripts() { opcode_202_run(); - opcode_204_run(); + + if (_elevatorRotationLeverMoving) + elevatorRotation_run(); + + if (_elevatorGoingMiddle) + elevatorGoMiddle_run(); + opcode_205_run(); opcode_206_run(); opcode_209_run(); @@ -131,13 +141,15 @@ uint16 Mechanical::getVar(uint16 var) { return _state.staircaseState; case 11: // Fortress Elevator Rotation Position return _state.elevatorRotation; -// case 12: // Fortress Elevator Rotation Cog Position -// return 0; -// return 1; -// return 2; -// return 3; -// return 4; -// return 5; + case 12: // Fortress Elevator Rotation Cog Position + return 5 - (uint16)(_elevatorRotationGearPosition + 0.5) % 6; + case 13: // Elevator position + return _elevatorPosition; + case 14: // Elevator going down when at top + if (_elevatorGoingDown && _elevatorTooLate) + return 2; + else + return _elevatorGoingDown; case 15: // Code Lock Execute Button Script if (_mystStaircaseState) return 0; @@ -184,6 +196,9 @@ void Mechanical::toggleVar(uint16 var) { case 19: // Code Lock Shape #4 - Right _state.codeShape[var - 16] = (_state.codeShape[var - 16] + 1) % 10; break; + case 23: // Elevator player is in cabin + _elevatorInCabin = false; + break; case 102: // Red page if (!(_globals.redPagesInBook & 4)) { if (_globals.heldPage == 9) @@ -210,6 +225,11 @@ bool Mechanical::setVarValue(uint16 var, uint16 value) { bool refresh = false; switch (var) { + case 13: + _elevatorPosition = value; + case 14: // Elevator going down when at top + _elevatorGoingDown = value; + break; default: refresh = MystScriptParser::setVarValue(var, value); break; @@ -245,47 +265,165 @@ void Mechanical::o_fortressStaircaseMovie(uint16 op, uint16 var, uint16 argc, ui _vm->_video->waitUntilMovieEnds(staircase); } +void Mechanical::o_elevatorRotationStart(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Elevator rotation lever start", op); -void Mechanical::opcode_121(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - varUnusedCheck(op, var); + MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + lever->drawFrame(0); - if (argc == 2) { - uint16 startTime = argv[0]; - uint16 endTime = argv[1]; + _elevatorRotationLeverMoving = true; + _elevatorRotationSpeed = 0; - warning("TODO: Opcode %d Movie Time Index %d to %d\n", op, startTime, endTime); - // TODO: Need version of playMovie blocking which allows selection - // of start and finish points. - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("ewindow", kMechanicalStack), 253, 0); - } else - unknown(op, var, argc, argv); + _vm->_sound->stopBackgroundMyst(); + + _vm->_cursor->setCursor(700); } -void Mechanical::opcode_122(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - if (argc == 0) { - // Used on Card 6120 (Elevator) - // Called when Exit Midde Button Pressed +void Mechanical::o_elevatorRotationMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Elevator rotation lever move", op); - // TODO: hcelev? Movie of Elevator? - } else - unknown(op, var, argc, argv); + const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); + MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + + // Make the handle follow the mouse + int16 maxStep = lever->getNumFrames() - 1; + Common::Rect rect = lever->getRect(); + int16 step = ((rect.bottom - mouse.y) * lever->getNumFrames()) / rect.height(); + step = CLIP<int16>(step, 0, maxStep); + + _elevatorRotationSpeed = step * 0.1; + + // Draw current frame + lever->drawFrame(step); } -void Mechanical::opcode_123(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - varUnusedCheck(op, var); +void Mechanical::o_elevatorRotationStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Elevator rotation lever stop", op); - if (argc == 2) { - // Used on Card 6154 - uint16 start_time = argv[0]; - uint16 end_time = argv[1]; + const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); + MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); - warning("TODO: Opcode %d Movie Time Index %d to %d\n", op, start_time, end_time); - // TODO: Need version of playMovie blocking which allows selection - // of start and finish points. - // TODO: Not 100% sure about movie position - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("hcelev", kMechanicalStack), 205, 40); - } else - unknown(op, var, argc, argv); + // Get current lever frame + int16 maxStep = lever->getNumFrames() - 1; + Common::Rect rect = lever->getRect(); + int16 step = ((rect.bottom - mouse.y) * lever->getNumFrames()) / rect.height(); + step = CLIP<int16>(step, 0, maxStep); + + // Release lever + for (int i = step; i >= 0; i--) { + lever->drawFrame(i); + _vm->_system->delayMillis(10); + } + + // Stop persistent script + _elevatorRotationLeverMoving = false; + + float speed = _elevatorRotationSpeed * 10; + + if (speed > 0) { + + // Decrease speed + while (speed > 2) { + speed -= 0.5; + + _elevatorRotationGearPosition += speed * 0.1; + + if (_elevatorRotationGearPosition > 12) + break; + + _vm->redrawArea(12); + _vm->_system->delayMillis(100); + } + + // Increment position + _state.elevatorRotation = (_state.elevatorRotation + 1) % 10; + + _vm->_sound->replaceSoundMyst(_elevatorRotationSoundId); + _vm->redrawArea(11); + } + + _vm->checkCursorHints(); +} + +void Mechanical::o_elevatorWindowMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + uint16 startTime = argv[0]; + uint16 endTime = argv[1]; + + debugC(kDebugScript, "Opcode %d Movie Time Index %d to %d", op, startTime, endTime); + + VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("ewindow", kMechanicalStack), 253, 0); + _vm->_video->setVideoBounds(window, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); + _vm->_video->waitUntilMovieEnds(window); +} + +void Mechanical::o_elevatorGoMiddle(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Elevator go middle from top", op); + + _elevatorTooLate = false; + _elevatorTopCounter = 5; + _elevatorGoingMiddle = true; + _elevatorInCabin = true; + _elevatorNextTime = _vm->_system->getMillis() + 1000; +} + +void Mechanical::elevatorGoMiddle_run() { + uint32 time = _vm->_system->getMillis(); + if (_elevatorNextTime < time) { + _elevatorNextTime = time + 1000; + _elevatorTopCounter--; + + if (_elevatorTopCounter > 0) { + // Draw button pressed + if (_elevatorInCabin) { + _vm->_gfx->copyImageSectionToScreen(6332, Common::Rect(0, 35, 51, 63), Common::Rect(10, 137, 61, 165)); + _vm->_system->updateScreen(); + } + + // Blip + _vm->_sound->playSoundBlocking(14120); + + // Restore button + if (_elevatorInCabin) { + _vm->_gfx->copyBackBufferToScreen(Common::Rect(10, 137, 61, 165)); + _vm->_system->updateScreen(); + } + } else if (_elevatorInCabin) { + _elevatorTooLate = true; + + // Elevator going to middle animation + _vm->_cursor->hideCursor(); + _vm->_sound->playSoundBlocking(11120); + _vm->_gfx->copyImageToBackBuffer(6118, Common::Rect(544, 333)); + _vm->_sound->replaceSoundMyst(12120); + _vm->_gfx->runTransition(2, Common::Rect(177, 0, 370, 333), 25, 0); + _vm->_sound->playSoundBlocking(13120); + _vm->_sound->replaceSoundMyst(8120); + _vm->_gfx->copyImageToBackBuffer(6327, Common::Rect(544, 333)); + _vm->_system->delayMillis(500); + _vm->_sound->replaceSoundMyst(9120); + static uint16 moviePos[2] = { 3540, 5380 }; + o_elevatorWindowMovie(121, 0, 2, moviePos); + _vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333)); + _vm->_sound->replaceSoundMyst(10120); + _vm->_cursor->showCursor(); + + _elevatorGoingMiddle = false; + _elevatorPosition = 1; + + _vm->changeToCard(6327, true); + } + } +} + +void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + uint16 startTime = argv[0]; + uint16 endTime = argv[1]; + + debugC(kDebugScript, "Opcode %d Movie Time Index %d to %d", op, startTime, endTime); + + VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("hcelev", kMechanicalStack), 206, 38); + _vm->_video->setVideoBounds(window, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); + _vm->_video->waitUntilMovieEnds(window); } void Mechanical::opcode_124(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -403,36 +541,29 @@ void Mechanical::o_snakeBox_init(uint16 op, uint16 var, uint16 argc, uint16 *arg _snakeBox = static_cast<MystResourceType6 *>(_invokingResource); } -static struct { - bool enabled; - uint16 soundId; -} g_opcode204Parameters; - -void Mechanical::opcode_204_run() { - if (g_opcode204Parameters.enabled) { - // TODO: Fill in Logic. - // Var 12 holds Large Cog Position in range 0 to 5 - // - For animation - // Var 11 holds C position in range 0 to 9 - // - 4 for Correct Answer - // C Movement Sound - //_vm->_sound->playSound(g_opcode204Parameters.soundId); +void Mechanical::elevatorRotation_run() { + _vm->redrawArea(12); + + _elevatorRotationGearPosition += _elevatorRotationSpeed; + + if (_elevatorRotationGearPosition > 12) { + uint16 position = (uint16)_elevatorRotationGearPosition; + _elevatorRotationGearPosition = _elevatorRotationGearPosition - position + position % 6; + + _state.elevatorRotation = (_state.elevatorRotation + 1) % 10; + + _vm->_sound->replaceSoundMyst(_elevatorRotationSoundId); + _vm->redrawArea(11); + _vm->_system->delayMillis(100); } } -void Mechanical::opcode_204_disable() { - g_opcode204Parameters.enabled = false; -} +void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Elevator rotation init", op); -void Mechanical::opcode_204(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - varUnusedCheck(op, var); - - // Used for Card 6180 (Lower Elevator Puzzle) - if (argc == 1) { - g_opcode204Parameters.soundId = argv[0]; - g_opcode204Parameters.enabled = true; - } else - unknown(op, var, argc, argv); + _elevatorRotationSoundId = argv[0]; + _elevatorRotationGearPosition = 0; + _elevatorRotationLeverMoving = false; } static struct { @@ -532,11 +663,5 @@ void Mechanical::opcode_209(uint16 op, uint16 var, uint16 argc, uint16 *argv) { unknown(op, var, argc, argv); } -void Mechanical::opcode_300(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - // Used in Card 6156 (Fortress Elevator View) - varUnusedCheck(op, var); - // TODO: Fill in Logic. Clearing Variable for View? -} - } // End of namespace MystStacks } // End of namespace Mohawk diff --git a/engines/mohawk/myst_stacks/mechanical.h b/engines/mohawk/myst_stacks/mechanical.h index 7142425eb4..6a0aa30f5c 100644 --- a/engines/mohawk/myst_stacks/mechanical.h +++ b/engines/mohawk/myst_stacks/mechanical.h @@ -54,8 +54,8 @@ private: void opcode_202_run(); void opcode_202_disable(); - void opcode_204_run(); - void opcode_204_disable(); + void elevatorRotation_run(); + void elevatorGoMiddle_run(); void opcode_205_run(); void opcode_205_disable(); void opcode_206_run(); @@ -66,9 +66,12 @@ private: DECLARE_OPCODE(o_throneEnablePassage); DECLARE_OPCODE(o_snakeBoxTrigger); DECLARE_OPCODE(o_fortressStaircaseMovie); - DECLARE_OPCODE(opcode_121); - DECLARE_OPCODE(opcode_122); - DECLARE_OPCODE(opcode_123); + DECLARE_OPCODE(o_elevatorRotationStart); + DECLARE_OPCODE(o_elevatorRotationMove); + DECLARE_OPCODE(o_elevatorRotationStop); + DECLARE_OPCODE(o_elevatorWindowMovie); + DECLARE_OPCODE(o_elevatorGoMiddle); + DECLARE_OPCODE(o_elevatorTopMovie); DECLARE_OPCODE(opcode_124); DECLARE_OPCODE(o_mystStaircaseMovie); DECLARE_OPCODE(opcode_126); @@ -83,19 +86,31 @@ private: DECLARE_OPCODE(o_fortressStaircase_init); DECLARE_OPCODE(opcode_202); DECLARE_OPCODE(o_snakeBox_init); - DECLARE_OPCODE(opcode_204); + DECLARE_OPCODE(o_elevatorRotation_init); DECLARE_OPCODE(opcode_205); DECLARE_OPCODE(opcode_206); DECLARE_OPCODE(opcode_209); - DECLARE_OPCODE(opcode_300); - MystGameState::Mechanical &_state; bool _mystStaircaseState; // 76 uint16 _fortressPosition; // 82 + uint16 _elevatorGoingDown; // 112 + + float _elevatorRotationSpeed; // 120 + float _elevatorRotationGearPosition; // 124 + uint16 _elevatorRotationSoundId; // 128 + bool _elevatorRotationLeverMoving; // 184 + + bool _elevatorGoingMiddle; // 148 + bool _elevatorTooLate; + uint16 _elevatorPosition; // 104 + bool _elevatorInCabin; // 108 + uint16 _elevatorTopCounter; + uint32 _elevatorNextTime; + uint16 _crystalLit; // 130 MystResourceType6 *_snakeBox; // 156 diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp index 29048eab39..caadf09f56 100644 --- a/engines/mohawk/myst_stacks/myst.cpp +++ b/engines/mohawk/myst_stacks/myst.cpp @@ -50,6 +50,7 @@ Myst::Myst(MohawkEngine_Myst *vm) : _towerRotationBlinkLabel = false; _libraryBookcaseChanged = false; _dockVaultState = 0; + _cabinDoorOpened = 0; _cabinMatchState = 2; _matchBurning = false; _tree = 0; diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index d77ac858c8..de0cfe20d8 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -40,12 +40,14 @@ #include "sci/sound/music.h" #include "sci/sound/drivers/mididriver.h" #include "sci/sound/drivers/map-mt32-to-gm.h" +#include "sci/graphics/cache.h" #include "sci/graphics/cursor.h" #include "sci/graphics/screen.h" #include "sci/graphics/paint.h" #include "sci/graphics/paint16.h" #include "sci/graphics/paint32.h" #include "sci/graphics/palette.h" +#include "sci/graphics/view.h" #include "sci/parser/vocabulary.h" @@ -1503,7 +1505,14 @@ bool Console::cmdDrawCel(int argc, const char **argv) { uint16 loopNo = atoi(argv[2]); uint16 celNo = atoi(argv[3]); - _engine->_gfxPaint->kernelDrawCel(resourceId, loopNo, celNo, 50, 50, 0, 0, false, NULL_REG); + if (_engine->_gfxPaint16) { + _engine->_gfxPaint16->kernelDrawCel(resourceId, loopNo, celNo, 50, 50, 0, 0, 128, 128, false, NULL_REG); + } else { + GfxView *view = _engine->_gfxCache->getView(resourceId); + Common::Rect celRect(50, 50, 50 + view->getWidth(loopNo, celNo), 50 + view->getHeight(loopNo, celNo)); + view->draw(celRect, celRect, celRect, loopNo, celNo, 255, 0, false); + _engine->_gfxScreen->copyRectToScreen(celRect); + } return true; } @@ -3758,11 +3767,16 @@ int Console::printObject(reg_t pos) { return 0; } +static void printChar(byte c) { + if (c < 32 || c >= 127) + c = '.'; + debugN("%c", c); +} + void Console::hexDumpReg(const reg_t *data, int len, int regsPerLine, int startOffset, bool isArray) { // reg_t version of Common::hexdump assert(1 <= regsPerLine && regsPerLine <= 8); int i; - byte c; int offset = startOffset; while (len >= regsPerLine) { debugN("%06x: ", offset); @@ -3771,14 +3785,13 @@ void Console::hexDumpReg(const reg_t *data, int len, int regsPerLine, int startO } debugN(" |"); for (i = 0; i < regsPerLine; i++) { - c = data[i].toUint16() & 0xff; - if (c < 32 || c >= 127) - c = '.'; - debugN("%c", c); - c = data[i].toUint16() >> 8; - if (c < 32 || c >= 127) - c = '.'; - debugN("%c", c); + if (g_sci->isBE()) { + printChar(data[i].toUint16() >> 8); + printChar(data[i].toUint16() & 0xff); + } else { + printChar(data[i].toUint16() & 0xff); + printChar(data[i].toUint16() >> 8); + } } debugN("|\n"); data += regsPerLine; @@ -3798,14 +3811,13 @@ void Console::hexDumpReg(const reg_t *data, int len, int regsPerLine, int startO } debugN(" |"); for (i = 0; i < len; i++) { - c = data[i].toUint16() & 0xff; - if (c < 32 || c >= 127) - c = '.'; - debugN("%c", c); - c = data[i].toUint16() >> 8; - if (c < 32 || c >= 127) - c = '.'; - debugN("%c", c); + if (g_sci->isBE()) { + printChar(data[i].toUint16() >> 8); + printChar(data[i].toUint16() & 0xff); + } else { + printChar(data[i].toUint16() & 0xff); + printChar(data[i].toUint16() >> 8); + } } for (; i < regsPerLine; i++) debugN(" "); diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index c7ef720e1f..0158aa46ff 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -1992,31 +1992,31 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, - // Larry 6 - English/German/French DOS CD - LORES + // Larry 6 - English/German/French DOS CD - LOWRES // SCI interpreter version 1.001.115 {"lsl6", "", { {"resource.map", 0, "0b91234b7112782962cb480b7791b6e2", 7263}, {"resource.000", 0, "57d5fe8bb9e044158514476ea7678eb0", 5754790}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NONE }, + Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO_NONE }, - // Larry 6 - German DOS CD - LORES (provided by richiefs in bug report #2670691) + // Larry 6 - German DOS CD - LOWRES (provided by richiefs in bug report #2670691) // SCI interpreter version 1.001.115 {"lsl6", "", { {"resource.map", 0, "bafe85f32738854135991d4324ad147e", 7268}, {"resource.000", 0, "f6cbc6da7b90ea135883e0759848ca2c", 5773160}, AD_LISTEND}, - Common::DE_DEU, Common::kPlatformPC, 0, GUIO_NONE }, + Common::DE_DEU, Common::kPlatformPC, ADGF_CD, GUIO_NONE }, - // Larry 6 - French DOS CD - LORES (provided by richiefs in bug report #2670691) + // Larry 6 - French DOS CD - LOWRES (provided by richiefs in bug report #2670691) // SCI interpreter version 1.001.115 {"lsl6", "", { {"resource.map", 0, "97797ea775baaf18a1907d357d3c0ea6", 7268}, {"resource.000", 0, "f6cbc6da7b90ea135883e0759848ca2c", 5776092}, AD_LISTEND}, - Common::FR_FRA, Common::kPlatformPC, 0, GUIO_NONE }, + Common::FR_FRA, Common::kPlatformPC, ADGF_CD, GUIO_NONE }, - // Larry 6 - Spanish DOS - LORES (from the Leisure Suit Larry Collection) + // Larry 6 - Spanish DOS - LOWRES (from the Leisure Suit Larry Collection) // Executable scanning reports "1.001.113", VERSION file reports "1.000, 11.06.93, FIVE PATCHES ADDED TO DISK 6 ON 11-18-93" {"lsl6", "", { {"resource.map", 0, "633bf8f42170b6271019917c8009989b", 6943}, @@ -2588,13 +2588,13 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO_NOSPEECH }, #ifdef ENABLE_SCI32 - // Police Quest 4 - English DOS (from the Police Quest Collection) + // Police Quest 4 - English DOS CD (from the Police Quest Collection) // Executable scanning reports "2.100.002", VERSION file reports "1.100.000" - {"pq4", "", { + {"pq4", "CD", { {"resource.map", 0, "379dfe80ed6bd16c47e4b950c4722eac", 11374}, {"resource.000", 0, "fd316a09b628b7032248139003369022", 18841068}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO_NONE }, // Police Quest 4 - English DOS // SCI interpreter version 2.000.000 (a guess?) @@ -2628,6 +2628,14 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO_NOSPEECH }, + // Police Quest: SWAT - English DOS (from GOG.com) + // Executable scanning reports "2.100.002", VERSION file reports "1.0c" + {"pqswat", "", { + {"resmap.000", 0, "1c2563fee189885e29d9348f37306d94", 12175}, + {"ressci.000", 0, "b2e1826ca81ce2e7e764587f5a14eee9", 127149181}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NONE }, + // Police Quest: SWAT - English Windows (from the Police Quest Collection) // Executable scanning reports "2.100.002", VERSION file reports "1.0c" // Original DOS/Windows release VERSION file reports "1.000" is the same @@ -2641,7 +2649,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resmap.004", 0, "4228038906f041623e65789500b22285", 6835}, {"ressci.004", 0, "b7e619e6ecf62fe65d5116a3a422e5f0", 46223872}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, 0, GUIO_NOSPEECH }, + Common::EN_ANY, Common::kPlatformWindows, 0, GUIO_NONE }, #endif // ENABLE_SCI32 // Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy (supplied by merkur in bug report #2718784) @@ -2959,13 +2967,13 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::DE_DEU, Common::kPlatformPC, 0, GUIO_NOSPEECH }, - // Quest for Glory 4 - English DOS/Windows (from jvprat) + // Quest for Glory 4 CD - English DOS/Windows (from jvprat) // Executable scanning reports "2.100.002", VERSION file reports "1.0" - {"qfg4", "", { + {"qfg4", "CD", { {"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246}, {"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformPC, 0, GUIO_NOSPEECH }, + Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO_NONE }, // RAMA - English DOS/Windows Demo // Executable scanning reports "2.100.002", VERSION file reports "000.000.008" diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index 206624f87e..964097f57d 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -285,20 +285,16 @@ SciVersion GameFeatures::detectLofsType() { } // Find a function of the "Game" object (which is the game super class) which invokes lofsa/lofss - reg_t gameSuperClass = g_sci->getGameSuperClassAddress(); + const Object *gameObject = _segMan->getObject(g_sci->getGameObject()); + const Object *gameSuperObject = _segMan->getObject(gameObject->getSuperClassSelector()); bool found = false; - if (!gameSuperClass.isNull()) { - Common::String gameSuperClassName = _segMan->getObjectName(gameSuperClass); - const Object *gameSuperObject = _segMan->getObject(gameSuperClass); + if (gameSuperObject) { + Common::String gameSuperClassName = _segMan->getObjectName(gameObject->getSuperClassSelector()); - if (gameSuperObject) { - for (uint m = 0; m < gameSuperObject->getMethodCount(); m++) { - found = autoDetectLofsType(gameSuperClassName, m); - if (found) - break; - } - } else { - warning("detectLofsType(): Could not get superclass object"); + for (uint m = 0; m < gameSuperObject->getMethodCount(); m++) { + found = autoDetectLofsType(gameSuperClassName, m); + if (found) + break; } } else { warning("detectLofsType(): Could not find superclass of game object"); diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp index 7692613ee5..85238ec851 100644 --- a/engines/sci/engine/gc.cpp +++ b/engines/sci/engine/gc.cpp @@ -25,6 +25,7 @@ #include "sci/engine/gc.h" #include "common/array.h" +#include "sci/graphics/ports.h" namespace Sci { @@ -84,6 +85,18 @@ static void processWorkList(SegManager *segMan, WorklistManager &wm, const Commo } } +static void processEngineHunkList(WorklistManager &wm) { + PortList windowList = g_sci->_gfxPorts->_windowList; + + for (PortList::const_iterator it = windowList.begin(); it != windowList.end(); ++it) { + if ((*it)->isWindow()) { + Window *wnd = ((Window *)*it); + wm.push(wnd->hSaved1); + wm.push(wnd->hSaved2); + } + } +} + AddrSet *findAllActiveReferences(EngineState *s) { assert(!s->_executionStack.empty()); @@ -143,6 +156,9 @@ AddrSet *findAllActiveReferences(EngineState *s) { processWorkList(s->_segMan, wm, heap); + if (getSciVersion() <= SCI_VERSION_1_1) + processEngineHunkList(wm); + return normalizeAddresses(s->_segMan, wm._map); } diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 71892a8bea..6dd08d78ff 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -56,7 +56,7 @@ struct SciKernelMapSubEntry { #define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE #define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 -#define SIG_SCI1 SCI_VERSION_1_EGA, SCI_VERSION_1_LATE +#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE #define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 #define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE #define SIG_SCI21 SCI_VERSION_2_1, SCI_VERSION_3 diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index f6dec5da64..45182761aa 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -253,6 +253,7 @@ reg_t kFGets(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelFile, "kFGets(%d, %d)", handle, maxsize); int readBytes = fgets_wrapper(s, buf, maxsize, handle); s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); + delete[] buf; return readBytes ? argv[0] : NULL_REG; } @@ -1155,10 +1156,11 @@ reg_t kCD(EngineState *s, int argc, reg_t *argv) { reg_t kSave(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { - case 0: // Called by kq7 when starting chapters + case 0: return kSaveGame(s, argc - 1,argv + 1); - case 2: // GetSaveDir - // Yay! Reusing the old kernel function! + case 1: + return kRestoreGame(s, argc - 1,argv + 1); + case 2: return kGetSaveDir(s, argc - 1, argv + 1); case 5: // TODO diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 8730724d68..fd052bff6a 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -58,6 +58,17 @@ namespace Sci { +static int16 adjustGraphColor(int16 color) { + // WORKAROUND: SCI1 EGA and Amiga games can set invalid colors (above 0 - 15). + // Colors above 15 are all white in SCI1 EGA games, which is why this was never + // observed. We clip them all to (0, 15) instead, as colors above 15 are used + // for the undithering algorithm in EGA games - bug #3048908. + if (getSciVersion() >= SCI_VERSION_1_EARLY && g_sci->getResMan()->getViewType() == kViewEga) + return color & 0x0F; // 0 - 15 + else + return color; +} + void showScummVMDialog(const Common::String &message) { GUI::MessageDialog dialog(message, "OK"); dialog.runModal(); @@ -242,23 +253,14 @@ reg_t kGraph(EngineState *s, int argc, reg_t *argv) { } reg_t kGraphGetColorCount(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isAmiga32color()) - return make_reg(0, 32); - return make_reg(0, !g_sci->getResMan()->isVGA() ? 16 : 256); + return make_reg(0, g_sci->_gfxPalette->getTotalColorCount()); } reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv) { - int16 color = argv[4].toSint16(); + int16 color = adjustGraphColor(argv[4].toSint16()); int16 priority = (argc > 5) ? argv[5].toSint16() : -1; int16 control = (argc > 6) ? argv[6].toSint16() : -1; - // WORKAROUND: SCI1 EGA games can set invalid colors (above 0 - 15). - // Colors above 15 are all white in SCI1 EGA games, which is why this was never - // observed. We clip them all to (0, 15) instead, as colors above 15 are used - // for the undithering algorithm in EGA games - bug #3048908. - if (g_sci->getResMan()->getViewType() == kViewEga && getSciVersion() >= SCI_VERSION_1_EARLY) - color &= 0x0F; - g_sci->_gfxPaint16->kernelGraphDrawLine(getGraphPoint(argv), getGraphPoint(argv + 2), color, priority, control); return s->r_acc; } @@ -290,17 +292,10 @@ reg_t kGraphFillBoxForeground(EngineState *s, int argc, reg_t *argv) { reg_t kGraphFillBoxAny(EngineState *s, int argc, reg_t *argv) { Common::Rect rect = getGraphRect(argv); int16 colorMask = argv[4].toUint16(); - int16 color = argv[5].toSint16(); + int16 color = adjustGraphColor(argv[5].toSint16()); int16 priority = argv[6].toSint16(); // yes, we may read from stack sometimes here int16 control = argv[7].toSint16(); // sierra did the same - // WORKAROUND: SCI1 EGA games can set invalid colors (above 0 - 15). - // Colors above 15 are all white in SCI1 EGA games, which is why this was never - // observed. We clip them all to (0, 15) instead, as colors above 15 are used - // for the undithering algorithm in EGA games - bug #3048908. - if (g_sci->getResMan()->getViewType() == kViewEga && getSciVersion() >= SCI_VERSION_1_EARLY) - color &= 0x0F; - g_sci->_gfxPaint16->kernelGraphFillBox(rect, colorMask, color, priority, control); return s->r_acc; } @@ -364,6 +359,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { if (!g_sci->_gfxText16) { // TODO: Implement this textWidth = 0; textHeight = 0; + warning("TODO: implement kTextSize for SCI32"); } else #endif g_sci->_gfxText16->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); @@ -553,8 +549,6 @@ reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -// we are called on EGA/amiga games as well, this doesnt make sense. -// doing this would actually break the system EGA/amiga palette reg_t kPalette(EngineState *s, int argc, reg_t *argv) { if (!s) return make_reg(0, getSciVersion()); @@ -562,86 +556,85 @@ reg_t kPalette(EngineState *s, int argc, reg_t *argv) { } reg_t kPaletteSetFromResource(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - GuiResourceId resourceId = argv[0].toUint16(); - bool force = false; - if (argc == 2) - force = argv[1].toUint16() == 2 ? true : false; - g_sci->_gfxPalette->kernelSetFromResource(resourceId, force); - } + GuiResourceId resourceId = argv[0].toUint16(); + bool force = false; + if (argc == 2) + force = argv[1].toUint16() == 2 ? true : false; + + // Non-VGA games don't use palette resources. + // This has been changed to 64 colors because Longbow Amiga does have + // one palette (palette 999). + if (g_sci->_gfxPalette->getTotalColorCount() < 64) + return s->r_acc; + + g_sci->_gfxPalette->kernelSetFromResource(resourceId, force); return s->r_acc; } reg_t kPaletteSetFlag(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); - uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); - uint16 flags = argv[2].toUint16(); - g_sci->_gfxPalette->kernelSetFlag(fromColor, toColor, flags); - } + uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); + uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); + uint16 flags = argv[2].toUint16(); + g_sci->_gfxPalette->kernelSetFlag(fromColor, toColor, flags); return s->r_acc; } reg_t kPaletteUnsetFlag(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); - uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); - uint16 flags = argv[2].toUint16(); - g_sci->_gfxPalette->kernelUnsetFlag(fromColor, toColor, flags); - } + uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); + uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); + uint16 flags = argv[2].toUint16(); + g_sci->_gfxPalette->kernelUnsetFlag(fromColor, toColor, flags); return s->r_acc; } reg_t kPaletteSetIntensity(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); - uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); - uint16 intensity = argv[2].toUint16(); - bool setPalette = (argc < 4) ? true : (argv[3].isNull()) ? true : false; + uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); + uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); + uint16 intensity = argv[2].toUint16(); + bool setPalette = (argc < 4) ? true : (argv[3].isNull()) ? true : false; - g_sci->_gfxPalette->kernelSetIntensity(fromColor, toColor, intensity, setPalette); - } + // Palette intensity in non-VGA SCI1 games has been removed + if (g_sci->_gfxPalette->getTotalColorCount() < 256) + return s->r_acc; + + g_sci->_gfxPalette->kernelSetIntensity(fromColor, toColor, intensity, setPalette); return s->r_acc; } reg_t kPaletteFindColor(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - uint16 r = argv[0].toUint16(); - uint16 g = argv[1].toUint16(); - uint16 b = argv[2].toUint16(); - return make_reg(0, g_sci->_gfxPalette->kernelFindColor(r, g, b)); - } - return NULL_REG; + uint16 r = argv[0].toUint16(); + uint16 g = argv[1].toUint16(); + uint16 b = argv[2].toUint16(); + return make_reg(0, g_sci->_gfxPalette->kernelFindColor(r, g, b)); } reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - int16 argNr; - bool paletteChanged = false; - for (argNr = 0; argNr < argc; argNr += 3) { - uint16 fromColor = argv[argNr].toUint16(); - uint16 toColor = argv[argNr + 1].toUint16(); - int16 speed = argv[argNr + 2].toSint16(); - if (g_sci->_gfxPalette->kernelAnimate(fromColor, toColor, speed)) - paletteChanged = true; - } - if (paletteChanged) - g_sci->_gfxPalette->kernelAnimateSet(); + int16 argNr; + bool paletteChanged = false; + + // Palette animation in non-VGA SCI1 games has been removed + if (g_sci->_gfxPalette->getTotalColorCount() < 256) + return s->r_acc; + + for (argNr = 0; argNr < argc; argNr += 3) { + uint16 fromColor = argv[argNr].toUint16(); + uint16 toColor = argv[argNr + 1].toUint16(); + int16 speed = argv[argNr + 2].toSint16(); + if (g_sci->_gfxPalette->kernelAnimate(fromColor, toColor, speed)) + paletteChanged = true; } + if (paletteChanged) + g_sci->_gfxPalette->kernelAnimateSet(); + return s->r_acc; } reg_t kPaletteSave(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - return g_sci->_gfxPalette->kernelSave(); - } - return NULL_REG; + return g_sci->_gfxPalette->kernelSave(); } reg_t kPaletteRestore(EngineState *s, int argc, reg_t *argv) { - if (g_sci->getResMan()->isVGA()) { - g_sci->_gfxPalette->kernelRestore(argv[0]); - } + g_sci->_gfxPalette->kernelRestore(argv[0]); return argv[0]; } @@ -1082,17 +1075,8 @@ reg_t kNewWindow(EngineState *s, int argc, reg_t *argv) { int argextra = argc >= 13 ? 4 : 0; // Triggers in PQ3 and SCI1.1 games, argc 13 for DOS argc 15 for mac int style = argv[5 + argextra].toSint16(); int priority = (argc > 6 + argextra) ? argv[6 + argextra].toSint16() : -1; - int colorPen = (argc > 7 + argextra) ? argv[7 + argextra].toSint16() : 0; - int colorBack = (argc > 8 + argextra) ? argv[8 + argextra].toSint16() : 255; - - // WORKAROUND: SCI1 EGA games can set invalid colors (above 0 - 15). - // Colors above 15 are all white in SCI1 EGA games, which is why this was never - // observed. We clip them all to (0, 15) instead, as colors above 15 are used - // for the undithering algorithm in EGA games - bug #3048908. - if (g_sci->getResMan()->getViewType() == kViewEga && getSciVersion() >= SCI_VERSION_1_EARLY) { - colorPen &= 0x0F; - colorBack &= 0x0F; - } + int colorPen = adjustGraphColor((argc > 7 + argextra) ? argv[7 + argextra].toSint16() : 0); + int colorBack = adjustGraphColor((argc > 8 + argextra) ? argv[8 + argextra].toSint16() : 255); // const char *title = argv[4 + argextra].segment ? kernel_dereference_char_pointer(s, argv[4 + argextra], 0) : NULL; if (argc>=13) { @@ -1201,14 +1185,12 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) { } reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { - // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now - kStub(s, argc, argv); - uint16 operation = argv[0].toUint16(); switch (operation) { - case 0: { // Initialize remapping to base. 0 turns remapping off. - //int16 unk1 = (argc >= 2) ? argv[1].toSint16() : 0; + case 0: { // Set remapping to base. 0 turns remapping off. + int16 base = (argc >= 2) ? argv[1].toSint16() : 0; + warning("kRemapColors: Set remapping to base %d", base); } break; case 1: { // unknown @@ -1218,18 +1200,32 @@ reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { //int16 unk3 = argv[3].toSint16(); //uint16 unk4 = argv[4].toUint16(); //uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0; + kStub(s, argc, argv); } break; case 2: { // remap by percent - //int16 unk1 = argv[1].toSint16(); - //uint16 percent = argv[2].toUint16(); - //uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0; + // This adjusts the alpha value of a specific color, and it operates on + // an RGBA palette. Since we're operating on an RGB palette, we just + // modify the color intensity instead + // TODO: From what I understand, palette remapping should be placed + // separately, so that it can be reset by case 0 above. Thus, we + // should adjust the functionality of the Palette class accordingly. + int16 color = argv[1].toSint16(); + if (color >= 10) + color -= 10; + uint16 percent = argv[2].toUint16(); // 0 - 100 + if (argc >= 4) + warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); + g_sci->_gfxPalette->kernelSetIntensity(color, 255, percent, false); } break; case 3: { // remap to gray - //int16 unk1 = argv[1].toSint16(); - //int16 percent = argv[2].toSint16(); // 0 - 100 - //uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0; + // NOTE: This adjusts the alpha value of a specific color, and it operates on + // an RGBA palette + int16 color = argv[1].toSint16(); // this is subtracted from a maximum color value, and can be offset by 10 + int16 percent = argv[2].toSint16(); // 0 - 100 + uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0; + warning("kRemapColors: RemapToGray color %d by %d percent (unk3 = %d)", color, percent, unk3); } break; case 4: { // unknown @@ -1237,11 +1233,13 @@ reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { //uint16 unk2 = argv[2].toUint16(); //uint16 unk3 = argv[3].toUint16(); //uint16 unk4 = (argc >= 5) ? argv[4].toUint16() : 0; + kStub(s, argc, argv); } break; case 5: { // increment color //int16 unk1 = argv[1].toSint16(); //uint16 unk2 = argv[2].toUint16(); + kStub(s, argc, argv); } break; default: diff --git a/engines/sci/engine/kmenu.cpp b/engines/sci/engine/kmenu.cpp index 428c27ca73..3986966a71 100644 --- a/engines/sci/engine/kmenu.cpp +++ b/engines/sci/engine/kmenu.cpp @@ -29,6 +29,7 @@ #include "sci/engine/kernel.h" #include "sci/graphics/cursor.h" #include "sci/graphics/menu.h" +#include "sci/graphics/screen.h" namespace Sci { @@ -71,7 +72,7 @@ reg_t kDrawStatus(EngineState *s, int argc, reg_t *argv) { reg_t textReference = argv[0]; Common::String text; int16 colorPen = (argc > 1) ? argv[1].toSint16() : 0; - int16 colorBack = (argc > 2) ? argv[2].toSint16() : g_sci->getResMan()->isVGA() ? 255 : 15; + int16 colorBack = (argc > 2) ? argv[2].toSint16() : g_sci->_gfxScreen->getColorWhite(); if (!textReference.isNull()) { // Sometimes this is called without giving text, if thats the case dont process it. diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index 6d7c4580e6..73c92a9394 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -298,12 +298,9 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { error("Attempt to peek invalid memory at %04x:%04x", PRINT_REG(argv[1])); return s->r_acc; } - if (ref.isRaw) { - if (g_sci->getPlatform() == Common::kPlatformAmiga) - return make_reg(0, (int16)READ_BE_UINT16(ref.raw)); // Amiga versions are BE - else - return make_reg(0, (int16)READ_LE_UINT16(ref.raw)); - } else { + if (ref.isRaw) + return make_reg(0, (int16)READ_SCIENDIAN_UINT16(ref.raw)); + else { if (ref.skipByte) error("Attempt to peek memory at odd offset %04X:%04X", PRINT_REG(argv[1])); return *(ref.reg); @@ -323,10 +320,7 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { error("Attempt to poke memory reference %04x:%04x to %04x:%04x", PRINT_REG(argv[2]), PRINT_REG(argv[1])); return s->r_acc; } - if (g_sci->getPlatform() == Common::kPlatformAmiga) - WRITE_BE_UINT16(ref.raw, argv[2].offset); // Amiga versions are BE - else - WRITE_LE_UINT16(ref.raw, argv[2].offset); + WRITE_SCIENDIAN_UINT16(ref.raw, argv[2].offset); // Amiga versions are BE } else { if (ref.skipByte) error("Attempt to poke memory at odd offset %04X:%04X", PRINT_REG(argv[1])); diff --git a/engines/sci/engine/kmovement.cpp b/engines/sci/engine/kmovement.cpp index 3c516f63f2..392db56419 100644 --- a/engines/sci/engine/kmovement.cpp +++ b/engines/sci/engine/kmovement.cpp @@ -275,7 +275,7 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { bool completed = false; bool handleMoveCount = g_sci->_features->handleMoveCount(); - if (getSciVersion() >= SCI_VERSION_1_EGA) { + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) { uint client_signal = readSelectorValue(segMan, client, SELECTOR(signal)); writeSelectorValue(segMan, client, SELECTOR(signal), client_signal & ~kSignalHitObstacle); } @@ -307,7 +307,7 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { int16 mover_org_i2 = mover_i2; int16 mover_org_di = mover_di; - if ((getSciVersion() >= SCI_VERSION_1_EGA)) { + if ((getSciVersion() >= SCI_VERSION_1_EGA_ONLY)) { // save current position into mover writeSelectorValue(segMan, mover, SELECTOR(xLast), client_x); writeSelectorValue(segMan, mover, SELECTOR(yLast), client_y); @@ -374,26 +374,23 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { writeSelectorValue(segMan, mover, SELECTOR(b_i2), mover_i2); writeSelectorValue(segMan, mover, SELECTOR(b_di), mover_di); - if ((getSciVersion() >= SCI_VERSION_1_EGA)) { - // this calling code here was right before the last return in - // sci1ega and got changed to this position since sci1early - // this was an uninitialized issue in sierra sci - if ((handleMoveCount) && (getSciVersion() >= SCI_VERSION_1_EARLY)) + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) { + // In sci1egaonly this block of code was outside of the main if, + // but client_x/client_y aren't set there, so it was an + // uninitialized read in SSCI. (This issue was fixed in sci1early.) + if (handleMoveCount) writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt); // We need to compare directly in here, complete may have happened during // the current move if ((client_x == mover_x) && (client_y == mover_y)) invokeSelector(s, mover, SELECTOR(moveDone), argc, argv); - if (getSciVersion() >= SCI_VERSION_1_EARLY) - return s->r_acc; + return s->r_acc; } } - if (handleMoveCount) { - if (getSciVersion() <= SCI_VERSION_1_EGA) - writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt); - else - writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), client_moveSpeed); - } + + if (handleMoveCount) + writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt); + return s->r_acc; } diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp index e8f8ee7152..c0a5e95811 100644 --- a/engines/sci/engine/kparse.cpp +++ b/engines/sci/engine/kparse.cpp @@ -169,7 +169,7 @@ reg_t kSetSynonyms(EngineState *s, int argc, reg_t *argv) { Vocabulary *voc = g_sci->getVocabulary(); // Only SCI0-SCI1 EGA games had a parser. In newer versions, this is a stub - if (getSciVersion() > SCI_VERSION_1_EGA) + if (getSciVersion() > SCI_VERSION_1_EGA_ONLY) return s->r_acc; voc->clearSynonyms(); diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index cb70cf91e0..7786f9b093 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -264,9 +264,9 @@ struct PathfindingState { static Common::Point readPoint(SegmentRef list_r, int offset) { Common::Point point; - if (list_r.isRaw) { - point.x = (int16)READ_LE_UINT16(list_r.raw + offset * POLY_POINT_SIZE); - point.y = (int16)READ_LE_UINT16(list_r.raw + offset * POLY_POINT_SIZE + 2); + if (list_r.isRaw) { // dynmem blocks are raw + point.x = (int16)READ_SCIENDIAN_UINT16(list_r.raw + offset * POLY_POINT_SIZE); + point.y = (int16)READ_SCIENDIAN_UINT16(list_r.raw + offset * POLY_POINT_SIZE + 2); } else { point.x = list_r.reg[offset * 2].toUint16(); point.y = list_r.reg[offset * 2 + 1].toUint16(); @@ -275,9 +275,9 @@ static Common::Point readPoint(SegmentRef list_r, int offset) { } static void writePoint(SegmentRef ref, int offset, const Common::Point &point) { - if (ref.isRaw) { - WRITE_LE_UINT16(ref.raw + offset * POLY_POINT_SIZE, point.x); - WRITE_LE_UINT16(ref.raw + offset * POLY_POINT_SIZE + 2, point.y); + if (ref.isRaw) { // dynmem blocks are raw + WRITE_SCIENDIAN_UINT16(ref.raw + offset * POLY_POINT_SIZE, point.x); + WRITE_SCIENDIAN_UINT16(ref.raw + offset * POLY_POINT_SIZE + 2, point.y); } else { ref.reg[offset * 2] = make_reg(0, point.x); ref.reg[offset * 2 + 1] = make_reg(0, point.y); diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index 810e8a13ee..30c8b6a130 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -56,14 +56,6 @@ reg_t kUnLoad(EngineState *s, int argc, reg_t *argv) { ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16()); reg_t resnr = argv[1]; - // WORKAROUND for a broken script in room 320 in Castle of Dr. Brain. - // Script 377 tries to free the hunk memory allocated for the saved area - // (underbits) beneath the pop up window, which results in having the - // window stay on screen even when it's closed. Ignore this request here. - if (restype == kResourceTypeMemory && g_sci->getGameId() == GID_CASTLEBRAIN && - s->currentRoomNumber() == 320) - return s->r_acc; - if (restype == kResourceTypeMemory) s->_segMan->freeHunkEntry(resnr); } diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 5c6ef06910..730f7af9b8 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -113,7 +113,7 @@ reg_t kStrAt(EngineState *s, int argc, reg_t *argv) { reg_t &tmp = dest_r.reg[offset / 2]; bool oddOffset = offset & 1; - if (g_sci->getPlatform() == Common::kPlatformAmiga) + if (g_sci->isBE()) oddOffset = !oddOffset; if (!oddOffset) { @@ -159,17 +159,9 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { source++; } while (*source) { - if ((*source < '0') || (*source > '9')) { - // Sierra's atoi stopped processing at anything which is not - // a digit. Sometimes the input has a trailing space, that's - // fine (example: lsl3) - if (*source != ' ') { - // TODO: this happens in lsl5 right in the intro -> we get '1' '3' 0xCD 0xCD 0xCD 0xCD 0xCD - // find out why this happens and fix it - warning("Invalid character in kReadNumber input"); - } + if ((*source < '0') || (*source > '9')) + // Stop if we encounter anything other than a digit (like atoi) break; - } result *= 10; result += *source - 0x30; source++; diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 6719b73aa5..3c81a93767 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -129,6 +129,17 @@ void Script::load(ResourceManager *resMan) { Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0); assert(script != 0); + uint extraLocalsWorkaround = 0; + if (g_sci->getGameId() == GID_FANMADE && _nr == 1 && script->size == 11140) { + // WORKAROUND: Script 1 in Ocean Battle doesn't have enough locals to + // fit the string showing how many shots are left (a nasty script bug, + // corrupting heap memory). We add 10 more locals so that it has enough + // space to use as the target for its kFormat operation. Fixes bug + // #3059871. + extraLocalsWorkaround = 10; + } + _bufSize += extraLocalsWorkaround * 2; + _buf = (byte *)malloc(_bufSize); assert(_buf); @@ -187,6 +198,9 @@ void Script::load(ResourceManager *resMan) { _localsOffset = 24 + _numExports * 2; } + // WORKAROUND: Increase locals, if needed (check above) + _localsCount += extraLocalsWorkaround; + if (getSciVersion() == SCI_VERSION_0_EARLY) { // SCI0 early // Old script block. There won't be a localvar block in this case. @@ -202,7 +216,7 @@ void Script::load(ResourceManager *resMan) { if (_localsOffset + _localsCount * 2 + 1 >= (int)_bufSize) { error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, _bufSize); - _localsCount = (_bufSize - _localsOffset) >> 1; + //_localsCount = (_bufSize - _localsOffset) >> 1; } } } diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index f3b6ff8a46..8c622b1264 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -60,55 +60,6 @@ struct SciScriptSignature { // - if not EOS, an adjust offset and the actual bytes // - rinse and repeat -#if 0 - -// =========================================================================== -// Castle of Dr. Brain -// cipher::init (script 391) is called on room 380 init. This resets the word -// cipher puzzle. The puzzle sadly operates on some hep strings, which aren't -// saved in our sci. So saving/restoring in this room will break the puzzle -// Because of this issue, we just init the puzzle each time it's accessed. -// this is not 100% sierra behaviour, in fact we will actually reset the puzzle -// during each access which makes it impossible to cheat. -const byte castlebrainSignatureCipherPuzzle[] = { - 22, - 0x35, 0x00, // ldi 00 - 0xa3, 0x26, // sal local[26] - 0xa3, 0x25, // sal local[25] - 0x35, 0x00, // ldi 00 - 0xa3, 0x2a, // sal local[2a] (local is not used) - 0xa3, 0x29, // sal local[29] (local is not used) - 0x35, 0xff, // ldi ff - 0xa3, 0x2c, // sal local[2c] - 0xa3, 0x2b, // sal local[2b] - 0x35, 0x00, // ldi 00 - 0x65, 0x16, // aTop highlightedIcon - 0 -}; - -const uint16 castlebrainPatchCipherPuzzle[] = { - 0x39, 0x6b, // pushi 6b (selector init) - 0x76, // push0 - 0x55, 0x04, // self 04 - 0x35, 0x00, // ldi 00 - 0xa3, 0x25, // sal local[25] - 0xa3, 0x26, // sal local[26] - 0xa3, 0x29, // sal local[29] - 0x65, 0x16, // aTop highlightedIcon - 0x34, 0xff, 0xff, // ldi ffff - 0xa3, 0x2b, // sal local[2b] - 0xa3, 0x2c, // sal local[2c] - PATCH_END -}; - -// script, description, magic DWORD, adjust -const SciScriptSignature castlebrainSignatures[] = { - { 391, "cipher puzzle save/restore break", 1, PATCH_MAGICDWORD(0xa3, 0x26, 0xa3, 0x25), -2, castlebrainSignatureCipherPuzzle, castlebrainPatchCipherPuzzle }, - SCI_SIGNATUREENTRY_TERMINATOR -}; - -#endif - // =========================================================================== // stayAndHelp::changeState (0) is called when ego swims to the left or right // boundaries of room 660. Normally a textbox is supposed to get on screen @@ -497,74 +448,6 @@ const SciScriptSignature gk1Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; -#if 0 - -// =========================================================================== -// this here gets called on entry and when going out of game windows -// uEvt::port will not get changed after kDisposeWindow but a bit later, so -// we would get an invalid port handle to a kSetPort call. We just patch in -// resetting of the port selector. We destroy the stop/fade code in there, -// it seems it isn't used at all in the game. -const byte hoyle4SignaturePortFix[] = { - 28, - 0x39, 0x09, // pushi 09 - 0x89, 0x0b, // lsg 0b - 0x39, 0x64, // pushi 64 - 0x38, 0xc8, 0x00, // pushi 00c8 - 0x38, 0x2c, 0x01, // pushi 012c - 0x38, 0x90, 0x01, // pushi 0190 - 0x38, 0xf4, 0x01, // pushi 01f4 - 0x38, 0x58, 0x02, // pushi 0258 - 0x38, 0xbc, 0x02, // pushi 02bc - 0x38, 0x20, 0x03, // pushi 0320 - 0x46, // calle [xxxx] [xxxx] [xx] - +5, 43, // [skip 5 bytes] - 0x30, 0x27, 0x00, // bnt 0027 -> end of routine - 0x87, 0x00, // lap 00 - 0x30, 0x19, 0x00, // bnt 0019 -> fade out - 0x87, 0x01, // lap 01 - 0x30, 0x14, 0x00, // bnt 0014 -> fade out - 0x38, 0xa7, 0x00, // pushi 00a7 - 0x76, // push0 - 0x80, 0x29, 0x01, // lag 0129 - 0x4a, 0x04, // send 04 - call song::stop - 0x39, 0x27, // pushi 27 - 0x78, // push1 - 0x8f, 0x01, // lsp 01 - 0x51, 0x54, // class 54 - 0x4a, 0x06, // send 06 - call PlaySong::play - 0x33, 0x09, // jmp 09 -> end of routine - 0x38, 0xaa, 0x00, // pushi 00aa - 0x76, // push0 - 0x80, 0x29, 0x01, // lag 0129 - 0x4a, 0x04, // send 04 - 0x48, // ret - 0 -}; - -const uint16 hoyle4PatchPortFix[] = { - PATCH_ADDTOOFFSET | +33, - 0x38, 0x31, 0x01, // pushi 0131 (selector curEvent) - 0x76, // push0 - 0x80, 0x50, 0x00, // lag 0050 (global var 80h, "User") - 0x4a, 0x04, // send 04 - read User::curEvent - - 0x38, 0x93, 0x00, // pushi 0093 (selector port) - 0x78, // push1 - 0x76, // push0 - 0x4a, 0x06, // send 06 - write 0 to that object::port - 0x48, // ret - PATCH_END -}; - -// script, description, magic DWORD, adjust -const SciScriptSignature hoyle4Signatures[] = { - { 0, "port fix when disposing windows", PATCH_MAGICDWORD(0x64, 0x38, 0xC8, 0x00), -5, hoyle4SignaturePortFix, hoyle4PatchPortFix }, - { 0, NULL, 0, 0, NULL, NULL } -}; - -#endif - // =========================================================================== // at least during harpy scene export 29 of script 0 is called in kq5cd and // has an issue for those calls, where temp 3 won't get inititialized, but @@ -622,6 +505,36 @@ const SciScriptSignature kq5Signatures[] = { }; // =========================================================================== +// When giving the milk bottle to one of the babies in the garden in KQ6 (room +// 480), script 481 starts a looping baby cry sound. However, that particular +// script also has an overriden check method (cryMusic::check). This method +// explicitly restarts the sound, even if it's set to be looped, thus the same +// sound is played twice, squelching all other sounds. We just rip the +// unnecessary cryMusic::check method out, thereby stopping the sound from +// constantly restarting (since it's being looped anyway), thus the normal +// game speech can work while the baby cry sound is heard. Fixes bug #3034579. +const byte kq6SignatureDuplicateBabyCry[] = { + 10, + 0x83, 0x00, // lal 00 + 0x31, 0x1e, // bnt 1e [07f4] + 0x78, // push1 + 0x39, 0x04, // pushi 04 + 0x43, 0x75, 0x02, // callk DoAudio[75] 02 + 0 +}; + +const uint16 kq6PatchDuplicateBabyCry[] = { + 0x48, // ret + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature kq6Signatures[] = { + { 481, "duplicate baby cry", 1, PATCH_MAGICDWORD(0x83, 0x00, 0x31, 0x1e), 0, kq6SignatureDuplicateBabyCry, kq6PatchDuplicateBabyCry }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +// =========================================================================== // this is called on every death dialog. Problem is at least the german // version of lsl6 gets title text that is far too long for the // available temp space resulting in temp space corruption @@ -673,7 +586,7 @@ const SciScriptSignature larry6Signatures[] = { }; // =========================================================================== -// rm560::doit was supposed to close the painting, when heimlich enters the +// rm560::doit was supposed to close the painting, when Heimlich enters the // room. The code is buggy, so it actually closes the painting, when heimlich // is not in the room. We fix that. const byte laurabow2SignaturePaintingClosing[] = { @@ -796,44 +709,10 @@ const uint16 qfg1vgaPatchFightEvents[] = { PATCH_END }; -// When QFG1VGA and QFG3 dispose of a child window. For example, when choosing -// a spell (parent window), if the spell can't be casted, a subsequent window -// opens, notifying that it can't be casted. When showing the child window, the -// scripts restore the area below the parent window, draw the child window, and -// then attempt to redraw the parent window, which leads to the background -// picture (which has just been restored) overwriting the child window. It -// appers that kGraph(redrawBox) is different in QFG1VGA and QFG3. However, we -// can just remove the window redraw and update calls when the window is -// supposed to be disposed, and the window is disposed of correctly. Fixes bug -// #3053093. -const byte qfg1vgaWindowDispose[] = { - 17, - 0x39, 0x05, // pushi 05 - 0x39, 0x0d, // pushi 0d - 0x67, 0x2e, // pTos 2e - 0x67, 0x30, // pTos 30 - 0x67, 0x32, // pTos 32 - 0x67, 0x34, // pTos 34 - 0x43, 0x6c, 0x0a, // callk kGraph 10 - 0x39, 0x06, // pushi 06 - 0 -}; - -const uint16 qfg1vgaPatchWindowDispose[] = { - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x33, 0x3e, // jmp 0x3e (skip 62 bytes - this skips the subsequent 2 kGraph(update) calls, before kDisposeWindow is invoked) - PATCH_END -}; - // script, description, magic DWORD, adjust const SciScriptSignature qfg1vgaSignatures[] = { { 215, "fight event issue", 1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07), -1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents }, { 216, "weapon master event issue", 1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07), -1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents }, - { 559, "window dispose", 1, PATCH_MAGICDWORD(0x39, 0x05, 0x39, 0x0d), 0, qfg1vgaWindowDispose, qfg1vgaPatchWindowDispose }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -897,37 +776,6 @@ const uint16 qfg3PatchImportDialog[] = { PATCH_END }; -// When QFG1VGA and QFG3 dispose of a child window. For example, when choosing -// a spell (parent window), if the spell can't be casted, a subsequent window -// opens, notifying that it can't be casted. When showing the child window, the -// scripts restore the area below the parent window, draw the child window, and -// then attempt to redraw the parent window, which leads to the background -// picture (which has just been restored) overwriting the child window. It -// appers that kGraph(redrawBox) is different in QFG1VGA and QFG3. However, we -// can just remove the window redraw and update calls when the window is -// supposed to be disposed, and the window is disposed of correctly. Fixes bug -// #3053093. -const byte qfg3WindowDispose[] = { - 15, - 0x39, 0x05, // pushi 05 - 0x39, 0x0d, // pushi 0d - 0x67, 0x2e, // pTos 2e - 0x67, 0x30, // pTos 30 - 0x67, 0x32, // pTos 32 - 0x67, 0x34, // pTos 34 - 0x43, 0x6c, 0x0a, // callk kGraph 10 - 0 -}; - -const uint16 qfg3PatchWindowDispose[] = { - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - 0x34, 0x00, 0x00, // ldi 0000 (dummy) - PATCH_END -}; - // Script 23 in QFG3 has a typo/bug which makes it loop endlessly and // read garbage. Fixes bug #3040722. const byte qfg3DialogCrash[] = { @@ -946,7 +794,6 @@ const uint16 qfg3PatchDialogCrash[] = { // script, description, magic DWORD, adjust const SciScriptSignature qfg3Signatures[] = { - { 22, "window dispose", 1, PATCH_MAGICDWORD(0x39, 0x05, 0x39, 0x0d), 0, qfg3WindowDispose, qfg3PatchWindowDispose }, { 23, "dialog crash", 1, PATCH_MAGICDWORD(0xe7, 0x03, 0x22, 0x33), -1, qfg3DialogCrash, qfg3PatchDialogCrash }, { 944, "import dialog continuous calls", 1, PATCH_MAGICDWORD(0x2a, 0x31, 0x0b, 0x7a), -1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, SCI_SIGNATUREENTRY_TERMINATOR @@ -1065,55 +912,6 @@ const SciScriptSignature sq4Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; -// =========================================================================== -// It seems to scripts warp ego outside the screen somehow (or maybe kDoBresen?) -// ego::mover is set to 0 and rm119::doit will crash in that case. This here -// fixes part of the problem and actually checks ego::mover to be 0 and skips -// TODO: this should get further investigated by waltervn and maybe properly -// patched. For now ego will shortly disappear and reappear a bit after -// this isn't good, but sierra sci also "crashed" (endless looped) so this -// is at least better than the original code -const byte sq5SignatureScrubbing[] = { - 19, - 0x18, // not - 0x31, 0x37, // bnt 37 - 0x78, // push1 (selector x) - 0x76, // push0 - 0x39, 0x38, // pushi 38 (selector mover) - 0x76, // push0 - 0x81, 0x00, // lag 00 - 0x4a, 0x04, // send 04 - read ego::mover - 0x4a, 0x04, // send 04 - read ego::mover::x - 0x36, // push - 0x34, 0xa0, 0x00, // ldi 00a0 - 0x1c, // ne? - 0 -}; - -const uint16 sq5PatchScrubbing[] = { - 0x18, // not - 0x31, 0x37, // bnt 37 -// 0x2f, 0x38, // bt 37 (would save another byte, isn't needed - 0x39, 0x38, // pushi 38 (selector mover) - 0x76, // push0 - 0x81, 0x00, // lag 00 - 0x4a, 0x04, // send 04 - read ego::mover - 0x31, 0x2e, // bnt 2e (jump if ego::mover is 0) - 0x78, // push1 (selector x) - 0x76, // push0 - 0x4a, 0x04, // send 04 - read ego::mover::x - 0x39, 0xa0, // pushi a0 (saving 2 bytes) - 0x1c, // ne? - PATCH_END -}; - -// script, description, magic DWORD, adjust -const SciScriptSignature sq5Signatures[] = { - { 119, "scrubbing send crash", 1, PATCH_MAGICDWORD(0x18, 0x31, 0x37, 0x78), 0, sq5SignatureScrubbing, sq5PatchScrubbing }, - SCI_SIGNATUREENTRY_TERMINATOR -}; - - // will actually patch previously found signature area void Script::applyPatch(const uint16 *patch, byte *scriptData, const uint32 scriptSize, int32 signatureOffset) { byte orgData[PATCH_VALUELIMIT]; @@ -1206,12 +1004,6 @@ int32 Script::findSignature(const SciScriptSignature *signature, const byte *scr void Script::matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uint32 scriptSize) { const SciScriptSignature *signatureTable = NULL; switch (g_sci->getGameId()) { - // Dr. Brain now works because we properly maintain the state of the string heap in savegames -#if 0 - case GID_CASTLEBRAIN: - signatureTable = castlebrainSignatures; - break; -#endif case GID_ECOQUEST: signatureTable = ecoquest1Signatures; break; @@ -1227,15 +1019,12 @@ void Script::matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uin case GID_GK1: signatureTable = gk1Signatures; break; - // hoyle4 now works due to workaround inside GfxPorts -#if 0 - case GID_HOYLE4: - signatureTable = hoyle4Signatures; - break; -#endif case GID_KQ5: signatureTable = kq5Signatures; break; + case GID_KQ6: + signatureTable = kq6Signatures; + break; case GID_LAURABOW2: signatureTable = laurabow2Signatures; break; @@ -1257,9 +1046,6 @@ void Script::matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uin case GID_SQ4: signatureTable = sq4Signatures; break; - case GID_SQ5: - signatureTable = sq5Signatures; - break; default: break; } diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index ffc81f0fde..c75ceab280 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -403,7 +403,7 @@ void SegManager::freeHunkEntry(reg_t addr) { return; } - ht->freeEntry(addr.offset); + ht->freeEntryContents(addr.offset); } reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) { @@ -602,13 +602,13 @@ static inline char getChar(const SegmentRef &ref, uint offset) { warning("Attempt to read character from non-raw data"); bool oddOffset = offset & 1; - if (g_sci->getPlatform() == Common::kPlatformAmiga) - oddOffset = !oddOffset; // Amiga versions are BE + if (g_sci->isBE()) + oddOffset = !oddOffset; return (oddOffset ? val.offset >> 8 : val.offset & 0xff); } -static inline void setChar(const SegmentRef &ref, uint offset, char value) { +static inline void setChar(const SegmentRef &ref, uint offset, byte value) { if (ref.skipByte) offset++; @@ -617,8 +617,8 @@ static inline void setChar(const SegmentRef &ref, uint offset, char value) { val->segment = 0; bool oddOffset = offset & 1; - if (g_sci->getPlatform() == Common::kPlatformAmiga) - oddOffset = !oddOffset; // Amiga versions are BE + if (g_sci->isBE()) + oddOffset = !oddOffset; if (oddOffset) val->offset = (val->offset & 0x00ff) | (value << 8); diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index 05d914cffb..d1f56f0da2 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -275,6 +275,11 @@ Common::Array<reg_t> DataStack::listAllOutgoingReferences(reg_t object) const { } +//-------------------- hunk --------------------- +void HunkTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { + freeEntry(sub_addr.offset); +} + //-------------------- lists -------------------- void ListTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { freeEntry(sub_addr.offset); diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index 9aaa3a4b08..56a6c71590 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -315,15 +315,18 @@ struct ListTable : public Table<List> { struct HunkTable : public Table<Hunk> { HunkTable() : Table<Hunk>(SEG_TYPE_HUNK) {} - virtual void freeEntry(int idx) { - Table<Hunk>::freeEntry(idx); - - if (!_table[idx].mem) - warning("Attempt to free an already freed hunk"); + void freeEntryContents(int idx) { free(_table[idx].mem); _table[idx].mem = 0; } + virtual void freeEntry(int idx) { + Table<Hunk>::freeEntry(idx); + freeEntryContents(idx); + } + + virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); + virtual void saveLoadWithSerializer(Common::Serializer &ser); }; diff --git a/engines/sci/engine/static_selectors.cpp b/engines/sci/engine/static_selectors.cpp index 4bb61a0658..96507e3e1b 100644 --- a/engines/sci/engine/static_selectors.cpp +++ b/engines/sci/engine/static_selectors.cpp @@ -185,7 +185,7 @@ Common::StringArray Kernel::checkStaticSelectorNames() { // dispose comes right after init names[initSelectorPos + 1] = "dispose"; - if ((getSciVersion() >= SCI_VERSION_1_EGA)) { + if ((getSciVersion() >= SCI_VERSION_1_EGA_ONLY)) { // Find the xLast and yLast selectors, used in kDoBresen // xLast and yLast always come between illegalBits and xStep @@ -205,7 +205,7 @@ Common::StringArray Kernel::checkStaticSelectorNames() { names[xLastSelectorPos] = "xLast"; names[yLastSelectorPos] = "yLast"; - } // if ((getSciVersion() >= SCI_VERSION_1_EGA)) + } // if ((getSciVersion() >= SCI_VERSION_1_EGA_ONLY)) } // if (actorClass) _segMan->uninstantiateScript(998); diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index a70ff5ab72..24f3c96f62 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -47,6 +47,10 @@ const reg_t NULL_REG = {0, 0}; const reg_t SIGNAL_REG = {0, SIGNAL_OFFSET}; const reg_t TRUE_REG = {0, 1}; //#define VM_DEBUG_SEND +// Enable the define below to have the VM abort on cases where a conditional +// statement is followed by an unconditional jump (which will most likely lead +// to an infinite loop). Aids in detecting script bugs such as #3040722. +//#define ABORT_ON_INFINITE_LOOP #define SCI_XS_CALLEE_LOCALS ((SegmentId)-1) @@ -114,8 +118,8 @@ static reg_t &validate_property(EngineState *s, Object *obj, int index) { if (index < 0 || (uint)index >= obj->getVarCount()) { // This is same way sierra does it and there are some games, that contain such scripts like // iceman script 998 (fred::canBeHere, executed right at the start) - debugC(kDebugLevelVM, "[VM] Invalid property #%d (out of [0..%d]) requested!", - index, obj->getVarCount()); + debugC(kDebugLevelVM, "[VM] Invalid property #%d (out of [0..%d]) requested from object %04x:%04x (%s)", + index, obj->getVarCount(), PRINT_REG(obj->getPos()), s->_segMan->getObjectName(obj->getPos())); return dummyReg; } @@ -628,18 +632,41 @@ static void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunct debugN(" (%s)", s->_segMan->getObjectName(argv[parmNr])); break; case SIG_TYPE_REFERENCE: - if (kernelCall->function == kSaid) { - SegmentRef saidSpec = s->_segMan->dereference(argv[parmNr]); - if (saidSpec.isRaw) { - debugN(" ('"); - g_sci->getVocabulary()->debugDecipherSaidBlock(saidSpec.raw); - debugN("')"); + { + SegmentObj *mobj = s->_segMan->getSegmentObj(argv[parmNr].segment); + switch (mobj->getType()) { + case SEG_TYPE_HUNK: + { + HunkTable *ht = (HunkTable*)mobj; + int index = argv[parmNr].offset; + if (ht->isValidEntry(index)) { + // NOTE: This ", deleted" isn't as useful as it could + // be because it prints the status _after_ the kernel + // call. + debugN(" ('%s' hunk%s)", ht->_table[index].type, ht->_table[index].mem ? "" : ", deleted"); + } else + debugN(" (INVALID hunk ref)"); + break; + } + default: + // TODO: Any other segment types which could + // use special handling? + + if (kernelCall->function == kSaid) { + SegmentRef saidSpec = s->_segMan->dereference(argv[parmNr]); + if (saidSpec.isRaw) { + debugN(" ('"); + g_sci->getVocabulary()->debugDecipherSaidBlock(saidSpec.raw); + debugN("')"); + } else { + debugN(" (non-raw said-spec)"); + } } else { - debugN(" (non-raw said-spec)"); + debugN(" ('%s')", s->_segMan->getString(argv[parmNr]).c_str()); } - } else { - debugN(" ('%s')", s->_segMan->getString(argv[parmNr]).c_str()); + break; } + } default: break; } @@ -871,6 +898,10 @@ void run_vm(EngineState *s) { s->_executionStackPosChanged = true; // Force initialization +#ifdef ABORT_ON_INFINITE_LOOP + byte prevOpcode = 0xFF; +#endif + while (1) { int var_type; // See description below int var_number; @@ -935,7 +966,22 @@ void run_vm(EngineState *s) { byte extOpcode; s->xs->addr.pc.offset += readPMachineInstruction(scr->getBuf() + s->xs->addr.pc.offset, extOpcode, opparams); const byte opcode = extOpcode >> 1; - //debug("%s: %d, %d, %d, %d, acc = %04x:%04x", opcodeNames[opcode], opparams[0], opparams[1], opparams[2], opparams[3], PRINT_REG(s->r_acc)); + //debug("%s: %d, %d, %d, %d, acc = %04x:%04x, script %d, local script %d", opcodeNames[opcode], opparams[0], opparams[1], opparams[2], opparams[3], PRINT_REG(s->r_acc), scr->getScriptNumber(), local_script->getScriptNumber()); + +#ifdef ABORT_ON_INFINITE_LOOP + if (prevOpcode != 0xFF) { + if (prevOpcode == op_eq_ || prevOpcode == op_ne_ || + prevOpcode == op_gt_ || prevOpcode == op_ge_ || + prevOpcode == op_lt_ || prevOpcode == op_le_ || + prevOpcode == op_ugt_ || prevOpcode == op_uge_ || + prevOpcode == op_ult_ || prevOpcode == op_ule_) { + if (opcode == op_jmp) + error("Infinite loop detected in script %d", scr->getScriptNumber()); + } + } + + prevOpcode = opcode; +#endif switch (opcode) { diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index dea4d63bf0..0b790bb8af 100644 --- a/engines/sci/engine/vm_types.cpp +++ b/engines/sci/engine/vm_types.cpp @@ -31,13 +31,11 @@ namespace Sci { -extern const char *opcodeNames[]; // from scriptdebug.cpp - reg_t reg_t::lookForWorkaround(const reg_t right) const { SciTrackOriginReply originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply); if (solution.type == WORKAROUND_NONE) - error("arithmetic operation on non-integer (%04x:%04x, %04x:%04x) from method %s::%s (script %d, room %d, localCall %x)", + error("Invalid arithmetic operation (params: %04x:%04x and %04x:%04x) from method %s::%s (script %d, room %d, localCall %x)", PRINT_REG(*this), PRINT_REG(right), originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), originReply.localCallOffset); @@ -46,7 +44,7 @@ reg_t reg_t::lookForWorkaround(const reg_t right) const { } reg_t reg_t::operator+(const reg_t right) const { - if (isPointer() && isInitialized()) { + if (isPointer() && right.isNumber()) { // Pointer arithmetics. Only some pointer types make sense here SegmentObj *mobj = g_sci->getEngineState()->_segMan->getSegmentObj(segment); @@ -58,29 +56,18 @@ reg_t reg_t::operator+(const reg_t right) const { case SEG_TYPE_SCRIPT: case SEG_TYPE_STACK: case SEG_TYPE_DYNMEM: - // Make sure that we are adding an offset to the pointer - if (right.isPointer()) - return lookForWorkaround(right); return make_reg(segment, offset + right.toSint16()); default: return lookForWorkaround(right); } - } else if (isNumber() && isInitialized() && right.isPointer()) { + } else if (isNumber() && right.isPointer()) { // Adding a pointer to a number, flip the order return right + *this; + } else if (isNumber() && right.isNumber()) { + // Normal arithmetics + return make_reg(0, toSint16() + right.toSint16()); } else { - // Normal arithmetics. Make sure we're adding a number - if (right.isPointer()) - return lookForWorkaround(right); - // If the current variable is uninitialized, it'll be set - // to zero in order to perform the operation. Such a case - // happens in SQ1, room 28, when throwing the water at Orat. - if (!isInitialized()) - return make_reg(0, right.toSint16()); - else if (!right.isInitialized()) - return *this; - else - return make_reg(0, toSint16() + right.toSint16()); + return lookForWorkaround(right); } } @@ -97,24 +84,19 @@ reg_t reg_t::operator-(const reg_t right) const { reg_t reg_t::operator*(const reg_t right) const { if (isNumber() && right.isNumber()) return make_reg(0, toSint16() * right.toSint16()); - else if (!isInitialized() || !right.isInitialized()) - return NULL_REG; // unitialized variables - always return 0 else return lookForWorkaround(right); } reg_t reg_t::operator/(const reg_t right) const { - if (isNumber() && right.isNumber()) { - if (right.isNull()) - return NULL_REG; // division by zero - else - return make_reg(0, toSint16() / right.toSint16()); - } else + if (isNumber() && right.isNumber() && !right.isNull()) + return make_reg(0, toSint16() / right.toSint16()); + else return lookForWorkaround(right); } reg_t reg_t::operator%(const reg_t right) const { - if (isNumber() && right.isNumber()) { + if (isNumber() && right.isNumber() && !right.isNull()) { // Support for negative numbers was added in Iceman, and perhaps in // SCI0 0.000.685 and later. Theoretically, this wasn't really used // in SCI0, so the result is probably unpredictable. Such a case @@ -125,7 +107,7 @@ reg_t reg_t::operator%(const reg_t right) const { warning("Modulo of a negative number has been requested for SCI0. This *could* lead to issues"); int16 value = toSint16(); int16 modulo = ABS(right.toSint16()); - int16 result = (modulo != 0 ? value % modulo : 0); + int16 result = value % modulo; if (result < 0) result += modulo; return make_reg(0, result); @@ -159,6 +141,8 @@ uint16 reg_t::requireUint16() const { if (isNumber()) return toUint16(); else + // The right parameter is NULL_REG because + // we're not comparing *this with anything here. return lookForWorkaround(NULL_REG).toUint16(); } @@ -166,6 +150,8 @@ int16 reg_t::requireSint16() const { if (isNumber()) return toSint16(); else + // The right parameter is NULL_REG because + // we're not comparing *this with anything here. return lookForWorkaround(NULL_REG).toSint16(); } @@ -190,77 +176,51 @@ reg_t reg_t::operator^(const reg_t right) const { return lookForWorkaround(right); } -bool reg_t::operator>(const reg_t right) const { - if (isNumber() && right.isNumber()) - return toSint16() > right.toSint16(); - else if (isPointer() && segment == right.segment) - return toUint16() > right.toUint16(); // pointer comparison - else if (pointerComparisonWithInteger(right)) - return true; - else if (right.pointerComparisonWithInteger(*this)) - return false; - else - return lookForWorkaround(right).toSint16(); -} - -bool reg_t::operator<(const reg_t right) const { - if (isNumber() && right.isNumber()) - return toSint16() < right.toSint16(); - else if (isPointer() && segment == right.segment) - return toUint16() < right.toUint16(); // pointer comparison - else if (pointerComparisonWithInteger(right)) - return false; - else if (right.pointerComparisonWithInteger(*this)) - return true; - else - return lookForWorkaround(right).toSint16(); -} - -bool reg_t::gtU(const reg_t right) const { - if (isNumber() && right.isNumber()) - return toUint16() > right.toUint16(); - else if (isPointer() && segment == right.segment) - return toUint16() > right.toUint16(); // pointer comparison - else if (pointerComparisonWithInteger(right)) - return true; - else if (right.pointerComparisonWithInteger(*this)) - return false; - else - return lookForWorkaround(right).toSint16(); -} - -bool reg_t::ltU(const reg_t right) const { - if (isNumber() && right.isNumber()) - return toUint16() < right.toUint16(); - else if (isPointer() && segment == right.segment) - return toUint16() < right.toUint16(); // pointer comparison - else if (pointerComparisonWithInteger(right)) - return false; - else if (right.pointerComparisonWithInteger(*this)) - return true; - else +int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { + if (segment == right.segment) { // can compare things in the same segment + if (treatAsUnsigned || !isNumber()) + return toUint16() - right.toUint16(); + else + return toSint16() - right.toSint16(); + } else if (pointerComparisonWithInteger(right)) { + return 1; + } else if (right.pointerComparisonWithInteger(*this)) { + return -1; + } else return lookForWorkaround(right).toSint16(); } bool reg_t::pointerComparisonWithInteger(const reg_t right) const { - // SCI0 - SCI1.1 scripts use this to check whether a parameter is a pointer - // or a far text reference. It is used e.g. by the standard library Print - // function to distinguish two ways of calling it: + // This function handles the case where a script tries to compare a pointer + // to a number. Normally, we would not want to allow that. However, SCI0 - + // SCI1 scripts do this in order to distinguish references to + // external resources (which are numbers) from pointers. In + // our SCI implementation, such a check may seem pointless, as + // one can simply use the segment value to achieve this goal. + // But Sierra's SCI did not have the notion of segment IDs, so + // both pointer and numbers were simple integers. + // + // But for some things, scripts had (and have) to distinguish between + // numbers and pointers. Lacking the segment information, Sierra's + // developers resorted to a hack: If an integer is smaller than a certain + // bound, it can be assumed to be a number, otherwise it is assumed to be a + // pointer. This allowed them to implement polymorphic functions, such as + // the Print function, which can be called in two different ways, with a + // pointer or a far text reference: // // (Print "foo") // Pointer to a string // (Print 420 5) // Reference to the fifth message in text resource 420 // It works because in those games, the maximum resource number is 999, // so any parameter value above that threshold must be a pointer. // PQ2 japanese compares pointers to 2000 to find out if its a pointer - // or a resource ID. - // There are cases where game scripts check for arbitrary numbers against - // pointers, e.g.: + // or a resource ID. Thus, we check for all integers <= 2000. + // + // Some examples where game scripts check for arbitrary numbers against + // pointers: // Hoyle 3, Pachisi, when any opponent is about to talk // SQ1, room 28, when throwing water at the Orat // SQ1, room 58, when giving the ID card to the robot - // QFG3, room 440, when talking to Uhura - // Thus we check for all integers <= 2000 - return (isPointer() && right.isNumber() && right.offset <= 2000 && getSciVersion() <= SCI_VERSION_1_1); + return (isPointer() && right.isNumber() && right.offset <= 2000 && getSciVersion() <= SCI_VERSION_1_LATE); } } // End of namespace Sci diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index ac23bbe7dc..b927df339e 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -37,20 +37,20 @@ struct reg_t { SegmentId segment; uint16 offset; - bool isNull() const { - return !(offset || segment); + inline bool isNull() const { + return (offset | segment) == 0; } - uint16 toUint16() const { + inline uint16 toUint16() const { return offset; } - int16 toSint16() const { - return (int16) offset; + inline int16 toSint16() const { + return (int16)offset; } bool isNumber() const { - return !segment; + return segment == 0; } bool isPointer() const { @@ -60,7 +60,7 @@ struct reg_t { uint16 requireUint16() const; int16 requireSint16() const; - bool isInitialized() const { + inline bool isInitialized() const { return segment != 0xFFFF; } @@ -73,35 +73,39 @@ struct reg_t { return (offset != x.offset) || (segment != x.segment); } - bool operator>(const reg_t right) const; + bool operator>(const reg_t right) const { + return cmp(right, false) > 0; + } + bool operator>=(const reg_t right) const { - if (*this == right) - return true; - return *this > right; + return cmp(right, false) >= 0; + } + + bool operator<(const reg_t right) const { + return cmp(right, false) < 0; } - bool operator<(const reg_t right) const; + bool operator<=(const reg_t right) const { - if (*this == right) - return true; - return *this < right; + return cmp(right, false) <= 0; } // Same as the normal operators, but perform unsigned // integer checking - bool gtU(const reg_t right) const; + bool gtU(const reg_t right) const { + return cmp(right, true) > 0; + } + bool geU(const reg_t right) const { - if (*this == right) - return true; - return gtU(right); + return cmp(right, true) >= 0; } - bool ltU(const reg_t right) const; - bool leU(const reg_t right) const { - if (*this == right) - return true; - return ltU(right); + + bool ltU(const reg_t right) const { + return cmp(right, true) < 0; } - bool pointerComparisonWithInteger(const reg_t right) const; + bool leU(const reg_t right) const { + return cmp(right, true) <= 0; + } // Arithmetic operators reg_t operator+(const reg_t right) const; @@ -125,7 +129,17 @@ struct reg_t { reg_t operator|(const reg_t right) const; reg_t operator^(const reg_t right) const; +private: + /** + * Compares two reg_t's. + * Returns: + * - a positive number if *this > right + * - 0 if *this == right + * - a negative number if *this < right + */ + int cmp(const reg_t right, bool treatAsUnsigned) const; reg_t lookForWorkaround(const reg_t right) const; + bool pointerComparisonWithInteger(const reg_t right) const; }; static inline reg_t make_reg(SegmentId segment, uint16 offset) { diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 64cbc5ec90..2b60b1aa81 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -44,8 +44,6 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = { { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue. { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer bug #3039879 - // TODO: The SQ5 workaround below may no longer be necessary - { GID_SQ5, 200, 939, 0, "Osc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_dpToa: when going back to bridge the crew is goofing off, we get an object as cycle count SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -120,6 +118,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_MOTHERGOOSEHIRES,-1,64950, 1, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // see above { GID_PEPPER, -1, 894, 0, "Package", "doVerb", -1, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #3040012 { GID_PEPPER, 150, 928, 0, "Narrator", "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper + { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning { GID_QFG1, -1, 210, 0, "Encounter", "init", 0xbd0, 0, { WORKAROUND_FAKE, 0 } }, // hq1: going to the brigands hideout { GID_QFG1, -1, 210, 0, "Encounter", "init", 0xbe4, 0, { WORKAROUND_FAKE, 0 } }, // qfg1: going to the brigands hideout { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #3053268 @@ -273,10 +272,6 @@ const SciWorkaroundEntry kGraphSaveBox_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = { - { GID_LSL6, -1, 85, 0, "rScroller", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring (sometimes), same as the one below - { GID_LSL6, -1, 85, 0, "lScroller", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring (sometimes), same as the one below - { GID_LSL6, -1, 86, 0, "LL6Inv", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring, is called with hunk segment, but hunk is not allocated at that time - // ^^ TODO: check, if this is really a script error or an issue with our restore code { GID_LSL6, -1, 86, 0, "LL6Inv", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens during the game, gets called with 1 extra parameter { GID_SQ5, 850, 850, 0, NULL, "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens while playing Battle Cruiser (invalid segment) - bug #3056811 SCI_WORKAROUNDENTRY_TERMINATOR @@ -316,7 +311,7 @@ const SciWorkaroundEntry kGraphRedrawBox_workarounds[] = { const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = { { GID_ECOQUEST2, 100, 333, 0, "showEcorder", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // necessary workaround for our ecorder script patch, because there isn't enough space to patch the function { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #3038077 - { GID_PQ3, 202, 202, 0, "MapEdit", "addPt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #3038077 + { GID_PQ3, 202, 202, 0, "MapEdit", "addPt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #3038077 SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -330,7 +325,7 @@ const SciWorkaroundEntry kIsObject_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry kMemory_workarounds[] = { - { GID_LAURABOW2, -1, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // during the intro, when exiting the train, talking to Mr. Augustini, etc. - bug #3034490 + { GID_LAURABOW2, -1, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // during the intro, when exiting the train (room 160), talking to Mr. Augustini, etc. - bug #3034490 SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -361,7 +356,7 @@ const SciWorkaroundEntry kSetCursor_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry kSetPort_workarounds[] = { { GID_LSL6, 740, 740, 0, "rm740", "drawPic", -1, 0, { WORKAROUND_IGNORE, 0 } }, // ending scene, is called with additional 3 (!) parameters - { GID_QFG3, 830, 830, 0, "portalOpens", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when the portal appears during the end, bug #3040844 + { GID_QFG3, 830, 830, 0, "portalOpens", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when the portal appears during the end, gets called with 4 parameters (bug #3040844) SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -386,23 +381,13 @@ const SciWorkaroundEntry kStrLen_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry kUnLoad_workarounds[] = { - { GID_CAMELOT, 921, 921, 1, "Script", "changeState", 0x36, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: While showing Camelot (and other places), the reference is invalid - bug #3035000 - { GID_CAMELOT, 921, 921, 1, "Script", "init", 0x36, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: When being attacked by the boar (and other places), the reference is invalid - bug #3035000 - { GID_CASTLEBRAIN, 320, 377, 0, "SWord", "upDate", -1, 0, { WORKAROUND_IGNORE, 0 } }, // after solving the cross-word-puzzle, trying to unload invalid reference - { GID_CASTLEBRAIN, 320, 377, 0, "theWord", "show", -1, 0, { WORKAROUND_IGNORE, 0 } }, // 2nd word puzzle, when exiting before solving, trying to unload invalid reference - bug #3034473 - { GID_ECOQUEST, 380, 61, 0, "gotIt", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // after talking to the dolphin the first time - { GID_ECOQUEST, 380, 69, 0, "lookAtBlackBoard", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // German version, when closing the blackboard closeup in the dolphin room - bug #3098353 - { GID_LAURABOW2, 1, 1, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #3034902 - { GID_LAURABOW2, 2, 2, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #3034902 - { GID_LAURABOW2, 4, 4, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: inside the museum, a 3rd parameter is passed by accident - bug #3034902 - { GID_LAURABOW2, 6, 6, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: after the murder, a 3rd parameter is passed by accident - bug #3034902 - { GID_LAURABOW2, 7, 7, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: after the logo is shown, a 3rd parameter is passed by accident - bug #3034902 + { GID_ECOQUEST, 380, 61, 0, "gotIt", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // CD version: after talking to the dolphin the first time, a 3rd parameter is passed by accident + { GID_ECOQUEST, 380, 69, 0, "lookAtBlackBoard", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // German version, when closing the blackboard closeup in the dolphin room, a 3rd parameter is passed by accident - bug #3098353 + { GID_LAURABOW2, -1, 1, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #3034902 { GID_LSL6, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident { GID_LSL6, 740, 740, 0, "showCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident { GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident - { GID_PQ3, 877, 998, 0, "View", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when getting run over on the freeway, the reference is invalid { GID_SQ1, 43, 303, 0, "slotGuy", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error - { GID_SQ3, 2, 998, 0, "View", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // clicking the mouse button during the intro, after the escape pod gets pulled into the garbage freighter, the reference is invalid - bug #3050856 SCI_WORKAROUNDENTRY_TERMINATOR }; diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp index 2e5e94c52a..e02b27c788 100644 --- a/engines/sci/graphics/animate.cpp +++ b/engines/sci/graphics/animate.cpp @@ -191,11 +191,88 @@ void GfxAnimate::makeSortedList(List *list) { Common::sort(_list.begin(), _list.end(), sortHelper); } -void GfxAnimate::applyGlobalScaling(AnimateList::iterator entry, GfxView *view) { - reg_t curObject = entry->object; +void GfxAnimate::fill(byte &old_picNotValid) { + GfxView *view = NULL; + AnimateList::iterator it; + const AnimateList::iterator end = _list.end(); + + for (it = _list.begin(); it != end; ++it) { + // Get the corresponding view + view = _cache->getView(it->viewId); + + adjustInvalidCels(view, it); + processViewScaling(view, it); + setNsRect(view, it); + + //warning("%s view %d, loop %d, cel %d, signal %x", _s->_segMan->getObjectName(curObject), it->viewId, it->loopNo, it->celNo, it->signal); + + // Calculate current priority according to y-coordinate + if (!(it->signal & kSignalFixedPriority)) { + it->priority = _ports->kernelCoordinateToPriority(it->y); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(priority), it->priority); + } + + if (it->signal & kSignalNoUpdate) { + if ((it->signal & (kSignalForceUpdate | kSignalViewUpdated)) + || (it->signal & kSignalHidden && !(it->signal & kSignalRemoveView)) + || (!(it->signal & kSignalHidden) && it->signal & kSignalRemoveView) + || (it->signal & kSignalAlwaysUpdate)) + old_picNotValid++; + it->signal &= ~kSignalStopUpdate; + } else { + if ((it->signal & kSignalStopUpdate) || (it->signal & kSignalAlwaysUpdate)) + old_picNotValid++; + it->signal &= ~kSignalForceUpdate; + } + } +} + +void GfxAnimate::adjustInvalidCels(GfxView *view, AnimateList::iterator it) { + // adjust loop and cel, if any of those is invalid + // this seems to be completely crazy code + // sierra sci checked signed int16 to be above or equal the counts and reseted to 0 in those cases + // later during view processing those are compared unsigned again and then set to maximum count - 1 + // Games rely on this behaviour. For example laura bow 1 has a knight standing around in room 37 + // which has cel set to 3. This cel does not exist and the actual knight is 0 + // In kq5 on the other hand during the intro, when the trunk is opened, cel is set to some real + // high number, which is negative when considered signed. This actually requires to get fixed to + // maximum cel, otherwise the trunk would be closed. + int16 viewLoopCount = view->getLoopCount(); + if (it->loopNo >= viewLoopCount) { + it->loopNo = 0; + writeSelectorValue(_s->_segMan, it->object, SELECTOR(loop), it->loopNo); + } else if (it->loopNo < 0) { + it->loopNo = viewLoopCount - 1; + // not setting selector is right, sierra sci didn't do it during view processing as well + } + int16 viewCelCount = view->getCelCount(it->loopNo); + if (it->celNo >= viewCelCount) { + it->celNo = 0; + writeSelectorValue(_s->_segMan, it->object, SELECTOR(cel), it->celNo); + } else if (it->celNo < 0) { + it->celNo = viewCelCount - 1; + } +} + +void GfxAnimate::processViewScaling(GfxView *view, AnimateList::iterator it) { + if (!view->isScaleable()) { + // Laura Bow 2 (especially floppy) depends on this, some views are not supposed to be scaleable + // this "feature" was removed in later versions of SCI1.1 + it->scaleSignal = 0; + it->scaleY = it->scaleX = 128; + } else { + // Process global scaling, if needed + if (it->scaleSignal & kScaleSignalDoScaling) { + if (it->scaleSignal & kScaleSignalGlobalScaling) { + applyGlobalScaling(it, view); + } + } + } +} +void GfxAnimate::applyGlobalScaling(AnimateList::iterator entry, GfxView *view) { // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY - int16 maxScale = readSelectorValue(_s->_segMan, curObject, SELECTOR(maxScale)); + int16 maxScale = readSelectorValue(_s->_segMan, entry->object, SELECTOR(maxScale)); int16 celHeight = view->getHeight(entry->loopNo, entry->celNo); int16 maxCelHeight = (maxScale * celHeight) >> 7; reg_t globalVar2 = _s->variables[VAR_GLOBAL][2]; // current room object @@ -215,120 +292,43 @@ void GfxAnimate::applyGlobalScaling(AnimateList::iterator entry, GfxView *view) entry->scaleX = entry->scaleY; // and set objects scale selectors - writeSelectorValue(_s->_segMan, curObject, SELECTOR(scaleX), entry->scaleX); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(scaleY), entry->scaleY); + writeSelectorValue(_s->_segMan, entry->object, SELECTOR(scaleX), entry->scaleX); + writeSelectorValue(_s->_segMan, entry->object, SELECTOR(scaleY), entry->scaleY); } -void GfxAnimate::fill(byte &old_picNotValid) { - reg_t curObject; - uint16 signal; - GfxView *view = NULL; - AnimateList::iterator it; - const AnimateList::iterator end = _list.end(); - - for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - - // Get the corresponding view - view = _cache->getView(it->viewId); - - // adjust loop and cel, if any of those is invalid - // this seems to be completely crazy code - // sierra sci checked signed int16 to be above or equal the counts and reseted to 0 in those cases - // later during view processing those are compared unsigned again and then set to maximum count - 1 - // Games rely on this behaviour. For example laura bow 1 has a knight standing around in room 37 - // which has cel set to 3. This cel does not exist and the actual knight is 0 - // In kq5 on the other hand during the intro, when the trunk is opened, cel is set to some real - // high number, which is negative when considered signed. This actually requires to get fixed to - // maximum cel, otherwise the trunk would be closed. - int16 viewLoopCount = view->getLoopCount(); - if (it->loopNo >= viewLoopCount) { - it->loopNo = 0; - writeSelectorValue(_s->_segMan, curObject, SELECTOR(loop), it->loopNo); - } else if (it->loopNo < 0) { - it->loopNo = viewLoopCount - 1; - // not setting selector is right, sierra sci didn't do it during view processing as well - } - int16 viewCelCount = view->getCelCount(it->loopNo); - if (it->celNo >= viewCelCount) { - it->celNo = 0; - writeSelectorValue(_s->_segMan, curObject, SELECTOR(cel), it->celNo); - } else if (it->celNo < 0) { - it->celNo = viewCelCount - 1; - } - - if (!view->isScaleable()) { - // Laura Bow 2 (especially floppy) depends on this, some views are not supposed to be scaleable - // this "feature" was removed in later versions of SCI1.1 - it->scaleSignal = 0; - it->scaleY = it->scaleX = 128; - } else { - // Process global scaling, if needed - if (it->scaleSignal & kScaleSignalDoScaling) { - if (it->scaleSignal & kScaleSignalGlobalScaling) { - applyGlobalScaling(it, view); - } - } - } - - //warning("%s view %d, loop %d, cel %d, signal %x", _s->_segMan->getObjectName(curObject), it->viewId, it->loopNo, it->celNo, it->signal); - - bool setNsRect = true; +void GfxAnimate::setNsRect(GfxView *view, AnimateList::iterator it) { + bool shouldSetNsRect = true; - // Create rect according to coordinates and given cel - if (it->scaleSignal & kScaleSignalDoScaling) { - view->getCelScaledRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->scaleX, it->scaleY, it->celRect); - // when being scaled, only set nsRect, if object will get drawn - if ((signal & kSignalHidden) && !(signal & kSignalAlwaysUpdate)) - setNsRect = false; + // Create rect according to coordinates and given cel + if (it->scaleSignal & kScaleSignalDoScaling) { + view->getCelScaledRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->scaleX, it->scaleY, it->celRect); + // when being scaled, only set nsRect, if object will get drawn + if ((it->signal & kSignalHidden) && !(it->signal & kSignalAlwaysUpdate)) + shouldSetNsRect = false; + } else { + // This special handling is not included in the other SCI1.1 interpreters and MUST NOT be + // checked in those cases, otherwise we will break games (e.g. EcoQuest 2, room 200) + if ((g_sci->getGameId() == GID_HOYLE4) && (it->scaleSignal & kScaleSignalHoyle4SpecialHandling)) { + it->celRect.left = readSelectorValue(_s->_segMan, it->object, SELECTOR(nsLeft)); + it->celRect.top = readSelectorValue(_s->_segMan, it->object, SELECTOR(nsTop)); + it->celRect.right = readSelectorValue(_s->_segMan, it->object, SELECTOR(nsRight)); + it->celRect.bottom = readSelectorValue(_s->_segMan, it->object, SELECTOR(nsBottom)); + view->getCelSpecialHoyle4Rect(it->loopNo, it->celNo, it->x, it->y, it->z, it->celRect); + shouldSetNsRect = false; } else { - // This special handling is not included in the other SCI1.1 interpreters and MUST NOT be - // checked in those cases, otherwise we will break games (e.g. EcoQuest 2, room 200) - if ((g_sci->getGameId() == GID_HOYLE4) && (it->scaleSignal & kScaleSignalHoyle4SpecialHandling)) { - it->celRect.left = readSelectorValue(_s->_segMan, curObject, SELECTOR(nsLeft)); - it->celRect.top = readSelectorValue(_s->_segMan, curObject, SELECTOR(nsTop)); - it->celRect.right = readSelectorValue(_s->_segMan, curObject, SELECTOR(nsRight)); - it->celRect.bottom = readSelectorValue(_s->_segMan, curObject, SELECTOR(nsBottom)); - view->getCelSpecialHoyle4Rect(it->loopNo, it->celNo, it->x, it->y, it->z, it->celRect); - setNsRect = false; - } else { - view->getCelRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->celRect); - } - } - - if (setNsRect) { - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsLeft), it->celRect.left); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsTop), it->celRect.top); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsRight), it->celRect.right); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(nsBottom), it->celRect.bottom); - } - - // Calculate current priority according to y-coordinate - if (!(signal & kSignalFixedPriority)) { - it->priority = _ports->kernelCoordinateToPriority(it->y); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(priority), it->priority); + view->getCelRect(it->loopNo, it->celNo, it->x, it->y, it->z, it->celRect); } + } - if (signal & kSignalNoUpdate) { - if (signal & (kSignalForceUpdate | kSignalViewUpdated) - || (signal & kSignalHidden && !(signal & kSignalRemoveView)) - || (!(signal & kSignalHidden) && signal & kSignalRemoveView) - || (signal & kSignalAlwaysUpdate)) - old_picNotValid++; - signal &= ~kSignalStopUpdate; - } else { - if (signal & kSignalStopUpdate || signal & kSignalAlwaysUpdate) - old_picNotValid++; - signal &= ~kSignalForceUpdate; - } - it->signal = signal; + if (shouldSetNsRect) { + writeSelectorValue(_s->_segMan, it->object, SELECTOR(nsLeft), it->celRect.left); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(nsTop), it->celRect.top); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(nsRight), it->celRect.right); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(nsBottom), it->celRect.bottom); } } void GfxAnimate::update() { - reg_t curObject; - uint16 signal; reg_t bitsHandle; Common::Rect rect; AnimateList::iterator it; @@ -336,81 +336,66 @@ void GfxAnimate::update() { // Remove all no-update cels, if requested for (it = _list.reverse_begin(); it != end; --it) { - curObject = it->object; - signal = it->signal; - - if (signal & kSignalNoUpdate) { - if (!(signal & kSignalRemoveView)) { - bitsHandle = readSelector(_s->_segMan, curObject, SELECTOR(underBits)); + if (it->signal & kSignalNoUpdate) { + if (!(it->signal & kSignalRemoveView)) { + bitsHandle = readSelector(_s->_segMan, it->object, SELECTOR(underBits)); if (_screen->_picNotValid != 1) { _paint16->bitsRestore(bitsHandle); it->showBitsFlag = true; } else { _paint16->bitsFree(bitsHandle); } - writeSelectorValue(_s->_segMan, curObject, SELECTOR(underBits), 0); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(underBits), 0); } - signal &= ~kSignalForceUpdate; - if (signal & kSignalViewUpdated) - signal &= ~(kSignalViewUpdated | kSignalNoUpdate); - } else if (signal & kSignalStopUpdate) { - signal &= ~kSignalStopUpdate; - signal |= kSignalNoUpdate; + it->signal &= ~kSignalForceUpdate; + if (it->signal & kSignalViewUpdated) + it->signal &= ~(kSignalViewUpdated | kSignalNoUpdate); + } else if (it->signal & kSignalStopUpdate) { + it->signal &= ~kSignalStopUpdate; + it->signal |= kSignalNoUpdate; } - it->signal = signal; } // Draw always-update cels for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - - if (signal & kSignalAlwaysUpdate) { + if (it->signal & kSignalAlwaysUpdate) { // draw corresponding cel _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); it->showBitsFlag = true; - signal &= ~(kSignalStopUpdate | kSignalViewUpdated | kSignalNoUpdate | kSignalForceUpdate); - if ((signal & kSignalIgnoreActor) == 0) { + it->signal &= ~(kSignalStopUpdate | kSignalViewUpdated | kSignalNoUpdate | kSignalForceUpdate); + if (!(it->signal & kSignalIgnoreActor)) { rect = it->celRect; rect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(it->priority) - 1, rect.top, rect.bottom - 1); _paint16->fillRect(rect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); } - it->signal = signal; } } // Saving background for all NoUpdate-cels for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - - if (signal & kSignalNoUpdate) { - if (signal & kSignalHidden) { - signal |= kSignalRemoveView; + if (it->signal & kSignalNoUpdate) { + if (it->signal & kSignalHidden) { + it->signal |= kSignalRemoveView; } else { - signal &= ~kSignalRemoveView; - if (signal & kSignalIgnoreActor) + it->signal &= ~kSignalRemoveView; + if (it->signal & kSignalIgnoreActor) bitsHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY); else bitsHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_ALL); - writeSelector(_s->_segMan, curObject, SELECTOR(underBits), bitsHandle); + writeSelector(_s->_segMan, it->object, SELECTOR(underBits), bitsHandle); } - it->signal = signal; } } // Draw NoUpdate cels for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - - if (signal & kSignalNoUpdate && !(signal & kSignalHidden)) { + if (it->signal & kSignalNoUpdate && !(it->signal & kSignalHidden)) { // draw corresponding cel _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); it->showBitsFlag = true; - if (!(signal & kSignalIgnoreActor)) { + if (!(it->signal & kSignalIgnoreActor)) { rect = it->celRect; rect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(it->priority) - 1, rect.top, rect.bottom - 1); _paint16->fillRect(rect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); @@ -420,30 +405,23 @@ void GfxAnimate::update() { } void GfxAnimate::drawCels() { - reg_t curObject; - uint16 signal; reg_t bitsHandle; AnimateList::iterator it; const AnimateList::iterator end = _list.end(); _lastCastData.clear(); for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - - if (!(signal & (kSignalNoUpdate | kSignalHidden | kSignalAlwaysUpdate))) { + if (!(it->signal & (kSignalNoUpdate | kSignalHidden | kSignalAlwaysUpdate))) { // Save background bitsHandle = _paint16->bitsSave(it->celRect, GFX_SCREEN_MASK_ALL); - writeSelector(_s->_segMan, curObject, SELECTOR(underBits), bitsHandle); + writeSelector(_s->_segMan, it->object, SELECTOR(underBits), bitsHandle); // draw corresponding cel _paint16->drawCel(it->viewId, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); it->showBitsFlag = true; - if (signal & kSignalRemoveView) { - signal &= ~kSignalRemoveView; - } - it->signal = signal; + if (it->signal & kSignalRemoveView) + it->signal &= ~kSignalRemoveView; // Remember that entry in lastCast _lastCastData.push_back(*it); @@ -452,23 +430,18 @@ void GfxAnimate::drawCels() { } void GfxAnimate::updateScreen(byte oldPicNotValid) { - reg_t curObject; - uint16 signal; AnimateList::iterator it; const AnimateList::iterator end = _list.end(); Common::Rect lsRect; Common::Rect workerRect; for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - - if (it->showBitsFlag || !(signal & (kSignalRemoveView | kSignalNoUpdate) || - (!(signal & kSignalRemoveView) && (signal & kSignalNoUpdate) && oldPicNotValid))) { - lsRect.left = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsLeft)); - lsRect.top = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsTop)); - lsRect.right = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsRight)); - lsRect.bottom = readSelectorValue(_s->_segMan, curObject, SELECTOR(lsBottom)); + if (it->showBitsFlag || !(it->signal & (kSignalRemoveView | kSignalNoUpdate) || + (!(it->signal & kSignalRemoveView) && (it->signal & kSignalNoUpdate) && oldPicNotValid))) { + lsRect.left = readSelectorValue(_s->_segMan, it->object, SELECTOR(lsLeft)); + lsRect.top = readSelectorValue(_s->_segMan, it->object, SELECTOR(lsTop)); + lsRect.right = readSelectorValue(_s->_segMan, it->object, SELECTOR(lsRight)); + lsRect.bottom = readSelectorValue(_s->_segMan, it->object, SELECTOR(lsBottom)); workerRect = lsRect; workerRect.clip(it->celRect); @@ -480,17 +453,16 @@ void GfxAnimate::updateScreen(byte oldPicNotValid) { _paint16->bitsShow(lsRect); workerRect = it->celRect; } - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsLeft), it->celRect.left); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsTop), it->celRect.top); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsRight), it->celRect.right); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(lsBottom), it->celRect.bottom); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(lsLeft), it->celRect.left); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(lsTop), it->celRect.top); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(lsRight), it->celRect.right); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(lsBottom), it->celRect.bottom); // may get used for debugging //_paint16->frameRect(workerRect); _paint16->bitsShow(workerRect); - if (signal & kSignalHidden) { + if (it->signal & kSignalHidden) it->signal |= kSignalRemoveView; - } } } // use this for debug purposes @@ -498,37 +470,30 @@ void GfxAnimate::updateScreen(byte oldPicNotValid) { } void GfxAnimate::restoreAndDelete(int argc, reg_t *argv) { - reg_t curObject; - uint16 signal; AnimateList::iterator it; const AnimateList::iterator end = _list.end(); - // This has to be done in a separate loop. At least in sq1 some .dispose // modifies FIXEDLOOP flag in signal for another object. In that case we // would overwrite the new signal with our version of the old signal. for (it = _list.begin(); it != end; ++it) { - curObject = it->object; - signal = it->signal; - // Finally update signal - writeSelectorValue(_s->_segMan, curObject, SELECTOR(signal), signal); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(signal), it->signal); } for (it = _list.reverse_begin(); it != end; --it) { - curObject = it->object; // We read out signal here again, this is not by accident but to ensure // that we got an up-to-date signal - signal = readSelectorValue(_s->_segMan, curObject, SELECTOR(signal)); + it->signal = readSelectorValue(_s->_segMan, it->object, SELECTOR(signal)); - if ((signal & (kSignalNoUpdate | kSignalRemoveView)) == 0) { - _paint16->bitsRestore(readSelector(_s->_segMan, curObject, SELECTOR(underBits))); - writeSelectorValue(_s->_segMan, curObject, SELECTOR(underBits), 0); + if ((it->signal & (kSignalNoUpdate | kSignalRemoveView)) == 0) { + _paint16->bitsRestore(readSelector(_s->_segMan, it->object, SELECTOR(underBits))); + writeSelectorValue(_s->_segMan, it->object, SELECTOR(underBits), 0); } - if (signal & kSignalDisposeMe) { + if (it->signal & kSignalDisposeMe) { // Call .delete_ method of that object - invokeSelector(_s, curObject, SELECTOR(delete_), argc, argv, 0); + invokeSelector(_s, it->object, SELECTOR(delete_), argc, argv, 0); } } } @@ -591,7 +556,7 @@ void GfxAnimate::addToPicDrawCels() { // draw corresponding cel _paint16->drawCel(view, it->loopNo, it->celNo, it->celRect, it->priority, it->paletteNo, it->scaleX, it->scaleY); - if ((it->signal & kSignalIgnoreActor) == 0) { + if (!(it->signal & kSignalIgnoreActor)) { it->celRect.top = CLIP<int16>(_ports->kernelPriorityToCoordinate(it->priority) - 1, it->celRect.top, it->celRect.bottom - 1); _paint16->fillRect(it->celRect, GFX_SCREEN_MASK_CONTROL, 0, 0, 15); } @@ -665,10 +630,10 @@ void GfxAnimate::kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t // beginUpdate()/endUpdate() were introduced SCI1. // Calling those for SCI0 will work most of the time but breaks minor // stuff like percentage bar of qfg1ega at the character skill screen. - if (getSciVersion() >= SCI_VERSION_1_EGA) + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) _ports->beginUpdate(_ports->_picWind); update(); - if (getSciVersion() >= SCI_VERSION_1_EGA) + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) _ports->endUpdate(_ports->_picWind); } @@ -687,6 +652,10 @@ void GfxAnimate::kernelAnimate(reg_t listReference, bool cycle, int argc, reg_t _ports->setPort(oldPort); // Now trigger speed throttler + throttleSpeed(); +} + +void GfxAnimate::throttleSpeed() { switch (_lastCastData.size()) { case 0: // No entries drawn -> no speed throttler triggering diff --git a/engines/sci/graphics/animate.h b/engines/sci/graphics/animate.h index 44ffdd53af..87072b7a8d 100644 --- a/engines/sci/graphics/animate.h +++ b/engines/sci/graphics/animate.h @@ -115,6 +115,10 @@ private: void addToPicSetPicNotValid(); void animateShowPic(); + void throttleSpeed(); + void adjustInvalidCels(GfxView *view, AnimateList::iterator it); + void processViewScaling(GfxView *view, AnimateList::iterator it); + void setNsRect(GfxView *view, AnimateList::iterator it); EngineState *_s; GfxCache *_cache; diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp index 5f6aef0153..875b328d4f 100644 --- a/engines/sci/graphics/compare.cpp +++ b/engines/sci/graphics/compare.cpp @@ -81,23 +81,14 @@ reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect & curObject = curNode->value; if (curObject != checkObject) { signal = readSelectorValue(_segMan, curObject, SELECTOR(signal)); - if ((signal & (kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate)) == 0) { + if (!(signal & (kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate))) { curRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft)); curRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop)); curRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight)); curRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom)); // Check if curRect is within checkRect - // TODO: This check is slightly odd, because it means that a rect is not contained - // in itself. It may very well be that the original SCI engine did it just - // this way, so it should not be changed lightly. However, somebody should - // confirm whether the original engine really did it this way. Then, update - // this comment accordingly, and, if necessary, fix the code. - if (curRect.right > checkRect.left && - curRect.left < checkRect.right && - curRect.bottom > checkRect.top && - curRect.top < checkRect.bottom) { + if (checkRect.contains(curRect)) return curObject; - } } } curAddress = curNode->succ; diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index 567e02eeb9..9d1c3dabd6 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -435,9 +435,15 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu if (_macCursorRemap.empty()) { // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping - // KQ6 seems to use this mapping for its cursors - if (g_sci->getGameId() == GID_KQ6) - viewNum = loopNum * 1000 + celNum; + // KQ6 uses this mapping for its cursors + if (g_sci->getGameId() == GID_KQ6) { + if (viewNum == 990) // Inventory Cursors + viewNum = loopNum * 16 + celNum + 2000; + else if (viewNum == 998) // Regular Cursors + viewNum = celNum + 1000; + else // Unknown cursor, ignored + return; + } } else { // If we do have the list, we'll be using a remap based on what the // scripts have given us. diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index ab4a2c9c1a..fbfd140e6b 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -337,6 +337,8 @@ static int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font) { maxChars = curCharCount; // return count up to (but not including) breaking space break; } + if (width + font->getCharWidth(curChar) > maxWidth) + break; width += font->getCharWidth(curChar); curCharCount++; } @@ -592,9 +594,9 @@ void GfxFrameout::kernelFrameout() { const char *txt = text.c_str(); // HACK. The plane sometimes doesn't contain the correct width. This // hack breaks the dialog options when speaking with Grace, but it's - // the best we got up to now. - // TODO: Remove this, and figure out why the plane in question isn't - // initialized correctly (its width is 0). + // the best we got up to now. This happens because of the unimplemented + // kTextWidth function in SCI32. + // TODO: Remove this once kTextWidth has been implemented. uint16 w = it->planeRect.width() >= 20 ? it->planeRect.width() : _screen->getWidth() - 10; int16 charCount; diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index f6cb214a2b..343f3c7e6e 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -45,6 +45,10 @@ typedef int GuiResourceId; // is a resource-number and -1 means no parameter giv typedef int16 TextAlignment; +#define PORTS_FIRSTWINDOWID 2 +#define PORTS_FIRSTSCRIPTWINDOWID 3 + + struct Port { uint16 id; int16 top, left; @@ -62,6 +66,8 @@ struct Port { fontHeight(0), fontId(0), greyedOutput(false), penClr(0), backClr(0xFF), penMode(0), counterTillFree(0) { } + + bool isWindow() const { return id >= PORTS_FIRSTWINDOWID && id != 0xFFFF; } }; struct Window : public Port, public Common::Serializable { @@ -132,12 +138,14 @@ struct PalSchedule { uint32 schedule; }; +// Game view types, sorted by the number of colors enum ViewType { - kViewUnknown, - kViewEga, - kViewVga, - kViewVga11, - kViewAmiga + kViewUnknown, // uninitialized, or non-SCI + kViewEga, // EGA SCI0/SCI1 and Amiga SCI0/SCI1 ECS 16 colors + kViewAmiga, // Amiga SCI1 ECS 32 colors + kViewAmiga64, // Amiga SCI1 AGA 64 colors (i.e. Longbow) + kViewVga, // VGA SCI1 256 colors + kViewVga11 // VGA SCI1.1 and newer 256 colors }; } // End of namespace Sci diff --git a/engines/sci/graphics/maciconbar.cpp b/engines/sci/graphics/maciconbar.cpp index ea44db654b..f0931e0121 100644 --- a/engines/sci/graphics/maciconbar.cpp +++ b/engines/sci/graphics/maciconbar.cpp @@ -110,10 +110,12 @@ void GfxMacIconBar::drawDisabledImage(Graphics::Surface *surface, const Common:: newSurf.copyFrom(*surface); for (int i = 0; i < newSurf.h; i++) { - int startX = rect.left & 3; + // Start at the next four byte boundary + int startX = 3 - ((rect.left + 3) & 3); + // Start odd rows at two bytes past that (also properly aligned) if ((i + rect.top) & 1) - startX += 2; + startX = (startX + 2) & 3; for (int j = startX; j < newSurf.w; j += 4) *((byte *)newSurf.getBasePtr(j, i)) = 0; diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/graphics/paint.cpp index 50b0534ba7..c347da3c0f 100644 --- a/engines/sci/graphics/paint.cpp +++ b/engines/sci/graphics/paint.cpp @@ -43,9 +43,6 @@ GfxPaint::~GfxPaint() { void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { } -void GfxPaint::kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle) { -} - void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { } diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/paint.h index 994bc4e5e9..a79e8993c2 100644 --- a/engines/sci/graphics/paint.h +++ b/engines/sci/graphics/paint.h @@ -36,7 +36,6 @@ public: virtual ~GfxPaint(); virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); - virtual void kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle); virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); }; diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index 935dd4e62e..33986f1196 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -360,7 +360,7 @@ void GfxPaint16::bitsRestore(reg_t memoryHandle) { if (memoryPtr) { _screen->bitsRestore(memoryPtr); - _segMan->freeHunkEntry(memoryHandle); + bitsFree(memoryHandle); } } } @@ -532,20 +532,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { case SCI_DISPLAY_RESTOREUNDER: bitsGetRect(argv[0], &rect); rect.translate(-_ports->getPort()->left, -_ports->getPort()->top); - if (g_sci->getGameId() == GID_PQ3 && g_sci->getEngineState()->currentRoomNumber() == 29) { - // WORKAROUND: PQ3 calls this without calling the associated - // kDisplay(SCI_DISPLAY_SAVEUNDER) call before. Theoretically, - // this would result in no rect getting restored. However, we - // still maintain a pointer from the previous room, resulting - // in invalidated content being restored on screen, and causing - // graphics glitches. Thus, we simply don't restore a rect in - // that room. The correct fix for this would be to erase hunk - // pointers when changing rooms, but this will suffice for now, - // as restoring from a totally invalid pointer is very rare. - // Fixes bug #3037945. - } else { - bitsRestore(argv[0]); - } + bitsRestore(argv[0]); kernelGraphRedrawBox(rect); // finishing loop argc = 0; @@ -581,7 +568,10 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { // now drawing the text _text16->Size(rect, text, -1, width); rect.moveTo(_ports->getPort()->curLeft, _ports->getPort()->curTop); - if (getSciVersion() >= SCI_VERSION_1_LATE) { + // Note: This code has been found in SCI1 middle and newer games. It was + // previously only for SCI1 late and newer, but the LSL1 interpreter contains + // this code. + if (getSciVersion() >= SCI_VERSION_1_MIDDLE) { int16 leftPos = rect.right <= _screen->getWidth() ? 0 : _screen->getWidth() - rect.right; int16 topPos = rect.bottom <= _screen->getHeight() ? 0 : _screen->getHeight() - rect.bottom; _ports->move(leftPos, topPos); diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp index aa3bf8dfb3..69a278eb8b 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -65,17 +65,6 @@ void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, b delete picture; } -// This is "hacked" together, because its only used by debug command -void GfxPaint32::kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle) { - GfxView *view = _cache->getView(viewId); - Common::Rect celRect(50, 50, 50, 50); - Common::Rect translatedRect; - celRect.bottom += view->getHeight(loopNo, celNo); - celRect.right += view->getWidth(loopNo, celNo); - view->draw(celRect, celRect, celRect, loopNo, celNo, 255, 0, false); - _screen->copyRectToScreen(celRect); -} - void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control); } diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h index 3fa9d11d9b..e412bdf1c4 100644 --- a/engines/sci/graphics/paint32.h +++ b/engines/sci/graphics/paint32.h @@ -45,7 +45,6 @@ public: void fillRect(Common::Rect rect, byte color); void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); - void kernelDrawCel(GuiResourceId viewId, int16 loopNo, int16 celNo, uint16 leftPos, uint16 topPos, int16 priority, uint16 paletteNo, bool hiresMode, reg_t upscaledHiresHandle); void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); private: diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp index 534890315c..5a6b1859cd 100644 --- a/engines/sci/graphics/palette.cpp +++ b/engines/sci/graphics/palette.cpp @@ -76,6 +76,24 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen, bool useMergi #ifdef ENABLE_SCI32 _clutTable = 0; #endif + + switch (_resMan->getViewType()) { + case kViewEga: + _totalScreenColors = 16; + break; + case kViewAmiga: + _totalScreenColors = 32; + break; + case kViewAmiga64: + _totalScreenColors = 64; + break; + case kViewVga: + case kViewVga11: + _totalScreenColors = 256; + break; + default: + error("GfxPalette: Unknown view type"); + } } GfxPalette::~GfxPalette() { @@ -97,7 +115,7 @@ bool GfxPalette::isMerging() { void GfxPalette::setDefault() { if (_resMan->getViewType() == kViewEga) setEGA(); - else if (_resMan->isAmiga32color()) + else if (_resMan->getViewType() == kViewAmiga) setAmiga(); else kernelSetFromResource(999, true); @@ -418,10 +436,6 @@ void GfxPalette::getSys(Palette *pal) { } void GfxPalette::setOnScreen() { - // We dont change palette at all times for amiga - if (_resMan->isAmiga32color()) - return; - copySysPaletteToScreen(); } @@ -847,24 +861,28 @@ void GfxPalette::palVaryProcess(int signal, bool setPalette) { } } +static inline uint getMacColorDiff(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) { + // Use the difference of the top 4 bits and add together the differences + return ABS((r2 & 0xf0) - (r1 & 0xf0)) + ABS((g2 & 0xf0) - (g1 & 0xf0)) + ABS((b2 & 0xf0) - (b1 & 0xf0)); +} + byte GfxPalette::findMacIconBarColor(byte r, byte g, byte b) { // Find the best color for use with the Mac icon bar + // Check white, then the palette colors, and then black - // For black, always use 0 - if (r == 0 && g == 0 && b == 0) - return 0; + // Try white first + byte found = 0xff; + uint diff = getMacColorDiff(r, g, b, 0xff, 0xff, 0xff); - byte found = 0xFF; - uint diff = 0xFFFFFFFF; + if (diff == 0) + return found; + // Go through the main colors of the CLUT for (uint16 i = 1; i < 255; i++) { - int dr = _macClut[i * 3 ] - r; - int dg = _macClut[i * 3 + 1] - g; - int db = _macClut[i * 3 + 2] - b; + if (!colorIsFromMacClut(i)) + continue; - // Use the largest difference. This is what the Mac Palette Manager does. - uint cdiff = MAX<int>(ABS(dr), ABS(dg)); - cdiff = MAX<int>(cdiff, ABS(db)); + uint cdiff = getMacColorDiff(r, g, b, _macClut[i * 3], _macClut[i * 3 + 1], _macClut[i * 3 + 2]); if (cdiff == 0) return i; @@ -874,6 +892,10 @@ byte GfxPalette::findMacIconBarColor(byte r, byte g, byte b) { } } + // Also check black here + if (getMacColorDiff(r, g, b, 0, 0, 0) < diff) + return 0; + return found; } @@ -889,7 +911,9 @@ void GfxPalette::loadMacIconBarPalette() { clutStream->readUint32BE(); // seed clutStream->readUint16BE(); // flags uint16 colorCount = clutStream->readUint16BE() + 1; - _macClut = new byte[colorCount * 3]; + assert(colorCount == 256); + + _macClut = new byte[256 * 3]; for (uint16 i = 0; i < colorCount; i++) { clutStream->readUint16BE(); @@ -898,6 +922,19 @@ void GfxPalette::loadMacIconBarPalette() { _macClut[i * 3 + 2] = clutStream->readUint16BE() >> 8; } + // Adjust bounds on the KQ6 palette + // We don't use all of it for the icon bar + if (g_sci->getGameId() == GID_KQ6) + memset(_macClut + 32 * 3, 0, (256 - 32) * 3); + + // Force black/white + _macClut[0x00 * 3 ] = 0; + _macClut[0x00 * 3 + 1] = 0; + _macClut[0x00 * 3 + 2] = 0; + _macClut[0xff * 3 ] = 0xff; + _macClut[0xff * 3 + 1] = 0xff; + _macClut[0xff * 3 + 2] = 0xff; + delete clutStream; } diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h index 317401ac1f..d2e5151d6a 100644 --- a/engines/sci/graphics/palette.h +++ b/engines/sci/graphics/palette.h @@ -54,6 +54,7 @@ public: bool merge(Palette *pFrom, bool force, bool forceRealMerge); uint16 matchColor(byte r, byte g, byte b); void getSys(Palette *pal); + uint16 getTotalColorCount() const { return _totalScreenColors; } void setOnScreen(); void copySysPaletteToScreen(); @@ -123,6 +124,7 @@ private: uint16 _palVaryTicks; int _palVaryPaused; int _palVarySignal; + uint16 _totalScreenColors; void loadMacIconBarPalette(); byte *_macClut; diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index 7e71c1a258..e60a62e423 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -36,6 +36,8 @@ namespace Sci { +//#define DEBUG_PICTURE_DRAW + GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize) : _resMan(resMan), _coordAdjuster(coordAdjuster), _ports(ports), _screen(screen), _palette(palette), _resourceId(resourceId), _EGAdrawingVisualize(EGAdrawingVisualize) { assert(resourceId != -1); @@ -222,19 +224,20 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu } #endif +extern void unpackCelData(byte *inBuffer, byte *celBitmap, byte clearColor, int pixelCount, int rlePos, int literalPos, ViewType viewType, uint16 width, bool isMacSci11ViewData); + void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos, int literalPos, int16 drawX, int16 drawY, int16 pictureX) { byte *celBitmap = NULL; byte *ptr = NULL; byte *headerPtr = inbuffer + headerPos; byte *rlePtr = inbuffer + rlePos; - byte *literalPtr = inbuffer + literalPos; int16 displaceX, displaceY; byte priority = _addToFlag ? _priority : 0; byte clearColor; bool compression = true; - byte curByte, runLength; + byte curByte; int16 y, lastY, x, leftX, rightX; - int pixelNr, pixelCount; + int pixelCount; uint16 width, height; #ifdef ENABLE_SCI32 @@ -245,12 +248,11 @@ void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos height = READ_LE_UINT16(headerPtr + 2); displaceX = (signed char)headerPtr[4]; displaceY = (unsigned char)headerPtr[5]; - if (_resourceType == SCI_PICTURE_TYPE_SCI11) { + if (_resourceType == SCI_PICTURE_TYPE_SCI11) // SCI1.1 uses hardcoded clearcolor for pictures, even if cel header specifies otherwise clearColor = _screen->getColorWhite(); - } else { + else clearColor = headerPtr[6]; - } #ifdef ENABLE_SCI32 } else { width = READ_SCI11ENDIAN_UINT16(headerPtr + 0); @@ -266,91 +268,18 @@ void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos if (displaceX || displaceY) error("unsupported embedded cel-data in picture"); + // We will unpack cel-data into a temporary buffer and then plot it to screen + // That needs to be done cause a mirrored picture may be requested pixelCount = width * height; celBitmap = new byte[pixelCount]; if (!celBitmap) error("Unable to allocate temporary memory for picture drawing"); - if (compression) { - // We will unpack cel-data into a temporary buffer and then plot it to screen - // That needs to be done cause a mirrored picture may be requested - memset(celBitmap, clearColor, pixelCount); - pixelNr = 0; - ptr = celBitmap; - if (literalPos == 0) { - // decompression for data that has only one stream (vecor embedded view data) - switch (_resMan->getViewType()) { - case kViewEga: - while (pixelNr < pixelCount) { - curByte = *rlePtr++; - runLength = curByte >> 4; - memset(ptr + pixelNr, curByte & 0x0F, MIN<uint16>(runLength, pixelCount - pixelNr)); - pixelNr += runLength; - } - break; - case kViewVga: - case kViewVga11: - while (pixelNr < pixelCount) { - curByte = *rlePtr++; - runLength = curByte & 0x3F; - switch (curByte & 0xC0) { - case 0: // copy bytes as-is - while (runLength-- && pixelNr < pixelCount) - ptr[pixelNr++] = *rlePtr++; - break; - case 0x80: // fill with color - memset(ptr + pixelNr, *rlePtr++, MIN<uint16>(runLength, pixelCount - pixelNr)); - pixelNr += runLength; - break; - case 0xC0: // fill with transparent - pixelNr += runLength; - break; - } - } - break; - case kViewAmiga: - while (pixelNr < pixelCount) { - curByte = *rlePtr++; - if (curByte & 0x07) { // fill with color - runLength = curByte & 0x07; - curByte = curByte >> 3; - while (runLength-- && pixelNr < pixelCount) { - ptr[pixelNr++] = curByte; - } - } else { // fill with transparent - runLength = curByte >> 3; - pixelNr += runLength; - } - } - break; - - default: - error("Unsupported picture viewtype"); - } - } else { - // decompression for data that has two separate streams (probably SCI 1.1 picture) - while (pixelNr < pixelCount) { - curByte = *rlePtr++; - runLength = curByte & 0x3F; - switch (curByte & 0xC0) { - case 0: // copy bytes as-is - while (runLength-- && pixelNr < pixelCount) - ptr[pixelNr++] = *literalPtr++; - break; - case 0x80: // fill with color - memset(ptr + pixelNr, *literalPtr++, MIN<uint16>(runLength, pixelCount - pixelNr)); - pixelNr += runLength; - break; - case 0xC0: // fill with transparent - pixelNr += runLength; - break; - } - } - } - } else { + if (compression) + unpackCelData(inbuffer, celBitmap, clearColor, pixelCount, rlePos, literalPos, _resMan->getViewType(), width, false); + else // No compression (some SCI32 pictures) memcpy(celBitmap, rlePtr, pixelCount); - } Common::Rect displayArea = _coordAdjuster->pictureGetDisplayArea(); @@ -443,6 +372,7 @@ enum { PIC_OP_OPX = 0xfe, PIC_OP_TERMINATE = 0xff }; + #define PIC_OP_FIRST PIC_OP_SET_COLOR enum { @@ -465,6 +395,47 @@ enum { PIC_OPX_VGA_PRIORITY_TABLE_EXPLICIT = 4 }; +#ifdef DEBUG_PICTURE_DRAW +const char *picOpcodeNames[] = { + "Set color", + "Disable visual", + "Set priority", + "Disable priority", + "Short patterns", + "Medium lines", + "Long lines", + "Short lines", + "Fill", + "Set pattern", + "Absolute pattern", + "Set control", + "Disable control", + "Medium patterns", + "Extended opcode", + "Terminate" +}; + +const char *picExOpcodeNamesEGA[] = { + "Set palette entries", + "Set palette", + "Mono0", + "Mono1", + "Mono2", + "Mono3", + "Mono4", + "Embedded view", + "Set priority table" +}; + +const char *picExOpcodeNamesVGA[] = { + "Set palette entries", + "Embedded view", + "Set palette", + "Set priority table (eqdist)", + "Set priority table (explicit)" +}; +#endif + #define PIC_EGAPALETTE_COUNT 4 #define PIC_EGAPALETTE_SIZE 40 #define PIC_EGAPALETTE_TOTALSIZE PIC_EGAPALETTE_COUNT*PIC_EGAPALETTE_SIZE @@ -543,7 +514,9 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { // Drawing while (curPos < dataSize) { - //warning("%X at %d", data[curPos], curPos); +#ifdef DEBUG_PICTURE_DRAW + debug("Picture op: %X (%s) at %d", data[curPos], picOpcodeNames[data[curPos] - 0xF0], curPos); +#endif switch (pic_op = data[curPos++]) { case PIC_OP_SET_COLOR: pic_color = data[curPos++]; @@ -681,6 +654,9 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { case PIC_OP_OPX: // Extended functions if (isEGA) { +#ifdef DEBUG_PICTURE_DRAW + debug("* Picture ex op: %X (%s) at %d", data[curPos], picExOpcodeNamesEGA[data[curPos]], curPos); +#endif switch (pic_op = data[curPos++]) { case PIC_OPX_EGA_SET_PALETTE_ENTRIES: while (vectorIsNonOpcode(data[curPos])) { @@ -726,6 +702,9 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { error("Unsupported sci1 extended pic-operation %X", pic_op); } } else { +#ifdef DEBUG_PICTURE_DRAW + debug("* Picture ex op: %X (%s) at %d", data[curPos], picExOpcodeNamesVGA[data[curPos]], curPos); +#endif switch (pic_op = data[curPos++]) { case PIC_OPX_VGA_SET_PALETTE_ENTRIES: while (vectorIsNonOpcode(data[curPos])) { @@ -733,7 +712,7 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { } break; case PIC_OPX_VGA_SET_PALETTE: - if (_resMan->isAmiga32color()) { + if (_resMan->getViewType() == kViewAmiga || _resMan->getViewType() == kViewAmiga64) { if ((data[curPos] == 0x00) && (data[curPos + 1] == 0x01) && ((data[curPos + 32] & 0xF0) != 0xF0)) { // Left-Over VGA palette, we simply ignore it curPos += 256 + 4 + 1024; @@ -866,6 +845,8 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by byte matchedMask, matchMask; int16 w, e, a_set, b_set; + bool isEGA = (_resMan->getViewType() == kViewEga); + p.x = x + curPort->left; p.y = y + curPort->top; stack.push(p); @@ -874,6 +855,18 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by byte searchPriority = _screen->getPriority(p.x, p.y); byte searchControl = _screen->getControl(p.x, p.y); + if (isEGA) { + // In EGA games a pixel in the framebuffer is only 4 bits. We store + // a full byte per pixel to allow undithering, but when comparing + // pixels for flood-fill purposes, we should only compare the + // visible color of a pixel. + + if ((x ^ y) & 1) + searchColor = (searchColor ^ (searchColor >> 4)) & 0x0F; + else + searchColor = searchColor & 0x0F; + } + // This logic was taken directly from sierra sci, floodfill will get aborted on various occations if (screenMask & GFX_SCREEN_MASK_VISUAL) { if ((color == _screen->getColorWhite()) || (searchColor != _screen->getColorWhite())) @@ -913,20 +906,20 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by int b = curPort->rect.bottom + curPort->top - 1; while (stack.size()) { p = stack.pop(); - if ((matchedMask = _screen->isFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl)) == 0) // already filled + if ((matchedMask = _screen->isFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)) == 0) // already filled continue; _screen->putPixel(p.x, p.y, screenMask, color, priority, control); w = p.x; e = p.x; // moving west and east pointers as long as there is a matching color to fill - while (w > l && (matchedMask = _screen->isFillMatch(w - 1, p.y, matchMask, searchColor, searchPriority, searchControl))) + while (w > l && (matchedMask = _screen->isFillMatch(w - 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA))) _screen->putPixel(--w, p.y, screenMask, color, priority, control); - while (e < r && (matchedMask = _screen->isFillMatch(e + 1, p.y, matchMask, searchColor, searchPriority, searchControl))) + while (e < r && (matchedMask = _screen->isFillMatch(e + 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA))) _screen->putPixel(++e, p.y, screenMask, color, priority, control); // checking lines above and below for possible flood targets a_set = b_set = 0; while (w <= e) { - if (p.y > t && (matchedMask = _screen->isFillMatch(w, p.y - 1, matchMask, searchColor, searchPriority, searchControl))) { // one line above + if (p.y > t && (matchedMask = _screen->isFillMatch(w, p.y - 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line above if (a_set == 0) { p1.x = w; p1.y = p.y - 1; @@ -936,7 +929,7 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by } else a_set = 0; - if (p.y < b && (matchedMask = _screen->isFillMatch(w, p.y + 1, matchMask, searchColor, searchPriority, searchControl))) { // one line below + if (p.y < b && (matchedMask = _screen->isFillMatch(w, p.y + 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line below if (b_set == 0) { p1.x = w; p1.y = p.y + 1; diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp index 57c76126c0..c89f46b300 100644 --- a/engines/sci/graphics/ports.cpp +++ b/engines/sci/graphics/ports.cpp @@ -246,8 +246,10 @@ void GfxPorts::beginUpdate(Window *wnd) { PortList::iterator it = _windowList.reverse_begin(); const PortList::iterator end = Common::find(_windowList.begin(), _windowList.end(), wnd); while (it != end) { - // FIXME: We also store Port objects in the window list. - // We should add a check that we really only pass windows here... + // We also store Port objects in the window list, but they + // shouldn't be encountered during this iteration. + assert((*it)->isWindow()); + updateWindow((Window *)*it); --it; } @@ -263,11 +265,16 @@ void GfxPorts::endUpdate(Window *wnd) { assert(it != end); while (++it != end) { - // FIXME: We also store Port objects in the window list. - // We should add a check that we really only pass windows here... + // We also store Port objects in the window list, but they + // shouldn't be encountered during this iteration. + assert((*it)->isWindow()); + updateWindow((Window *)*it); } + if (getSciVersion() < SCI_VERSION_1_EGA_ONLY) + g_sci->_gfxPaint16->kernelGraphRedrawBox(_curPort->rect); + setPort(oldPort); } @@ -300,7 +307,14 @@ Window *GfxPorts::addWindow(const Common::Rect &dims, const Common::Rect *restor } _windowsById[id] = pwnd; - if (style & SCI_WINDOWMGR_STYLE_TOPMOST) + + // KQ1sci, KQ4, iceman, QfG2 always add windows to the back of the list. + // KQ5CD checks style. + // Hoyle3-demo also always adds to the back (#3036763). + bool forceToBack = (getSciVersion() <= SCI_VERSION_1_EGA_ONLY) || + (g_sci->getGameId() == GID_HOYLE3 && g_sci->isDemo()); + + if (!forceToBack && (style & SCI_WINDOWMGR_STYLE_TOPMOST)) _windowList.push_front(pwnd); else _windowList.push_back(pwnd); @@ -311,7 +325,7 @@ Window *GfxPorts::addWindow(const Common::Rect &dims, const Common::Rect *restor // bit of the left dimension in their interpreter. It seems Sierra did it // for EGA byte alignment (EGA uses 1 byte for 2 pixels) and left it in // their interpreter even in the newer VGA games. - r.left = r.left & 0x7FFE; + r.left = r.left & 0xFFFE; if (r.width() > _screen->getWidth()) { // We get invalid dimensions at least at the end of sq3 (script bug!). diff --git a/engines/sci/graphics/ports.h b/engines/sci/graphics/ports.h index 21c6d31ebd..9faee2be8d 100644 --- a/engines/sci/graphics/ports.h +++ b/engines/sci/graphics/ports.h @@ -37,9 +37,6 @@ class GfxPaint16; class GfxScreen; class GfxText16; -#define PORTS_FIRSTWINDOWID 2 -#define PORTS_FIRSTSCRIPTWINDOWID 3 - // window styles enum { SCI_WINDOWMGR_STYLE_TRANSPARENT = (1 << 0), @@ -49,6 +46,9 @@ enum { SCI_WINDOWMGR_STYLE_USER = (1 << 7) }; +typedef Common::List<Port *> PortList; +typedef Common::Array<Port *> PortArray; + /** * Ports class, includes all port managment for SCI0->SCI1.1 games. Ports are some sort of windows in SCI * this class also handles adjusting coordinates to a specific port @@ -115,8 +115,12 @@ public: virtual void saveLoadWithSerializer(Common::Serializer &ser); + /** The list of open 'windows' (and ports), in visual order. */ + PortList _windowList; + private: - typedef Common::List<Port *> PortList; + /** The list of all open 'windows' (and ports), ordered by their id. */ + PortArray _windowsById; SegManager *_segMan; GfxPaint16 *_paint16; @@ -130,12 +134,6 @@ private: // counts windows that got disposed but are not freed yet uint16 _freeCounter; - /** The list of open 'windows' (and ports), in visual order. */ - PortList _windowList; - - /** The list of all open 'windows' (and ports), ordered by their id. */ - Common::Array<Port *> _windowsById; - Common::Rect _bounds; // Priority Bands related variables diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index b019853a2b..757fdc53c7 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -113,7 +113,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { _unditherState = true; _fontIsUpscaled = false; - if (_resMan->isVGA() || (_resMan->isAmiga32color())) { + if (_resMan->getViewType() != kViewEga) { // It is not 100% accurate to set white to be 255 for Amiga 32-color // games. But 255 is defined as white in our SCI at all times, so it // doesn't matter. @@ -353,12 +353,30 @@ byte GfxScreen::getControl(int x, int y) { return _controlScreen[y * _width + x]; } -byte GfxScreen::isFillMatch(int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con) { +byte GfxScreen::isFillMatch(int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) { int offset = y * _width + x; byte match = 0; - if ((screenMask & GFX_SCREEN_MASK_VISUAL) && *(_visualScreen + offset) == t_color) - match |= GFX_SCREEN_MASK_VISUAL; + // FIXME: + if (screenMask & GFX_SCREEN_MASK_VISUAL) { + if (!isEGA) { + if (*(_visualScreen + offset) == t_color) + match |= GFX_SCREEN_MASK_VISUAL; + } else { + // In EGA games a pixel in the framebuffer is only 4 bits. We store + // a full byte per pixel to allow undithering, but when comparing + // pixels for flood-fill purposes, we should only compare the + // visible color of a pixel. + + byte c = *(_visualScreen + offset); + if ((x ^ y) & 1) + c = (c ^ (c >> 4)) & 0x0F; + else + c = c & 0x0F; + if (c == t_color) + match |= GFX_SCREEN_MASK_VISUAL; + } + } if ((screenMask & GFX_SCREEN_MASK_PRIORITY) && *(_priorityScreen + offset) == t_pri) match |= GFX_SCREEN_MASK_PRIORITY; if ((screenMask & GFX_SCREEN_MASK_CONTROL) && *(_controlScreen + offset) == t_con) diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index fc1f38ed41..6f9b08deff 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -99,7 +99,7 @@ public: byte getVisual(int x, int y); byte getPriority(int x, int y); byte getControl(int x, int y); - byte isFillMatch(int16 x, int16 y, byte drawMask, byte t_color, byte t_pri, byte t_con); + byte isFillMatch(int16 x, int16 y, byte drawMask, byte t_color, byte t_pri, byte t_con, bool isEGA); int bitsGetDataSize(Common::Rect rect, byte mask); void bitsSave(Common::Rect rect, byte mask, byte *memoryPtr); diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp index 21605ccb8e..d7a92042eb 100644 --- a/engines/sci/graphics/text16.cpp +++ b/engines/sci/graphics/text16.cpp @@ -204,6 +204,8 @@ int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgF maxChars = curCharCount; // return count up to (but not including) breaking space break; } + if (width + _font->getCharWidth(curChar) > maxWidth) + break; width += _font->getCharWidth(curChar); curCharCount++; } diff --git a/engines/sci/graphics/transitions.cpp b/engines/sci/graphics/transitions.cpp index e025a2f9ab..fac9a97efe 100644 --- a/engines/sci/graphics/transitions.cpp +++ b/engines/sci/graphics/transitions.cpp @@ -39,8 +39,8 @@ namespace Sci { //#define DISABLE_TRANSITIONS // uncomment to disable room transitions (for development only! helps in testing games quickly) -GfxTransitions::GfxTransitions(GfxScreen *screen, GfxPalette *palette, bool isVGA) - : _screen(screen), _palette(palette), _isVGA(isVGA) { +GfxTransitions::GfxTransitions(GfxScreen *screen, GfxPalette *palette) + : _screen(screen), _palette(palette) { init(); } @@ -124,6 +124,7 @@ void GfxTransitions::setup(int16 number, bool blackoutFlag) { _number = SCI_TRANSITIONS_NONE; #endif _blackoutFlag = blackoutFlag; + debugC(kDebugLevelGraphics, "Transition %d, blackout %d", number, blackoutFlag); } } @@ -271,7 +272,7 @@ void GfxTransitions::doTransition(int16 number, bool blackoutFlag) { } void GfxTransitions::setNewPalette(bool blackoutFlag) { - if (!blackoutFlag && _isVGA) + if (!blackoutFlag) _palette->setOnScreen(); } diff --git a/engines/sci/graphics/transitions.h b/engines/sci/graphics/transitions.h index 674b7a8173..a8f0ca6f07 100644 --- a/engines/sci/graphics/transitions.h +++ b/engines/sci/graphics/transitions.h @@ -65,7 +65,7 @@ class Screen; */ class GfxTransitions { public: - GfxTransitions(GfxScreen *screen, GfxPalette *palette, bool isVGA); + GfxTransitions(GfxScreen *screen, GfxPalette *palette); ~GfxTransitions(); void setup(int16 number, bool blackoutFlag); @@ -97,7 +97,6 @@ private: GfxScreen *_screen; GfxPalette *_palette; - bool _isVGA; const GfxTransitionTranslateEntry *_translationTable; int16 _number; bool _blackoutFlag; diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index 3c1f417740..fd74714495 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -104,9 +104,10 @@ void GfxView::initData(GuiResourceId resourceId) { } switch (curViewType) { - case kViewEga: // View-format SCI0 (and Amiga 16 colors) + case kViewEga: // SCI0 (and Amiga 16 colors) isEGA = true; - case kViewAmiga: // View-format Amiga (32 colors) + case kViewAmiga: // Amiga ECS (32 colors) + case kViewAmiga64: // Amiga AGA (64 colors) case kViewVga: // View-format SCI1 // LoopCount:WORD MirrorMask:WORD Version:WORD PaletteOffset:WORD LoopOffset0:WORD LoopOffset1:WORD... @@ -132,7 +133,7 @@ void GfxView::initData(GuiResourceId resourceId) { // SCI1 VGA conversion games (which will get detected as SCI1EARLY/MIDDLE/LATE) have some views // with broken mapping tables. I guess those games won't use the mapping, so I rather disable it // for them - if (getSciVersion() == SCI_VERSION_1_EGA) { + if (getSciVersion() == SCI_VERSION_1_EGA_ONLY) { _EGAmapping = &_resourceData[palOffset]; for (EGAmapNr = 0; EGAmapNr < SCI_VIEW_EGAMAPPING_COUNT; EGAmapNr++) { if (memcmp(_EGAmapping, EGAmappingStraight, SCI_VIEW_EGAMAPPING_SIZE) != 0) @@ -370,22 +371,129 @@ void GfxView::getCelScaledRect(int16 loopNo, int16 celNo, int16 x, int16 y, int1 outRect.top = outRect.bottom - scaledHeight; } +void unpackCelData(byte *inBuffer, byte *celBitmap, byte clearColor, int pixelCount, int rlePos, int literalPos, ViewType viewType, uint16 width, bool isMacSci11ViewData) { + byte *outPtr = celBitmap; + byte curByte, runLength; + byte *rlePtr = inBuffer + rlePos; + byte *literalPtr = inBuffer + literalPos; + int pixelNr = 0; + + memset(celBitmap, clearColor, pixelCount); + + if (!literalPos) { + // decompression for data that has only one combined stream + switch (viewType) { + case kViewEga: + while (pixelNr < pixelCount) { + curByte = *rlePtr++; + runLength = curByte >> 4; + memset(outPtr + pixelNr, curByte & 0x0F, MIN<uint16>(runLength, pixelCount - pixelNr)); + pixelNr += runLength; + } + break; + case kViewAmiga: + while (pixelNr < pixelCount) { + curByte = *rlePtr++; + if (curByte & 0x07) { // fill with color + runLength = curByte & 0x07; + curByte = curByte >> 3; + while (runLength-- && pixelNr < pixelCount) + outPtr[pixelNr++] = curByte; + } else { // fill with transparent + runLength = curByte >> 3; + pixelNr += runLength; + } + } + break; + case kViewAmiga64: + // TODO: This isn't 100% right. Implement it fully. + while (pixelNr < pixelCount) { + curByte = *rlePtr++; + runLength = curByte >> 6; + memset(outPtr + pixelNr, curByte & 0x3F, MIN<uint16>(runLength, pixelCount - pixelNr)); + pixelNr += runLength; + } + break; + case kViewVga: + case kViewVga11: + while (pixelNr < pixelCount) { + curByte = *rlePtr++; + runLength = curByte & 0x3F; + switch (curByte & 0xC0) { + case 0: // copy bytes as-is + while (runLength-- && pixelNr < pixelCount) + outPtr[pixelNr++] = *rlePtr++; + break; + case 0x40: // copy bytes as is (In copy case, runLength can go upto 127 i.e. pixel & 0x40). Fixes bug #3135872. + runLength += 64; + break; + case 0x80: // fill with color + memset(outPtr + pixelNr, *rlePtr++, MIN<uint16>(runLength, pixelCount - pixelNr)); + pixelNr += runLength; + break; + case 0xC0: // fill with transparent + pixelNr += runLength; + break; + } + } + break; + default: + error("Unsupported picture viewtype"); + } + } else { + // decompression for data that has two separate streams (probably a SCI 1.1 view) + if (isMacSci11ViewData) { + // KQ6/Freddy Pharkas use byte lengths, all others use uint16 + // The SCI devs must have realized that a max of 255 pixels wide + // was not very good for 320 or 640 width games. + bool hasByteLengths = (g_sci->getGameId() == GID_KQ6 || g_sci->getGameId() == GID_FREDDYPHARKAS); + + // compression for SCI1.1+ Mac + while (pixelNr < pixelCount) { + uint32 pixelLine = pixelNr; + + if (hasByteLengths) { + pixelNr += *rlePtr++; + runLength = *rlePtr++; + } else { + pixelNr += READ_BE_UINT16(rlePtr); + runLength = READ_BE_UINT16(rlePtr + 2); + rlePtr += 4; + } + + while (runLength-- && pixelNr < pixelCount) + outPtr[pixelNr++] = *literalPtr++; + + pixelNr = pixelLine + width; + } + } else { + while (pixelNr < pixelCount) { + curByte = *rlePtr++; + runLength = curByte & 0x3F; + switch (curByte & 0xC0) { + case 0: // copy bytes as-is + while (runLength-- && pixelNr < pixelCount) + outPtr[pixelNr++] = *literalPtr++; + break; + case 0x80: // fill with color + memset(outPtr + pixelNr, *literalPtr++, MIN<uint16>(runLength, pixelCount - pixelNr)); + pixelNr += runLength; + break; + case 0xC0: // fill with transparent + pixelNr += runLength; + break; + } + } + } + } +} + void GfxView::unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount) { const CelInfo *celInfo = getCelInfo(loopNo, celNo); - byte *rlePtr; - byte *literalPtr; - uint32 pixelNo = 0, runLength; - byte pixel; if (celInfo->offsetEGA) { // decompression for EGA views - literalPtr = _resourceData + _loop[loopNo].cel[celNo].offsetEGA; - while (pixelNo < pixelCount) { - pixel = *literalPtr++; - runLength = pixel >> 4; - memset(outPtr + pixelNo, pixel & 0x0F, MIN<uint32>(runLength, pixelCount - pixelNo)); - pixelNo += runLength; - } + unpackCelData(_resourceData, outPtr, 0, pixelCount, celInfo->offsetEGA, 0, _resMan->getViewType(), celInfo->width, false); } else { // We fill the buffer with transparent pixels, so that we can later skip // over pixels to automatically have them transparent @@ -408,100 +516,8 @@ void GfxView::unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCou clearColor = 0; } - memset(outPtr, clearColor, pixelCount); - - rlePtr = _resourceData + celInfo->offsetRLE; - if (!celInfo->offsetLiteral) { // no additional literal data - if (_resMan->isAmiga32color()) { - // decompression for amiga views - while (pixelNo < pixelCount) { - pixel = *rlePtr++; - if (pixel & 0x07) { // fill with color - runLength = pixel & 0x07; - pixel = pixel >> 3; - while (runLength-- && pixelNo < pixelCount) { - outPtr[pixelNo++] = pixel; - } - } else { // fill with transparent - runLength = pixel >> 3; - pixelNo += runLength; - } - } - } else { - // decompression for data that has just one combined stream - while (pixelNo < pixelCount) { - pixel = *rlePtr++; - runLength = pixel & 0x3F; - switch (pixel & 0xC0) { - case 0x40: // copy bytes as is (In copy case, runLength can go upto 127 i.e. pixel & 0x40) - runLength += 64; - case 0x00: // copy bytes as-is - while (runLength-- && pixelNo < pixelCount) - outPtr[pixelNo++] = *rlePtr++; - break; - case 0x80: // fill with color - memset(outPtr + pixelNo, *rlePtr++, MIN<uint32>(runLength, pixelCount - pixelNo)); - pixelNo += runLength; - break; - case 0xC0: // fill with transparent - pixelNo += runLength; - break; - } - } - } - } else { - literalPtr = _resourceData + celInfo->offsetLiteral; - if (celInfo->offsetRLE) { - if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() == SCI_VERSION_1_1) { - // KQ6/Freddy Pharkas use byte lengths, all others use uint16 - // The SCI devs must have realized that a max of 255 pixels wide - // was not very good for 320 or 640 width games. - bool hasByteLengths = (g_sci->getGameId() == GID_KQ6 || g_sci->getGameId() == GID_FREDDYPHARKAS); - - // compression for SCI1.1+ Mac - while (pixelNo < pixelCount) { - uint32 pixelLine = pixelNo; - - if (hasByteLengths) { - pixelNo += *rlePtr++; - runLength = *rlePtr++; - } else { - pixelNo += READ_BE_UINT16(rlePtr); - runLength = READ_BE_UINT16(rlePtr + 2); - rlePtr += 4; - } - - while (runLength-- && pixelNo < pixelCount) - outPtr[pixelNo++] = *literalPtr++; - - pixelNo = pixelLine + celInfo->width; - } - } else { - // decompression for data that has separate rle and literal streams - while (pixelNo < pixelCount) { - pixel = *rlePtr++; - runLength = pixel & 0x3F; - switch (pixel & 0xC0) { - case 0: // copy bytes as-is - while (runLength-- && pixelNo < pixelCount) - outPtr[pixelNo++] = *literalPtr++; - break; - case 0x80: // fill with color - memset(outPtr + pixelNo, *literalPtr++, MIN<uint32>(runLength, pixelCount - pixelNo)); - pixelNo += runLength; - break; - case 0xC0: // fill with transparent - pixelNo += runLength; - break; - } - } - } - } else { - // literal stream only, so no compression - memcpy(outPtr, literalPtr, pixelCount); - pixelNo = pixelCount; - } - } + bool isMacSci11ViewData = g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() == SCI_VERSION_1_1; + unpackCelData(_resourceData, outPtr, clearColor, pixelCount, celInfo->offsetRLE, celInfo->offsetLiteral, _resMan->getViewType(), celInfo->width, isMacSci11ViewData); // Swap 0 and 0xff pixels for Mac SCI1.1+ games (see above) if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1) { @@ -531,9 +547,8 @@ const byte *GfxView::getBitmap(int16 loopNo, int16 celNo) { // unpack the actual cel bitmap data unpackCel(loopNo, celNo, pBitmap, pixelCount); - if (!_resMan->isVGA()) { + if (_resMan->getViewType() == kViewEga) unditherBitmap(pBitmap, width, height, _loop[loopNo].cel[celNo].clearKey); - } // mirroring the cel if needed if (_loop[loopNo].mirrorFlag) { diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp index 8d59df5d58..25043401cc 100644 --- a/engines/sci/parser/vocabulary.cpp +++ b/engines/sci/parser/vocabulary.cpp @@ -63,7 +63,7 @@ Vocabulary::Vocabulary(ResourceManager *resMan, bool foreign) : _resMan(resMan), _resourceIdBranches += 10; } - if (getSciVersion() <= SCI_VERSION_1_EGA && loadParserWords()) { + if (getSciVersion() <= SCI_VERSION_1_EGA_ONLY && loadParserWords()) { loadSuffixes(); if (loadBranches()) // Now build a GNF grammar out of this diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index a16f7126c3..7a4534d3ac 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -67,7 +67,7 @@ const char *getSciVersionDesc(SciVersion version) { return "Late SCI0"; case SCI_VERSION_01: return "SCI01"; - case SCI_VERSION_1_EGA: + case SCI_VERSION_1_EGA_ONLY: return "SCI1 EGA"; case SCI_VERSION_1_EARLY: return "Early SCI1"; @@ -948,15 +948,18 @@ void ResourceManager::init(bool initFromFallbackDetector) { case kViewEga: debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources"); break; + case kViewAmiga: + debugC(1, kDebugLevelResMan, "resMan: Detected Amiga ECS graphic resources"); + break; + case kViewAmiga64: + debugC(1, kDebugLevelResMan, "resMan: Detected Amiga AGA graphic resources"); + break; case kViewVga: debugC(1, kDebugLevelResMan, "resMan: Detected VGA graphic resources"); break; case kViewVga11: debugC(1, kDebugLevelResMan, "resMan: Detected SCI1.1 VGA graphic resources"); break; - case kViewAmiga: - debugC(1, kDebugLevelResMan, "resMan: Detected Amiga graphic resources"); - break; default: #ifdef ENABLE_SCI32 error("resMan: Couldn't determine view type"); @@ -1535,10 +1538,18 @@ void ResourceManager::readResourcePatches() { mask += s_resourceTypeSuffixes[i]; SearchMan.listMatchingMembers(files, mask); - if (i == kResourceTypeScript && files.size() == 0) { - // SCI3 (we can't use getSciVersion() at this point) - mask = "*.csc"; - SearchMan.listMatchingMembers(files, mask); + if (i == kResourceTypeView) { + SearchMan.listMatchingMembers(files, "*.v16"); // EGA SCI1 view patches + SearchMan.listMatchingMembers(files, "*.v32"); // Amiga SCI1 view patches + SearchMan.listMatchingMembers(files, "*.v64"); // Amiga AGA SCI1 (i.e. Longbow) view patches + } else if (i == kResourceTypePic) { + SearchMan.listMatchingMembers(files, "*.p16"); // EGA SCI1 picture patches + SearchMan.listMatchingMembers(files, "*.p32"); // Amiga SCI1 picture patches + SearchMan.listMatchingMembers(files, "*.p64"); // Amiga AGA SCI1 (i.e. Longbow) picture patches + } else if (i == kResourceTypeScript) { + if (files.size() == 0) + // SCI3 (we can't use getSciVersion() at this point) + SearchMan.listMatchingMembers(files, "*.csc"); } for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { @@ -2049,7 +2060,13 @@ ViewType ResourceManager::detectViewType() { switch (res->data[1]) { case 128: - // If the 2nd byte is 128, it's a VGA game + // If the 2nd byte is 128, it's a VGA game. + // However, Longbow Amiga (AGA, 64 colors), also sets this byte + // to 128, but it's a mixed VGA/Amiga format. Detect this from + // the platform here. + if (g_sci && g_sci->getPlatform() == Common::kPlatformAmiga) + return kViewAmiga64; + return kViewVga; case 0: // EGA or Amiga, try to read as Amiga view @@ -2127,9 +2144,9 @@ void ResourceManager::detectSciVersion() { if (viewCompression != kCompLZW) { // If it's a different compression type from kCompLZW, the game is probably - // SCI_VERSION_1_EGA or later. If the views are uncompressed, it is + // SCI_VERSION_1_EGA_ONLY or later. If the views are uncompressed, it is // likely not an early disk game. - s_sciVersion = SCI_VERSION_1_EGA; + s_sciVersion = SCI_VERSION_1_EGA_ONLY; oldDecompressors = false; } @@ -2243,9 +2260,9 @@ void ResourceManager::detectSciVersion() { return; } - // New decompressors. It's either SCI_VERSION_1_EGA or SCI_VERSION_1_EARLY. + // New decompressors. It's either SCI_VERSION_1_EGA_ONLY or SCI_VERSION_1_EARLY. if (hasSci1Voc900()) { - s_sciVersion = SCI_VERSION_1_EGA; + s_sciVersion = SCI_VERSION_1_EGA_ONLY; return; } @@ -2255,6 +2272,9 @@ void ResourceManager::detectSciVersion() { case kResVersionSci1Middle: case kResVersionKQ5FMT: s_sciVersion = SCI_VERSION_1_MIDDLE; + // Amiga SCI1 middle games are actually SCI1 late + if (_viewType == kViewAmiga || _viewType == kViewAmiga64) + s_sciVersion = SCI_VERSION_1_LATE; return; case kResVersionSci1Late: if (_volVersion == kResVersionSci11) { diff --git a/engines/sci/resource.h b/engines/sci/resource.h index 76b5a421ee..e941f666d9 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -331,8 +331,6 @@ public: int getAudioLanguage() const; void changeAudioDirectory(Common::String path); bool isGMTrackIncluded(); - bool isVGA() const { return (_viewType == kViewVga) || (_viewType == kViewVga11); } - bool isAmiga32color() const { return _viewType == kViewAmiga; } bool isSci11Mac() const { return _volVersion == kResVersionSci11Mac; } ViewType getViewType() const { return _viewType; } const char *getMapVersionDesc() const { return versionDescription(_mapVersion); } diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 7147b17b82..3dfa5e0a97 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -213,7 +213,6 @@ Common::Error SciEngine::run() { // Add the after market GM patches for the specified game, if they exist _resMan->addNewGMPatch(_gameId); _gameObjectAddress = _resMan->findGameObject(); - _gameSuperClassAddress = NULL_REG; SegManager *segMan = new SegManager(_resMan); @@ -227,7 +226,7 @@ Common::Error SciEngine::run() { _features = new GameFeatures(segMan, _kernel); // Only SCI0, SCI01 and SCI1 EGA games used a parser - _vocabulary = (getSciVersion() <= SCI_VERSION_1_EGA) ? new Vocabulary(_resMan, false) : NULL; + _vocabulary = (getSciVersion() <= SCI_VERSION_1_EGA_ONLY) ? new Vocabulary(_resMan, false) : NULL; // Also, XMAS1990 apparently had a parser too. Refer to http://forums.scummvm.org/viewtopic.php?t=9135 if (getGameId() == GID_CHRISTMAS1990) _vocabulary = new Vocabulary(_resMan, false); @@ -250,7 +249,6 @@ Common::Error SciEngine::run() { warning("Could not get game object, aborting..."); return Common::kUnknownError; } - _gameSuperClassAddress = gameObject->getSuperClassSelector(); script_adjust_opcode_formats(); @@ -267,8 +265,6 @@ Common::Error SciEngine::run() { // Initialize all graphics related subsystems initGraphics(); - debug("Emulating SCI version %s\n", getSciVersionDesc(getSciVersion())); - // Patch in our save/restore code, so that dialogs are replaced patchGameSaveRestore(); setLauncherLanguage(); @@ -427,34 +423,65 @@ static byte patchGameRestoreSave[] = { 0x76, // push0 0x38, 0xff, 0xff, // pushi -1 0x76, // push0 - 0x43, 0xff, 0x06, // call kRestoreGame/kSaveGame (will get fixed directly) + 0x43, 0xff, 0x06, // callk kRestoreGame/kSaveGame (will get changed afterwards) + 0x48, // ret +}; + +// SCI2 version: Same as above, but the second parameter to callk is a word +static byte patchGameRestoreSaveSci2[] = { + 0x39, 0x03, // pushi 03 + 0x76, // push0 + 0x38, 0xff, 0xff, // pushi -1 + 0x76, // push0 + 0x43, 0xff, 0x06, 0x00, // callk kRestoreGame/kSaveGame (will get changed afterwards) + 0x48, // ret +}; + +// SCI21 version: Same as above, but the second parameter to callk is a word +static byte patchGameRestoreSaveSci21[] = { + 0x39, 0x04, // pushi 04 + 0x76, // push0 // 0: save, 1: restore (will get changed afterwards) + 0x76, // push0 + 0x38, 0xff, 0xff, // pushi -1 + 0x76, // push0 + 0x43, 0xff, 0x08, 0x00, // callk kSave (will get changed afterwards) 0x48, // ret }; +static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, byte id) { + Script *script = segMan->getScript(methodAddress.segment); + byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.offset)); + if (getSciVersion() <= SCI_VERSION_1_1) + memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave)); + else // SCI2+ + memcpy(patchPtr, patchGameRestoreSaveSci2, sizeof(patchGameRestoreSaveSci2)); + patchPtr[8] = id; +} + +static void patchGameSaveRestoreCodeSci21(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) { + Script *script = segMan->getScript(methodAddress.segment); + byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.offset)); + memcpy(patchPtr, patchGameRestoreSaveSci21, sizeof(patchGameRestoreSaveSci21)); + if (doRestore) + patchPtr[2] = 0x78; // push1 + patchPtr[9] = id; +} + void SciEngine::patchGameSaveRestore() { SegManager *segMan = _gamestate->_segMan; const Object *gameObject = segMan->getObject(_gameObjectAddress); - const uint16 gameMethodCount = gameObject->getMethodCount(); - const Object *gameSuperObject = segMan->getObject(_gameSuperClassAddress); + const Object *gameSuperObject = segMan->getObject(gameObject->getSuperClassSelector()); if (!gameSuperObject) gameSuperObject = gameObject; // happens in KQ5CD, when loading saved games before r54510 - const uint16 gameSuperMethodCount = gameSuperObject->getMethodCount(); - reg_t methodAddress; - const uint16 kernelCount = _kernel->getKernelNamesSize(); - const byte *scriptRestorePtr = NULL; byte kernelIdRestore = 0; - const byte *scriptSavePtr = NULL; byte kernelIdSave = 0; - // this feature is currently not supported on SCI32 - if (getSciVersion() >= SCI_VERSION_2) - return; - switch (_gameId) { - case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs - case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required case GID_HOYLE1: // gets confused, although the game doesnt support saving/restoring at all case GID_HOYLE2: // gets confused, see hoyle1 + case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required + case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs + case GID_PHANTASMAGORIA: // has custom save/load code return; default: break; @@ -463,61 +490,53 @@ void SciEngine::patchGameSaveRestore() { if (ConfMan.getBool("sci_originalsaveload")) return; - for (uint16 kernelNr = 0; kernelNr < kernelCount; kernelNr++) { + uint16 kernelNamesSize = _kernel->getKernelNamesSize(); + for (uint16 kernelNr = 0; kernelNr < kernelNamesSize; kernelNr++) { Common::String kernelName = _kernel->getKernelName(kernelNr); if (kernelName == "RestoreGame") kernelIdRestore = kernelNr; if (kernelName == "SaveGame") kernelIdSave = kernelNr; + if (kernelName == "Save") + kernelIdSave = kernelIdRestore = kernelNr; } - // Search for gameobject-superclass ::restore - for (uint16 methodNr = 0; methodNr < gameSuperMethodCount; methodNr++) { + // Search for gameobject superclass ::restore + uint16 gameSuperObjectMethodCount = gameSuperObject->getMethodCount(); + for (uint16 methodNr = 0; methodNr < gameSuperObjectMethodCount; methodNr++) { uint16 selectorId = gameSuperObject->getFuncSelector(methodNr); Common::String methodName = _kernel->getSelectorName(selectorId); if (methodName == "restore") { - methodAddress = gameSuperObject->getFunction(methodNr); - Script *script = segMan->getScript(methodAddress.segment); - scriptRestorePtr = script->getBuf(methodAddress.offset); + if (kernelIdSave != kernelIdRestore) + patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore); + else + patchGameSaveRestoreCodeSci21(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore, true); } - if (methodName == "save") { - methodAddress = gameSuperObject->getFunction(methodNr); - Script *script = segMan->getScript(methodAddress.segment); - scriptSavePtr = script->getBuf(methodAddress.offset); + else if (methodName == "save") { + if (_gameId != GID_FAIRYTALES) { // Fairy Tales saves automatically without a dialog + if (kernelIdSave != kernelIdRestore) + patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave); + else + patchGameSaveRestoreCodeSci21(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false); + } } } - // Search for gameobject ::save, if there is one patch that one instead - for (uint16 methodNr = 0; methodNr < gameMethodCount; methodNr++) { + // Search for gameobject ::save, if there is one patch that one too + uint16 gameObjectMethodCount = gameObject->getMethodCount(); + for (uint16 methodNr = 0; methodNr < gameObjectMethodCount; methodNr++) { uint16 selectorId = gameObject->getFuncSelector(methodNr); Common::String methodName = _kernel->getSelectorName(selectorId); if (methodName == "save") { - methodAddress = gameObject->getFunction(methodNr); - Script *script = segMan->getScript(methodAddress.segment); - scriptSavePtr = script->getBuf(methodAddress.offset); + if (_gameId != GID_FAIRYTALES) { // Fairy Tales saves automatically without a dialog + if (kernelIdSave != kernelIdRestore) + patchGameSaveRestoreCode(segMan, gameObject->getFunction(methodNr), kernelIdSave); + else + patchGameSaveRestoreCodeSci21(segMan, gameObject->getFunction(methodNr), kernelIdSave, false); + } break; } } - - switch (_gameId) { - case GID_FAIRYTALES: // fairy tales automatically saves w/o dialog - scriptSavePtr = NULL; - default: - break; - } - - if (scriptRestorePtr) { - // Now patch in our code - byte *patchPtr = const_cast<byte *>(scriptRestorePtr); - memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave)); - patchPtr[8] = kernelIdRestore; - } - if (scriptSavePtr) { - // Now patch in our code - byte *patchPtr = const_cast<byte *>(scriptSavePtr); - memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave)); - patchPtr[8] = kernelIdSave; - } } bool SciEngine::initGame() { @@ -623,7 +642,7 @@ void SciEngine::initGraphics() { _gfxCoordAdjuster = new GfxCoordAdjuster16(_gfxPorts); _gfxCursor->init(_gfxCoordAdjuster, _eventMan); _gfxCompare = new GfxCompare(_gamestate->_segMan, _kernel, _gfxCache, _gfxScreen, _gfxCoordAdjuster); - _gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette, _resMan->isVGA()); + _gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette); _gfxPaint16 = new GfxPaint16(_resMan, _gamestate->_segMan, _kernel, _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette, _gfxTransitions, _audio); _gfxPaint = _gfxPaint16; _gfxAnimate = new GfxAnimate(_gamestate, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette, _gfxCursor, _gfxTransitions); @@ -758,6 +777,16 @@ bool SciEngine::isCD() const { return _gameDescription->flags & ADGF_CD; } +bool SciEngine::isBE() const{ + switch(_gameDescription->platform) { + case Common::kPlatformAmiga: + case Common::kPlatformMacintosh: + return true; + default: + return false; + } +} + bool SciEngine::hasMacIconBar() const { return _resMan->isSci11Mac() && getSciVersion() == SCI_VERSION_1_1 && (getGameId() == GID_KQ6 || getGameId() == GID_FREDDYPHARKAS); diff --git a/engines/sci/sci.h b/engines/sci/sci.h index b3e398325f..26ddb00a01 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -180,20 +180,24 @@ enum SciGameId { GID_FANMADE // FIXME: Do we really need/want this? }; -/** SCI versions */ +/** + * SCI versions + * For more information, check here: + * http://wiki.scummvm.org/index.php/Sierra_Game_Versions#SCI_Games + */ enum SciVersion { SCI_VERSION_NONE, - SCI_VERSION_0_EARLY, // Early KQ4, 1988 xmas card + SCI_VERSION_0_EARLY, // KQ4 early, LSL2 early, XMAS card 1988 SCI_VERSION_0_LATE, // KQ4, LSL2, LSL3, SQ3 etc SCI_VERSION_01, // KQ1 and multilingual games (S.old.*) - SCI_VERSION_1_EGA, // EGA with parser, QFG2 - SCI_VERSION_1_EARLY, // KQ5. (EGA/VGA) - SCI_VERSION_1_MIDDLE, // LSL1, JONESCD. (EGA?/VGA) - SCI_VERSION_1_LATE, // ECO1, LSL5. (EGA/VGA) - SCI_VERSION_1_1, // KQ6, ECO2 - SCI_VERSION_2, // GK1, PQ4 (Floppy), QFG4 (Floppy) - SCI_VERSION_2_1, // GK2, KQ7, SQ6, Torin - SCI_VERSION_3 // LSL7, RAMA, Lighthouse + SCI_VERSION_1_EGA_ONLY, // SCI 1 EGA with parser (i.e. QFG2 only) + SCI_VERSION_1_EARLY, // KQ5 floppy, SQ4 floppy, XMAS card 1990, Fairy tales, Jones floppy + SCI_VERSION_1_MIDDLE, // LSL1, Jones CD + SCI_VERSION_1_LATE, // Dr. Brain 1, EcoQuest 1, Longbow, PQ3, SQ1, LSL5, KQ5 CD + SCI_VERSION_1_1, // Dr. Brain 2, EcoQuest 1 CD, EcoQuest 2, KQ6, QFG3, SQ4CD, XMAS 1992 and many more + SCI_VERSION_2, // GK1, PQ4 floppy, QFG4 floppy + SCI_VERSION_2_1, // GK2, KQ7, LSL6 hires, MUMG Deluxe, Phantasmagoria 1, PQ4CD, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin + SCI_VERSION_3 // LSL7, Lighthouse, RAMA, Phantasmagoria 2 }; /** Supported languages */ @@ -234,6 +238,10 @@ public: Common::Platform getPlatform() const; bool isDemo() const; bool isCD() const; + + /** Returns true if the game's original platform is big-endian. */ + bool isBE() const; + bool hasMacIconBar() const; inline ResourceManager *getResMan() const { return _resMan; } @@ -242,7 +250,6 @@ public: inline Vocabulary *getVocabulary() const { return _vocabulary; } inline EventManager *getEventManager() const { return _eventMan; } inline reg_t getGameObject() const { return _gameObjectAddress; } - inline reg_t getGameSuperClassAddress() const { return _gameSuperClassAddress; } Common::RandomSource &getRNG() { return _rng; } @@ -371,7 +378,6 @@ private: int16 _vocabularyLanguage; EventManager *_eventMan; reg_t _gameObjectAddress; /**< Pointer to the game object */ - reg_t _gameSuperClassAddress; // Address of the super class of the game object Console *_console; Common::RandomSource _rng; Common::MacResManager _macExecutable; diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index 057b7c177f..65a8e2e3da 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -55,7 +55,7 @@ public: virtual ~MidiDriver_AdLib() { } // MidiDriver - int open(bool isSCI0); + int openAdLib(bool isSCI0); void close(); void send(uint32 b); MidiChannel *allocateChannel() { return NULL; } @@ -215,7 +215,7 @@ static const int ym3812_note[13] = { 0x2ae }; -int MidiDriver_AdLib::open(bool isSCI0) { +int MidiDriver_AdLib::openAdLib(bool isSCI0) { int rate = _mixer->getOutputRate(); _stereo = STEREO; @@ -273,10 +273,6 @@ void MidiDriver_AdLib::send(uint32 b) { case 0x90: noteOn(channel, op1, op2); break; - case 0xe0: - _channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7); - renewNotes(channel, true); - break; case 0xb0: switch (op1) { case 0x07: @@ -321,7 +317,9 @@ void MidiDriver_AdLib::send(uint32 b) { case 0xa0: // Polyphonic key pressure (aftertouch) case 0xd0: // Channel pressure (aftertouch) break; - case 0xf0: // SysEx, ignore it + case 0xe0: + _channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7); + renewNotes(channel, true); break; default: warning("ADLIB: Unknown event %02x", command); @@ -828,7 +826,7 @@ int MidiPlayer_AdLib::open(ResourceManager *resMan) { return -1; } - return static_cast<MidiDriver_AdLib *>(_driver)->open(_version <= SCI_VERSION_0_LATE); + return static_cast<MidiDriver_AdLib *>(_driver)->openAdLib(_version <= SCI_VERSION_0_LATE); } void MidiPlayer_AdLib::close() { diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp index 0ec4c283f7..d64dfac23c 100644 --- a/engines/sci/sound/drivers/amigamac.cpp +++ b/engines/sci/sound/drivers/amigamac.cpp @@ -34,7 +34,7 @@ namespace Sci { -/* #define DEBUG */ +//#define DEBUG class MidiDriver_AmigaMac : public MidiDriver_Emulated { public: @@ -289,9 +289,9 @@ void MidiDriver_AmigaMac::playInstrument(int16 *dest, Voice *channel, int count) void MidiDriver_AmigaMac::changeInstrument(int channel, int instrument) { #ifdef DEBUG if (_bank.instruments[instrument][0]) - debugN("[sfx:seq:amiga] Setting channel %i to \"%s\" (%i)\n", channel, _bank.instruments[instrument]->name, instrument); + debugN("Amiga/Mac driver: Setting channel %i to \"%s\" (%i)\n", channel, _bank.instruments[instrument].name, instrument); else - warning("[sfx:seq:amiga] instrument %i does not exist (channel %i)", instrument, channel); + warning("Amiga/Mac driver: instrument %i does not exist (channel %i)", instrument, channel); #endif _channels[channel].instrument = instrument; } @@ -326,7 +326,7 @@ void MidiDriver_AmigaMac::stopNote(int ch, int note) { if (channel == kChannels) { #ifdef DEBUG - warning("[sfx:seq:amiga] cannot stop note %i on channel %i", note, ch); + warning("Amiga/Mac driver: cannot stop note %i on channel %i", note, ch); #endif return; } @@ -366,7 +366,7 @@ void MidiDriver_AmigaMac::setOutputFrac(int voice) { fnote += instrument->transpose; if (fnote < 0 || fnote > 127) { - warning("[sfx:seq:amiga] illegal note %i", fnote); + warning("Amiga/Mac driver: illegal note %i", fnote); return; } } else @@ -403,14 +403,14 @@ void MidiDriver_AmigaMac::startNote(int ch, int note, int velocity) { int channel; if (_channels[ch].instrument < 0 || _channels[ch].instrument > 255) { - warning("[sfx:seq:amiga] invalid instrument %i on channel %i", _channels[ch].instrument, ch); + warning("Amiga/Mac driver: invalid instrument %i on channel %i", _channels[ch].instrument, ch); return; } InstrumentSample *instrument = findInstrument(_channels[ch].instrument, note); if (!instrument) { - warning("[sfx:seq:amiga] instrument %i does not exist", _channels[ch].instrument); + warning("Amiga/Mac driver: instrument %i does not exist", _channels[ch].instrument); return; } @@ -419,7 +419,7 @@ void MidiDriver_AmigaMac::startNote(int ch, int note, int velocity) { break; if (channel == kChannels) { - warning("[sfx:seq:amiga] could not find a free channel"); + warning("Amiga/Mac driver: could not find a free channel"); return; } @@ -447,14 +447,14 @@ MidiDriver_AmigaMac::InstrumentSample *MidiDriver_AmigaMac::readInstrumentSCI0(C byte header[61]; if (file.read(header, 61) < 61) { - warning("[sfx:seq:amiga] failed to read instrument header"); + warning("Amiga/Mac driver: failed to read instrument header"); return NULL; } int seg_size[3]; - seg_size[0] = READ_BE_UINT16(header + 35) * 2; - seg_size[1] = READ_BE_UINT16(header + 41) * 2; - seg_size[2] = READ_BE_UINT16(header + 47) * 2; + seg_size[0] = (int16)READ_BE_UINT16(header + 35) * 2; + seg_size[1] = (int16)READ_BE_UINT16(header + 41) * 2; + seg_size[2] = (int16)READ_BE_UINT16(header + 47) * 2; InstrumentSample *instrument = new InstrumentSample; @@ -489,18 +489,18 @@ MidiDriver_AmigaMac::InstrumentSample *MidiDriver_AmigaMac::readInstrumentSCI0(C instrument->name[29] = 0; #ifdef DEBUG - debugN("[sfx:seq:amiga] Reading instrument %i: \"%s\" (%i bytes)\n", + debugN("Amiga/Mac driver: Reading instrument %i: \"%s\" (%i bytes)\n", *id, instrument->name, size); debugN(" Mode: %02x\n", instrument->mode); debugN(" Looping: %s\n", instrument->mode & kModeLoop ? "on" : "off"); debugN(" Pitch changes: %s\n", instrument->mode & kModePitch ? "on" : "off"); debugN(" Segment sizes: %i %i %i\n", seg_size[0], seg_size[1], seg_size[2]); - debugN(" Segment offsets: 0 %i %i\n", loop_offset, read_int32(header + 43)); + debugN(" Segment offsets: 0 %i %i\n", loop_offset, (int32)READ_BE_UINT32(header + 43)); #endif instrument->samples = (int8 *) malloc(size + 1); if (file.read(instrument->samples, size) < (unsigned int)size) { - warning("[sfx:seq:amiga] failed to read instrument samples"); + warning("Amiga/Mac driver: failed to read instrument samples"); free(instrument->samples); delete instrument; return NULL; @@ -512,14 +512,14 @@ MidiDriver_AmigaMac::InstrumentSample *MidiDriver_AmigaMac::readInstrumentSCI0(C if (instrument->mode & kModeLoop) { if (loop_offset + seg_size[1] > size) { #ifdef DEBUG - warning("[sfx:seq:amiga] looping samples extend %i bytes past end of sample block", + warning("Amiga/Mac driver: looping samples extend %i bytes past end of sample block", loop_offset + seg_size[1] - size); #endif seg_size[1] = size - loop_offset; } if (seg_size[1] < 0) { - warning("[sfx:seq:amiga] invalid looping point"); + warning("Amiga/Mac driver: invalid looping point"); free(instrument->samples); delete instrument; return NULL; @@ -666,26 +666,42 @@ void MidiDriver_AmigaMac::send(uint32 b) { case 0x07: _channels[channel].volume = op2; break; - case 0x0a: + case 0x0a: // pan + // TODO #ifdef DEBUG - warning("[sfx:seq:amiga] ignoring pan 0x%02x event for channel %i", op2, channel); + warning("Amiga/Mac driver: ignoring pan 0x%02x event for channel %i", op2, channel); #endif break; + case 0x40: // hold + // TODO +#ifdef DEBUG + warning("Amiga/Mac driver: ignoring hold 0x%02x event for channel %i", op2, channel); +#endif + break; + case 0x4b: // voice mapping + break; + case 0x4e: // velocity + break; case 0x7b: stopChannel(channel); break; default: - warning("[sfx:seq:amiga] unknown control event 0x%02x", op1); + //warning("Amiga/Mac driver: unknown control event 0x%02x", op1); + break; } break; case 0xc0: changeInstrument(channel, op1); break; + // The original MIDI driver from sierra ignores aftertouch completely, so should we + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + break; case 0xe0: pitchWheel(channel, (op2 << 7) | op1); break; default: - warning("[sfx:seq:amiga] unknown event %02x", command); + warning("Amiga/Mac driver: unknown event %02x", command); } } @@ -738,7 +754,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0(Common::File &file) { byte header[40]; if (file.read(header, 40) < 40) { - warning("[sfx:seq:amiga] failed to read header of file bank.001"); + warning("Amiga/Mac driver: failed to read header of file bank.001"); return false; } @@ -746,7 +762,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0(Common::File &file) { strncpy(_bank.name, (char *) header + 8, 29); _bank.name[29] = 0; #ifdef DEBUG - debugN("[sfx:seq:amiga] Reading %i instruments from bank \"%s\"\n", _bank.size, _bank.name); + debugN("Amiga/Mac driver: Reading %i instruments from bank \"%s\"\n", _bank.size, _bank.name); #endif for (uint i = 0; i < _bank.size; i++) { @@ -754,12 +770,12 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0(Common::File &file) { InstrumentSample *instrument = readInstrumentSCI0(file, &id); if (!instrument) { - warning("[sfx:seq:amiga] failed to read bank.001"); + warning("Amiga/Mac driver: failed to read bank.001"); return false; } if (id < 0 || id > 255) { - warning("[sfx:seq:amiga] Error: instrument ID out of bounds"); + warning("Amiga/Mac driver: Error: instrument ID out of bounds"); return false; } @@ -777,7 +793,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0Mac(Common::SeekableReadStream &fil byte header[40]; if (file.read(header, 40) < 40) { - warning("[sfx:seq:amiga] failed to read header of file patch.200"); + warning("Amiga/Mac driver: failed to read header of file patch.200"); return false; } @@ -785,7 +801,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0Mac(Common::SeekableReadStream &fil strncpy(_bank.name, (char *) header + 8, 29); _bank.name[29] = 0; #ifdef DEBUG - debugN("[sfx:seq:amiga] Reading %i instruments from bank \"%s\"\n", _bank.size, _bank.name); + debugN("Amiga/Mac driver: Reading %i instruments from bank \"%s\"\n", _bank.size, _bank.name); #endif Common::Array<uint32> instrumentOffsets; @@ -848,7 +864,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0Mac(Common::SeekableReadStream &fil instrument->samples = (int8 *)malloc(size + 1); if (file.read(instrument->samples, size) < size) { - warning("[sfx:seq:amiga] failed to read instrument sample"); + warning("Amiga/Mac driver: failed to read instrument sample"); free(instrument->samples); delete instrument; continue; diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp index 08e93d3213..d16655928e 100644 --- a/engines/sci/sound/drivers/midi.cpp +++ b/engines/sci/sound/drivers/midi.cpp @@ -248,11 +248,17 @@ void MidiPlayer_Midi::controlChange(int channel, int control, int value) { _channels[channel].hold = value; break; + case 0x4b: // voice mapping + break; + case 0x4e: // velocity + break; case 0x7b: if (!_channels[channel].playing) return; _channels[channel].playing = false; + default: + break; } _driver->send(0xb0 | channel, control, value); @@ -604,41 +610,83 @@ void MidiPlayer_Midi::readMt32DrvData() { if (f.open("MT32.DRV")) { int size = f.size(); - assert(size >= 166); - - // Send before-SysEx text - f.seek(0x59); + // Skip before-SysEx text + if (size == 1773 || size == 1759) // XMAS88 / KQ4 early + f.seek(0x59); + else if (size == 2771) // LSL2 early + f.seek(0x29); + else + error("Unknown MT32.DRV size (%d)", size); // Skip 2 extra 0 bytes in some drivers if (f.readUint16LE() != 0) f.seek(-2, SEEK_CUR); + // Send before-SysEx text sendMt32SysEx(0x200000, static_cast<Common::SeekableReadStream *>(&f), 20); - // Send after-SysEx text (SSCI sends this before every song) - sendMt32SysEx(0x200000, static_cast<Common::SeekableReadStream *>(&f), 20); + if (size != 2271) { + // Send after-SysEx text (SSCI sends this before every song). + // There aren't any SysEx calls in old drivers, so this can + // be sent right after the before-SysEx text. + sendMt32SysEx(0x200000, static_cast<Common::SeekableReadStream *>(&f), 20); + } else { + // Skip the after-SysEx text in the newer patch version, we'll send + // it after the SysEx messages are sent. + f.skip(20); + } - // Save goodbye message + // Save goodbye message. This isn't a C string, so it may not be + // nul-terminated. f.read(_goodbyeMsg, 20); // Set volume byte volume = CLIP<uint16>(f.readUint16LE(), 0, 100); setMt32Volume(volume); - byte reverbSysEx[13]; - // This old driver should have a full reverb SysEx - if ((f.read(reverbSysEx, 13) != 13) || (reverbSysEx[0] != 0xf0) || (reverbSysEx[12] != 0xf7)) - error("Error reading MT32.DRV"); + if (size == 2771) { + // MT32.DRV in LSL2 early contains more data, like a normal patch + byte reverb = f.readByte(); - // Send reverb SysEx - sysEx(reverbSysEx + 1, 11); - _hasReverb = false; + _hasReverb = true; - f.seek(0x29); + // Skip reverb SysEx message + f.skip(11); - // Read AdLib->MT-32 patch map - for (int i = 0; i < 48; i++) { - _patchMap[i] = f.readByte(); + // Read reverb data (stored vertically - patch #3117434) + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < kReverbConfigNr; i++) { + _reverbConfig[i][j] = f.readByte(); + } + } + + f.skip(2235); // skip driver code + + // Patches 1-48 + sendMt32SysEx(0x50000, static_cast<Common::SeekableReadStream *>(&f), 256); + sendMt32SysEx(0x50200, static_cast<Common::SeekableReadStream *>(&f), 128); + + setReverb(reverb); + + // Send the after-SysEx text + f.seek(0x3d); + sendMt32SysEx(0x200000, static_cast<Common::SeekableReadStream *>(&f), 20); + } else { + byte reverbSysEx[13]; + // This old driver should have a full reverb SysEx + if ((f.read(reverbSysEx, 13) != 13) || (reverbSysEx[0] != 0xf0) || (reverbSysEx[12] != 0xf7)) + error("Error reading MT32.DRV"); + + // Send reverb SysEx + sysEx(reverbSysEx + 1, 11); + _hasReverb = false; + + f.seek(0x29); + + // Read AdLib->MT-32 patch map + for (int i = 0; i < 48; i++) { + _patchMap[i] = f.readByte(); + } } f.close(); @@ -913,7 +961,7 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { // TODO: The MT-32 <-> GM mapping hasn't been worked on for SCI1 games. Throw // a warning to the user - if (getSciVersion() >= SCI_VERSION_1_EGA) + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) warning("The automatic mapping for General MIDI hasn't been worked on for " "SCI1 games. Music might sound wrong or broken. Please choose another " "music driver for this game (e.g. Adlib or MT-32) if you are " diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index f0963e7d64..a028617e74 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -75,7 +75,7 @@ void SciMusic::init() { deviceFlags |= MDT_PREFER_GM; // Currently our CMS implementation only supports SCI1(.1) - if (getSciVersion() >= SCI_VERSION_1_EGA && getSciVersion() <= SCI_VERSION_1_1) + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY && getSciVersion() <= SCI_VERSION_1_1) deviceFlags |= MDT_CMS; uint32 dev = MidiDriver::detectDevice(deviceFlags); @@ -318,8 +318,21 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) { channelFilterMask = pSnd->soundRes->getChannelFilterMask(_pMidiDrv->getPlayId(), _pMidiDrv->hasRhythmChannel()); pSnd->pMidiParser->mainThreadBegin(); + // loadMusic() below calls jumpToTick. + // Disable sound looping and hold before jumpToTick is called, + // otherwise the song may keep looping forever when it ends in + // jumpToTick (e.g. LSL3, when going left from room 210). + uint16 prevLoop = pSnd->loop; + int16 prevHold = pSnd->hold; + pSnd->loop = 0; + pSnd->hold = -1; + pSnd->pMidiParser->loadMusic(track, pSnd, channelFilterMask, _soundVersion); pSnd->reverb = pSnd->pMidiParser->getSongReverb(); + + // Restore looping and hold + pSnd->loop = prevLoop; + pSnd->hold = prevHold; pSnd->pMidiParser->mainThreadEnd(); _mutex.unlock(); } @@ -434,18 +447,26 @@ void SciMusic::soundPlay(MusicEntry *pSnd) { if (pSnd->status != kSoundPaused) pSnd->pMidiParser->sendInitCommands(); pSnd->pMidiParser->setVolume(pSnd->volume); - if (pSnd->status == kSoundStopped) { + + // Disable sound looping and hold before jumpToTick is called, + // otherwise the song may keep looping forever when it ends in jumpToTick. + // This is needed when loading saved games, or when a game + // stops the same sound twice (e.g. LSL3 Amiga, going left from + // room 210 to talk with Kalalau). Fixes bugs #3083151 and #3106107. + uint16 prevLoop = pSnd->loop; + int16 prevHold = pSnd->hold; + pSnd->loop = 0; + pSnd->hold = -1; + + if (pSnd->status == kSoundStopped) pSnd->pMidiParser->jumpToTick(0); - } else { - // Disable sound looping before fast forwarding to the last position, - // when loading a saved game. Fixes bug #3083151. - uint16 prevLoop = pSnd->loop; - pSnd->loop = 0; + else // Fast forward to the last position and perform associated events when loading pSnd->pMidiParser->jumpToTick(pSnd->ticker, true, true, true); - // Restore looping - pSnd->loop = prevLoop; - } + + // Restore looping and hold + pSnd->loop = prevLoop; + pSnd->hold = prevHold; pSnd->pMidiParser->mainThreadEnd(); _mutex.unlock(); } diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index 45a3e09453..1e6d0aef87 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -445,8 +445,16 @@ void SoundCommandParser::processUpdateCues(reg_t obj) { if (musicSlot->fadeCompleted) { musicSlot->fadeCompleted = false; - // We need signal for sci0 at least in iceman as well (room 14, fireworks) - writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); + // We need signal for sci0 at least in iceman as well (room 14, + // fireworks). + // It is also needed in other games, e.g. LSL6 when talking to the + // receptionist (bug #3192166). + if (g_sci->getGameId() == GID_LONGBOW && g_sci->getEngineState()->currentRoomNumber() == 95) { + // HACK: Don't set a signal here in the intro of Longbow, as that makes some dialog + // boxes disappear too soon (bug #3044844). + } else { + writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); + } if (_soundVersion <= SCI_VERSION_0_LATE) { processStopSound(obj, false); } else { diff --git a/engines/sci/util.cpp b/engines/sci/util.cpp index f6a2465682..f346adddeb 100644 --- a/engines/sci/util.cpp +++ b/engines/sci/util.cpp @@ -30,25 +30,39 @@ namespace Sci { +uint16 READ_SCIENDIAN_UINT16(const void *ptr) { + if (g_sci->isBE()) + return READ_BE_UINT16(ptr); + else + return READ_LE_UINT16(ptr); +} + +void WRITE_SCIENDIAN_UINT16(void *ptr, uint16 val) { + if (g_sci->isBE()) + WRITE_BE_UINT16(ptr, val); + else + WRITE_LE_UINT16(ptr, val); +} + uint16 READ_SCI11ENDIAN_UINT16(const void *ptr) { if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1) return READ_BE_UINT16(ptr); - - return READ_LE_UINT16(ptr); + else + return READ_LE_UINT16(ptr); } uint16 READ_SCI32ENDIAN_UINT16(const void *ptr) { if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1) return READ_BE_UINT16(ptr); - - return READ_LE_UINT16(ptr); + else + return READ_LE_UINT16(ptr); } uint32 READ_SCI11ENDIAN_UINT32(const void *ptr) { if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1) return READ_BE_UINT32(ptr); - - return READ_LE_UINT32(ptr); + else + return READ_LE_UINT32(ptr); } void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val) { diff --git a/engines/sci/util.h b/engines/sci/util.h index d9ced5c9f6..7a2abb1873 100644 --- a/engines/sci/util.h +++ b/engines/sci/util.h @@ -30,6 +30,11 @@ namespace Sci { +// Wrappers for reading/writing 16-bit values in the endianness +// of the original game platform. +uint16 READ_SCIENDIAN_UINT16(const void *ptr); +void WRITE_SCIENDIAN_UINT16(void *ptr, uint16 val); + // Wrappers for reading integer values for SCI1.1+. // Mac versions have big endian data for some fields. uint16 READ_SCI11ENDIAN_UINT16(const void *ptr); diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp index bf52de67d5..ecdce3bd6b 100644 --- a/engines/sci/video/robot_decoder.cpp +++ b/engines/sci/video/robot_decoder.cpp @@ -251,6 +251,11 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() { _fileStream->skip(4); // unknown, almost always 0 uint16 frameX = _fileStream->readUint16(); uint16 frameY = _fileStream->readUint16(); + // TODO: In v4 robot files, frameX and frameY have a different meaning. + // Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up + // correctly. + if (_header.version == 4) + frameX = frameY = 0; uint16 compressedSize = _fileStream->readUint16(); uint16 frameFragments = _fileStream->readUint16(); _fileStream->skip(4); // unknown diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp index 3c506c0dae..8d1a9836f4 100644 --- a/engines/sword2/animation.cpp +++ b/engines/sword2/animation.cpp @@ -110,7 +110,7 @@ void MoviePlayer::play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadI terminated = !playVideo(); - closeTextObject(_currentMovieText, NULL); + closeTextObject(_currentMovieText, NULL, 0); if (terminated) { _snd->stopHandle(*_bgSoundHandle); @@ -165,7 +165,7 @@ void MoviePlayer::openTextObject(uint32 index) { } } -void MoviePlayer::closeTextObject(uint32 index, byte *screen) { +void MoviePlayer::closeTextObject(uint32 index, byte *screen, uint16 pitch) { if (index < _numMovieTexts) { MovieText *text = &_movieTexts[index]; @@ -182,20 +182,21 @@ void MoviePlayer::closeTextObject(uint32 index, byte *screen) { int frameHeight = _decoder->getHeight(); int frameX = (_system->getWidth() - frameWidth) / 2; int frameY = (_system->getHeight() - frameHeight) / 2; + byte black = findBlackPalIndex(); - byte *dst = screen + _textY * _system->getWidth(); + byte *dst = screen + _textY * pitch; for (int y = 0; y < text->_textSprite.h; y++) { if (_textY + y < frameY || _textY + y >= frameY + frameHeight) { - memset(dst + _textX, findBlackPalIndex(), text->_textSprite.w); + memset(dst + _textX, black, text->_textSprite.w); } else { if (frameX > _textX) - memset(dst + _textX, findBlackPalIndex(), frameX - _textX); + memset(dst + _textX, black, frameX - _textX); if (frameX + frameWidth < _textX + text->_textSprite.w) - memset(dst + frameX + frameWidth, findBlackPalIndex(), _textX + text->_textSprite.w - (frameX + frameWidth)); + memset(dst + frameX + frameWidth, black, _textX + text->_textSprite.w - (frameX + frameWidth)); } - dst += _system->getWidth(); + dst += pitch; } } @@ -205,7 +206,7 @@ void MoviePlayer::closeTextObject(uint32 index, byte *screen) { } } -void MoviePlayer::drawTextObject(uint32 index, byte *screen) { +void MoviePlayer::drawTextObject(uint32 index, byte *screen, uint16 pitch) { MovieText *text = &_movieTexts[index]; byte white = findWhitePalIndex(); @@ -217,14 +218,15 @@ void MoviePlayer::drawTextObject(uint32 index, byte *screen) { uint16 height = text->_textSprite.h; // Resize text sprites for PSX version + byte *psxSpriteBuffer = 0; if (Sword2Engine::isPsx()) { height *= 2; - byte *buffer = (byte *)malloc(width * height); - Screen::resizePsxSprite(buffer, src, width, height); - src = buffer; + psxSpriteBuffer = (byte *)malloc(width * height); + Screen::resizePsxSprite(psxSpriteBuffer, src, width, height); + src = psxSpriteBuffer; } - byte *dst = screen + _textY * RENDERWIDE + _textX; + byte *dst = screen + _textY * pitch + _textX; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { @@ -234,12 +236,16 @@ void MoviePlayer::drawTextObject(uint32 index, byte *screen) { dst[x] = white; } src += width; - dst += RENDERWIDE; + dst += pitch; } + + // Free buffer used to resize psx sprite + if (Sword2Engine::isPsx()) + free(psxSpriteBuffer); } } -void MoviePlayer::performPostProcessing(byte *screen) { +void MoviePlayer::performPostProcessing(byte *screen, uint16 pitch) { MovieText *text; int frame = _decoder->getCurFrame(); @@ -261,9 +267,9 @@ void MoviePlayer::performPostProcessing(byte *screen) { _vm->_sound->playCompSpeech(text->_speechId, 16, 0); } if (frame < text->_endFrame) { - drawTextObject(_currentMovieText, screen); + drawTextObject(_currentMovieText, screen, pitch); } else { - closeTextObject(_currentMovieText, screen); + closeTextObject(_currentMovieText, screen, pitch); _currentMovieText++; } } @@ -313,7 +319,7 @@ bool MoviePlayer::playVideo() { } Graphics::Surface *screen = _vm->_system->lockScreen(); - performPostProcessing((byte *)screen->pixels); + performPostProcessing((byte *)screen->pixels, screen->pitch); _vm->_system->unlockScreen(); _vm->_system->updateScreen(); } diff --git a/engines/sword2/animation.h b/engines/sword2/animation.h index 550ac0fac4..afe7dfcc68 100644 --- a/engines/sword2/animation.h +++ b/engines/sword2/animation.h @@ -96,12 +96,12 @@ protected: uint32 _leadOut; int _leadOutFrame; - void performPostProcessing(byte *screen); + void performPostProcessing(byte *screen, uint16 pitch); bool playVideo(); void openTextObject(uint32 index); - void closeTextObject(uint32 index, byte *screen); - void drawTextObject(uint32 index, byte *screen); + void closeTextObject(uint32 index, byte *screen, uint16 pitch); + void drawTextObject(uint32 index, byte *screen, uint16 pitch); byte findBlackPalIndex(); byte findWhitePalIndex(); diff --git a/engines/testbed/graphics.cpp b/engines/testbed/graphics.cpp index a0e2754fe4..3cdff5f432 100644 --- a/engines/testbed/graphics.cpp +++ b/engines/testbed/graphics.cpp @@ -229,16 +229,16 @@ void rotatePalette(byte *palette, int size) { // Rotate the colors starting from address palette "size" times // take a temporary palette color - byte tColor[4] = {0}; + byte tColor[3] = {0}; // save first color in it. - memcpy(tColor, &palette[0], 4 * sizeof(byte)); + memcpy(tColor, &palette[0], 3 * sizeof(byte)); // Move each color upward by 1 for (int i = 0; i < size - 1; i++) { - memcpy(&palette[i * 4], &palette[(i + 1) * 4], 4 * sizeof(byte)); + memcpy(&palette[i * 3], &palette[(i + 1) * 3], 3 * sizeof(byte)); } // Assign last color to tcolor - memcpy(&palette[(size - 1) * 4], tColor, 4 * sizeof(byte)); + memcpy(&palette[(size - 1) * 3], tColor, 3 * sizeof(byte)); } /** diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index 29d4dbc92d..7461cfca72 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -115,26 +115,6 @@ static const int enhancedAudioSCNVersion[] = { 97, 98, 99, 99 // 151-154 }; -// TODO. This mapping is wrong -static const int enhancedAudioSCNVersionALT[] = { - 301, 302, 2, 1, 1, 301, 302, 3, 3, 4, // 1-10 - 4, 5, 6, 1, 7, 8, 9, 10, 8, 11, // 11-20 - 11, 12, 13, 13, 13, 13, 13, 14, 13, 13, // 21-30 - 15, 16, 17, 15, 18, 19, 20, 338, 21, 21, // 31-40 - 341, 342, 22, 22, 23, 24, 25, 26, 27, 28, // 41-50 - 29, 30, 31, 32, 33, 34, 35, 35, 36, 37, // 51-60 - 38, 39, 39, 39, 39, 40, 39, 41, 41, 42, // 61-70 - 43, 42, 44, 45, 41, 46, 48, 47, 48, 49, // 71-80 - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, // 81-90 - 60, 61, 62, 63, 61, 64, 65, 66, 67, 68, // 91-100 - 69, 70, 68, 71, 72, 73, 74, 75, 12, 76, // 101-110 - 77, 78, 79, 80, 4, 4, 82, 83, 77, 4, // 111-120 - 84, 85, 86, 3124, 88, 89, 90, 88, 2, 2, // 121-130 - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 131-140 - 3142, 91, 92, 93, 94, 94, 95, 96, 52, 4, // 141-150 - 97, 98, 99 // 151-153 -}; - int GetTrackNumber(SCNHANDLE hMidi) { for (int i = 0; i < ARRAYSIZE(midiOffsets); i++) if (midiOffsets[i] == hMidi) @@ -179,11 +159,13 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { // Support for external music from the music enhancement project if (_vm->getFeatures() & GF_ENHANCED_AUDIO_SUPPORT) { int trackNumber = GetTrackNumber(dwFileOffset); + // Track 8 has been removed in the German CD re-release "Neon Edition" + if ((_vm->getFeatures() & GF_ALT_MIDI) && trackNumber >= 8) + trackNumber++; + int track = 0; if (trackNumber >= 0) { - if (_vm->getFeatures() & GF_ALT_MIDI) - track = enhancedAudioSCNVersionALT[trackNumber]; - else if (_vm->getFeatures() & GF_SCNFILES) + if (_vm->getFeatures() & GF_SCNFILES) track = enhancedAudioSCNVersion[trackNumber]; else track = enhancedAudioGRAVersion[trackNumber]; diff --git a/engines/toon/movie.cpp b/engines/toon/movie.cpp index c6b57d96e2..bf4b6639fa 100644 --- a/engines/toon/movie.cpp +++ b/engines/toon/movie.cpp @@ -38,8 +38,8 @@ void ToonstruckSmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, ui Video::SmackerDecoder::handleAudioTrack(track, chunkSize, unpackedSize); } -bool ToonstruckSmackerDecoder::loadFile(const Common::String &filename, int forcedflags) { - debugC(1, kDebugMovie, "loadFile(%s, %d)", filename.c_str(), forcedflags); +bool ToonstruckSmackerDecoder::loadFile(const Common::String &filename) { + debugC(1, kDebugMovie, "loadFile(%s)", filename.c_str()); _lowRes = false; @@ -88,7 +88,7 @@ void Movie::play(Common::String video, int32 flags) { _playing = true; if (flags & 1) _vm->getAudioManager()->setMusicVolume(0); - _decoder->loadFile(video.c_str(), flags); + _decoder->loadFile(video.c_str()); playVideo(isFirstIntroVideo); _vm->flushPalette(false); if (flags & 1) diff --git a/engines/toon/movie.h b/engines/toon/movie.h index 2a9173850f..bed2ceceae 100644 --- a/engines/toon/movie.h +++ b/engines/toon/movie.h @@ -36,7 +36,7 @@ public: ToonstruckSmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType); virtual ~ToonstruckSmackerDecoder() {} void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize); - bool loadFile(const Common::String &filename, int forcedflags); + bool loadFile(const Common::String &filename); bool isLowRes() { return _lowRes; } protected: bool _lowRes; diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp index ee83ca620c..026370abf1 100644 --- a/gui/ThemeEngine.cpp +++ b/gui/ThemeEngine.cpp @@ -1193,7 +1193,7 @@ void ThemeEngine::debugWidgetPosition(const char *name, const Common::Rect &r) { /********************************************************** * Screen/overlay management *********************************************************/ -void ThemeEngine::updateScreen() { +void ThemeEngine::updateScreen(bool render) { if (!_bufferQueue.empty()) { _vectorRenderer->setSurface(&_backBuffer); @@ -1218,7 +1218,8 @@ void ThemeEngine::updateScreen() { _screenQueue.clear(); } - renderDirtyScreen(); + if (render) + renderDirtyScreen(); } void ThemeEngine::addDirtyRect(Common::Rect r) { diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h index e852760e44..78ea06f3b6 100644 --- a/gui/ThemeEngine.h +++ b/gui/ThemeEngine.h @@ -32,7 +32,7 @@ #include "graphics/surface.h" #include "graphics/font.h" -#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.2" +#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.3" namespace Graphics { struct DrawStep; @@ -280,7 +280,7 @@ public: * It processes all the drawing queues and then copies dirty rects * in the current Screen surface to the overlay. */ - void updateScreen(); + void updateScreen(bool render = true); /** @name FONT MANAGEMENT METHODS */ diff --git a/gui/ThemeParser.cpp b/gui/ThemeParser.cpp index 70d81a962e..e3523d11e8 100644 --- a/gui/ThemeParser.cpp +++ b/gui/ThemeParser.cpp @@ -861,30 +861,50 @@ bool ThemeParser::resolutionCheck(const Common::String &resolution) { return true; Common::StringTokenizer globTokenizer(resolution, ", "); - Common::String cur, w, h; - bool definedRes = false; + Common::String cur; while (!globTokenizer.empty()) { - bool ignore = false; cur = globTokenizer.nextToken(); - if (cur[0] == '-') { - ignore = true; - cur.deleteChar(0); + bool lt; + int val; + + if (cur.size() < 5) { + warning("Invalid theme 'resolution' token '%s'", resolution.c_str()); + return false; + } + + if (cur[0] == 'x') { + val = g_system->getOverlayWidth(); + } else if (cur[0] == 'y') { + val = g_system->getOverlayHeight(); + } else { + warning("Error parsing theme 'resolution' token '%s'", resolution.c_str()); + return false; + } + + if (cur[1] == '<') { + lt = true; + } else if (cur[1] == '>') { + lt = false; } else { - definedRes = true; + warning("Error parsing theme 'resolution' token '%s'", resolution.c_str()); + return false; } - Common::StringTokenizer resTokenizer(cur, "x"); - w = resTokenizer.nextToken(); - h = resTokenizer.nextToken(); + int token = atoi(cur.c_str() + 2); - if ((w == "X" || atoi(w.c_str()) == g_system->getOverlayWidth()) && - (h == "Y" || atoi(h.c_str()) == g_system->getOverlayHeight())) - return !ignore; + // check inverse for unfulfilled requirements + if (lt) { + if (val >= token) + return false; + } else { + if (val <= token) + return false; + } } - return !definedRes; + return true; } } // End of namespace GUI diff --git a/gui/credits.h b/gui/credits.h index 926adf4c9c..a75af3c0dd 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -456,7 +456,7 @@ static const char *credits[] = { "C0""Martin Doucha", "C2""CinE engine objectification", "C0""Thomas Fach-Pedersen", -"C2""ProTracker module player", +"C2""ProTracker module player, Smacker video decoder", "C0""Tobias Gunkel", "C2""Sound support for C64 version of MM/Zak, Loom PCE support", "C0""Janne Huttunen", diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp index 7644cbe7b2..3ad4b2ee18 100644 --- a/gui/gui-manager.cpp +++ b/gui/gui-manager.cpp @@ -201,14 +201,15 @@ void GuiManager::redraw() { _theme->clearAll(); _theme->openDialog(true, ThemeEngine::kShadingNone); - for (i = 0; i < _dialogStack.size() - 1; i++) { + for (i = 0; i < _dialogStack.size() - 1; i++) _dialogStack[i]->drawDialog(); - } _theme->finishBuffering(); + // fall through + case kRedrawOpenDialog: - _theme->updateScreen(); + _theme->updateScreen(false); _theme->openDialog(true, shading); _dialogStack.top()->drawDialog(); _theme->finishBuffering(); diff --git a/gui/themes/default.inc b/gui/themes/default.inc index 3236071055..2716e6ca72 100644 --- a/gui/themes/default.inc +++ b/gui/themes/default.inc @@ -1,5 +1,5 @@ "<?xml version = '1.0'?>" -"<layout_info resolution='-320xY,-256x240,-Xx272,-544x332,-Xx350'> " +"<layout_info resolution='y>399'> " "<globals> " "<def var='Line.Height' value='16' /> " "<def var='Font.Height' value='16' /> " @@ -789,7 +789,7 @@ "</layout> " "</dialog> " "</layout_info> " -"<layout_info resolution='320xY,256x240,Xx272,544x332,Xx350'> " +"<layout_info resolution='y<400'> " "<globals> " "<def var='Line.Height' value='12' /> " "<def var='Font.Height' value='10' /> " @@ -1602,21 +1602,21 @@ "<font id='text_default' " "file='helvb12.bdf' " "/> " -"<font resolution='320xY,256x240' " +"<font resolution='y<400' " "id='text_default' " "file='clR6x12.bdf' " "/> " "<font id='text_button' " "file='helvb12.bdf' " "/> " -"<font resolution='320xY,256x240' " +"<font resolution='y<400' " "id='text_button' " "file='clR6x12.bdf' " "/> " "<font id='text_normal' " "file='helvb12.bdf' " "/> " -"<font resolution='320xY,256x240' " +"<font resolution='y<400' " "id='text_normal' " "file='clR6x12.bdf' " "/> " diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip Binary files differindex 9fd2c187fd..4dbedd4f14 100644 --- a/gui/themes/scummclassic.zip +++ b/gui/themes/scummclassic.zip diff --git a/gui/themes/scummclassic/THEMERC b/gui/themes/scummclassic/THEMERC index f0276969fe..17e934d5ef 100644 --- a/gui/themes/scummclassic/THEMERC +++ b/gui/themes/scummclassic/THEMERC @@ -1 +1 @@ -[SCUMMVM_STX0.8.2:ScummVM Classic Theme:No Author] +[SCUMMVM_STX0.8.3:ScummVM Classic Theme:No Author] diff --git a/gui/themes/scummclassic/classic_gfx.stx b/gui/themes/scummclassic/classic_gfx.stx index d672db2540..3fd00abbb9 100644 --- a/gui/themes/scummclassic/classic_gfx.stx +++ b/gui/themes/scummclassic/classic_gfx.stx @@ -46,21 +46,21 @@ <font id = 'text_default' file = 'helvb12.bdf' /> - <font resolution = '320xY, 256x240' + <font resolution = 'y<400' id = 'text_default' file = 'clR6x12.bdf' /> <font id = 'text_button' file = 'helvb12.bdf' /> - <font resolution = '320xY, 256x240' + <font resolution = 'y<400' id = 'text_button' file = 'clR6x12.bdf' /> <font id = 'text_normal' file = 'helvb12.bdf' /> - <font resolution = '320xY, 256x240' + <font resolution = 'y<400' id = 'text_normal' file = 'clR6x12.bdf' /> diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx index 416ffb30eb..f09c29e360 100644 --- a/gui/themes/scummclassic/classic_layout.stx +++ b/gui/themes/scummclassic/classic_layout.stx @@ -23,7 +23,7 @@ - $Id$ - --> -<layout_info resolution = '-320xY, -256x240, -Xx272, -544x332, -Xx350'> +<layout_info resolution = 'y>399'> <globals> <def var = 'Line.Height' value = '16' /> <def var = 'Font.Height' value = '16' /> diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx index fe0eb66b8d..a440be7694 100644 --- a/gui/themes/scummclassic/classic_layout_lowres.stx +++ b/gui/themes/scummclassic/classic_layout_lowres.stx @@ -23,7 +23,7 @@ - $Id$ - --> -<layout_info resolution = "320xY, 256x240, Xx272, 544x332, Xx350"> +<layout_info resolution = 'y<400'> <globals> <def var = 'Line.Height' value = '12' /> <def var = 'Font.Height' value = '10' /> diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip Binary files differindex f4e18ef00c..77951475e6 100644 --- a/gui/themes/scummmodern.zip +++ b/gui/themes/scummmodern.zip diff --git a/gui/themes/scummmodern/THEMERC b/gui/themes/scummmodern/THEMERC index b8f41fc207..f947a5685a 100644 --- a/gui/themes/scummmodern/THEMERC +++ b/gui/themes/scummmodern/THEMERC @@ -1 +1 @@ -[SCUMMVM_STX0.8.2:ScummVM Modern Theme:No Author] +[SCUMMVM_STX0.8.3:ScummVM Modern Theme:No Author] diff --git a/gui/themes/scummmodern/scummmodern_gfx.stx b/gui/themes/scummmodern/scummmodern_gfx.stx index cfe00a7016..a325d4982b 100644 --- a/gui/themes/scummmodern/scummmodern_gfx.stx +++ b/gui/themes/scummmodern/scummmodern_gfx.stx @@ -108,21 +108,21 @@ <font id = 'text_default' file = 'helvb12.bdf' /> - <font resolution = '320xY, 256x240' + <font resolution = 'y<400' id = 'text_default' file = 'clR6x12.bdf' /> <font id = 'text_button' file = 'helvb12.bdf' /> - <font resolution = '320xY, 256x240' + <font resolution = 'y<400' id = 'text_button' file = 'clR6x12.bdf' /> <font id = 'text_normal' file = 'helvb12.bdf' /> - <font resolution = '320xY, 256x240' + <font resolution = 'y<400' id = 'text_normal' file = 'clR6x12.bdf' /> @@ -178,7 +178,7 @@ <!-- <defaults fill = 'gradient' fg_color = 'white'/> --> <cursor file = 'cursor.bmp' hotspot = '0, 0' scale = '3'/> - <cursor resolution = '320xY, 256x240' file = 'cursor_small.bmp' hotspot = '0, 0' scale = '3'/> + <cursor resolution = 'y<400' file = 'cursor_small.bmp' hotspot = '0, 0' scale = '3'/> <!-- Selection (text or list items) --> <drawdata id = 'text_selection' cache = 'false'> diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx index 879be2aafe..32d6d19d1a 100644 --- a/gui/themes/scummmodern/scummmodern_layout.stx +++ b/gui/themes/scummmodern/scummmodern_layout.stx @@ -23,7 +23,7 @@ - $Id$ - --> -<layout_info resolution = '-320xY, -256x240, -Xx272, -544x332, -Xx350'> +<layout_info resolution = 'y>399'> <globals> <def var = 'Line.Height' value = '16' /> <def var = 'Font.Height' value = '16' /> diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx index 3a7a4b63b9..06916a80f1 100644 --- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx +++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx @@ -23,7 +23,7 @@ - $Id$ - --> -<layout_info resolution = "320xY, 256x240, Xx272, 544x332, Xx350"> +<layout_info resolution = 'y<400'> <globals> <def var = 'Line.Height' value = '12' /> <def var = 'Font.Height' value = '10' /> diff --git a/tools/credits.pl b/tools/credits.pl index b72d38bcd1..0d5b3326e6 100755 --- a/tools/credits.pl +++ b/tools/credits.pl @@ -958,7 +958,7 @@ begin_credits("Credits"); add_person("Stuart Caie", "", "Decoders for Amiga and AtariST data files (AGOS engine)"); add_person("Paolo Costabel", "", "PSP port contributions"); add_person("Martin Doucha", "next_ghost", "CinE engine objectification"); - add_person("Thomas Fach-Pedersen", "madmoose", "ProTracker module player"); + add_person("Thomas Fach-Pedersen", "madmoose", "ProTracker module player, Smacker video decoder"); add_person("Tobias Gunkel", "hennymcc", "Sound support for C64 version of MM/Zak, Loom PCE support"); add_person("Janne Huttunen", "", "V3 actor mask support, Dig/FT SMUSH audio"); add_person("Kovács Endre János", "", "Several fixes for Simon1"); |