/* 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 "sci/video/robot_decoder.h" #include "common/archive.h" // for SearchMan #include "common/debug.h" // for debugC #include "common/endian.h" // for MKTAG #include "common/memstream.h" // for MemoryReadStream #include "common/platform.h" // for Platform::kPlatformMacintosh #include "common/rational.h" // for operator*, Rational #include "common/str.h" // for String #include "common/stream.h" // for SeekableReadStream #include "common/substream.h" // for SeekableSubReadStreamEndian #include "common/textconsole.h" // for error, warning #include "common/types.h" // for Flag::NO, Flag::YES #include "sci/engine/seg_manager.h" // for SegManager #include "sci/graphics/celobj32.h" // for Ratio, ::kLowResX, ::kLowResY #include "sci/graphics/text32.h" // for BitmapResource #include "sci/sound/audio32.h" // for Audio32 #include "sci/sci.h" // for kDebugLevels::kDebugLevelVideo #include "sci/util.h" // for READ_SCI11ENDIAN_UINT16, READ_SC... namespace Sci { #pragma mark RobotAudioStream extern void deDPCM16Mono(int16 *out, const byte *in, const uint32 numBytes, int16 &sample); RobotAudioStream::RobotAudioStream(const int32 bufferSize) : _loopBuffer((byte *)malloc(bufferSize)), _loopBufferSize(bufferSize), _decompressionBuffer(nullptr), _decompressionBufferSize(0), _decompressionBufferPosition(-1), _waiting(true), _finished(false), _firstPacketPosition(-1) {} RobotAudioStream::~RobotAudioStream() { free(_loopBuffer); free(_decompressionBuffer); } static void interpolateChannel(int16 *buffer, int32 numSamples, const int8 bufferIndex) { if (numSamples <= 0) { return; } if (bufferIndex) { int16 lastSample = *buffer; int sample = lastSample; int16 *target = buffer + 1; const int16 *source = buffer + 2; --numSamples; while (numSamples--) { sample = *source + lastSample; lastSample = *source; sample /= 2; *target = sample; source += 2; target += 2; } *target = sample; } else { int16 *target = buffer; const int16 *source = buffer + 1; int16 lastSample = *source; while (numSamples--) { int sample = *source + lastSample; lastSample = *source; sample /= 2; *target = sample; source += 2; target += 2; } } } static void copyEveryOtherSample(int16 *out, const int16 *in, int numSamples) { while (numSamples--) { *out = *in++; out += 2; } } bool RobotAudioStream::addPacket(const RobotAudioPacket &packet) { Common::StackLock lock(_mutex); if (_finished) { warning("Packet %d sent to finished robot audio stream", packet.position); return false; } // `packet.position` is the decompressed (doubled) position of the packet, // so values of `position` will always be divisible either by 2 (even) or by // 4 (odd). const int8 bufferIndex = packet.position % 4 ? 1 : 0; // Packet 0 is the first primer, packet 2 is the second primer, // packet 4+ are regular audio data if (packet.position <= 2 && _firstPacketPosition == -1) { _readHead = 0; _readHeadAbs = 0; _maxWriteAbs = _loopBufferSize; _writeHeadAbs = 2; _jointMin[0] = 0; _jointMin[1] = 2; _waiting = true; _finished = false; _firstPacketPosition = packet.position; fillRobotBuffer(packet, bufferIndex); return true; } const int32 packetEndByte = packet.position + (packet.dataSize * sizeof(int16) * kEOSExpansion); // Already read all the way past this packet (or already wrote valid samples // to this channel all the way past this packet), so discard it if (packetEndByte <= MAX(_readHeadAbs, _jointMin[bufferIndex])) { debugC(kDebugLevelVideo, "Rejecting packet %d, read past %d / %d", packet.position, _readHeadAbs, _jointMin[bufferIndex]); return true; } // The loop buffer is full, so tell the caller to send the packet again // later if (_maxWriteAbs <= _jointMin[bufferIndex]) { debugC(kDebugLevelVideo, "Rejecting packet %d, full buffer", packet.position); return false; } fillRobotBuffer(packet, bufferIndex); // This packet is the second primer, so allow playback to begin if (_firstPacketPosition != -1 && _firstPacketPosition != packet.position) { debugC(kDebugLevelVideo, "Done waiting. Robot audio begins"); _waiting = false; _firstPacketPosition = -1; } // Only part of the packet could be read into the loop buffer before it was // full, so tell the caller to send the packet again later if (packetEndByte > _maxWriteAbs) { debugC(kDebugLevelVideo, "Partial read of packet %d (%d / %d)", packet.position, packetEndByte - _maxWriteAbs, packetEndByte - packet.position); return false; } // The entire packet was successfully read into the loop buffer return true; } void RobotAudioStream::fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex) { int32 sourceByte = 0; const int32 decompressedSize = packet.dataSize * sizeof(int16); if (_decompressionBufferPosition != packet.position) { if (decompressedSize != _decompressionBufferSize) { _decompressionBuffer = (byte *)realloc(_decompressionBuffer, decompressedSize); _decompressionBufferSize = decompressedSize; } int16 carry = 0; deDPCM16Mono((int16 *)_decompressionBuffer, packet.data, packet.dataSize, carry); _decompressionBufferPosition = packet.position; } int32 numBytes = decompressedSize; int32 packetPosition = packet.position; int32 endByte = packet.position + decompressedSize * kEOSExpansion; int32 startByte = MAX(_readHeadAbs + bufferIndex * 2, _jointMin[bufferIndex]); int32 maxWriteByte = _maxWriteAbs + bufferIndex * 2; if (packetPosition < startByte) { sourceByte = (startByte - packetPosition) / kEOSExpansion; numBytes -= sourceByte; packetPosition = startByte; } if (packetPosition > maxWriteByte) { numBytes += (packetPosition - maxWriteByte) / kEOSExpansion; packetPosition = maxWriteByte; } if (endByte > maxWriteByte) { numBytes -= (endByte - maxWriteByte) / kEOSExpansion; endByte = maxWriteByte; } const int32 maxJointMin = MAX(_jointMin[0], _jointMin[1]); if (endByte > maxJointMin) { _writeHeadAbs += endByte - maxJointMin; } if (packetPosition > _jointMin[bufferIndex]) { int32 packetEndByte = packetPosition % _loopBufferSize; int32 targetBytePosition; int32 numBytesToEnd; if ((packetPosition & ~3) > (_jointMin[1 - bufferIndex] & ~3)) { targetBytePosition = _jointMin[1 - bufferIndex] % _loopBufferSize; if (targetBytePosition >= packetEndByte) { numBytesToEnd = _loopBufferSize - targetBytePosition; memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd); targetBytePosition = (1 - bufferIndex) ? 2 : 0; } numBytesToEnd = packetEndByte - targetBytePosition; if (numBytesToEnd > 0) { memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd); } } targetBytePosition = _jointMin[bufferIndex] % _loopBufferSize; if (targetBytePosition >= packetEndByte) { numBytesToEnd = _loopBufferSize - targetBytePosition; interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0); targetBytePosition = bufferIndex ? 2 : 0; } numBytesToEnd = packetEndByte - targetBytePosition; if (numBytesToEnd > 0) { interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0); } } if (numBytes > 0) { int32 targetBytePosition = packetPosition % _loopBufferSize; int32 packetEndByte = endByte % _loopBufferSize; int32 numBytesToEnd = 0; if (targetBytePosition >= packetEndByte) { numBytesToEnd = (_loopBufferSize - (targetBytePosition & ~3)) / kEOSExpansion; copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte), numBytesToEnd / kEOSExpansion); targetBytePosition = bufferIndex ? 2 : 0; } copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte + numBytesToEnd), (packetEndByte - targetBytePosition) / sizeof(int16) / kEOSExpansion); } _jointMin[bufferIndex] = endByte; } void RobotAudioStream::interpolateMissingSamples(int32 numSamples) { int32 numBytes = numSamples * sizeof(int16) * kEOSExpansion; int32 targetPosition = _readHead; if (_readHeadAbs > _jointMin[1]) { if (_readHeadAbs > _jointMin[0]) { if (targetPosition + numBytes >= _loopBufferSize) { const int32 numBytesToEdge = (_loopBufferSize - targetPosition); memset(_loopBuffer + targetPosition, 0, numBytesToEdge); numBytes -= numBytesToEdge; targetPosition = 0; } memset(_loopBuffer + targetPosition, 0, numBytes); _jointMin[0] += numBytes; _jointMin[1] += numBytes; } else { if (targetPosition + numBytes >= _loopBufferSize) { const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion; interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 1); numSamples -= numSamplesToEdge; targetPosition = 0; } interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 1); _jointMin[1] += numBytes; } } else if (_readHeadAbs > _jointMin[0]) { if (targetPosition + numBytes >= _loopBufferSize) { const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion; interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 0); numSamples -= numSamplesToEdge; targetPosition = 2; } interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 0); _jointMin[0] += numBytes; } } void RobotAudioStream::finish() { Common::StackLock lock(_mutex); _finished = true; } RobotAudioStream::StreamState RobotAudioStream::getStatus() const { Common::StackLock lock(_mutex); StreamState status; status.bytesPlaying = _readHeadAbs; status.rate = getRate(); status.bits = 8 * sizeof(int16); return status; } int RobotAudioStream::readBuffer(Audio::st_sample_t *outBuffer, int numSamples) { Common::StackLock lock(_mutex); if (_waiting) { return 0; } assert(!((_writeHeadAbs - _readHeadAbs) & 1)); const int maxNumSamples = (_writeHeadAbs - _readHeadAbs) / sizeof(Audio::st_sample_t); numSamples = MIN(numSamples, maxNumSamples); if (!numSamples) { return 0; } interpolateMissingSamples(numSamples); Audio::st_sample_t *inBuffer = (Audio::st_sample_t *)(_loopBuffer + _readHead); assert(!((_loopBufferSize - _readHead) & 1)); const int numSamplesToEnd = (_loopBufferSize - _readHead) / sizeof(Audio::st_sample_t); int numSamplesToRead = MIN(numSamples, numSamplesToEnd); Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer); if (numSamplesToRead < numSamples) { inBuffer = (Audio::st_sample_t *)_loopBuffer; outBuffer += numSamplesToRead; numSamplesToRead = numSamples - numSamplesToRead; Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer); } const int32 numBytes = numSamples * sizeof(Audio::st_sample_t); _readHead += numBytes; if (_readHead > _loopBufferSize) { _readHead -= _loopBufferSize; } _readHeadAbs += numBytes; _maxWriteAbs += numBytes; assert(!(_readHead & 1)); assert(!(_readHeadAbs & 1)); return numSamples; } #pragma mark - #pragma mark RobotDecoder RobotDecoder::RobotDecoder(SegManager *segMan) : _delayTime(this), _segMan(segMan), _status(kRobotStatusUninitialized), _audioBuffer(nullptr), _rawPalette((uint8 *)malloc(kRawPaletteSize)) {} RobotDecoder::~RobotDecoder() { close(); free(_rawPalette); free(_audioBuffer); } #pragma mark - #pragma mark RobotDecoder - Initialization void RobotDecoder::initStream(const GuiResourceId robotId) { const Common::String fileName = Common::String::format("%d.rbt", robotId); Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); _fileOffset = 0; if (stream == nullptr) { error("Unable to open robot file %s", fileName.c_str()); } const uint16 id = stream->readUint16LE(); if (id != 0x16) { error("Invalid robot file %s", fileName.c_str()); } // TODO: Mac version not tested, so this could be totally wrong _stream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), g_sci->getPlatform() == Common::kPlatformMacintosh, DisposeAfterUse::YES); _stream->seek(2, SEEK_SET); if (_stream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) { error("Resource %s is not Robot type!", fileName.c_str()); } } void RobotDecoder::initPlayback() { _startFrameNo = 0; _startTime = -1; _startingFrameNo = -1; _cueForceShowFrame = -1; _previousFrameNo = -1; _currentFrameNo = 0; _status = kRobotStatusPaused; } void RobotDecoder::initAudio() { _syncFrame = true; _audioRecordInterval = RobotAudioStream::kRobotSampleRate / _frameRate; // TODO: Might actually be for all games newer than Lighthouse; check to // see which games have this condition. if (g_sci->getGameId() != GID_LIGHTHOUSE && !(_audioRecordInterval & 1)) { ++_audioRecordInterval; } _expectedAudioBlockSize = _audioBlockSize - kAudioBlockHeaderSize; _audioBuffer = (byte *)realloc(_audioBuffer, kRobotZeroCompressSize + _expectedAudioBlockSize); if (_primerReservedSize != 0) { const int32 primerHeaderPosition = _stream->pos(); _totalPrimerSize = _stream->readSint32(); const int16 compressionType = _stream->readSint16(); _evenPrimerSize = _stream->readSint32(); _oddPrimerSize = _stream->readSint32(); _primerPosition = _stream->pos(); if (compressionType) { error("Unknown audio header compression type %d", compressionType); } if (_evenPrimerSize + _oddPrimerSize != _primerReservedSize) { _stream->seek(primerHeaderPosition + _primerReservedSize, SEEK_SET); } } else if (_primerZeroCompressFlag) { _evenPrimerSize = 19922; _oddPrimerSize = 21024; } _firstAudioRecordPosition = _evenPrimerSize * 2; const int usedEachFrame = (RobotAudioStream::kRobotSampleRate / 2) / _frameRate; _maxSkippablePackets = MAX(0, _audioBlockSize / usedEachFrame - 1); } void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize) { _position = Common::Point(x, y); if (scale != 128) { _scaleInfo.x = scale; _scaleInfo.y = scale; _scaleInfo.signal = kScaleSignalManual; } _plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane); if (_plane == nullptr) { error("Invalid plane %04x:%04x passed to RobotDecoder::open", PRINT_REG(plane)); } _minFrameRate = _frameRate - kMaxFrameRateDrift; _maxFrameRate = _frameRate + kMaxFrameRateDrift; if (_xResolution == 0 || _yResolution == 0) { // TODO: Default values were taken from RESOURCE.CFG hires property // if it exists, so need to check games' configuration files for those _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; } if (hasPalette) { _stream->read(_rawPalette, paletteSize); } else { _stream->seek(paletteSize, SEEK_CUR); } _screenItemList.reserve(kScreenItemListSize); _maxCelArea.reserve(kFixedCelListSize); // Fixed cel buffers are for version 5 and newer _fixedCels.reserve(MIN(_maxCelsPerFrame, (int16)kFixedCelListSize)); _celDecompressionBuffer.reserve(_maxCelArea[0] + SciBitmap::getBitmapHeaderSize() + kRawPaletteSize); _celDecompressionArea = _maxCelArea[0]; } void RobotDecoder::initRecordAndCuePositions() { PositionList recordSizes; _videoSizes.reserve(_numFramesTotal); _recordPositions.reserve(_numFramesTotal); recordSizes.reserve(_numFramesTotal); switch(_version) { case 5: // 16-bit sizes and positions for (int i = 0; i < _numFramesTotal; ++i) { _videoSizes.push_back(_stream->readUint16()); } for (int i = 0; i < _numFramesTotal; ++i) { recordSizes.push_back(_stream->readUint16()); } break; case 6: // 32-bit sizes and positions for (int i = 0; i < _numFramesTotal; ++i) { _videoSizes.push_back(_stream->readSint32()); } for (int i = 0; i < _numFramesTotal; ++i) { recordSizes.push_back(_stream->readSint32()); } break; default: error("Unknown Robot version %d", _version); } for (int i = 0; i < kCueListSize; ++i) { _cueTimes[i] = _stream->readSint32(); } for (int i = 0; i < kCueListSize; ++i) { _cueValues[i] = _stream->readUint16(); } Common::copy(_cueTimes, _cueTimes + kCueListSize, _masterCueTimes); int bytesRemaining = (_stream->pos() - _fileOffset) % kRobotFrameSize; if (bytesRemaining != 0) { _stream->seek(kRobotFrameSize - bytesRemaining, SEEK_CUR); } int position = _stream->pos(); _recordPositions.push_back(position); for (int i = 0; i < _numFramesTotal - 1; ++i) { position += recordSizes[i]; _recordPositions.push_back(position); } } #pragma mark - #pragma mark RobotDecoder - Playback void RobotDecoder::open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale) { if (_status != kRobotStatusUninitialized) { close(); } initStream(robotId); _version = _stream->readUint16(); // TODO: Version 4 for PQ:SWAT demo? if (_version < 5 || _version > 6) { error("Unsupported version %d of Robot resource", _version); } debugC(kDebugLevelVideo, "Opening version %d robot %d", _version, robotId); initPlayback(); _audioBlockSize = _stream->readUint16(); _primerZeroCompressFlag = _stream->readSint16(); _stream->seek(2, SEEK_CUR); // unused _numFramesTotal = _stream->readUint16(); const uint16 paletteSize = _stream->readUint16(); _primerReservedSize = _stream->readUint16(); _xResolution = _stream->readSint16(); _yResolution = _stream->readSint16(); const bool hasPalette = (bool)_stream->readByte(); _hasAudio = (bool)_stream->readByte(); _stream->seek(2, SEEK_CUR); // unused _frameRate = _normalFrameRate = _stream->readSint16(); _isHiRes = (bool)_stream->readSint16(); _maxSkippablePackets = _stream->readSint16(); _maxCelsPerFrame = _stream->readSint16(); // used for memory preallocation of fixed cels _maxCelArea.push_back(_stream->readSint32()); _maxCelArea.push_back(_stream->readSint32()); _maxCelArea.push_back(_stream->readSint32()); _maxCelArea.push_back(_stream->readSint32()); _stream->seek(8, SEEK_CUR); // reserved if (_hasAudio) { initAudio(); } else { _stream->seek(_primerReservedSize, SEEK_CUR); } _priority = priority; initVideo(x, y, scale, plane, hasPalette, paletteSize); initRecordAndCuePositions(); } void RobotDecoder::close() { if (_status == kRobotStatusUninitialized) { return; } debugC(kDebugLevelVideo, "Closing robot"); _status = kRobotStatusUninitialized; _videoSizes.clear(); _recordPositions.clear(); _celDecompressionBuffer.clear(); _doVersion5Scratch.clear(); delete _stream; _stream = nullptr; for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) { if (_celHandles[i].status == CelHandleInfo::kFrameLifetime) { _segMan->freeBitmap(_celHandles[i].bitmapId); } } _celHandles.clear(); for (FixedCelsList::size_type i = 0; i < _fixedCels.size(); ++i) { _segMan->freeBitmap(_fixedCels[i]); } _fixedCels.clear(); if (g_sci->_gfxFrameout->getPlanes().findByObject(_plane->_object) != nullptr) { for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { if (_screenItemList[i] != nullptr) { g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]); } } } _screenItemList.clear(); if (_hasAudio) { _audioList.reset(); } } void RobotDecoder::pause() { if (_status != kRobotStatusPlaying) { return; } if (_hasAudio) { _audioList.stopAudioNow(); } _status = kRobotStatusPaused; _frameRate = _normalFrameRate; } void RobotDecoder::resume() { if (_status != kRobotStatusPaused) { return; } _startingFrameNo = _currentFrameNo; _status = kRobotStatusPlaying; if (_hasAudio) { primeAudio(_currentFrameNo * 60 / _frameRate); _syncFrame = true; } setRobotTime(_currentFrameNo); for (int i = 0; i < kCueListSize; ++i) { if (_masterCueTimes[i] != -1 && _masterCueTimes[i] < _currentFrameNo) { _cueTimes[i] = -1; } else { _cueTimes[i] = _masterCueTimes[i]; } } } void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority) { debugC(kDebugLevelVideo, "Show frame %d (%d %d %d)", frameNo, newX, newY, newPriority); if (newX != kUnspecified) { _position.x = newX; } if (newY != kUnspecified) { _position.y = newY; } if (newPriority != kUnspecified) { _priority = newPriority; } _currentFrameNo = frameNo; pause(); if (frameNo != _previousFrameNo) { seekToFrame(frameNo); doVersion5(false); } else { for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { if (_isHiRes) { SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[i].bitmapId); const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; if (scriptWidth == kLowResX && scriptHeight == kLowResY) { const Ratio lowResToScreenX(screenWidth, kLowResX); const Ratio lowResToScreenY(screenHeight, kLowResY); const Ratio screenToLowResX(kLowResX, screenWidth); const Ratio screenToLowResY(kLowResY, screenHeight); const int16 scaledX = _originalScreenItemX[i] + (_position.x * lowResToScreenX).toInt(); const int16 scaledY1 = _originalScreenItemY[i] + (_position.y * lowResToScreenY).toInt(); const int16 scaledY2 = scaledY1 + bitmap.getHeight() - 1; const int16 lowResX = (scaledX * screenToLowResX).toInt(); const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); bitmap.setOrigin(Common::Point( (scaledX - (lowResX * lowResToScreenX).toInt()) * -1, (lowResY * lowResToScreenY).toInt() - scaledY1 )); _screenItemX[i] = lowResX; _screenItemY[i] = lowResY; } else { const int16 scaledX = _originalScreenItemX[i] + _position.x; const int16 scaledY = _originalScreenItemY[i] + _position.y + bitmap.getHeight() - 1; bitmap.setOrigin(Common::Point(0, bitmap.getHeight() - 1)); _screenItemX[i] = scaledX; _screenItemY[i] = scaledY; } } else { _screenItemX[i] = _originalScreenItemX[i] + _position.x; _screenItemY[i] = _originalScreenItemY[i] + _position.y; } if (_screenItemList[i] == nullptr) { CelInfo32 celInfo; celInfo.type = kCelTypeMem; celInfo.bitmap = _celHandles[i].bitmapId; ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo); _screenItemList[i] = screenItem; screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]); if (_priority == -1) { screenItem->_fixedPriority = false; } else { screenItem->_priority = _priority; screenItem->_fixedPriority = true; } g_sci->_gfxFrameout->addScreenItem(*screenItem); } else { ScreenItem *screenItem = _screenItemList[i]; screenItem->_celInfo.bitmap = _celHandles[i].bitmapId; screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]); if (_priority == -1) { screenItem->_fixedPriority = false; } else { screenItem->_priority = _priority; screenItem->_fixedPriority = true; } g_sci->_gfxFrameout->updateScreenItem(*screenItem); } } } _previousFrameNo = frameNo; } int16 RobotDecoder::getCue() const { if (_status == kRobotStatusUninitialized || _status == kRobotStatusPaused || _syncFrame) { return 0; } if (_status == kRobotStatusEnd) { return -1; } const uint16 estimatedNextFrameNo = MIN(calculateNextFrameNo(_delayTime.predictedTicks()), _numFramesTotal); for (int i = 0; i < kCueListSize; ++i) { if (_cueTimes[i] != -1 && _cueTimes[i] <= estimatedNextFrameNo) { if (_cueTimes[i] >= _previousFrameNo) { _cueForceShowFrame = _cueTimes[i] + 1; } _cueTimes[i] = -1; return _cueValues[i]; } } return 0; } int16 RobotDecoder::getFrameNo() const { if (_status == kRobotStatusUninitialized) { return 0; } return _currentFrameNo; } RobotDecoder::RobotStatus RobotDecoder::getStatus() const { return _status; } bool RobotDecoder::seekToFrame(const int frameNo) { return _stream->seek(_recordPositions[frameNo], SEEK_SET); } void RobotDecoder::setRobotTime(const int frameNo) { _startTime = getTickCount(); _startFrameNo = frameNo; } #pragma mark - #pragma mark RobotDecoder - Timing RobotDecoder::DelayTime::DelayTime(RobotDecoder *decoder) : _decoder(decoder) { for (int i = 0; i < kDelayListSize; ++i) { _timestamps[i] = i; _delays[i] = 0; } _oldestTimestamp = 0; _newestTimestamp = kDelayListSize - 1; _startTime = 0; } void RobotDecoder::DelayTime::startTiming() { _startTime = _decoder->getTickCount(); } void RobotDecoder::DelayTime::endTiming() { const int timeDelta = _decoder->getTickCount() - _startTime; for (uint i = 0; i < kDelayListSize; ++i) { if (_timestamps[i] == _oldestTimestamp) { _timestamps[i] = ++_newestTimestamp; _delays[i] = timeDelta; break; } } ++_newestTimestamp; _startTime = 0; sortList(); } bool RobotDecoder::DelayTime::timingInProgress() const { return _startTime != 0; } int RobotDecoder::DelayTime::predictedTicks() const { return _delays[kDelayListSize / 2]; } void RobotDecoder::DelayTime::sortList() { for (uint i = 0; i < kDelayListSize - 1; ++i) { int smallestDelay = _delays[i]; uint smallestIndex = i; for (uint j = i + 1; j < kDelayListSize - 1; ++j) { if (_delays[j] < smallestDelay) { smallestDelay = _delays[j]; smallestIndex = j; } } if (smallestIndex != i) { SWAP(_delays[i], _delays[smallestIndex]); SWAP(_timestamps[i], _timestamps[smallestIndex]); } } } uint16 RobotDecoder::calculateNextFrameNo(const uint32 extraTicks) const { return ticksToFrames(getTickCount() + extraTicks - _startTime) + _startFrameNo; } uint32 RobotDecoder::ticksToFrames(const uint32 ticks) const { return (ticks * _frameRate) / 60; } uint32 RobotDecoder::getTickCount() const { return g_sci->getTickCount(); } #pragma mark - #pragma mark RobotDecoder - Audio RobotDecoder::AudioList::AudioList() : _blocks(), _blocksSize(0), _oldestBlockIndex(0), _newestBlockIndex(0), _startOffset(0), _status(kRobotAudioReady) {} void RobotDecoder::AudioList::startAudioNow() { submitDriverMax(); g_sci->_audio32->resume(kRobotChannel); _status = kRobotAudioPlaying; } void RobotDecoder::AudioList::stopAudio() { g_sci->_audio32->finishRobotAudio(); freeAudioBlocks(); _status = kRobotAudioStopping; } void RobotDecoder::AudioList::stopAudioNow() { if (_status == kRobotAudioPlaying || _status == kRobotAudioStopping || _status == kRobotAudioPaused) { g_sci->_audio32->stopRobotAudio(); _status = kRobotAudioStopped; } freeAudioBlocks(); } void RobotDecoder::AudioList::submitDriverMax() { while (_blocksSize != 0) { if (!_blocks[_oldestBlockIndex]->submit(_startOffset)) { return; } delete _blocks[_oldestBlockIndex]; _blocks[_oldestBlockIndex] = nullptr; ++_oldestBlockIndex; if (_oldestBlockIndex == kAudioListSize) { _oldestBlockIndex = 0; } --_blocksSize; } } void RobotDecoder::AudioList::addBlock(const int position, const int size, const byte *data) { assert(data != nullptr); assert(size >= 0); assert(position >= -1); if (_blocksSize == kAudioListSize) { delete _blocks[_oldestBlockIndex]; _blocks[_oldestBlockIndex] = nullptr; ++_oldestBlockIndex; if (_oldestBlockIndex == kAudioListSize) { _oldestBlockIndex = 0; } --_blocksSize; } if (_blocksSize == 0) { _oldestBlockIndex = _newestBlockIndex = 0; } else { ++_newestBlockIndex; if (_newestBlockIndex == kAudioListSize) { _newestBlockIndex = 0; } } _blocks[_newestBlockIndex] = new AudioBlock(position, size, data); ++_blocksSize; } void RobotDecoder::AudioList::reset() { stopAudioNow(); _startOffset = 0; _status = kRobotAudioReady; } void RobotDecoder::AudioList::prepareForPrimer() { g_sci->_audio32->pause(kRobotChannel); _status = kRobotAudioPaused; } void RobotDecoder::AudioList::setAudioOffset(const int offset) { _startOffset = offset; } RobotDecoder::AudioList::AudioBlock::AudioBlock(const int position, const int size, const byte* const data) : _position(position), _size(size) { _data = (byte *)malloc(size); memcpy(_data, data, size); } RobotDecoder::AudioList::AudioBlock::~AudioBlock() { free(_data); } bool RobotDecoder::AudioList::AudioBlock::submit(const int startOffset) { assert(_data != nullptr); RobotAudioStream::RobotAudioPacket packet(_data, _size, (_position - startOffset) * 2); return g_sci->_audio32->playRobotAudio(packet); } void RobotDecoder::AudioList::freeAudioBlocks() { while (_blocksSize != 0) { delete _blocks[_oldestBlockIndex]; _blocks[_oldestBlockIndex] = nullptr; ++_oldestBlockIndex; if (_oldestBlockIndex == kAudioListSize) { _oldestBlockIndex = 0; } --_blocksSize; } } bool RobotDecoder::primeAudio(const uint32 startTick) { bool success = true; _audioList.reset(); if (startTick == 0) { _audioList.prepareForPrimer(); byte *evenPrimerBuff = new byte[_evenPrimerSize]; byte *oddPrimerBuff = new byte[_oddPrimerSize]; success = readPrimerData(evenPrimerBuff, oddPrimerBuff); if (success) { if (_evenPrimerSize != 0) { _audioList.addBlock(0, _evenPrimerSize, evenPrimerBuff); } if (_oddPrimerSize != 0) { _audioList.addBlock(1, _oddPrimerSize, oddPrimerBuff); } } delete[] evenPrimerBuff; delete[] oddPrimerBuff; } else { assert(_evenPrimerSize * 2 >= _audioRecordInterval || _oddPrimerSize * 2 >= _audioRecordInterval); int audioStartFrame = 0; int videoStartFrame = startTick * _frameRate / 60; assert(videoStartFrame < _numFramesTotal); int audioStartPosition = (startTick * RobotAudioStream::kRobotSampleRate) / 60; if (audioStartPosition & 1) { audioStartPosition--; } _audioList.setAudioOffset(audioStartPosition); _audioList.prepareForPrimer(); if (audioStartPosition < _evenPrimerSize * 2 || audioStartPosition + 1 < _oddPrimerSize * 2) { byte *evenPrimerBuffer = new byte[_evenPrimerSize]; byte *oddPrimerBuffer = new byte[_oddPrimerSize]; success = readPrimerData(evenPrimerBuffer, oddPrimerBuffer); if (success) { int halfAudioStartPosition = audioStartPosition / 2; if (audioStartPosition < _evenPrimerSize * 2) { _audioList.addBlock(audioStartPosition, _evenPrimerSize - halfAudioStartPosition, &evenPrimerBuffer[halfAudioStartPosition]); } if (audioStartPosition + 1 < _oddPrimerSize * 2) { _audioList.addBlock(audioStartPosition + 1, _oddPrimerSize - halfAudioStartPosition, &oddPrimerBuffer[halfAudioStartPosition]); } } delete[] evenPrimerBuffer; delete[] oddPrimerBuffer; } if (audioStartPosition >= _firstAudioRecordPosition) { int audioRecordSize = _expectedAudioBlockSize; assert(audioRecordSize > 0); assert(_audioRecordInterval > 0); assert(_firstAudioRecordPosition >= 0); audioStartFrame = (audioStartPosition - _firstAudioRecordPosition) / _audioRecordInterval; assert(audioStartFrame < videoStartFrame); if (audioStartFrame > 0) { int lastAudioFrame = audioStartFrame - 1; int oddRemainder = lastAudioFrame & 1; int audioRecordStart = (lastAudioFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition; int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition; if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) { --audioStartFrame; } } assert(!(audioStartPosition & 1)); if (audioStartFrame & 1) { ++audioStartPosition; } if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition)) { return false; } ++audioStartFrame; assert(audioStartFrame < videoStartFrame); int oddRemainder = audioStartFrame & 1; int audioRecordStart = (audioStartFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition; int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition; if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) { if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition + 1)) { return false; } ++audioStartFrame; } } int audioPosition, audioSize; for (int i = audioStartFrame; i < videoStartFrame; i++) { if (!readAudioDataFromRecord(i, _audioBuffer, audioPosition, audioSize)) { break; } _audioList.addBlock(audioPosition, audioSize, _audioBuffer); } } return success; } bool RobotDecoder::readPrimerData(byte *outEvenBuffer, byte *outOddBuffer) { if (_primerReservedSize != 0) { if (_totalPrimerSize != 0) { _stream->seek(_primerPosition, SEEK_SET); if (_evenPrimerSize > 0) { _stream->read(outEvenBuffer, _evenPrimerSize); } if (_oddPrimerSize > 0) { _stream->read(outOddBuffer, _oddPrimerSize); } } } else if (_primerZeroCompressFlag) { memset(outEvenBuffer, 0, _evenPrimerSize); memset(outOddBuffer, 0, _oddPrimerSize); } else { error("ReadPrimerData - Flags corrupt"); } return !_stream->err(); } bool RobotDecoder::readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize) { _stream->seek(_recordPositions[frameNo] + _videoSizes[frameNo], SEEK_SET); _audioList.submitDriverMax(); // Compressed absolute position of the audio block in the audio stream const int position = _stream->readSint32(); // Size of the block of audio, excluding the audio block header int size = _stream->readSint32(); assert(size <= _expectedAudioBlockSize); if (position == 0) { return false; } if (size != _expectedAudioBlockSize) { memset(outBuffer, 0, kRobotZeroCompressSize); _stream->read(outBuffer + kRobotZeroCompressSize, size); size += kRobotZeroCompressSize; } else { _stream->read(outBuffer, size); } outAudioPosition = position; outAudioSize = size; return !_stream->err(); } bool RobotDecoder::readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition) { int audioPosition, audioSize; bool success = readAudioDataFromRecord(startFrame, _audioBuffer, audioPosition, audioSize); if (success) { const int relativeStartOffset = (startPosition - audioPosition) / 2; _audioList.addBlock(startPosition, audioSize - relativeStartOffset, _audioBuffer + relativeStartOffset); } return success; } #pragma mark - #pragma mark RobotDecoder - Rendering uint16 RobotDecoder::getFrameSize(Common::Rect &outRect) const { outRect.clip(0, 0); for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { ScreenItem &screenItem = *_screenItemList[i]; outRect.extend(screenItem.getNowSeenRect(*_plane)); } return _numFramesTotal; } void RobotDecoder::doRobot() { if (_status != kRobotStatusPlaying) { return; } if (!_syncFrame) { if (_cueForceShowFrame != -1) { _currentFrameNo = _cueForceShowFrame; _cueForceShowFrame = -1; } else { const int nextFrameNo = calculateNextFrameNo(_delayTime.predictedTicks()); if (nextFrameNo < _currentFrameNo) { return; } _currentFrameNo = nextFrameNo; } } if (_currentFrameNo >= _numFramesTotal) { const int finalFrameNo = _numFramesTotal - 1; if (_previousFrameNo == finalFrameNo) { _status = kRobotStatusEnd; if (_hasAudio) { _audioList.stopAudio(); _frameRate = _normalFrameRate; _hasAudio = false; } return; } else { _currentFrameNo = finalFrameNo; } } if (_currentFrameNo == _previousFrameNo) { _audioList.submitDriverMax(); return; } if (_hasAudio) { for (int candidateFrameNo = _previousFrameNo + _maxSkippablePackets + 1; candidateFrameNo < _currentFrameNo; candidateFrameNo += _maxSkippablePackets + 1) { _audioList.submitDriverMax(); int audioPosition, audioSize; if (readAudioDataFromRecord(candidateFrameNo, _audioBuffer, audioPosition, audioSize)) { _audioList.addBlock(audioPosition, audioSize, _audioBuffer); } } _audioList.submitDriverMax(); } _delayTime.startTiming(); seekToFrame(_currentFrameNo); doVersion5(); if (_hasAudio) { _audioList.submitDriverMax(); } } void RobotDecoder::frameAlmostVisible() { if (_status == kRobotStatusPlaying && !_syncFrame) { if (_previousFrameNo != _currentFrameNo) { while (calculateNextFrameNo() < _currentFrameNo) { _audioList.submitDriverMax(); } } } } void RobotDecoder::frameNowVisible() { if (_status != kRobotStatusPlaying) { return; } if (_syncFrame) { _syncFrame = false; if (_hasAudio) { _audioList.startAudioNow(); _checkAudioSyncTime = _startTime + kAudioSyncCheckInterval; } setRobotTime(_currentFrameNo); } if (_delayTime.timingInProgress()) { _delayTime.endTiming(); } if (_hasAudio) { _audioList.submitDriverMax(); } if (_previousFrameNo != _currentFrameNo) { _previousFrameNo = _currentFrameNo; } if (!_syncFrame && _hasAudio && getTickCount() >= _checkAudioSyncTime) { RobotAudioStream::StreamState status; const bool success = g_sci->_audio32->queryRobotAudio(status); if (!success) { return; } const int bytesPerFrame = status.rate / _normalFrameRate * (status.bits == 16 ? 2 : 1); // check again in 1/3rd second _checkAudioSyncTime = getTickCount() + 60 / 3; const int currentVideoFrameNo = calculateNextFrameNo() - _startingFrameNo; const int currentAudioFrameNo = status.bytesPlaying / bytesPerFrame; debugC(kDebugLevelVideo, "Video frame %d %s audio frame %d", currentVideoFrameNo, currentVideoFrameNo == currentAudioFrameNo ? "=" : currentVideoFrameNo < currentAudioFrameNo ? "<" : ">", currentAudioFrameNo); if (currentVideoFrameNo < _numFramesTotal && currentAudioFrameNo < _numFramesTotal) { bool shouldResetRobotTime = false; if (currentAudioFrameNo < currentVideoFrameNo - 1 && _frameRate != _minFrameRate) { debugC(kDebugLevelVideo, "[v] Reducing frame rate"); _frameRate = _minFrameRate; shouldResetRobotTime = true; } else if (currentAudioFrameNo > currentVideoFrameNo + 1 && _frameRate != _maxFrameRate) { debugC(kDebugLevelVideo, "[^] Increasing frame rate"); _frameRate = _maxFrameRate; shouldResetRobotTime = true; } else if (_frameRate != _normalFrameRate) { debugC(kDebugLevelVideo, "[=] Setting to normal frame rate"); _frameRate = _normalFrameRate; shouldResetRobotTime = true; } if (shouldResetRobotTime) { if (currentAudioFrameNo < _currentFrameNo) { setRobotTime(_currentFrameNo); } else { setRobotTime(currentAudioFrameNo); } } } } } void RobotDecoder::expandCel(byte* target, const byte* source, const int16 celWidth, const int16 celHeight) const { assert(source != nullptr && target != nullptr); const int sourceHeight = (celHeight * _verticalScaleFactor) / 100; assert(sourceHeight > 0); const int16 numerator = celHeight; const int16 denominator = sourceHeight; int remainder = 0; for (int16 y = sourceHeight - 1; y >= 0; --y) { remainder += numerator; int16 linesToDraw = remainder / denominator; remainder %= denominator; while (linesToDraw--) { memcpy(target, source, celWidth); target += celWidth; } source += celWidth; } } void RobotDecoder::setPriority(const int16 newPriority) { _priority = newPriority; } void RobotDecoder::doVersion5(const bool shouldSubmitAudio) { const RobotScreenItemList::size_type oldScreenItemCount = _screenItemList.size(); const int videoSize = _videoSizes[_currentFrameNo]; _doVersion5Scratch.resize(videoSize); byte *videoFrameData = _doVersion5Scratch.begin(); if (!_stream->read(videoFrameData, videoSize)) { error("RobotDecoder::doVersion5: Read error"); } const RobotScreenItemList::size_type screenItemCount = READ_SCI11ENDIAN_UINT16(videoFrameData); if (screenItemCount > kScreenItemListSize) { return; } if (_hasAudio && (getSciVersion() < SCI_VERSION_3 || shouldSubmitAudio)) { int audioPosition, audioSize; if (readAudioDataFromRecord(_currentFrameNo, _audioBuffer, audioPosition, audioSize)) { _audioList.addBlock(audioPosition, audioSize, _audioBuffer); } } if (screenItemCount > oldScreenItemCount) { _screenItemList.resize(screenItemCount); _screenItemX.resize(screenItemCount); _screenItemY.resize(screenItemCount); _originalScreenItemX.resize(screenItemCount); _originalScreenItemY.resize(screenItemCount); } createCels5(videoFrameData + 2, screenItemCount, true); for (RobotScreenItemList::size_type i = 0; i < screenItemCount; ++i) { Common::Point position(_screenItemX[i], _screenItemY[i]); // TODO: Version 6 robot? // int scaleXRemainder; if (_scaleInfo.signal == kScaleSignalManual) { position.x = (position.x * _scaleInfo.x) / 128; // TODO: Version 6 robot? // scaleXRemainder = (position.x * _scaleInfo.x) % 128; position.y = (position.y * _scaleInfo.y) / 128; } if (_screenItemList[i] == nullptr) { CelInfo32 celInfo; celInfo.bitmap = _celHandles[i].bitmapId; ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo, position, _scaleInfo); _screenItemList[i] = screenItem; // TODO: Version 6 robot? // screenItem->_field_30 = scaleXRemainder; if (_priority == -1) { screenItem->_fixedPriority = false; } else { screenItem->_fixedPriority = true; screenItem->_priority = _priority; } g_sci->_gfxFrameout->addScreenItem(*screenItem); } else { ScreenItem *screenItem = _screenItemList[i]; screenItem->_celInfo.bitmap = _celHandles[i].bitmapId; screenItem->_position = position; // TODO: Version 6 robot? // screenItem->_field_30 = scaleXRemainder; if (_priority == -1) { screenItem->_fixedPriority = false; } else { screenItem->_fixedPriority = true; screenItem->_priority = _priority; } g_sci->_gfxFrameout->updateScreenItem(*screenItem); } } for (RobotScreenItemList::size_type i = screenItemCount; i < oldScreenItemCount; ++i) { if (_screenItemList[i] != nullptr) { g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]); _screenItemList[i] = nullptr; } } } void RobotDecoder::createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette) { preallocateCelMemory(rawVideoData, numCels); for (int16 i = 0; i < numCels; ++i) { rawVideoData += createCel5(rawVideoData, i, usePalette); } } uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette) { _verticalScaleFactor = rawVideoData[1]; const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2); const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4); const Common::Point celPosition((int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 10), (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 12)); const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14); const int16 numDataChunks = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 16); rawVideoData += kCelHeaderSize; const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; Common::Point origin; if (scriptWidth == kLowResX && scriptHeight == kLowResY) { const Ratio lowResToScreenX(screenWidth, kLowResX); const Ratio lowResToScreenY(screenHeight, kLowResY); const Ratio screenToLowResX(kLowResX, screenWidth); const Ratio screenToLowResY(kLowResY, screenHeight); const int16 scaledX = celPosition.x + (_position.x * lowResToScreenX).toInt(); const int16 scaledY1 = celPosition.y + (_position.y * lowResToScreenY).toInt(); const int16 scaledY2 = scaledY1 + celHeight - 1; const int16 lowResX = (scaledX * screenToLowResX).toInt(); const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); origin.x = (scaledX - (lowResX * lowResToScreenX).toInt()) * -1; origin.y = (lowResY * lowResToScreenY).toInt() - scaledY1; _screenItemX[screenItemIndex] = lowResX; _screenItemY[screenItemIndex] = lowResY; debugC(kDebugLevelVideo, "Low resolution position c: %d %d l: %d/%d %d/%d d: %d %d s: %d/%d %d/%d x: %d y: %d", celPosition.x, celPosition.y, lowResX, scriptWidth, lowResY, scriptHeight, origin.x, origin.y, scaledX, screenWidth, scaledY2, screenHeight, scaledX - origin.x, scaledY2 - origin.y); } else { const int16 highResX = celPosition.x + _position.x; const int16 highResY = celPosition.y + _position.y + celHeight - 1; origin.x = 0; origin.y = celHeight - 1; _screenItemX[screenItemIndex] = highResX; _screenItemY[screenItemIndex] = highResY; debugC(kDebugLevelVideo, "High resolution position c: %d %d s: %d %d d: %d %d", celPosition.x, celPosition.y, highResX, highResY, origin.x, origin.y); } _originalScreenItemX[screenItemIndex] = celPosition.x; _originalScreenItemY[screenItemIndex] = celPosition.y; assert(_celHandles[screenItemIndex].area >= celWidth * celHeight); SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[screenItemIndex].bitmapId); assert(bitmap.getWidth() == celWidth && bitmap.getHeight() == celHeight); assert(bitmap.getXResolution() == _xResolution && bitmap.getYResolution() == _yResolution); assert(bitmap.getHunkPaletteOffset() == (uint32)bitmap.getWidth() * bitmap.getHeight() + SciBitmap::getBitmapHeaderSize()); bitmap.setOrigin(origin); byte *targetBuffer = nullptr; if (_verticalScaleFactor == 100) { // direct copy to bitmap targetBuffer = bitmap.getPixels(); } else { // go through squashed cel decompressor _celDecompressionBuffer.resize(_celDecompressionArea >= celWidth * (celHeight * _verticalScaleFactor / 100)); targetBuffer = _celDecompressionBuffer.begin(); } for (int i = 0; i < numDataChunks; ++i) { uint compressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData); uint decompressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData + 4); uint16 compressionType = READ_SCI11ENDIAN_UINT16(rawVideoData + 8); rawVideoData += 10; switch (compressionType) { case kCompressionLZS: { Common::MemoryReadStream videoDataStream(rawVideoData, compressedSize, DisposeAfterUse::NO); _decompressor.unpack(&videoDataStream, targetBuffer, compressedSize, decompressedSize); break; } case kCompressionNone: Common::copy(rawVideoData, rawVideoData + decompressedSize, targetBuffer); break; default: error("Unknown compression type %d!", compressionType); } rawVideoData += compressedSize; targetBuffer += decompressedSize; } if (_verticalScaleFactor != 100) { expandCel(bitmap.getPixels(), _celDecompressionBuffer.begin(), celWidth, celHeight); } if (usePalette) { Common::copy(_rawPalette, _rawPalette + kRawPaletteSize, bitmap.getHunkPalette()); } return kCelHeaderSize + dataSize; } void RobotDecoder::preallocateCelMemory(const byte *rawVideoData, const int16 numCels) { for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) { CelHandleInfo &celHandle = _celHandles[i]; if (celHandle.status == CelHandleInfo::kFrameLifetime) { _segMan->freeBitmap(celHandle.bitmapId); celHandle.bitmapId = NULL_REG; celHandle.status = CelHandleInfo::kNoCel; celHandle.area = 0; } } _celHandles.resize(numCels); const int numFixedCels = MIN(numCels, (int16)kFixedCelListSize); for (int i = 0; i < numFixedCels; ++i) { CelHandleInfo &celHandle = _celHandles[i]; // NOTE: There was a check to see if the cel handle was not allocated // here, for some reason, which would mean that nothing was ever // allocated from fixed cels, because the _celHandles array just got // deleted and recreated... if (celHandle.bitmapId == NULL_REG) { break; } celHandle.bitmapId = _fixedCels[i]; celHandle.status = CelHandleInfo::kRobotLifetime; celHandle.area = _maxCelArea[i]; } uint maxFrameArea = 0; for (int i = 0; i < numCels; ++i) { const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2); const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4); const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14); const uint area = celWidth * celHeight; if (area > maxFrameArea) { maxFrameArea = area; } CelHandleInfo &celHandle = _celHandles[i]; if (celHandle.status == CelHandleInfo::kRobotLifetime) { if (_maxCelArea[i] < area) { _segMan->freeBitmap(celHandle.bitmapId); _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false); celHandle.area = area; celHandle.status = CelHandleInfo::kFrameLifetime; } } else if (celHandle.status == CelHandleInfo::kNoCel) { _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false); celHandle.area = area; celHandle.status = CelHandleInfo::kFrameLifetime; } else { error("Cel Handle has bad status"); } rawVideoData += kCelHeaderSize + dataSize; } if (maxFrameArea > _celDecompressionBuffer.size()) { _celDecompressionBuffer.resize(maxFrameArea); } } } // End of namespace Sci