/* 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$ * */ // Based on Deniz Oezmen's code: http://oezmen.eu/ #include "lastexpress/data/sequence.h" #include "lastexpress/debug.h" #include "common/stream.h" #include "common/textconsole.h" namespace LastExpress { void FrameInfo::read(Common::SeekableReadStream *in, bool isSequence) { // Save the current position int32 basePos = in->pos(); dataOffset = in->readUint32LE(); unknown = in->readUint32LE(); paletteOffset = in->readUint32LE(); xPos1 = in->readUint32LE(); yPos1 = in->readUint32LE(); xPos2 = in->readUint32LE(); yPos2 = in->readUint32LE(); initialSkip = in->readUint32LE(); decompressedEndOffset = in->readUint32LE(); // Read the compression type for NIS files if (!isSequence) { in->seek(basePos + 0x124); } else { hotspot.left = (int16)in->readUint16LE(); hotspot.right = (int16)in->readUint16LE(); hotspot.top = (int16)in->readUint16LE(); hotspot.bottom = (int16)in->readUint16LE(); } compressionType = in->readByte(); subType = (FrameSubType)in->readByte(); // Sequence information field_2E = in->readByte(); keepPreviousFrame = in->readByte(); field_30 = in->readByte(); field_31 = in->readByte(); soundAction = in->readByte(); field_33 = in->readByte(); position = in->readByte(); field_35 = in->readByte(); field_36 = in->readUint16LE(); field_38 = in->readUint32LE(); entityPosition = (EntityPosition)in->readUint16LE(); location = in->readUint16LE(); next = in->readUint32LE(); } // AnimFrame AnimFrame::AnimFrame(Common::SeekableReadStream *in, const FrameInfo &f) : _palette(NULL) { _palSize = 1; // TODO: use just the needed rectangle _image.create(640, 480, Graphics::PixelFormat::createFormatCLUT8()); //debugC(6, kLastExpressDebugGraphics, " Offsets: data=%d, unknown=%d, palette=%d", f.dataOffset, f.unknown, f.paletteOffset); //debugC(6, kLastExpressDebugGraphics, " Position: (%d, %d) - (%d, %d)", f.xPos1, f.yPos1, f.xPos2, f.yPos2); //debugC(6, kLastExpressDebugGraphics, " Initial Skip: %d", f.initialSkip); //debugC(6, kLastExpressDebugGraphics, " Decompressed end offset: %d", f.decompressedEndOffset); //debugC(6, kLastExpressDebugGraphics, " Hotspot: (%d, %d) x (%d, %d)\n", f.hotspot.left, f.hotspot.top, f.hotspot.right, f.hotspot.bottom); //debugC(6, kLastExpressDebugGraphics, " Compression type: %u / %u", f.compressionType, f.subType); //debugC(6, kLastExpressDebugGraphics, " Unknown: %u - %u - %u - %u - %u - %u - %u - %d", f.field_2E, f.field_2F, f.field_30, f.field_31, f.field_33, f.field_35, f.field_36, f.field_38); //debugC(6, kLastExpressDebugGraphics, " Sound action: %u", f.soundAction); //debugC(6, kLastExpressDebugGraphics, " Position: %d", f.position); //debugC(6, kLastExpressDebugGraphics, " Entity Position: %d", f.entityPosition); //debugC(6, kLastExpressDebugGraphics, " Location: %d", f.location); //debugC(6, kLastExpressDebugGraphics, " next: %d", f.next); switch (f.compressionType) { case 0: // Empty frame break; case 3: decomp3(in, f); break; case 4: decomp4(in, f); break; case 5: decomp5(in, f); break; case 7: decomp7(in, f); break; case 255: decompFF(in, f); break; default: error("Unknown frame compression: %d", f.compressionType); } readPalette(in, f); _rect = Common::Rect((int16)f.xPos1, (int16)f.yPos1, (int16)f.xPos2, (int16)f.yPos2); //_rect.debugPrint(0, "Frame rect:"); } AnimFrame::~AnimFrame() { _image.free(); delete[] _palette; } Common::Rect AnimFrame::draw(Graphics::Surface *s) { byte *inp = (byte *)_image.pixels; uint16 *outp = (uint16 *)s->pixels; for (int i = 0; i < 640 * 480; i++, inp++, outp++) { if (*inp) *outp = _palette[*inp]; } return _rect; } void AnimFrame::readPalette(Common::SeekableReadStream *in, const FrameInfo &f) { // Read the palette in->seek((int)f.paletteOffset); _palette = new uint16[_palSize]; for (uint32 i = 0; i < _palSize; i++) { _palette[i] = in->readUint16LE(); } } void AnimFrame::decomp3(Common::SeekableReadStream *in, const FrameInfo &f) { decomp34(in, f, 0x7, 3); } void AnimFrame::decomp4(Common::SeekableReadStream *in, const FrameInfo &f) { decomp34(in, f, 0xf, 4); } void AnimFrame::decomp34(Common::SeekableReadStream *in, const FrameInfo &f, byte mask, byte shift) { byte *p = (byte *)_image.getBasePtr(0, 0); uint32 skip = f.initialSkip / 2; uint32 size = f.decompressedEndOffset / 2; //warning("skip: %d, %d", skip % 640, skip / 640); //warning("size: %d, %d", size % 640, size / 640); //assert (f.yPos1 == skip / 640); //assert (f.yPos2 == size / 640); uint32 numBlanks = 640 - (f.xPos2 - f.xPos1); in->seek((int)f.dataOffset); for (uint32 out = skip; out < size; ) { uint16 opcode = in->readByte(); if (opcode & 0x80) { if (opcode & 0x40) { opcode &= 0x3f; out += numBlanks + opcode + 1; } else { opcode &= 0x3f; if (opcode & 0x20) { opcode = ((opcode & 0x1f) << 8) + in->readByte(); if (opcode & 0x1000) { out += opcode & 0xfff; continue; } } out += opcode + 2; } } else { byte value = opcode & mask; opcode >>= shift; if (_palSize <= value) _palSize = value + 1; if (!opcode) opcode = in->readByte(); for (int i = 0; i < opcode; i++, out++) { p[out] = value; } } } } void AnimFrame::decomp5(Common::SeekableReadStream *in, const FrameInfo &f) { byte *p = (byte *)_image.getBasePtr(0, 0); uint32 skip = f.initialSkip / 2; uint32 size = f.decompressedEndOffset / 2; //warning("skip: %d, %d", skip % 640, skip / 640); //warning("size: %d, %d", size % 640, size / 640); //assert (f.yPos1 == skip / 640); //assert (f.yPos2 == size / 640); in->seek((int)f.dataOffset); for (uint32 out = skip; out < size; ) { uint16 opcode = in->readByte(); if (!(opcode & 0x1f)) { opcode = (uint16)((opcode << 3) + in->readByte()); if (opcode & 0x400) { // skip these 10 bits out += (opcode & 0x3ff); } else { out += opcode + 2; } } else { byte value = opcode & 0x1f; opcode >>= 5; if (_palSize <= value) _palSize = value + 1; if (!opcode) opcode = in->readByte(); for (int i = 0; i < opcode; i++, out++) { p[out] = value; } } } } void AnimFrame::decomp7(Common::SeekableReadStream *in, const FrameInfo &f) { byte *p = (byte *)_image.getBasePtr(0, 0); uint32 skip = f.initialSkip / 2; uint32 size = f.decompressedEndOffset / 2; //warning("skip: %d, %d", skip % 640, skip / 640); //warning("size: %d, %d", size % 640, size / 640); //assert (f.yPos1 == skip / 640); //assert (f.yPos2 == size / 640); uint32 numBlanks = 640 - (f.xPos2 - f.xPos1); in->seek((int)f.dataOffset); for (uint32 out = skip; out < size; ) { uint16 opcode = in->readByte(); if (opcode & 0x80) { if (opcode & 0x40) { if (opcode & 0x20) { opcode &= 0x1f; out += numBlanks + opcode + 1; } else { opcode &= 0x1f; if (opcode & 0x10) { opcode = ((opcode & 0xf) << 8) + in->readByte(); if (opcode & 0x800) { // skip these 11 bits out += (opcode & 0x7ff); continue; } } // skip these 4 bits out += opcode + 2; } } else { opcode &= 0x3f; byte value = in->readByte(); if (_palSize <= value) _palSize = value + 1; for (int i = 0; i < opcode; i++, out++) { p[out] = value; } } } else { if (_palSize <= opcode) _palSize = opcode + 1; // set the given value p[out] = (byte)opcode; out++; } } } void AnimFrame::decompFF(Common::SeekableReadStream *in, const FrameInfo &f) { byte *p = (byte *)_image.getBasePtr(0, 0); uint32 skip = f.initialSkip / 2; uint32 size = f.decompressedEndOffset / 2; in->seek((int)f.dataOffset); for (uint32 out = skip; out < size; ) { uint16 opcode = in->readByte(); if (opcode < 0x80) { if (_palSize <= opcode) _palSize = opcode + 1; // set the given value p[out] = (byte)opcode; out++; } else { if (opcode < 0xf0) { if (opcode < 0xe0) { // copy old part uint32 old = out + ((opcode & 0x7) << 8) + in->readByte() - 2048; opcode = ((opcode >> 3) & 0xf) + 3; for (int i = 0; i < opcode; i++, out++, old++) { p[out] = p[old]; } } else { opcode = (opcode & 0xf) + 1; byte value = in->readByte(); if (_palSize <= value) _palSize = value + 1; for (int i = 0; i < opcode; i++, out++) { p[out] = value; } } } else { out += ((opcode & 0xf) << 8) + in->readByte(); } } } } ////////////////////////////////////////////////////////////////////////// // SEQUENCE ////////////////////////////////////////////////////////////////////////// Sequence::~Sequence() { reset(); } void Sequence::reset() { _frames.clear(); delete _stream; _stream = NULL; } Sequence *Sequence::load(Common::String name, Common::SeekableReadStream *stream, byte field30) { Sequence *sequence = new Sequence(name); if (!sequence->load(stream, field30)) { delete sequence; return NULL; } return sequence; } bool Sequence::load(Common::SeekableReadStream *stream, byte field30) { if (!stream) return false; // Reset data reset(); _field30 = field30; // Keep stream for later decoding of sequence _stream = stream; // Read header to get the number of frames _stream->seek(0); uint32 numframes = _stream->readUint32LE(); uint32 unknown = _stream->readUint32LE(); debugC(3, kLastExpressDebugGraphics, "Number of frames in sequence: %d / unknown=0x%x", numframes, unknown); // Store frames information for (uint i = 0; i < numframes; i++) { // Move stream to start of frame _stream->seek((int32)(_sequenceHeaderSize + i * _sequenceFrameSize), SEEK_SET); if (_stream->eos()) error("Couldn't seek to the current frame data"); // Check if there is enough data if ((unsigned)(_stream->size() - _stream->pos()) < _sequenceFrameSize) error("The sequence frame does not have a valid header"); FrameInfo info; info.read(_stream, true); _frames.push_back(info); } _isLoaded = true; return true; } FrameInfo *Sequence::getFrameInfo(uint16 index) { if (_frames.size() == 0) error("Trying to decode a sequence before loading its data"); if (index > _frames.size() - 1) error("Invalid sequence frame requested: %d, max %d", index, _frames.size() - 1); return &_frames[index]; } AnimFrame *Sequence::getFrame(uint16 index) { FrameInfo *frame = getFrameInfo(index); if (!frame) return NULL; // Skip "invalid" frames if (frame->compressionType == 0) return NULL; debugC(9, kLastExpressDebugGraphics, "Decoding sequence %s: frame %d / %d", _name.c_str(), index, _frames.size() - 1); return new AnimFrame(_stream, *frame); } ////////////////////////////////////////////////////////////////////////// // SequenceFrame SequenceFrame::~SequenceFrame() { if (_dispose && _sequence) { delete _sequence; } _sequence = NULL; } Common::Rect SequenceFrame::draw(Graphics::Surface *surface) { if (!_sequence || _frame >= _sequence->count()) return Common::Rect(); AnimFrame *f = _sequence->getFrame(_frame); if (!f) return Common::Rect(); Common::Rect rect = f->draw(surface); delete f; return rect; } bool SequenceFrame::setFrame(uint16 frame) { if (!_sequence) return false; if (frame < _sequence->count()) { _frame = frame; return true; } else return false; } bool SequenceFrame::nextFrame() { return setFrame(_frame + 1); } FrameInfo *SequenceFrame::getInfo() { if (!_sequence) error("SequenceFrame::getFrameInfo: Invalid sequence!"); return _sequence->getFrameInfo(_frame); } Common::String SequenceFrame::getName() { if (!_sequence) error("SequenceFrame::getName: Invalid sequence!"); return _sequence->getName(); } bool SequenceFrame::equal(const SequenceFrame *other) const { return _sequence->getName() == other->_sequence->getName() && _frame == other->_frame; } } // End of namespace LastExpress