Files
scummvm/engines/mtropolis/runtime.cpp
T
2022-06-16 21:58:09 +02:00

292 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 "mtropolis/runtime.h"
#include "mtropolis/data.h"
#include "mtropolis/vthread.h"
#include "common/file.h"
#include "common/substream.h"
namespace MTropolis {
ProjectDescription::ProjectDescription() {
}
ProjectDescription::~ProjectDescription() {
}
void ProjectDescription::addSegment(int volumeID, const char *filePath) {
SegmentDescription desc;
desc.volumeID = volumeID;
desc.filePath = filePath;
_segments.push_back(desc);
}
const Common::Array<SegmentDescription> &ProjectDescription::getSegments() const {
return _segments;
}
Structural::~Structural() {
}
ProjectPresentationSettings::ProjectPresentationSettings() : width(640), height(480), bitsPerPixel(8) {
}
const Common::Array<Common::SharedPtr<Structural> > &Structural::getChildren() const {
return _children;
}
const Common::Array<Common::SharedPtr<Modifier> > &Structural::getModifiers() const {
return _modifiers;
}
Runtime::Runtime() {
_vthread.reset(new VThread());
}
void Runtime::runFrame() {
VThreadState state = _vthread->step();
if (state != kVThreadReturn) {
// Still doing blocking tasks
return;
}
if (_queuedProjectDesc) {
Common::SharedPtr<ProjectDescription> desc = _queuedProjectDesc;
_queuedProjectDesc.reset();
_project.reset();
_project.reset(new Project());
_project->loadFromDescription(*desc);
}
}
void Runtime::queueProject(const Common::SharedPtr<ProjectDescription> &desc) {
_queuedProjectDesc = desc;
}
void Runtime::addVolume(int volumeID, const char *name, bool isMounted) {
VolumeState volume;
volume.name = name;
volume.isMounted = isMounted;
volume.volumeID = volumeID;
_volumes.push_back(volume);
}
Project::Project()
: _projectFormat(Data::kProjectFormatUnknown), _isBigEndian(false) {
}
Project::~Project() {
}
void Project::loadFromDescription(const ProjectDescription& desc) {
size_t numSegments = desc.getSegments().size();
_segments.resize(numSegments);
for (size_t i = 0; i < numSegments; i++) {
_segments[i].desc = desc.getSegments()[i];
}
// Try to open the first segment
openSegmentStream(0);
Common::SeekableReadStream *baseStream = _segments[0].stream.get();
uint16_t startValue = baseStream->readUint16LE();
if (startValue == 1) {
// Windows format
_isBigEndian = false;
_projectFormat = Data::kProjectFormatWindows;
} else if (startValue == 0) {
// Mac format
_isBigEndian = true;
_projectFormat = Data::kProjectFormatMacintosh;
} else {
error("Unrecognized project segment header");
}
Common::SeekableSubReadStreamEndian stream(baseStream, 2, baseStream->size(), _isBigEndian);
if (stream.readUint32() != 0xaa55a5a5 || stream.readUint32() != 0 || stream.readUint32() != 14) {
error("Unrecognized project segment header");
}
Data::DataReader reader(stream, _projectFormat);
Common::SharedPtr<Data::DataObject> dataObject;
Data::loadDataObject(reader, dataObject);
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectHeader) {
error("Expected project header but found something else");
}
Data::loadDataObject(reader, dataObject);
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectCatalog) {
error("Expected project catalog but found something else");
}
Data::ProjectCatalog *catalog = static_cast<Data::ProjectCatalog *>(dataObject.get());
if (catalog->segments.size() != desc.getSegments().size()) {
error("Project declared a different number of segments than the project description provided");
}
_streams.resize(catalog->streams.size());
for (size_t i = 0; i < _streams.size(); i++) {
StreamDesc &streamDesc = _streams[i];
const Data::ProjectCatalog::StreamDesc &srcStream = catalog->streams[i];
if (!strcmp(srcStream.streamType, "assetStream"))
streamDesc.streamType = kStreamTypeAsset;
else if (!strcmp(srcStream.streamType, "bootStream"))
streamDesc.streamType = kStreamTypeBoot;
else if (!strcmp(srcStream.streamType, "sceneStream"))
streamDesc.streamType = kStreamTypeScene;
else
streamDesc.streamType = kStreamTypeUnknown;
streamDesc.segmentIndex = srcStream.segmentIndexPlusOne - 1;
streamDesc.size = srcStream.size;
streamDesc.pos = srcStream.pos;
}
// Locate the boot stream
size_t bootStreamIndex = 0;
bool foundBootStream = false;
for (size_t i = 0; i < _streams.size(); i++) {
if (_streams[i].streamType == kStreamTypeBoot) {
bootStreamIndex = i;
foundBootStream = true;
break;
}
}
if (!foundBootStream) {
error("Failed to find boot stream");
}
loadBootStream(bootStreamIndex);
}
void Project::openSegmentStream(int segmentIndex) {
if (segmentIndex < 0 || static_cast<size_t>(segmentIndex) > _segments.size()) {
error("Invalid segment index %i", segmentIndex);
}
Segment &segment = _segments[segmentIndex];
if (segment.stream)
return;
Common::File *f = new Common::File();
segment.stream.reset(f);
if (!f->open(segment.desc.filePath)) {
error("Failed to open segment file %s", segment.desc.filePath.c_str());
}
}
void Project::loadBootStream(size_t streamIndex) {
const StreamDesc &streamDesc = _streams[streamIndex];
size_t segmentIndex = streamDesc.segmentIndex;
openSegmentStream(segmentIndex);
Common::SeekableSubReadStreamEndian stream(_segments[segmentIndex].stream.get(), streamDesc.pos, streamDesc.pos + streamDesc.size, _isBigEndian);
Data::DataReader reader(stream, _projectFormat);
for (;;) {
Common::SharedPtr<Data::DataObject> dataObject;
Data::loadDataObject(reader, dataObject);
if (!dataObject) {
error("Failed to load project boot data");
}
switch (dataObject->getType()) {
case Data::DataObjectTypes::kPresentationSettings:
loadPresentationSettings(*static_cast<const Data::PresentationSettings *>(dataObject.get()));
break;
case Data::DataObjectTypes::kAssetCatalog:
loadAssetCatalog(*static_cast<const Data::AssetCatalog *>(dataObject.get()));
break;
case Data::DataObjectTypes::kStreamHeader:
// Ignore
break;
default:
error("Unexpected object type in boot stream");
}
}
}
void Project::loadPresentationSettings(const Data::PresentationSettings &presentationSettings) {
_presentationSettings.bitsPerPixel = presentationSettings.bitsPerPixel;
if (_presentationSettings.bitsPerPixel != 16) {
error("Unsupported bit depth");
}
_presentationSettings.width = presentationSettings.dimensions.x;
_presentationSettings.height = presentationSettings.dimensions.y;
}
void Project::loadAssetCatalog(const Data::AssetCatalog &assetCatalog) {
_assetsByID.clear();
_realAssets.clear();
_assetNameToID.clear();
size_t numRealAssets = 0;
for (size_t i = 0; i < assetCatalog.assets.size(); i++) {
const Data::AssetCatalog::AssetInfo &assetInfo = assetCatalog.assets[i];
if ((assetInfo.flags1 & Data::AssetCatalog::kFlag1Deleted) == 0)
numRealAssets++;
}
_realAssets.resize(numRealAssets);
_assetsByID.resize(assetCatalog.assets.size() + 1);
_assetsByID[0] = nullptr;
numRealAssets = 0;
for (size_t i = 0; i < assetCatalog.assets.size(); i++) {
const Data::AssetCatalog::AssetInfo &assetInfo = assetCatalog.assets[i];
if (assetInfo.flags1 & Data::AssetCatalog::kFlag1Deleted) {
_assetsByID[i + 1] = nullptr;
} else {
AssetDesc &assetDesc = _realAssets[numRealAssets++];
assetDesc.id = i + 1;
assetDesc.name = assetInfo.name;
assetDesc.typeCode = assetInfo.assetType;
_assetsByID[assetDesc.id] = &assetDesc;
if (!assetDesc.name.empty())
_assetNameToID[assetDesc.name] = assetDesc.id;
}
}
}
} // End of namespace MTropolis