mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
292 lines
8.2 KiB
C++
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
|