diff options
Diffstat (limited to 'engines/sherlock/scalpel/tsage')
-rw-r--r-- | engines/sherlock/scalpel/tsage/logo.cpp | 684 | ||||
-rw-r--r-- | engines/sherlock/scalpel/tsage/logo.h | 250 | ||||
-rw-r--r-- | engines/sherlock/scalpel/tsage/resources.cpp | 373 | ||||
-rw-r--r-- | engines/sherlock/scalpel/tsage/resources.h | 139 |
4 files changed, 1446 insertions, 0 deletions
diff --git a/engines/sherlock/scalpel/tsage/logo.cpp b/engines/sherlock/scalpel/tsage/logo.cpp new file mode 100644 index 0000000000..4eab01947a --- /dev/null +++ b/engines/sherlock/scalpel/tsage/logo.cpp @@ -0,0 +1,684 @@ +/* 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. + * + */ + +#include "common/scummsys.h" +#include "sherlock/scalpel/tsage/logo.h" +#include "sherlock/scalpel/scalpel.h" + +namespace Sherlock { +namespace Scalpel { +namespace TsAGE { + +TLib *Visage::_tLib; + +Visage::Visage() { + _resNum = -1; + _rlbNum = -1; + _stream = nullptr; +} + +void Visage::setVisage(int resNum, int rlbNum) { + if ((_resNum != resNum) || (_rlbNum != rlbNum)) { + _resNum = resNum; + _rlbNum = rlbNum; + delete _stream; + + // Games after Ringworld have an extra indirection via the visage index file + Common::SeekableReadStream *stream = _tLib->getResource(RES_VISAGE, resNum, 9999); + if (rlbNum == 0) + rlbNum = 1; + + // Check how many slots there are + uint16 count = stream->readUint16LE(); + if (rlbNum > count) + rlbNum = count; + + // Get the flags/rlbNum to use + stream->seek((rlbNum - 1) * 4 + 2); + uint32 v = stream->readUint32LE(); + int flags = v >> 30; + + if (flags & 3) { + rlbNum = (int)(v & 0xff); + } + assert((flags & 3) == 0); + delete stream; + + _stream = _tLib->getResource(RES_VISAGE, resNum, rlbNum); + } +} + +void Visage::clear() { + delete _stream; + _stream = nullptr; +} + +Visage::~Visage() { + delete _stream; +} + +void Visage::getFrame(ObjectSurface &s, int frameNum) { + _stream->seek(0); + int numFrames = _stream->readUint16LE(); + if (frameNum > numFrames) + frameNum = numFrames; + if (frameNum > 0) + --frameNum; + + _stream->seek(frameNum * 4 + 2); + int offset = _stream->readUint32LE(); + _stream->seek(offset); + + surfaceFromRes(s); +} + +int Visage::getFrameCount() const { + _stream->seek(0); + return _stream->readUint16LE(); +} + +bool Visage::isLoaded() const { + return _stream != nullptr; +} + +void Visage::surfaceFromRes(ObjectSurface &s) { + int frameWidth = _stream->readUint16LE(); + int frameHeight = _stream->readUint16LE(); + Common::Rect r(0, 0, frameWidth, frameHeight); + s.create(r.width(), r.height()); + + s._centroid.x = _stream->readSint16LE(); + s._centroid.y = _stream->readSint16LE(); + + _stream->skip(1); + byte flags = _stream->readByte(); + bool rleEncoded = (flags & 2) != 0; + + byte *destP = (byte *)s.getPixels(); + + if (!rleEncoded) { + _stream->read(destP, r.width() * r.height()); + } else { + Common::fill(destP, destP + (r.width() * r.height()), 0xff); + + for (int yp = 0; yp < r.height(); ++yp) { + int width = r.width(); + destP = (byte *)s.getBasePtr(0, yp); + + while (width > 0) { + uint8 controlVal = _stream->readByte(); + if ((controlVal & 0x80) == 0) { + // Copy specified number of bytes + _stream->read(destP, controlVal); + width -= controlVal; + destP += controlVal; + } else if ((controlVal & 0x40) == 0) { + // Skip a specified number of output pixels + destP += controlVal & 0x3f; + width -= controlVal & 0x3f; + } else { + // Copy a specified pixel a given number of times + controlVal &= 0x3f; + int pixel = _stream->readByte(); + + Common::fill(destP, destP + controlVal, pixel); + destP += controlVal; + width -= controlVal; + } + } + assert(width == 0); + } + } +} + +/*--------------------------------------------------------------------------*/ + +ScalpelEngine *Object::_vm; + +Object::Object() { + _vm = nullptr; + _isAnimating = _finished = false; + _frame = 0; + _numFrames = 0; + _frameChange = 0; + _angle = _changeCtr = 0; + _walkStartFrame = 0; + _majorDiff = _minorDiff = 0; +} + +void Object::setVisage(int visage, int strip) { + _visage.setVisage(visage, strip); +} + +void Object::setAnimMode(bool isAnimating) { + _isAnimating = isAnimating; + _finished = false; + + _updateStartFrame = _vm->_events->getFrameCounter(); + if (_numFrames) + _updateStartFrame += 60 / _numFrames; + _frameChange = 1; +} + +void Object::setDestination(const Common::Point &pt) { + _destination = pt; + + int moveRate = 10; + _walkStartFrame = _vm->_events->getFrameCounter(); + _walkStartFrame += 60 / moveRate; + + calculateMoveAngle(); + + // Get the difference + int diffX = _destination.x - _position.x; + int diffY = _destination.y - _position.y; + int xSign = (diffX < 0) ? -1 : (diffX > 0 ? 1 : 0); + int ySign = (diffY < 0) ? -1 : (diffY > 0 ? 1 : 0); + diffX = ABS(diffX); + diffY = ABS(diffY); + + if (diffX < diffY) { + _minorDiff = diffX / 2; + _majorDiff = diffY; + } else { + _minorDiff = diffY / 2; + _majorDiff = diffX; + } + + // Set the destination position + _moveDelta = Common::Point(diffX, diffY); + _moveSign = Common::Point(xSign, ySign); + _changeCtr = 0; + + assert(diffX || diffY); +} + +void Object::erase() { + Screen &screen = *_vm->_screen; + + if (_visage.isLoaded() && !_oldBounds.isEmpty()) + screen.blitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); +} + +void Object::update() { + Screen &screen = *_vm->_screen; + + if (_visage.isLoaded()) { + if (isMoving()) { + uint32 currTime = _vm->_events->getFrameCounter(); + if (_walkStartFrame <= currTime) { + int moveRate = 10; + int frameInc = 60 / moveRate; + _walkStartFrame = currTime + frameInc; + move(); + } + } + + if (_isAnimating) { + if (_frame < _visage.getFrameCount()) + _frame = changeFrame(); + else + _finished = true; + } + + // Get the new frame + ObjectSurface s; + _visage.getFrame(s, _frame); + + // Display the frame + _oldBounds = Common::Rect(_position.x, _position.y, _position.x + s.w(), _position.y + s.h()); + _oldBounds.translate(-s._centroid.x, -s._centroid.y); + screen.transBlitFrom(s, Common::Point(_oldBounds.left, _oldBounds.top)); + } +} + +int Object::changeFrame() { + int frameNum = _frame; + uint32 currentFrame = _vm->_events->getFrameCounter(); + + if (_updateStartFrame <= currentFrame) { + if (_numFrames > 0) { + int v = 60 / _numFrames; + _updateStartFrame = currentFrame + v; + + frameNum = getNewFrame(); + } + } + + return frameNum; +} + +int Object::getNewFrame() { + int frameNum = _frame + _frameChange; + + if (_frameChange > 0) { + if (frameNum > _visage.getFrameCount()) { + frameNum = 1; + } + } else if (frameNum < 1) { + frameNum = _visage.getFrameCount(); + } + + return frameNum; +} + +void Object::calculateMoveAngle() { + int xDiff = _destination.x - _position.x, yDiff = _position.y - _destination.y; + + if (!xDiff && !yDiff) + _angle = 0; + else if (!xDiff) + _angle = (_destination.y >= _position.y) ? 180 : 0; + else if (!yDiff) + _angle = (_destination.x >= _position.x) ? 90 : 270; + else { + int result = (((xDiff * 100) / ((abs(xDiff) + abs(yDiff))) * 90) / 100); + + if (yDiff < 0) + result = 180 - result; + else if (xDiff < 0) + result += 360; + + _angle = result; + } +} + +bool Object::isAnimEnded() const { + return _finished; +} + +bool Object::isMoving() const { + return (_destination.x != 0) && (_destination != _position); +} + +void Object::move() { + Common::Point currPos = _position; + Common::Point moveDiff(5, 3); + int percent = 100; + + if (dontMove()) + return; + + if (_moveDelta.x >= _moveDelta.y) { + int xAmount = _moveSign.x * moveDiff.x * percent / 100; + if (!xAmount) + xAmount = _moveSign.x; + currPos.x += xAmount; + + int yAmount = ABS(_destination.y - currPos.y); + int yChange = _majorDiff / ABS(xAmount); + int ySign; + + if (!yChange) + ySign = _moveSign.y; + else { + int v = yAmount / yChange; + _changeCtr += yAmount % yChange; + if (_changeCtr >= yChange) { + ++v; + _changeCtr -= yChange; + } + + ySign = _moveSign.y * v; + } + + currPos.y += ySign; + _majorDiff -= ABS(xAmount); + } else { + int yAmount = _moveSign.y * moveDiff.y * percent / 100; + if (!yAmount) + yAmount = _moveSign.y; + currPos.y += yAmount; + + int xAmount = ABS(_destination.x - currPos.x); + int xChange = _majorDiff / ABS(yAmount); + int xSign; + + if (!xChange) + xSign = _moveSign.x; + else { + int v = xAmount / xChange; + _changeCtr += xAmount % xChange; + if (_changeCtr >= xChange) { + ++v; + _changeCtr -= xChange; + } + + xSign = _moveSign.x * v; + } + + currPos.x += xSign; + _majorDiff -= ABS(yAmount); + } + + _position = currPos; + if (dontMove()) + _position = _destination; +} + +bool Object::dontMove() const { + return (_majorDiff <= 0); +} + +void Object::endMove() { + _position = _destination; +} + +/*----------------------------------------------------------------*/ + +bool Logo::show(ScalpelEngine *vm) { + Events &events = *vm->_events; + Logo *logo = new Logo(vm); + bool interrupted = false; + + while (!logo->finished()) { + logo->nextFrame(); + + // Erase areas from previous frame, and update and re-draw objects + for (int idx = 0; idx < 4; ++idx) + logo->_objects[idx].erase(); + for (int idx = 0; idx < 4; ++idx) + logo->_objects[idx].update(); + + events.wait(2); + events.setButtonState(); + + interrupted = vm->shouldQuit() || events.kbHit() || events._pressed; + if (interrupted) { + // Keyboard or mouse button pressed, so break out of logo display + events.clearEvents(); + break; + } + } + + delete logo; + return !interrupted; +} + +Logo::Logo(ScalpelEngine *vm) : _vm(vm), _lib("sf3.rlb") { + Object::_vm = vm; + Visage::_tLib = &_lib; + + _finished = false; + + // Initialize counter + _counter = 0; + + // Initialize wait frame counters + _waitFrames = 0; + _waitStartFrame = 0; + + // Initialize animation counters + _animateObject = 0; + _animateStartFrame = 0; + _animateFrameDelay = 0; + _animateFrames = NULL; + _animateFrame = 0; + + // Save a copy of the original palette + _vm->_screen->getPalette(_originalPalette); + + // Set up the palettes + Common::fill(&_palette1[0], &_palette1[PALETTE_SIZE], 0); + Common::fill(&_palette1[0], &_palette2[PALETTE_SIZE], 0); + Common::fill(&_palette1[0], &_palette3[PALETTE_SIZE], 0); + + _lib.getPalette(_palette1, 1111); + _lib.getPalette(_palette1, 10); + _lib.getPalette(_palette2, 1111); + _lib.getPalette(_palette2, 1); + _lib.getPalette(_palette3, 1111); + _lib.getPalette(_palette3, 14); +} + +Logo::~Logo() { + // Restore the original palette + _vm->_screen->setPalette(_originalPalette); +} + +bool Logo::finished() const { + return _finished; +} + +const AnimationFrame handFrames[] = { + { 1, 33, 91 }, { 2, 44, 124 }, { 3, 64, 153 }, { 4, 87, 174 }, + { 5, 114, 191 }, { 6, 125, 184 }, { 7, 154, 187 }, { 8, 181, 182 }, + { 9, 191, 167 }, { 10, 190, 150 }, { 11, 182, 139 }, { 11, 170, 130 }, + { 11, 158, 121 }, { 0, 0, 0 } +}; + +const AnimationFrame companyFrames[] = { + { 1, 155, 94 }, { 2, 155, 94 }, { 3, 155, 94 }, { 4, 155, 94 }, + { 5, 155, 94 }, { 6, 155, 94 }, { 7, 155, 94 }, { 8, 155, 94 }, + { 0, 0, 0 } +}; + +void Logo::nextFrame() { + Screen &screen = *_vm->_screen; + + if (_waitFrames) { + uint32 currFrame = _vm->_events->getFrameCounter(); + if (currFrame - _waitStartFrame < _waitFrames) { + return; + } + _waitStartFrame = 0; + _waitFrames = 0; + } + + if (_animateFrames) { + uint32 currFrame = _vm->_events->getFrameCounter(); + if (currFrame > _animateStartFrame + _animateFrameDelay) { + AnimationFrame animationFrame = _animateFrames[_animateFrame]; + if (animationFrame.frame) { + _objects[_animateObject]._frame = animationFrame.frame; + _objects[_animateObject]._position = Common::Point(animationFrame.x, animationFrame.y); + _animateStartFrame += _animateFrameDelay; + _animateFrame++; + } else { + _animateObject = 0; + _animateFrameDelay = 0; + _animateFrames = NULL; + _animateStartFrame = 0; + _animateFrame = 0; + } + } + if (_animateFrames) + return; + } + + switch (_counter++) { + case 0: + // Load the background and fade it in + loadBackground(); + fade(_palette1); + break; + + case 1: + // First half of square, circle, and triangle arranging themselves + _objects[0].setVisage(16, 1); + _objects[0]._frame = 1; + _objects[0]._position = Common::Point(169, 107); + _objects[0]._numFrames = 7; + _objects[0].setAnimMode(true); + break; + + case 2: + // Keep waiting until first animation ends + if (!_objects[0].isAnimEnded()) { + --_counter; + } else { + // Start second half of the shapes animation + _objects[0].setVisage(16, 2); + _objects[0]._frame = 1; + _objects[0]._numFrames = 11; + _objects[0].setAnimMode(true); + } + break; + + case 3: + // Keep waiting until second animation of shapes ordering themselves ends + if (!_objects[0].isAnimEnded()) { + --_counter; + } else { + // Fade out the background but keep the shapes visible + fade(_palette2); + screen._backBuffer1.clear(); + } + waitFrames(10); + break; + + case 4: + // Load the new palette + byte palette[PALETTE_SIZE]; + Common::copy(&_palette2[0], &_palette2[PALETTE_SIZE], &palette[0]); + _lib.getPalette(palette, 12); + screen.clear(); + screen.setPalette(palette); + + // Morph into the EA logo + _objects[0].setVisage(12, 1); + _objects[0]._frame = 1; + _objects[0]._numFrames = 7; + _objects[0].setAnimMode(true); + _objects[0]._position = Common::Point(170, 142); + _objects[0].setDestination(Common::Point(158, 71)); + break; + + case 5: + // Wait until the logo has expanded upwards to form EA logo + if (_objects[0].isMoving()) + --_counter; + break; + + case 6: + fade(_palette3, 40); + break; + + case 7: + // Show the 'Electronic Arts' company name + _objects[1].setVisage(14, 1); + _objects[1]._frame = 1; + _objects[1]._position = Common::Point(152, 98); + waitFrames(120); + break; + + case 8: + // Start sequence of positioning and size hand cursor in an arc + _objects[2].setVisage(18, 1); + startAnimation(2, 5, &handFrames[0]); + break; + + case 9: + // Show a highlighting of the company name + _objects[1].remove(); + _objects[2].erase(); + _objects[2].remove(); + _objects[3].setVisage(19, 1); + startAnimation(3, 8, &companyFrames[0]); + break; + + case 10: + waitFrames(180); + break; + + case 11: + _finished = true; + break; + + default: + break; + } +} + +void Logo::waitFrames(uint frames) { + _waitFrames = frames; + _waitStartFrame = _vm->_events->getFrameCounter(); +} + +void Logo::startAnimation(uint object, uint frameDelay, const AnimationFrame *frames) { + _animateObject = object; + _animateFrameDelay = frameDelay; + _animateFrames = frames; + _animateStartFrame = _vm->_events->getFrameCounter(); + _animateFrame = 1; + + _objects[object]._frame = frames[0].frame; + _objects[object]._position = Common::Point(frames[0].x, frames[0].y); +} + +void Logo::loadBackground() { + Screen &screen = *_vm->_screen; + + for (int idx = 0; idx < 4; ++idx) { + // Get the portion of the screen + Common::SeekableReadStream *stream = _lib.getResource(RES_BITMAP, 10, idx); + + // Load it onto the surface + Common::Point pt((idx / 2) * (SHERLOCK_SCREEN_WIDTH / 2), (idx % 2) * (SHERLOCK_SCREEN_HEIGHT / 2)); + for (int y = 0; y < (SHERLOCK_SCREEN_HEIGHT / 2); ++y, ++pt.y) { + byte *pDest = (byte *)screen._backBuffer1.getBasePtr(pt.x, pt.y); + stream->read(pDest, SHERLOCK_SCREEN_WIDTH / 2); + } + + // _backgroundBounds = Rect(0, 0, READ_LE_UINT16(data), READ_LE_UINT16(data + 2)); + delete stream; + } + + // Default to a blank palette + byte palette[PALETTE_SIZE]; + Common::fill(&palette[0], &palette[PALETTE_SIZE], 0); + screen.setPalette(palette); + + // Copy the surface to the screen + screen.blitFrom(screen._backBuffer1); +} + +void Logo::fade(const byte palette[PALETTE_SIZE], int step) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + byte startPalette[PALETTE_SIZE]; + byte tempPalette[PALETTE_SIZE]; + + screen.getPalette(startPalette); + + for (int percent = 0; percent < 100; percent += step) { + for (int palIndex = 0; palIndex < 256; ++palIndex) { + const byte *pal1P = (const byte *)&startPalette[palIndex * 3]; + const byte *pal2P = (const byte *)&palette[palIndex * 3]; + byte *destP = &tempPalette[palIndex * 3]; + + for (int rgbIndex = 0; rgbIndex < 3; ++rgbIndex, ++pal1P, ++pal2P, ++destP) { + *destP = (int)*pal1P + ((int)*pal2P - (int)*pal1P) * percent / 100; + } + } + + screen.setPalette(tempPalette); + events.wait(1); + } + + // Set final palette + screen.setPalette(palette); +} + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock diff --git a/engines/sherlock/scalpel/tsage/logo.h b/engines/sherlock/scalpel/tsage/logo.h new file mode 100644 index 0000000000..c9fac00d9c --- /dev/null +++ b/engines/sherlock/scalpel/tsage/logo.h @@ -0,0 +1,250 @@ +/* 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. + * + */ + +#ifndef SHERLOCK_SCALPEL_TSAGE_LOGO_H +#define SHERLOCK_SCALPEL_TSAGE_LOGO_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/file.h" +#include "common/list.h" +#include "common/str.h" +#include "common/str-array.h" +#include "common/util.h" +#include "graphics/surface.h" +#include "sherlock/scalpel/tsage/resources.h" +#include "sherlock/screen.h" + +namespace Sherlock { +namespace Scalpel { + +class ScalpelEngine; + +namespace TsAGE { + +class ObjectSurface : public Surface { +public: + Common::Point _centroid; +public: + ObjectSurface() : Surface() {} + virtual ~ObjectSurface() {} +}; + +class Visage { +private: + Common::SeekableReadStream *_stream; + + /** + * Translates a raw image resource into a graphics surface + */ + void surfaceFromRes(ObjectSurface &s); +public: + static TLib *_tLib; + int _resNum; + int _rlbNum; +public: + Visage(); + ~Visage(); + + /** + * Set the visage number + */ + void setVisage(int resNum, int rlbNum = 9999); + + /** + * Clear the visage + */ + void clear(); + + /** + * Get a frame from the visage + */ + void getFrame(ObjectSurface &s, int frameNum); + + /** + * Return the number of frames + */ + int getFrameCount() const; + + /** + * Returns whether the visage is loaded + */ + bool isLoaded() const; +}; + +class Object { +private: + Visage _visage; + uint32 _updateStartFrame; + bool _isAnimating; + bool _finished; + uint32 _walkStartFrame; + int _angle; + int _changeCtr; + int _majorDiff, _minorDiff; + Common::Point _moveDelta; + Common::Point _moveSign; + + /** + * Return the next frame when the object is animating + */ + int changeFrame(); + + /** + * Gets the next frame in the sequence + */ + int getNewFrame(); + + /** + * Calculate the angle between the current position and a designated destination + */ + void calculateMoveAngle(); + + /** + * Handle any object movement + */ + void move(); + + /** + * Returns whether not to make any movement + */ + bool dontMove() const; + + /** + * Ends any current movement + */ + void endMove(); +public: + static ScalpelEngine *_vm; + Common::Point _position; + Common::Point _destination; + Common::Rect _oldBounds; + int _frame; + int _numFrames; + int _frameChange; +public: + Object(); + + /** + * Load the data for the object + */ + void setVisage(int visage, int strip); + + /** + * Sets whether the object is animating + */ + void setAnimMode(bool isAnimating); + + /** + * Starts an object moving to a given destination + */ + void setDestination(const Common::Point &pt); + + /** + * Returns true if an animation is ended + */ + bool isAnimEnded() const; + + /** + * Return true if object is moving + */ + bool isMoving() const; + + /** + * Erase the area the object was previously drawn at, by restoring the background + */ + void erase(); + + /** + * Update the frame + */ + void update(); + + /** + * Remove an object from being displayed + */ + void remove() { _visage.clear(); } +}; + +struct AnimationFrame { + int frame; + int x; + int y; +}; + +class Logo { +private: + ScalpelEngine *_vm; + TLib _lib; + int _counter; + bool _finished; + byte _originalPalette[PALETTE_SIZE]; + byte _palette1[PALETTE_SIZE]; + byte _palette2[PALETTE_SIZE]; + byte _palette3[PALETTE_SIZE]; + Object _objects[4]; + uint _waitFrames; + uint32 _waitStartFrame; + int _animateObject; + uint32 _animateStartFrame; + uint _animateFrameDelay; + const AnimationFrame *_animateFrames; + uint _animateFrame; + + Logo(ScalpelEngine *vm); + ~Logo(); + + void nextFrame(); + + bool finished() const; + + /** + * Wait for a number of frames. Note that the frame count in _events is + * not the same as the number of calls to nextFrame(). + */ + void waitFrames(uint frames); + + /** + * Start an animation sequence. Used for sequences that are described + * one frame at a time because they do unusual things, or run at + * unusual rates. + */ + void startAnimation(uint object, uint frameDelay, const AnimationFrame *frames); + + /** + * Load the background for the scene + */ + void loadBackground(); + + /** + * Fade from the current palette to a new one + */ + void fade(const byte palette[PALETTE_SIZE], int step = 6); +public: + static bool show(ScalpelEngine *vm); +}; + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/tsage/resources.cpp b/engines/sherlock/scalpel/tsage/resources.cpp new file mode 100644 index 0000000000..56b7021563 --- /dev/null +++ b/engines/sherlock/scalpel/tsage/resources.cpp @@ -0,0 +1,373 @@ +/* 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. + * + */ + +#include "common/scummsys.h" +#include "common/memstream.h" +#include "common/stack.h" +#include "sherlock/scalpel/tsage/resources.h" + +namespace Sherlock { +namespace Scalpel { +namespace TsAGE { + +static uint16 bitMasks[4] = {0x1ff, 0x3ff, 0x7ff, 0xfff}; + +uint16 BitReader::readToken() { + assert((numBits >= 9) && (numBits <= 12)); + uint16 result = _remainder; + int bitsLeft = numBits - _bitsLeft; + int bitOffset = _bitsLeft; + _bitsLeft = 0; + + while (bitsLeft >= 0) { + _remainder = readByte(); + result |= _remainder << bitOffset; + bitsLeft -= 8; + bitOffset += 8; + } + + _bitsLeft = -bitsLeft; + _remainder >>= 8 - _bitsLeft; + return result & bitMasks[numBits - 9]; +} + +/*-------------------------------------------------------------------------*/ + +TLib::TLib(const Common::String &filename) : _filename(filename) { + + // If the resource strings list isn't yet loaded, load them + if (_resStrings.size() == 0) { + Common::File f; + if (f.open("tsage.cfg")) { + while (!f.eos()) { + _resStrings.push_back(f.readLine()); + } + f.close(); + } + } + + if (!_file.open(filename)) + error("Missing file %s", filename.c_str()); + + loadIndex(); +} + +TLib::~TLib() { + _resStrings.clear(); +} + +/** + * Load a section index from the given position in the file + */ +void TLib::loadSection(uint32 fileOffset) { + _resources.clear(); + _file.seek(fileOffset); + _sections.fileOffset = fileOffset; + + loadSection(_file, _resources); +} + +struct DecodeReference { + uint16 vWord; + uint8 vByte; +}; + +/** + * Gets a resource from the currently loaded section + */ +Common::SeekableReadStream *TLib::getResource(uint16 id, bool suppressErrors) { + // Scan for an entry for the given Id + ResourceEntry *re = nullptr; + ResourceList::iterator iter; + for (iter = _resources.begin(); iter != _resources.end(); ++iter) { + if ((*iter).id == id) { + re = &(*iter); + break; + } + } + if (!re) { + if (suppressErrors) + return nullptr; + error("Could not find resource Id #%d", id); + } + + if (!re->isCompressed) { + // Read in the resource data and return it + byte *dataP = (byte *)malloc(re->size); + _file.seek(_sections.fileOffset + re->fileOffset); + _file.read(dataP, re->size); + + return new Common::MemoryReadStream(dataP, re->size, DisposeAfterUse::YES); + } + + /* + * Decompress the data block + */ + + _file.seek(_sections.fileOffset + re->fileOffset); + Common::ReadStream *compStream = _file.readStream(re->size); + BitReader bitReader(*compStream); + + byte *dataOut = (byte *)malloc(re->uncompressedSize); + byte *destP = dataOut; + uint bytesWritten = 0; + + uint16 ctrCurrent = 0x102, ctrMax = 0x200; + uint16 word_48050 = 0, currentToken = 0, word_48054 =0; + byte byte_49068 = 0, byte_49069 = 0; + + const uint tableSize = 0x1000; + DecodeReference *table = (DecodeReference *)malloc(tableSize * sizeof(DecodeReference)); + if (!table) + error("[TLib::getResource] Cannot allocate table buffer"); + + for (uint i = 0; i < tableSize; ++i) { + table[i].vByte = table[i].vWord = 0; + } + Common::Stack<uint16> tokenList; + + for (;;) { + // Get the next decode token + uint16 token = bitReader.readToken(); + + // Handle the token + if (token == 0x101) { + // End of compressed stream + break; + } else if (token == 0x100) { + // Reset bit-rate + bitReader.numBits = 9; + ctrMax = 0x200; + ctrCurrent = 0x102; + + // Set variables with next token + currentToken = word_48050 = bitReader.readToken(); + byte_49069 = byte_49068 = (byte)currentToken; + + ++bytesWritten; + assert(bytesWritten <= re->uncompressedSize); + *destP++ = byte_49069; + } else { + word_48054 = word_48050 = token; + + if (token >= ctrCurrent) { + word_48050 = currentToken; + tokenList.push(byte_49068); + } + + while (word_48050 >= 0x100) { + assert(word_48050 < 0x1000); + tokenList.push(table[word_48050].vByte); + word_48050 = table[word_48050].vWord; + } + + byte_49069 = byte_49068 = (byte)word_48050; + tokenList.push(word_48050); + + // Write out any cached tokens + while (!tokenList.empty()) { + ++bytesWritten; + assert(bytesWritten <= re->uncompressedSize); + *destP++ = tokenList.pop(); + } + + assert(ctrCurrent < 0x1000); + table[ctrCurrent].vByte = byte_49069; + table[ctrCurrent].vWord = currentToken; + ++ctrCurrent; + + currentToken = word_48054; + if ((ctrCurrent >= ctrMax) && (bitReader.numBits != 12)) { + // Move to the next higher bit-rate + ++bitReader.numBits; + ctrMax <<= 1; + } + } + } + + free(table); + + assert(bytesWritten == re->uncompressedSize); + delete compStream; + return new Common::MemoryReadStream(dataOut, re->uncompressedSize, DisposeAfterUse::YES); +} + +/** + * Finds the correct section and loads the specified resource within it + */ +Common::SeekableReadStream *TLib::getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors) { + SectionList::iterator i = _sections.begin(); + while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum)) + ++i; + if (i == _sections.end()) { + if (suppressErrors) + return nullptr; + error("Unknown resource type %d num %d", resType, resNum); + } + + loadSection((*i).fileOffset); + + return getResource(rlbNum, suppressErrors); +} + +/** + * Gets the offset of the start of a resource in the resource file + */ +uint32 TLib::getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry) { + // Find the correct section + SectionList::iterator i = _sections.begin(); + while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum)) + ++i; + if (i == _sections.end()) { + error("Unknown resource type %d num %d", resType, resNum); + } + + // Load in the section index + loadSection((*i).fileOffset); + + // Scan for an entry for the given Id + ResourceEntry *re = nullptr; + ResourceList::iterator iter; + for (iter = _resources.begin(); iter != _resources.end(); ++iter) { + if ((*iter).id == rlbNum) { + re = &(*iter); + break; + } + } + + // Throw an error if no resource was found, or the resource is compressed + if (!re || re->isCompressed) + error("Invalid resource Id #%d", rlbNum); + + // Return the resource entry as well as the file offset + entry = *re; + return _sections.fileOffset + entry.fileOffset; +} + +void TLib::loadIndex() { + uint16 resNum, configId, fileOffset; + + // Load the root resources section + loadSection(0); + + // Get the single resource from it + Common::SeekableReadStream *stream = getResource(0); + + _sections.clear(); + + // Loop through reading the entries + while ((resNum = stream->readUint16LE()) != 0xffff) { + configId = stream->readUint16LE(); + fileOffset = stream->readUint16LE(); + + SectionEntry se; + se.resNum = resNum; + se.resType = (ResourceType)(configId & 0x1f); + se.fileOffset = (((configId >> 5) & 0x7ff) << 16) | fileOffset; + + _sections.push_back(se); + } + + delete stream; +} + +/** + * Retrieves the specified palette resource and returns it's data + * + * @paletteNum Specefies the palette number + */ +void TLib::getPalette(byte palette[PALETTE_SIZE], int paletteNum) { + // Get the specified palette + Common::SeekableReadStream *stream = getResource(RES_PALETTE, paletteNum, 0, true); + if (!stream) + return; + + int startNum = stream->readUint16LE(); + int numEntries = stream->readUint16LE(); + assert((startNum < 256) && ((startNum + numEntries) <= 256)); + stream->skip(2); + + // Copy over the data + stream->read(&palette[startNum * 3], numEntries * 3); + + delete stream; +} + +/** + * Open up the given resource file using a passed file object. If the desired entry is found + * in the index, return the index entry for it, and move the file to the start of the resource + */ +bool TLib::scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum, + ResourceEntry &resEntry) { + // Load the root section index + ResourceList resList; + loadSection(f, resList); + + // Loop through the index for the desired entry + ResourceList::iterator iter; + for (iter = resList.begin(); iter != resList.end(); ++iter) { + ResourceEntry &re = *iter; + if (re.id == resNum) { + // Found it, so exit + resEntry = re; + f.seek(re.fileOffset); + return true; + } + } + + // No matching entry found + return false; +} + +/** + * Inner logic for decoding a section index into a passed resource list object + */ +void TLib::loadSection(Common::File &f, ResourceList &resources) { + if (f.readUint32BE() != 0x544D492D) + error("Data block is not valid Rlb data"); + + /*uint8 unknown1 = */f.readByte(); + uint16 numEntries = f.readByte(); + + for (uint i = 0; i < numEntries; ++i) { + uint16 id = f.readUint16LE(); + uint16 size = f.readUint16LE(); + uint16 uncSize = f.readUint16LE(); + uint8 sizeHi = f.readByte(); + uint8 type = f.readByte() >> 5; + assert(type <= 1); + uint32 offset = f.readUint32LE(); + + ResourceEntry re; + re.id = id; + re.fileOffset = offset; + re.isCompressed = type != 0; + re.size = ((sizeHi & 0xF) << 16) | size; + re.uncompressedSize = ((sizeHi & 0xF0) << 12) | uncSize; + + resources.push_back(re); + } +} + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock diff --git a/engines/sherlock/scalpel/tsage/resources.h b/engines/sherlock/scalpel/tsage/resources.h new file mode 100644 index 0000000000..3e09b6b0b1 --- /dev/null +++ b/engines/sherlock/scalpel/tsage/resources.h @@ -0,0 +1,139 @@ +/* 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. + * + */ + +#ifndef SHERLOCK_SCALPEL_TSAGE_RESOURCES_H +#define SHERLOCK_SCALPEL_TSAGE_RESOURCES_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/file.h" +#include "common/list.h" +#include "common/str.h" +#include "common/str-array.h" +#include "common/util.h" +#include "graphics/surface.h" +#include "sherlock/screen.h" + +namespace Sherlock { +namespace Scalpel { +namespace TsAGE { + +// Magic number used by original game to identify valid memory blocks +const uint32 MEMORY_ENTRY_ID = 0xE11DA722; + +const int MEMORY_POOL_SIZE = 1000; + +enum ResourceType { RES_LIBRARY, RES_STRIP, RES_IMAGE, RES_PALETTE, RES_VISAGE, RES_SOUND, RES_MESSAGE, + RES_FONT, RES_POINTER, RES_BANK, RES_SND_DRIVER, RES_PRIORITY, RES_CONTROL, RES_WALKRGNS, + RES_BITMAP, RES_SAVE, RES_SEQUENCE, + // Return to Ringworld specific resource types + RT17, RT18, RT19, RT20, RT21, RT22, RT23, RT24, RT25, RT26, RT27, RT28, RT29, RT30, RT31 +}; + +class SectionEntry { +public: + ResourceType resType; + uint16 resNum; + uint32 fileOffset; + + SectionEntry() { + resType = RES_LIBRARY; + resNum = 0; + fileOffset = 0; + } +}; + +class ResourceEntry { +public: + uint16 id; + bool isCompressed; + uint32 fileOffset; + uint32 size; + uint32 uncompressedSize; + + ResourceEntry() { + id = 0; + isCompressed = false; + fileOffset = 0; + size = 0; + uncompressedSize = 0; + } +}; + +typedef Common::List<ResourceEntry> ResourceList; + +class SectionList : public Common::List<SectionEntry> { +public: + uint32 fileOffset; + + SectionList() { + fileOffset = 0; + } +}; + +class BitReader { +private: + Common::ReadStream &_stream; + uint8 _remainder, _bitsLeft; + byte readByte() { return _stream.eos() ? 0 : _stream.readByte(); } +public: + BitReader(Common::ReadStream &s) : _stream(s) { + numBits = 9; + _remainder = 0; + _bitsLeft = 0; + } + uint16 readToken(); + + int numBits; +}; + +class TLib { +private: + Common::StringArray _resStrings; +private: + Common::File _file; + Common::String _filename; + ResourceList _resources; + SectionList _sections; + + void loadSection(uint32 fileOffset); + void loadIndex(); + + static bool scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum, ResourceEntry &resEntry); + static void loadSection(Common::File &f, ResourceList &resources); +public: + TLib(const Common::String &filename); + ~TLib(); + + const Common::String &getFilename() { return _filename; } + const SectionList &getSections() { return _sections; } + Common::SeekableReadStream *getResource(uint16 id, bool suppressErrors = false); + Common::SeekableReadStream *getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors = false); + uint32 getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry); + void getPalette(byte palette[PALETTE_SIZE], int paletteNum); +}; + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock + +#endif |