Files
2026-02-02 04:50:13 +01:00

287 lines
8.2 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/gfx/shape.h"
#include "ultima/ultima8/gfx/shape_frame.h"
#include "ultima/ultima8/gfx/raw_shape_frame.h"
#include "ultima/ultima8/convert/u8/convert_shape_u8.h"
#include "ultima/ultima8/convert/crusader/convert_shape_crusader.h"
#include "ultima/ultima8/misc/stream_util.h"
#include "common/memstream.h"
namespace Ultima {
namespace Ultima8 {
Shape::Shape(const uint8 *data, uint32 size, const ConvertShapeFormat *format,
const uint16 id, const uint32 shape)
: _flexId(id), _shapeNum(shape), _palette(nullptr) {
// NB: U8 style!
loadFrames(data, size, format);
delete[] const_cast<uint8 *>(data);
}
Shape::Shape(Common::SeekableReadStream *src, const ConvertShapeFormat *format)
: _flexId(0), _shapeNum(0), _palette(nullptr) {
// NB: U8 style!
uint32 size = src->size();
uint8 *data = new uint8[size];
src->read(data, size);
loadFrames(data, size, format);
delete[] data;
}
Shape::~Shape() {
for (uint i = 0; i < _frames.size(); ++i)
delete _frames[i];
}
void Shape::loadFrames(const uint8 *data, uint32 size, const ConvertShapeFormat *format) {
if (!format)
format = DetectShapeFormat(data, size);
if (!format) {
// Should be fatal?
warning("Unable to detect shape format");
return;
}
Common::Array<RawShapeFrame *> rawframes;
// Load it as u8
if (format == &U8ShapeFormat || format == &U82DShapeFormat)
rawframes = loadU8Format(data, size, format);
else if (format == &PentagramShapeFormat)
rawframes = loadPentagramFormat(data, size, format);
else
rawframes = loadGenericFormat(data, size, format);
for (uint i = 0; i < rawframes.size(); i++) {
_frames.push_back(new ShapeFrame(rawframes[i]));
delete rawframes[i];
}
}
void Shape::getShapeId(uint16 &id, uint32 &shape) const {
id = _flexId;
shape = _shapeNum;
}
// This will load a u8 style shape 'optimized'.
Common::Array<RawShapeFrame *> Shape::loadU8Format(const uint8 *data, uint32 size, const ConvertShapeFormat *format) {
Common::MemoryReadStream stream(data, size);
stream.skip(4); // skip header
unsigned int framecount = stream.readUint16LE();
Common::Array<RawShapeFrame *> frames;
if (framecount == 0) {
return loadGenericFormat(data, size, format);
}
frames.reserve(framecount);
for (uint i = 0; i < framecount; ++i) {
uint32 frameoffset = stream.readUint32LE() & 0xFFFFFF;
uint32 framesize = stream.readUint16LE();
frames.push_back(new RawShapeFrame(data + frameoffset, framesize, format));
}
return frames;
}
// This will load a pentagram style shape 'optimized'.
Common::Array<RawShapeFrame *> Shape::loadPentagramFormat(const uint8 *data, uint32 size, const ConvertShapeFormat *format) {
Common::MemoryReadStream stream(data, size);
stream.skip(4); // skip header
unsigned int framecount = stream.readUint16LE();
Common::Array<RawShapeFrame *> frames;
if (framecount == 0) {
return loadGenericFormat(data, size, format);
}
frames.reserve(framecount);
for (uint i = 0; i < framecount; ++i) {
uint32 frameoffset = stream.readUint32LE();
uint32 framesize = stream.readUint32LE();
frames.push_back(new RawShapeFrame(data + frameoffset, framesize, format));
}
return frames;
}
// This will load any sort of shape via a ConvertShapeFormat struct
Common::Array<RawShapeFrame *> Shape::loadGenericFormat(const uint8 *data, uint32 size, const ConvertShapeFormat *format) {
uint32 framecount;
uint32 frameoffset;
uint32 framesize;
Common::MemoryReadStream ds(data, size);
Common::Array<RawShapeFrame *> frames;
if (format->_bytes_ident) {
uint8 *ident = new uint8[format->_bytes_ident];
ds.read(ident, format->_bytes_ident);
bool match = memcmp(ident, format->_ident, format->_bytes_ident) == 0;
delete[] ident;
if (!match) {
frames.clear();
return frames;
}
}
// Read special buffer
uint8 special[256];
if (format->_bytes_special) {
memset(special, 0, 256);
for (uint32 i = 0; i < format->_bytes_special; i++) special[ds.readByte() & 0xFF] = i + 2;
}
// Skip unknown
if (format->_bytes_header_unk && format != &Crusader2DShapeFormat) {
//uint32 val =
readX(ds, format->_bytes_header_unk);
//uint16 lowval = val & 0xff;
//uint16 highval = (val >> 16) & 0xff;
//uint32 dummy = 0 + lowval + highval + val;
} else {
// Appears to be shape Width x Height for Crusader 2D shapes,
// not needed - we get them by frame.
ds.skip(format->_bytes_header_unk);
}
// Read framecount, default 1 if no
if (format->_bytes_num_frames) framecount = readX(ds, format->_bytes_num_frames);
else framecount = 1;
if (framecount == 0) framecount = ConvertShape::CalcNumFrames(ds, format, size, 0);
frames.reserve(framecount);
for (uint i = 0; i < framecount; ++i) {
// Read the offset
if (format->_bytes_frame_offset) frameoffset = readX(ds, format->_bytes_frame_offset) + format->_bytes_special;
else frameoffset = format->_len_header + (format->_len_frameheader * i);
// Skip the unknown
if (format->_bytes_frameheader_unk) {
readX(ds, format->_bytes_frameheader_unk);
}
// Read frame_length
if (format->_bytes_frame_length) framesize = readX(ds, format->_bytes_frame_length) + format->_bytes_frame_length_kludge;
else framesize = size - frameoffset;
if (framesize > size) {
warning("shape frame %d goes off the end of the buffer, stopping early", i);
break;
}
ConvertShapeFrame *prev = nullptr, p;
if (format->_bytes_special && i > 0) {
prev = &p;
frames[i - 1]->getConvertShapeFrame(p);
}
frames.push_back(new RawShapeFrame(data + frameoffset, framesize, format, special, prev));
}
return frames;
}
// This will detect the format of a shape
const ConvertShapeFormat *Shape::DetectShapeFormat(const uint8 *data, uint32 size) {
Common::MemoryReadStream ds(data, size);
return Shape::DetectShapeFormat(ds, size);
}
const ConvertShapeFormat *Shape::DetectShapeFormat(Common::SeekableReadStream &ds, uint32 size) {
const ConvertShapeFormat *ret = nullptr;
if (ConvertShape::CheckUnsafe(ds, &PentagramShapeFormat, size))
ret = &PentagramShapeFormat;
else if (ConvertShape::CheckUnsafe(ds, &U8SKFShapeFormat, size))
ret = &U8SKFShapeFormat;
else if (ConvertShape::CheckUnsafe(ds, &U8ShapeFormat, size))
ret = &U8ShapeFormat;
else if (ConvertShape::CheckUnsafe(ds, &U82DShapeFormat, size))
ret = &U82DShapeFormat;
else if (ConvertShape::CheckUnsafe(ds, &CrusaderShapeFormat, size))
ret = &CrusaderShapeFormat;
else if (ConvertShape::CheckUnsafe(ds, &Crusader2DShapeFormat, size))
ret = &Crusader2DShapeFormat;
else if (ConvertShape::CheckUnsafe(ds, &U8CMPShapeFormat, size))
ret = &U8CMPShapeFormat;
return ret;
}
void Shape::getTotalDimensions(int32 &w, int32 &h, int32 &x, int32 &y) const {
if (_frames.empty()) {
w = 0;
h = 0;
x = 0;
y = 0;
return;
}
int32 minx = 1000000, maxx = -1000000;
int32 miny = 1000000, maxy = -1000000;
for (uint i = 0; i < _frames.size(); ++i) {
ShapeFrame *frame = _frames[i];
if (-frame->_xoff < minx)
minx = -frame->_xoff;
if (-frame->_yoff < miny)
miny = -frame->_yoff;
if (frame->_width - frame->_xoff - 1 > maxx)
maxx = frame->_width - frame->_xoff - 1;
if (frame->_height - frame->_yoff - 1 > maxy)
maxy = frame->_height - frame->_yoff - 1;
}
w = maxx - minx + 1;
h = maxy - miny + 1;
x = -minx;
y = -miny;
}
const ShapeFrame *Shape::getFrame(unsigned int frame) const {
if (frame < _frames.size())
return _frames[frame];
else
return nullptr;
}
} // End of namespace Ultima8
} // End of namespace Ultima