/* 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/mediastation.h" #include "mediastation/context.h" #include "mediastation/datum.h" #include "mediastation/debugchannels.h" #include "mediastation/bitmap.h" #include "mediastation/assets/canvas.h" #include "mediastation/assets/palette.h" #include "mediastation/assets/image.h" #include "mediastation/assets/path.h" #include "mediastation/assets/sound.h" #include "mediastation/assets/movie.h" #include "mediastation/assets/sprite.h" #include "mediastation/assets/hotspot.h" #include "mediastation/assets/timer.h" #include "mediastation/assets/screen.h" #include "mediastation/assets/font.h" #include "mediastation/assets/text.h" namespace MediaStation { Context::Context(const Common::Path &path) : Datafile(path) { uint32 signature = _handle->readUint32BE(); if (signature != MKTAG('I', 'I', '\0', '\0')) { error("Context::Context(): Wrong signature for file %s: 0x%08x", _name.c_str(), signature); } _unk1 = _handle->readUint32LE(); _subfileCount = _handle->readUint32LE(); _fileSize = _handle->readUint32LE(); debugC(5, kDebugLoading, "Context::Context(): _unk1 = 0x%x", _unk1); Subfile subfile = getNextSubfile(); Chunk chunk = subfile.nextChunk(); if (g_engine->isFirstGenerationEngine()) { readOldStyleHeaderSections(subfile, chunk); } else { readNewStyleHeaderSections(subfile, chunk); } chunk = subfile._currentChunk; while (!subfile.atEnd()) { readAssetInFirstSubfile(chunk); if (!subfile.atEnd()) { chunk = subfile.nextChunk(); } } // Read assets in the rest of the subfiles. for (uint i = 1; i < _subfileCount; i++) { subfile = getNextSubfile(); readAssetFromLaterSubfile(subfile); } // Some sprites and images don't have any image data themselves, they just // reference the same image data in another asset. So we need to check for // these and create the appropriate references. for (auto it = _assets.begin(); it != _assets.end(); ++it) { Asset *asset = it->_value; uint referencedAssetId = asset->getHeader()->_assetReference; if (referencedAssetId != 0) { switch (asset->getHeader()->_type) { case kAssetTypeImage: { Image *image = static_cast(asset); Image *referencedImage = static_cast(getAssetById(referencedAssetId)); if (referencedImage == nullptr) { error("Context::Context(): Asset %d references non-existent asset %d", asset->getHeader()->_id, referencedAssetId); } image->_bitmap = referencedImage->_bitmap; break; } case kAssetTypeSprite: { Sprite *sprite = static_cast(asset); Sprite *referencedSprite = static_cast(getAssetById(referencedAssetId)); if (referencedSprite == nullptr) { error("Context::Context(): Asset %d references non-existent asset %d", asset->getHeader()->_id, referencedAssetId); } sprite->_frames = referencedSprite->_frames; break; } default: error("Context::Context(): Asset type %d referenced, but reference not implemented yet", asset->getHeader()->_type); } } } } Context::~Context() { delete _palette; _palette = nullptr; delete _contextName; _contextName = nullptr; for (auto it = _assets.begin(); it != _assets.end(); ++it) { delete it->_value; } _assets.clear(); // The same asset pointers are in here, so don't delete again. _assetsByChunkReference.clear(); for (auto it = _functions.begin(); it != _functions.end(); ++it) { delete it->_value; } _functions.clear(); } Asset *Context::getAssetById(uint assetId) { return _assets.getValOrDefault(assetId); } Asset *Context::getAssetByChunkReference(uint chunkReference) { return _assetsByChunkReference.getValOrDefault(chunkReference); } Function *Context::getFunctionById(uint functionId) { return _functions.getValOrDefault(functionId); } void Context::registerActiveAssets() { for (auto it = _assets.begin(); it != _assets.end(); ++it) { if (it->_value->isActive()) { g_engine->addPlayingAsset(it->_value); } } } void Context::readParametersSection(Chunk &chunk) { _fileNumber = Datum(chunk, kDatumTypeUint16_1).u.i; ContextParametersSectionType sectionType = static_cast(Datum(chunk, kDatumTypeUint16_1).u.i); while (sectionType != kContextParametersEmptySection) { debugC(5, kDebugLoading, "ContextParameters::ContextParameters: sectionType = 0x%x (@0x%llx)", static_cast(sectionType), static_cast(chunk.pos())); switch (sectionType) { case kContextParametersName: { uint repeatedFileNumber = Datum(chunk, kDatumTypeUint16_1).u.i; if (repeatedFileNumber != _fileNumber) { warning("ContextParameters::ContextParameters(): Repeated file number didn't match: %d != %d", repeatedFileNumber, _fileNumber); } _contextName = Datum(chunk, kDatumTypeString).u.string; uint endingFlag = Datum(chunk, kDatumTypeUint16_1).u.i; if (endingFlag != 0) { warning("ContextParameters::ContextParameters(): Got non-zero ending flag 0x%x", endingFlag); } break; } case kContextParametersFileNumber: { error("ContextParameters::ContextParameters(): Section type FILE_NUMBER not implemented yet"); break; } case kContextParametersVariable: { uint repeatedFileNumber = Datum(chunk, kDatumTypeUint16_1).u.i; if (repeatedFileNumber != _fileNumber) { warning("ContextParameters::ContextParameters(): Repeated file number didn't match: %d != %d", repeatedFileNumber, _fileNumber); } Variable *variable = new Variable(chunk); if (g_engine->_variables.contains(variable->_id)) { // Don't overwrite the variable if it already exists. This can happen if we have // unloaded a screen but are returning to it later. debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Skipping re-creation of existing global variable %d (type: %s)", variable->_id, variableTypeToStr(variable->_type)); delete variable; } else { g_engine->_variables.setVal(variable->_id, variable); debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Created global variable %d (type: %s)", variable->_id, variableTypeToStr(variable->_type)); } break; } case kContextParametersBytecode: { Function *function = new Function(chunk); _functions.setVal(function->_id, function); break; } default: error("ContextParameters::ContextParameters(): Unknown section type 0x%x", static_cast(sectionType)); } sectionType = static_cast(Datum(chunk, kDatumTypeUint16_1).u.i); } } void Context::readOldStyleHeaderSections(Subfile &subfile, Chunk &chunk) { error("Context::readOldStyleHeaderSections(): Not implemented yet"); } void Context::readNewStyleHeaderSections(Subfile &subfile, Chunk &chunk) { bool moreSectionsToRead = (chunk._id == MKTAG('i', 'g', 'o', 'd')); if (!moreSectionsToRead) { warning("Context::readNewStyleHeaderSections(): Got no header sections (@0x%llx)", static_cast(chunk.pos())); } while (moreSectionsToRead) { // Verify this chunk is a header. // TODO: What are the situations when it's not? uint16 sectionType = Datum(chunk, kDatumTypeUint16_1).u.i; debugC(5, kDebugLoading, "Context::readNewStyleHeaderSections(): sectionType = 0x%x (@0x%llx)", static_cast(sectionType), static_cast(chunk.pos())); bool chunkIsHeader = (sectionType == 0x000d); if (!chunkIsHeader) { error("Context::readNewStyleHeaderSections(): Expected header chunk, got %s (@0x%llx)", tag2str(chunk._id), static_cast(chunk.pos())); } // Read this header section. moreSectionsToRead = readHeaderSection(subfile, chunk); if (subfile.atEnd()) { break; } else { debugC(5, kDebugLoading, "\nContext::readNewStyleHeaderSections(): Getting next chunk (@0x%llx)", static_cast(chunk.pos())); chunk = subfile.nextChunk(); moreSectionsToRead = (chunk._id == MKTAG('i', 'g', 'o', 'd')); } } debugC(5, kDebugLoading, "Context::readNewStyleHeaderSections(): Finished reading sections (@0x%llx)", static_cast(chunk.pos())); } void Context::readAssetInFirstSubfile(Chunk &chunk) { if (chunk._id == MKTAG('i', 'g', 'o', 'd')) { warning("Context::readAssetInFirstSubfile(): Skippping \"igod\" asset link chunk"); chunk.skip(chunk.bytesRemaining()); return; } // TODO: Make sure this is not an asset link. Asset *asset = getAssetByChunkReference(chunk._id); if (asset == nullptr) { // We should only need to look in the global scope when there is an // install cache (INSTALL.CXT). asset = g_engine->getAssetByChunkReference(chunk._id); if (asset == nullptr) { error("Context::readAssetInFirstSubfile(): Asset for chunk \"%s\" (0x%x) does not exist or has not been read yet in this title. (@0x%llx)", tag2str(chunk._id), chunk._id, static_cast(chunk.pos())); } } debugC(5, kDebugLoading, "\nContext::readAssetInFirstSubfile(): Got asset with chunk ID %s in first subfile (type: 0x%x) (@0x%llx)", tag2str(chunk._id), static_cast(asset->type()), static_cast(chunk.pos())); asset->readChunk(chunk); } void Context::readAssetFromLaterSubfile(Subfile &subfile) { Chunk chunk = subfile.nextChunk(); Asset *asset = getAssetByChunkReference(chunk._id); if (asset == nullptr) { // We should only need to look in the global scope when there is an // install cache (INSTALL.CXT). asset = g_engine->getAssetByChunkReference(chunk._id); if (asset == nullptr) { error("Context::readAssetFromLaterSubfile(): Asset for chunk \"%s\" (0x%x) does not exist or has not been read yet in this title. (@0x%llx)", tag2str(chunk._id), chunk._id, static_cast(chunk.pos())); } } debugC(5, kDebugLoading, "\nContext::readAssetFromLaterSubfile(): Got asset with chunk ID %s in later subfile (type: 0x%x) (@0x%llx)", tag2str(chunk._id), asset->type(), static_cast(chunk.pos())); asset->readSubfile(subfile, chunk); } bool Context::readHeaderSection(Subfile &subfile, Chunk &chunk) { uint16 sectionType = Datum(chunk, kDatumTypeUint16_1).u.i; debugC(5, kDebugLoading, "Context::readHeaderSection(): sectionType = 0x%x (@0x%llx)", static_cast(sectionType), static_cast(chunk.pos())); switch (sectionType) { case kContextParametersSection: { readParametersSection(chunk); break; } case kContextAssetLinkSection: { warning("Context::readHeaderSection(): ASSET_LINK not implemented yet"); chunk.skip(chunk.bytesRemaining()); break; } case kContextPaletteSection: { if (_palette != nullptr) { error("Context::readHeaderSection(): Got multiple palettes (@0x%llx)", static_cast(chunk.pos())); } // TODO: Avoid the copying here! const uint PALETTE_ENTRIES = 256; const uint PALETTE_BYTES = PALETTE_ENTRIES * 3; byte* buffer = new byte[PALETTE_BYTES]; chunk.read(buffer, PALETTE_BYTES); _palette = new Graphics::Palette(buffer, PALETTE_ENTRIES); delete[] buffer; debugC(5, kDebugLoading, "Context::readHeaderSection(): Read palette"); // This is likely just an ending flag that we expect to be zero. uint endingFlag = Datum(chunk, kDatumTypeUint16_1).u.i; if (endingFlag != 0) { warning("Context::readHeaderSection(): Got non-zero ending flag 0x%x", endingFlag); } break; } case kContextAssetHeaderSection: { Asset *asset = nullptr; AssetHeader *header = new AssetHeader(chunk); switch (header->_type) { case kAssetTypeImage: asset = new Image(header); break; case kAssetTypeMovie: asset = new Movie(header); break; case kAssetTypeSound: asset = new Sound(header); break; case kAssetTypePalette: asset = new Palette(header); break; case kAssetTypePath: asset = new Path(header); break; case kAssetTypeTimer: asset = new Timer(header); break; case kAssetTypeHotspot: asset = new Hotspot(header); break; case kAssetTypeSprite: asset = new Sprite(header); break; case kAssetTypeCanvas: asset = new Canvas(header); break; case kAssetTypeScreen: asset = new Screen(header); _screenAsset = header; break; case kAssetTypeFont: asset = new Font(header); break; case kAssetTypeText: asset = new Text(header); break; default: error("Context::readHeaderSection(): No class for asset type 0x%x (@0x%llx)", static_cast(header->_type), static_cast(chunk.pos())); } if (g_engine->getAssetById(header->_id)) { error("Context::readHeaderSection(): Asset with ID 0x%d was already defined in this title", header->_id); } _assets.setVal(header->_id, asset); if (header->_chunkReference != 0) { debugC(5, kDebugLoading, "Context::readHeaderSection(): Storing asset with chunk ID \"%s\" (0x%x)", tag2str(header->_chunkReference), header->_chunkReference); _assetsByChunkReference.setVal(header->_chunkReference, asset); } // TODO: Store the movie chunk references better. if (header->_audioChunkReference != 0) { _assetsByChunkReference.setVal(header->_audioChunkReference, asset); } if (header->_animationChunkReference != 0) { _assetsByChunkReference.setVal(header->_animationChunkReference, asset); } // TODO: This datum only appears sometimes. uint unk2 = Datum(chunk).u.i; debugC(5, kDebugLoading, "Context::readHeaderSection(): Got unknown value at end of asset header section 0x%x", unk2); break; } case kContextFunctionSection: { Function *function = new Function(chunk); _functions.setVal(function->_id, function); if (!g_engine->isFirstGenerationEngine()) { uint endingFlag = Datum(chunk).u.i; if (endingFlag != 0) { warning("Context::readHeaderSection(): Got non-zero ending flag 0x%x in function section", endingFlag); } } break; } case kContextUnkAtEndSection: { int unk1 = Datum(chunk).u.i; int unk2 = Datum(chunk).u.i; debugC(5, kDebugLoading, "Context::readHeaderSection(): unk1 = %d, unk2 = %d", unk1, unk2); return false; } case kContextEmptySection: { error("Context::readHeaderSection(): EMPTY Not implemented yet"); break; } case kContextPoohSection: { error("Context::readHeaderSection(): POOH Not implemented yet"); break; } default: error("Context::readHeaderSection(): Unknown section type 0x%x (@0x%llx)", static_cast(sectionType), static_cast(chunk.pos())); } return true; } } // End of namespace MediaStation