mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-19 16:18:45 +00:00
482 lines
13 KiB
C++
482 lines
13 KiB
C++
/* 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.
|
|
*
|
|
*/
|
|
|
|
// Based on Deniz Oezmen's code: http://oezmen.eu/
|
|
|
|
#include "lastexpress/data/sequence.h"
|
|
|
|
#include "lastexpress/debug.h"
|
|
|
|
#include "common/stream.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, bool ignoreSubtype) : _palette(NULL), _ignoreSubtype(ignoreSubtype) {
|
|
_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("[AnimFrame::AnimFrame] 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("[Sequence::load] Couldn't seek to the current frame data");
|
|
|
|
// Check if there is enough data
|
|
if ((unsigned)(_stream->size() - _stream->pos()) < _sequenceFrameSize)
|
|
error("[Sequence::load] 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("[Sequence::getFrameInfo] Trying to decode a sequence before loading its data");
|
|
|
|
if (index > _frames.size() - 1)
|
|
error("[Sequence::getFrameInfo] 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::getInfo] 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
|