/* 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 . * */ #include "mediastation/datafile.h" #include "mediastation/debugchannels.h" #include "mediastation/mediastation.h" namespace MediaStation { void ParameterReadStream::readAndVerifyType(DatumType type) { DatumType actualType = static_cast(readUint16LE()); if (actualType != type) { error("%s: Expected datum type %d, got %d (@0x%llx)", __func__, type, actualType, static_cast(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(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(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(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(_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(_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(_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(_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