Files
scummvm-cursorfix/engines/mediastation/datafile.cpp
2026-02-02 04:50:13 +01:00

332 lines
10 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 "mediastation/datafile.h"
#include "mediastation/debugchannels.h"
#include "mediastation/mediastation.h"
namespace MediaStation {
void ParameterReadStream::readAndVerifyType(DatumType type) {
DatumType actualType = static_cast<DatumType>(readUint16LE());
if (actualType != type) {
error("%s: Expected datum type %d, got %d (@0x%llx)", __func__, type, actualType, static_cast<long long int>(pos()));
}
}
byte ParameterReadStream::readTypedByte() {
readAndVerifyType(kDatumTypeUint8);
return readByte();
}
uint16 ParameterReadStream::readTypedUint16() {
readAndVerifyType(kDatumTypeUint16);
return readUint16LE();
}
uint32 ParameterReadStream::readTypedUint32() {
readAndVerifyType(kDatumTypeUint32);
return readUint32LE();
}
int8 ParameterReadStream::readTypedSByte() {
readAndVerifyType(kDatumTypeInt8);
return readSByte();
}
int16 ParameterReadStream::readTypedSint16() {
readAndVerifyType(kDatumTypeInt16);
return readSint16LE();
}
int32 ParameterReadStream::readTypedSint32() {
readAndVerifyType(kDatumTypeInt32);
return readSint32LE();
}
float ParameterReadStream::readTypedFloat() {
readAndVerifyType(kDatumTypeFloat);
return readFloatLE();
}
double ParameterReadStream::readTypedDouble() {
readAndVerifyType(kDatumTypeDouble);
return readDoubleLE();
}
Common::String ParameterReadStream::readTypedFilename() {
readAndVerifyType(kDatumTypeFilename);
uint size = readTypedUint32();
return readString('\0', size);
}
Common::Rect ParameterReadStream::readTypedRect() {
readAndVerifyType(kDatumTypeRect);
Common::Point leftTop = readTypedPoint();
Common::Point dimensions = readTypedGraphicSize();
return Common::Rect(leftTop, dimensions.x, dimensions.y);
}
Common::Point ParameterReadStream::readTypedPoint() {
readAndVerifyType(kDatumTypePoint);
int16 x = readTypedGraphicUnit();
int16 y = readTypedGraphicUnit();
return Common::Point(x, y);
}
Common::Point ParameterReadStream::readTypedGraphicSize() {
readAndVerifyType(kDatumTypeGraphicSize);
int16 width = readTypedGraphicUnit();
int16 height = readTypedGraphicUnit();
return Common::Point(width, height);
}
int16 ParameterReadStream::readTypedGraphicUnit() {
readAndVerifyType(kDatumTypeGraphicUnit);
return readSint16LE();
}
double ParameterReadStream::readTypedTime() {
readAndVerifyType(kDatumTypeTime);
return readDoubleLE();
}
Common::String ParameterReadStream::readTypedString() {
readAndVerifyType(kDatumTypeString);
uint size = readTypedUint32();
return readString('\0', size);
}
VersionInfo ParameterReadStream::readTypedVersion() {
readAndVerifyType(kDatumTypeVersion);
VersionInfo version;
version.major = readTypedUint16();
version.minor = readTypedUint16();
version.patch = readTypedUint16();
return version;
}
ChannelIdent ParameterReadStream::readTypedChannelIdent() {
readAndVerifyType(kDatumTypeChannelIdent);
// This one is always BE.
return readUint32BE();
}
Polygon ParameterReadStream::readTypedPolygon() {
Polygon polygon;
uint totalPoints = readTypedUint16();
for (uint i = 0; i < totalPoints; ++i) {
Common::Point point = readTypedGraphicSize();
polygon.push_back(point);
}
return polygon;
}
Chunk::Chunk(Common::SeekableReadStream *stream) : _parentStream(stream) {
_id = _parentStream->readUint32BE();
_length = _parentStream->readUint32LE();
_dataStartOffset = pos();
_dataEndOffset = _dataStartOffset + _length;
debugC(5, kDebugLoading, "Chunk::Chunk(): Got chunk with ID \"%s\" and size 0x%x", tag2str(_id), _length);
}
uint32 Chunk::bytesRemaining() {
return _dataEndOffset - pos();
}
uint32 Chunk::read(void *dataPtr, uint32 dataSize) {
if (pos() > _dataEndOffset) {
uint overrun = pos() - _dataEndOffset;
error("%s: Attempted to read 0x%x bytes at a location 0x%x bytes past end of chunk (@0x%llx)", __func__, dataSize, overrun, static_cast<long long int>(pos()));
} else {
return _parentStream->read(dataPtr, dataSize);
}
}
bool Chunk::seek(int64 offset, int whence) {
bool result = _parentStream->seek(offset, whence);
if (result == false)
return false;
if (pos() < _dataStartOffset) {
uint overrun = _dataStartOffset - offset;
error("Attempted to seek 0x%x bytes before start of chunk (@0x%llx)", overrun, static_cast<long long int>(pos()));
} else if (pos() > _dataEndOffset) {
uint overrun = offset - _dataEndOffset;
error("Attempted to seek 0x%x bytes past end of chunk (@0x%llx)", overrun, static_cast<long long int>(pos()));
}
return true;
}
Subfile::Subfile(Common::SeekableReadStream *stream) : _stream(stream) {
// Verify file signature.
debugC(5, kDebugLoading, "\n*** Subfile::Subfile(): Got new subfile (@0x%llx) ***", static_cast<long long int>(_stream->pos()));
_rootChunk = nextChunk();
if (_rootChunk._id != MKTAG('R', 'I', 'F', 'F'))
error("%s: Expected \"RIFF\" chunk, got %s (@0x%llx)", __func__, tag2str(_rootChunk._id), static_cast<long long int>(_stream->pos()));
_stream->skip(4); // IMTS
// Read the RATE chunk.
// This chunk should always contain just one piece of data,
// the "rate" (whatever that is). Usually it is zero.
// TODO: Figure out what this actually is.
Chunk rateChunk = nextChunk();
if (rateChunk._id != MKTAG('r', 'a', 't', 'e'))
error("%s: Expected \"rate\" chunk, got %s (@0x%llx)", __func__, tag2str(_rootChunk._id), static_cast<long long int>(_stream->pos()));
_rate = _stream->readUint32LE();
// Queue up the first data chunk.
// First, we need to read past the LIST chunk.
nextChunk();
if (_stream->readUint32BE() != MKTAG('d', 'a', 't', 'a'))
error("%s: Expected \"data\" as first bytes of subfile, got %s @0x%llx)", __func__, tag2str(rateChunk._id), static_cast<long long int>(_stream->pos()));
}
Chunk Subfile::nextChunk() {
// Chunks always start on even-indexed bytes.
if (_stream->pos() & 1)
_stream->skip(1);
_currentChunk = Chunk(_stream);
return _currentChunk;
}
bool Subfile::atEnd() {
// There might be a padding byte at the end of the subfile, so
// we need to check for that.
if (_rootChunk.pos() % 2 == 0) {
return _rootChunk.bytesRemaining() == 0;
} else {
return _rootChunk.bytesRemaining() == 1;
}
}
void CdRomStream::openStream(uint streamId) {
const StreamInfo &streamInfo = g_engine->streamInfoForIdent(streamId);
if (streamInfo._fileId == 0) {
error("%s: Stream %d not found in current title", __func__, streamId);
}
const FileInfo &fileInfo = g_engine->fileInfoForIdent(streamInfo._fileId);
if (fileInfo._id == 0) {
error("%s: File %d for stream %d not found in current title", __func__, streamInfo._fileId, streamId);
}
bool requestedStreamAlreadyOpen = isOpen() && _fileId == streamInfo._fileId;
if (requestedStreamAlreadyOpen) {
seek(streamInfo._startOffsetInFile);
} else {
if (isOpen()) {
close();
}
Common::Path filename(fileInfo._name);
if (!open(filename)) {
error("%s: Failed to open %s", __func__, filename.toString().c_str());
}
seek(streamInfo._startOffsetInFile);
_fileId = streamInfo._fileId;
}
}
Subfile CdRomStream::getNextSubfile() {
return Subfile(_handle);
}
void ChannelClient::registerWithStreamManager() {
g_engine->getStreamFeedManager()->registerChannelClient(this);
}
void ChannelClient::unregisterWithStreamManager() {
g_engine->getStreamFeedManager()->unregisterChannelClient(this);
}
ImtStreamFeed::ImtStreamFeed(uint actorId) : StreamFeed(actorId) {
_stream = new CdRomStream();
}
ImtStreamFeed::~ImtStreamFeed() {
delete _stream;
_stream = nullptr;
}
void ImtStreamFeed::closeFeed() {
_stream->closeStream();
g_engine->getDocument()->streamDidClose(_id);
}
void ImtStreamFeed::openFeed(uint streamId, uint startOffset) {
// For CXT files, there is a 0x10-byte header before the first stream, but this header
// isn't actually used by the engine. This header is not present in BOOT.STM.
// [0x00-0x04] Byte order - either II\0\0 for Intel byte order or MM\0\0 for Motorola byte order.
// Motorola byte order never actually seems to be used for data files, even on big-endian
// platforms. Other parts of the engine perform the byte swapping on the fly.
// [0x05-0x08] Unknown.
// [0x09-0x0c] Stream (subfile) count in this file, uint32le.
// [0x0d-0x10] Total file size, uint32le.
// The original had an intermediary StreamProgress class here, with a StreamProgressClient
// class, but there as only
g_engine->getDocument()->streamDidOpen(streamId);
_stream->openStream(streamId);
}
void ImtStreamFeed::readData() {
Subfile subfile = _stream->getNextSubfile();
Chunk chunk = subfile.nextChunk();
g_engine->getDocument()->streamWillRead(_id);
g_engine->readHeaderSections(subfile, chunk);
g_engine->getDocument()->streamDidFinish(_id);
}
StreamFeedManager::~StreamFeedManager() {
for (auto it = _streamFeeds.begin(); it != _streamFeeds.end(); ++it) {
delete it->_value;
}
_streamFeeds.clear();
}
void StreamFeedManager::closeStreamFeed(StreamFeed *streamFeed) {
streamFeed->closeFeed();
_streamFeeds.erase(streamFeed->_id);
delete streamFeed;
}
void StreamFeedManager::registerChannelClient(ChannelClient *client) {
if (_channelClients.getValOrDefault(client->channelIdent()) != nullptr) {
error("%s: Channel ident %d already has a client", __func__, client->channelIdent());
}
_channelClients.setVal(client->channelIdent(), client);
}
void StreamFeedManager::unregisterChannelClient(ChannelClient *client) {
_channelClients.erase(client->channelIdent());
}
ImtStreamFeed *StreamFeedManager::openStreamFeed(uint actorId, uint offsetInStream, uint maxBytesToRead) {
ImtStreamFeed *streamFeed = new ImtStreamFeed(actorId);
streamFeed->openFeed(actorId, offsetInStream);
_streamFeeds.setVal(actorId, streamFeed);
return streamFeed;
}
} // End of namespace MediaStation