/* 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 "common/memstream.h"
#include "common/savefile.h"
#include "common/config-manager.h"
#include "director/director.h"
#include "director/archive.h"
#include "director/cast.h"
#include "director/movie.h"
#include "director/score.h"
#include "director/window.h"
#include "director/sprite.h"
#include "director/castmember/castmember.h"
#include "director/castmember/bitmap.h"
#include "director/castmember/text.h"
#include "director/castmember/palette.h"
#include "director/castmember/filmloop.h"
namespace Director {
bool RIFXArchive::writeToFile(Common::String filename, Movie *movie) {
// If the filename is empty, we save the movie with the name of the current movie
if (filename.empty()) {
filename = movie->getMacName();
}
Common::String saveFileName = g_director->getTargetName() + "-" + filename;
// Don't open the save file as compressed which doesn't support seeking
Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving(saveFileName, false);
if (!saveFile) {
warning("RIFXArchive::writeToFile: Failed to open file %s for saving", saveFileName.c_str());
return false;
}
// Update the resources, their sizes and offsets
Common::Array builtResources = rebuildResources(movie);
// ignoring the startOffset
// For RIFX stream, moreoffset = 0, we won't be writing macbinary
// Don't need to allocate this much size in case 'junk' and 'free' resources are ignored
// Or might need to allocate even more size if extra chunks are written
_size = getArchiveSize(builtResources);
saveFile->writeUint32LE(_metaTag); // The _metaTag is "RIFX" or "XFIR"
saveFile->writeUint32LE(getResourceSize(_metaTag, 0) - 8); // The size of the RIFX archive, except header and size
saveFile->writeUint32LE(_rifxType); // e.g. "MV93", "MV95"
switch (_rifxType) {
case MKTAG('M', 'V', '9', '3'):
case MKTAG('M', 'C', '9', '5'):
case MKTAG('A', 'P', 'P', 'L'):
writeMemoryMap(saveFile, builtResources);
break;
case MKTAG('F', 'G', 'D', 'M'):
case MKTAG('F', 'G', 'D', 'C'):
writeAfterBurnerMap(saveFile);
break;
default:
break;
}
Cast *cast = movie->getCast();
ResourceMap castResMap = _types[MKTAG('C', 'A', 'S', 't')];
for (auto &it : builtResources) {
debugC(5, kDebugSaving, "RIFXArchive::writeToFile: writing resource '%s': index: %d, size: %d, offset = %d, index: %d",
tag2str(it->tag), it->index, it->size, it->offset, it->index);
switch (it->tag) {
case MKTAG('R', 'I', 'F', 'X'):
case MKTAG('X', 'F', 'I', 'R'):
// meta resource
break;
case MKTAG('i', 'm', 'a', 'p'):
case MKTAG('m', 'm', 'a', 'p'):
// Already written
break;
case MKTAG('K', 'E', 'Y', '*'):
writeKeyTable(saveFile, it->offset);
break;
case MKTAG('C', 'A', 'S', '*'):
writeCast(saveFile, it->offset, it->libResourceId);
break;
case MKTAG('C', 'A', 'S', 't'):
cast = movie->getCastByLibResourceID(it->libResourceId);
cast->saveCastData(saveFile, it);
break;
case MKTAG('V', 'W', 'C', 'F'):
// There is only 'VWCF' resource, that is for the internal cast
// The external casts don't have a config
// movie->getCast() returns the internal cast
cast = movie->getCast();
cast->saveConfig(saveFile, it->offset);
break;
case MKTAG('B', 'I', 'T', 'D'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
cast = movie->getCastByLibResourceID(parent.libResourceId);
BitmapCastMember *target = (BitmapCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
target->writeBITDResource(saveFile, it->offset);
}
break;
case MKTAG('S', 'T', 'X', 'T'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
cast = movie->getCastByLibResourceID(parent.libResourceId);
TextCastMember *target = (TextCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
target->writeSTXTResource(saveFile, it->offset);
}
break;
case MKTAG('C', 'L', 'U', 'T'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
cast = movie->getCastByLibResourceID(parent.libResourceId);
PaletteCastMember *target = (PaletteCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
target->writePaletteData(saveFile, it->offset);
}
break;
case MKTAG('S', 'C', 'V', 'W'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
cast = movie->getCastByLibResourceID(parent.libResourceId);
FilmLoopCastMember *target = (FilmLoopCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
target->writeSCVWResource(saveFile, it->offset);
}
break;
case MKTAG('V', 'W', 'S', 'C'):
movie->getScore()->writeVWSCResource(saveFile, it->offset);
break;
default:
debugC(7, kDebugSaving, "Saving resource %s as it is, without modification", tag2str(it->tag));
saveFile->seek(it->offset, SEEK_SET);
saveFile->writeUint32LE(it->tag);
saveFile->writeUint32LE(it->size);
saveFile->writeStream(getResource(it->tag, it->index));
break;
}
}
Common::DumpFile out;
// Write the movie out, stored in dumpData
if (saveFile) {
saveFile->flush();
debugC(3, kDebugSaving, "RIFXArchive::writeStream: Saved the movie as file %s", saveFileName.c_str());
} else {
warning("RIFXArchive::writeStream: Error saving the file %s", saveFileName.c_str());
}
delete saveFile;
for (auto it : builtResources) {
delete it;
}
return true;
}
bool RIFXArchive::writeMemoryMap(Common::SeekableWriteStream *writeStream, Common::Array resources) {
Resource mmap;
for (auto it : resources) {
if (it->tag == MKTAG('m', 'm', 'a', 'p')) {
mmap = *it;
}
}
writeStream->writeUint32LE(MKTAG('i', 'm', 'a', 'p')); // The "imap" resource
writeStream->writeUint32LE(_imapLength); // length of "imap" resource
writeStream->writeUint32LE(_mapversion); // "imap" version
writeStream->writeUint32LE(mmap.offset); // offset of the "mmap" resource
writeStream->writeUint32LE(_version);
writeStream->seek(mmap.offset);
writeStream->writeUint32LE(MKTAG('m', 'm', 'a', 'p'));
writeStream->writeUint32LE(mmap.size);
writeStream->writeUint16LE(_mmapHeaderSize);
writeStream->writeUint16LE(_mmapEntrySize);
uint32 newResCount = resources.size();
writeStream->writeUint32LE(newResCount + _totalCount - _resCount); // _totalCount - _resCount is the number of empty entries
writeStream->writeUint32LE(newResCount);
writeStream->seek(8, SEEK_CUR); // In the original file, these 8 bytes are all 0xFF, so this will produce a diff
// ID of the first 'free' resource, we don't make use of it
writeStream->writeUint32LE(0);
for (auto &it : resources) {
debugC(3, kDebugSaving, "RIFXArchive::writeMemoryMap: Memory map entry: '%s', size: %d, offset: %08x, flags: %x, unk1: %x, nextFreeResourceID: %d",
tag2str(it->tag), it->size, it->offset, it->flags, it->unk1, it->nextFreeResourceID);
// Write down the tag, the size and offset of the current resource
writeStream->writeUint32LE(it->tag);
writeStream->writeUint32LE(it->size);
writeStream->writeUint32LE(it->offset);
// Currently ignoring flags, unk1 and nextFreeResourceID
writeStream->writeUint16LE(it->flags);
writeStream->writeUint16LE(it->unk1);
writeStream->writeUint32LE(it->nextFreeResourceID);
}
return true;
}
bool RIFXArchive::writeAfterBurnerMap(Common::SeekableWriteStream *writeStream) {
warning("RIFXArchive::writeAfterBurnerMap: STUB: Incomplete function, needs further changes, AfterBurnerMap not written");
return false;
#if 0
writeStream->writeUint32LE(MKTAG('F', 'v', 'e', 'r'));
writeStream->writeUint32LE(_fverLength);
uint32 start = writeStream->pos();
writeStream->writeUint32LE(_afterBurnerVersion);
uint32 end = writeStream->pos();
if (end - start != _fverLength) {
warning("RIFXArchive::writeAfterburnerMap(): Expected Fver of length %d but read %d bytes", _fverLength, end - start);
writeStream->seek(start + _fverLength);
}
writeStream->writeUint32LE(MKTAG('F', 'c', 'd', 'r'));
writeStream->writeUint32LE(_fcdrLength);
writeStream->seek(_fcdrLength, SEEK_CUR);
writeStream->writeUint32LE(MKTAG('A', 'B', 'M', 'P'));
return true;
#endif
}
bool RIFXArchive::writeKeyTable(Common::SeekableWriteStream *writeStream, uint32 offset) {
writeStream->seek(offset);
writeStream->writeUint32LE(MKTAG('K', 'E', 'Y', '*'));
writeStream->writeUint32LE(getResourceSize(MKTAG('K', 'E', 'Y', '*'), getResourceIDList(MKTAG('K', 'E', 'Y', '*'))[0]));
writeStream->writeUint16LE(_keyTableEntrySize);
writeStream->writeUint16LE(_keyTableEntrySize2);
writeStream->writeUint32LE(_keyTableEntryCount);
writeStream->writeUint32LE(_keyTableUsedCount);
debugC(3, kDebugSaving, "RIFXArchive::writeKeyTable: writing key table:");
for (auto &childTag : _keyData) {
KeyMap keyMap = childTag._value;
for (auto &parentIndex : keyMap) {
KeyArray keyArray = parentIndex._value;
for (auto childIndex : keyArray) {
debugC(3, kDebugSaving, "_keyData contains tag: %s, parentIndex: %d, childIndex: %d", tag2str(childTag._key), parentIndex._key, childIndex);
writeStream->writeUint32LE(childIndex);
writeStream->writeUint32LE(parentIndex._key);
writeStream->writeUint32LE(childTag._key);
}
}
}
return true;
}
bool RIFXArchive::writeCast(Common::SeekableWriteStream *writeStream, uint32 offset, uint32 libResourceId) {
writeStream->seek(offset);
uint32 casSize = getCASResourceSize(libResourceId);
uint castTag = MKTAG('C', 'A', 'S', 't');
writeStream->writeUint32LE(MKTAG('C', 'A', 'S', '*'));
writeStream->writeUint32LE(casSize);
Common::HashMap castIndexes;
// We can't just write all the 'CASt' indices randomly, we have to sort them by castId
// Since the order they appear matters, they are given castIds accordingly
uint32 maxCastId = 0;
for (auto &it : _types[castTag]) {
if (it._value.libResourceId == libResourceId) {
castIndexes[it._value.castId] = it._value.index;
maxCastId = MAX(maxCastId, it._value.castId);
}
}
debugC(5, kDebugSaving, "RIFXArchive::writeCast: Writing CAS* resource: size: %d, maxCastID: %d, libResourceID: %d", casSize, maxCastId, libResourceId);
debugCN(5, kDebugSaving, "'CASt' indexes: [");
for (uint32 i = 0; i <= maxCastId; i++) {
uint32 castIndex = castIndexes.getValOrDefault(i, 0);
if (castIndex) {
debugCN(5, kDebugSaving, (i == 0 ? "%d" : ", %d"), castIndex);
writeStream->writeUint32BE(castIndex);
}
}
debugC(5, kDebugSaving, "]");
return true;
}
Common::Array RIFXArchive::rebuildResources(Movie *movie) {
// Currently handled Resource types:
// imap // BITD
// mmap // CLUT
// RIFX // STXT
// KEY* // SCVW (filmloop)
// CAS* // VWCF
// CASt
// SCVW
// 'RTE0', 'RTE1', 'RTE2'
// First we'll have to update the _types table to include all the newly added
// cast members, and their 'CASt' resources
Cast *cast = nullptr;
ResourceMap &castResMap = _types[MKTAG('C', 'A', 'S', 't')];
// Iterate over all the casts
for (auto it : *(movie->getCasts())) {
cast = it._value;
// Iterate over all the loaded members of the cast to check for new cast members
for (auto jt : *(cast->_loadedCast)) {
// If the index is -1, that means the cast member is new or duplicated
if (jt._value->_index == -1) {
Resource *res = nullptr;
uint16 targetCastId = jt._value->getID() - cast->_castArrayStart;
// Checking if the castId already exists in the CASt resources
for (auto castRes : castResMap) {
if (castRes._value.castId == targetCastId) {
res = &castRes._value;
}
}
if (!res) {
// If the castId is new, create a new resource
// Assigning the next available index to the resource
res = &castResMap[_resources.size()];
res->tag = MKTAG('C', 'A', 'S', 't');
res->accessed = true;
res->libResourceId = cast->_libResourceId;
res->children = jt._value->_children;
res->index = _resources.size();
res->castId = jt._value->getID() - cast->_castArrayStart;
for (auto child : jt._value->_children) {
_keyData[child.tag][res->index].push_back(child.index);
_keyTableUsedCount += 1;
_keyTableEntryCount += 1;
}
_resources.push_back(res);
debugC(5, kDebugSaving, "RIFXArchive::rebuildResources(): new 'CASt' resource added");
} else {
// The castId is not new, overwrite the key data of the previous cast
for (auto child : res->children) {
// Remove the data of the previous (removed) 'CASt'
int8 count = _keyData[child.tag][res->index].size();
_keyData[child.tag][res->index].clear();
_keyTableUsedCount -= count;
_keyTableEntryCount -= count;
}
res->children = jt._value->_children;
for (auto child : res->children) {
_keyData[child.tag][res->index].push_back(child.index);
_keyTableUsedCount += 1;
_keyTableEntryCount += 1;
}
}
}
}
}
// TODO: Then we'll need to see if there are any other newly added resources
// Now when you use `duplicate`, say BitmapCastMember, the cast member is duplicated
// but its children resources are not, meaning the duplicated BitmapCastMember is also loaded from the same 'BITD' resource
// So it is not necessary to duplicate the 'BITD' resource
// However, in case an entirely new cast member is added, say a filmloop is recorded, then that requires a new 'SCVW' resource
// Same goes for if a new cast is added to the movie
// Ignoring that for now
// Next step is to recalculate the sizes and the offsets of all the resources
// Since the first 3 resources are determined (RIFX, imap and mmap)
// (the mmap doesn't need to be the third resource, but for simplicity, it's better there)
// We'll start writing after that RIFX header, mmap and imap resources
// The first 12 bytes are metaTag ('RIFX'), size of file, and RIFX type ('MV93', 'MV95', etc.)
// The +8 bytes are to account for the header and size
uint32 currentSize = 12 + (getImapSize() + 8) + (getMmapSize() + 8);
// need to make a new resources array, because we need the old offsets as well as new ones
Common::Array builtResources;
for (auto it: _resources) {
builtResources.push_back(new Resource(it));
}
uint32 resSize = 0;
for (auto &it : builtResources) {
switch (it->tag) {
case MKTAG('R', 'I', 'F', 'X'):
case MKTAG('X', 'F', 'I', 'R'):
// only one resource only
// Size will be determined after all other sizes have been calculated
it->offset = 0;
break;
case MKTAG('i', 'm', 'a', 'p'):
// one resource only
it->size = getImapSize();
it->offset = 12; // First 12 bytes are reserved for metaTag ('RIFX'), size of file, and RIFX type ('MV93', 'MV95', etc.)
break;
case MKTAG('m', 'm', 'a', 'p'):
// one resource only
it->size = getMmapSize();
it->offset = 12 + (getImapSize() + 8); // The +8 is to account for header and size
break;
case MKTAG('C', 'A', 'S', 't'):
{
cast = movie->getCastByLibResourceID(it->libResourceId);
// The castIds of cast members start from _castArrayStart
CastMember *target = cast->getCastMember(it->castId + cast->_castArrayStart);
if (target) {
resSize = target->getCastResourceSize();
it->size = resSize; // getCastResourceSize returns size without header and size
} else {
resSize = it->size;
}
it->offset = currentSize;
currentSize += resSize + 8;
}
break;
case MKTAG('C', 'A', 'S', '*'):
// Currently handling only movies with one 'CAS*' resource, i.e. only one cast
resSize = getCASResourceSize(it->libResourceId); // getCASResourceSize() returns size without header and size
it->size = resSize;
it->offset = currentSize;
currentSize += resSize + 8;
break;
case MKTAG('K', 'E', 'Y', '*'):
resSize = getKeyTableResourceSize();
it->size = resSize;
it->offset = currentSize;
currentSize += resSize + 8;
break;
case MKTAG('V', 'W', 'C', 'F'):
{
// Only one cast config per movie
// No need to update the key mapping
cast = movie->getCast();
resSize = cast->getConfigSize();
it->offset = currentSize;
currentSize += resSize + 8; // getConfigSize() doesn't include header and size
it->size = resSize;
}
break;
case MKTAG('S', 'T', 'X', 'T'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
TextCastMember *target = (TextCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
resSize = target->getSTXTResourceSize();
it->offset = currentSize;
it->size = resSize;
currentSize += resSize + 8;
}
break;
case MKTAG('C', 'L', 'U', 'T'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
// Get the appropriate cast in case of multiple casts
cast = movie->getCastByLibResourceID(parent.libResourceId);
PaletteCastMember *target = (PaletteCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
resSize = target->getPaletteDataSize();
it->offset = currentSize;
it->size = resSize;
currentSize += resSize + 8;
}
break;
case MKTAG('B', 'I', 'T', 'D'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
// Get the appropriate cast in case of multiple casts
cast = movie->getCastByLibResourceID(parent.libResourceId);
BitmapCastMember *target = (BitmapCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
resSize = target->getBITDResourceSize();
it->offset = currentSize;
it->size = resSize;
currentSize += resSize + 8;
}
break;
case MKTAG('S', 'C', 'V', 'W'):
{
uint32 parentIndex = findParentIndex(it->tag, it->index);
Resource parent = castResMap[parentIndex];
FilmLoopCastMember *target = (FilmLoopCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
resSize = target->getSCVWResourceSize();
it->offset = currentSize;
it->size = resSize;
currentSize += resSize + 8;
}
break;
case MKTAG('V', 'W', 'S', 'C'):
resSize = movie->getScore()->getVWSCResourceSize();
it->size = resSize;
it->offset = currentSize;
currentSize += resSize + 8; // The size doesn't include the header and the size entry
break;
case MKTAG('f', 'r', 'e', 'e'):
case MKTAG('j', 'u', 'n', 'k'):
// These resources do not hold any data
it->size = 0;
// We could just ignore these and not write them at all
it->offset = currentSize;
currentSize += 8;
break;
default:
it->offset = currentSize;
currentSize += it->size + 8; // This size doesn't include the header and size entry
break;
}
debugC(3, kDebugSaving, "Rebuild RIFX resource index %d: '%s', %d bytes @ 0x%08x (%d), flags: %x unk1: %x nextFreeResourceId: %d",
it->index, tag2str(it->tag), it->size, it->offset, it->offset, it->flags, it->unk1, it->nextFreeResourceID);
}
// Now that all sizes have been updated, we can safely calculate the overall archive size
for (auto &it : builtResources) {
if (it->tag == MKTAG('R', 'I', 'F', 'X') || it->tag == MKTAG('X', 'F', 'I', 'R')) {
it->size = getArchiveSize(builtResources) + 8;
}
}
return builtResources;
}
uint32 RIFXArchive::getImapSize() {
// The length of imap doesn't change
// This is the length without header and size
return _imapLength;
}
uint32 RIFXArchive::getMmapSize() {
// The headers: 24 bytes and and 20 bytes per resources
return 24 + 20 * _resources.size();
}
uint32 RIFXArchive::getArchiveSize(Common::Array resources) {
// This will be called after updating the size of all the resources
uint32 size = 0;
for (auto it : resources) {
if (it->tag != MKTAG('R', 'I', 'F', 'X') && it->tag != MKTAG('X', 'F', 'I', 'R')) {
size += it->size + 8; // The 8 is to account for the header and size
}
}
return size;
}
uint32 RIFXArchive::getCASResourceSize(uint32 libResourceID) {
uint castTag = MKTAG('C', 'A', 'S', 't');
uint32 maxCastId = 0;
// maxCastId is the basically the number of cast members present in the cast
// This is the number of entries present in the 'CAS*' resource
for (auto &it : _types[castTag]) {
if (it._value.libResourceId == libResourceID) {
maxCastId = MAX(maxCastId, it._value.castId);
}
}
return (maxCastId + 1) * 4;
}
uint32 RIFXArchive::getKeyTableResourceSize() {
// 12 bytes of header + 12 * number of entries
uint32 size = 0;
for (auto &childTag : _keyData) {
KeyMap keyMap = childTag._value;
for (auto &parentIndex : keyMap) {
KeyArray keyArray = parentIndex._value;
size += keyArray.size() * 12u;
}
}
return size + 12u;
}
void dumpFile(Common::String fileName, uint32 id, uint32 tag, byte *dumpData, uint32 dumpSize) {
debug("dumpFile():: dumping file %s, resource: %s (id: %d)", fileName.c_str(), tag2str(tag), id);
Common::DumpFile out;
Common::String fname = Common::String::format("./dumps/%d-%s-%s", id, tag2str(tag), fileName.c_str());
// Write the movie out, stored in dumpData
if (out.open(Common::Path(fname), true)) {
out.write(dumpData, dumpSize);
out.flush();
out.close();
} else {
warning("RIFXArchive::writeStream: Error saving the file %s", fname.c_str());
}
}
uint32 RIFXArchive::findParentIndex(uint32 tag, uint16 index) {
// We have to find the parent
// Look into the keyData, for all parents of resource tag
// If the parent contains this resource's index, that's our parent
for (auto &it : _keyData[tag]) {
for (auto &kt : it._value) {
if (kt == index) {
return it._key;
}
}
}
warning("RIFXArchive::findParentIndex: The parent for resource: %s, index: %d, was not found", tag2str(tag), index);
return 0;
}
SavedArchive::SavedArchive(Common::String target) {
Common::StringArray saveFileList = g_engine->getSaveFileManager()->listSavefiles(target + "-*");
debugC(3, kDebugLoading, "DirectorEngine:: loadSaveFiles: Loading save files");
for (auto saveFileName : saveFileList) {
// Derive the original file name from the save file name
// Save files are named target_name-save_filename
Common::String origFileName = saveFileName.substr(target.size() + 1);
debugC(3, kDebugLoading, "Found save file: %s -> %s", saveFileName.c_str(), origFileName.c_str());
_files[origFileName] = saveFileName;
}
}
bool SavedArchive::hasFile(const Common::Path &path) const {
return (_files.find(path.toString()) != _files.end());
}
int SavedArchive::listMembers(Common::ArchiveMemberList &list) const {
int count = 0;
for (FileMap::const_iterator i = _files.begin(); i != _files.end(); ++i) {
list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(i->_key, *this)));
++count;
}
return count;
}
const Common::ArchiveMemberPtr SavedArchive::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *SavedArchive::createReadStreamForMember(const Common::Path &path) const {
FileMap::const_iterator fDesc = _files.find(path.toString());
if (fDesc == _files.end())
return nullptr;
return g_engine->getSaveFileManager()->openForLoading(fDesc->_value);
}
} // End of namespace Director