scummvm/engines/director/resource.cpp
Scott Percival 7ac5047bc7 DIRECTOR: Move _mainArchive handle to DirectorEngine
_mainHandle is a pointer to the projector archive, and is set once at
the start of execution. DirectorEngine is a better place for it rather
than the first Window, as other Windows might try and access it.

Fixes crash when changing the cursor in Microphone Fiend.
2025-03-25 12:17:34 +08:00

899 lines
29 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 "common/archive.h"
#include "common/config-manager.h"
#include "common/error.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/macresman.h"
#include "common/memstream.h"
#include "common/bufferedstream.h"
#include "common/substream.h"
#include "common/formats/winexe.h"
#include "director/types.h"
#include "graphics/wincursor.h"
#include "director/director.h"
#include "director/archive.h"
#include "director/cast.h"
#include "director/movie.h"
#include "director/score.h"
#include "director/util.h"
#include "director/window.h"
namespace Director {
Archive *DirectorEngine::createArchive() {
if (getVersion() < 400) {
if (getPlatform() != Common::kPlatformWindows)
return new MacArchive();
return new RIFFArchive();
}
return new RIFXArchive();
}
Common::Error Window::loadInitialMovie() {
Common::String movie = (_vm->getGameGID() == GID_TESTALL) ? getNextMovieFromQueue().movie : _vm->getEXEName();
debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
debug(0, "@@@@ Loading initial movie '%s'", movie.c_str());
debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
if (movie.empty())
return Common::kPathNotFile;
loadINIStream();
if (g_director->getVersion() >= 500) {
loadXtrasFromPath();
}
Common::Path path = findPath(movie);
Archive *mainArchive = g_director->openArchive(path);
if (!mainArchive) {
warning("Window::loadInitialMovie: Cannot open main movie");
return Common::kNoGameDataFoundError;
}
g_director->setMainArchive(mainArchive);
probeResources(mainArchive);
// Load multiple-resources based executable file (Projector)
Common::String rawEXE = _vm->getRawEXEName();
ProjectorArchive *multiArchive = new ProjectorArchive(findPath(rawEXE));
if (multiArchive->isLoaded()) {
// A valid projector archive, add to SearchMan
SearchMan.add(_vm->getRawEXEName(), multiArchive);
if (ConfMan.getBool("dump_scripts"))
multiArchive->dumpArchive(Common::Path("./dumps").join(encodePathForDump(movie)));
} else {
delete multiArchive;
}
_currentMovie = new Movie(this);
_currentPath = getPath(movie, _currentPath);
Common::Path sharedCastPath = getSharedCastPath();
if (!sharedCastPath.empty() && !(sharedCastPath == path))
_currentMovie->loadSharedCastsFrom(sharedCastPath);
// load startup movie
Common::Path startupPath = g_director->getStartupPath();
if (!startupPath.empty()) {
Common::SeekableReadStream *const stream = SearchMan.createReadStreamForMember(startupPath);
if (stream) {
uint size = stream->size();
char *script = (char *)calloc(size + 1, 1);
stream->read(script, size);
LingoArchive *mainLingoArchive = g_director->getCurrentMovie()->getMainLingoArch();
mainLingoArchive->addCode(Common::U32String(script, Common::kMacRoman), kMovieScript, 65535);
_currentMovie->processEvent(kEventStartUp);
free(script);
delete stream;
} else {
warning("Window::LoadInitialMovie: failed to load startup scripts");
}
}
_currentMovie->setArchive(mainArchive);
_currentMovie->getScore()->_skipTransition = true;
// XLibs are usually loaded in the initial movie.
// These may not be present if a --start-movie is specified, so
// we sometimes need to load them manually.
if (!g_director->getStartMovie().startMovie.empty())
loadStartMovieXLibs();
return Common::kNoError;
}
void Window::probeResources(Archive *archive) {
if (archive->hasResource(MKTAG('B', 'N', 'D', 'L'), "Projector")) {
debugC(2, kDebugLoading, "Window::probeResources: Detected Projector file");
if (archive->hasResource(MKTAG('v', 'e', 'r', 's'), -1)) {
Common::Array<uint16> vers = archive->getResourceIDList(MKTAG('v', 'e', 'r', 's'));
for (auto &iterator : vers) {
Common::SeekableReadStreamEndian *vvers = archive->getResource(MKTAG('v', 'e', 'r', 's'), iterator);
Common::MacResManager::MacVers *v = Common::MacResManager::parseVers(vvers);
debugC(2, kDebugLoading, "Window::probeResources: Detected vers %d.%d %s.%d region %d '%s' '%s'", v->majorVer, v->minorVer, v->devStr.c_str(),
v->preReleaseVer, v->region, v->str.c_str(), v->msg.c_str());
delete v;
delete vvers;
}
}
if (archive->hasResource(MKTAG('S', 'T', 'R', '#'), 0)) {
if (_currentMovie)
_currentMovie->setArchive(archive);
Common::SeekableReadStreamEndian *name = archive->getResource(MKTAG('S', 'T', 'R', '#'), 0);
int num = name->readUint16();
if (num != 1) {
warning("Window::probeResources: Incorrect number of strings in Projector file");
}
if (num == 0)
error("Window::probeResources: No strings in Projector file");
Common::String sname = decodePlatformEncoding(name->readPascalString());
Common::Path moviePath = findMoviePath(sname);
if (!moviePath.empty()) {
_nextMovie.movie = moviePath.toString(g_director->_dirSeparator);
warning("Window::probeResources: Replaced score name with: %s (from %s)", _nextMovie.movie.c_str(), sname.c_str());
if (_currentMovie) {
delete _currentMovie;
_currentMovie = nullptr;
}
Archive *subMovie = g_director->openArchive(moviePath);
if (subMovie) {
probeResources(subMovie);
}
} else {
warning("Window::probeResources: Couldn't find score with name: %s", sname.c_str());
}
delete name;
}
}
if (g_director->getPlatform() == Common::kPlatformMacintosh) {
// On Macintosh, you can add additional chunks to the resource
// fork of the file to state which XObject or HyperCard XCMD/XFCNs
// need to be loaded in.
MacArchive *resFork = new MacArchive();
Common::Path resForkPathName = findPath(archive->getPathName());
if (resFork->openFile(resForkPathName)) {
if (resFork->hasResource(MKTAG('X', 'C', 'O', 'D'), -1)) {
Common::Array<uint16> xcod = resFork->getResourceIDList(MKTAG('X', 'C', 'O', 'D'));
for (auto &iterator : xcod) {
Resource res = resFork->getResourceDetail(MKTAG('X', 'C', 'O', 'D'), iterator);
debug(0, "Window::probeResources: Detected XObject '%s'", res.name.c_str());
g_lingo->openXLib(res.name, kXObj, resForkPathName);
}
}
if (resFork->hasResource(MKTAG('X', 'C', 'M', 'D'), -1)) {
Common::Array<uint16> xcmd = resFork->getResourceIDList(MKTAG('X', 'C', 'M', 'D'));
for (auto &iterator : xcmd) {
Resource res = resFork->getResourceDetail(MKTAG('X', 'C', 'M', 'D'), iterator);
debug(0, "Window::probeResources: Detected XCMD '%s'", res.name.c_str());
g_lingo->openXLib(res.name, kXObj, resForkPathName);
}
}
if (resFork->hasResource(MKTAG('X', 'F', 'C', 'N'), -1)) {
Common::Array<uint16> xfcn = resFork->getResourceIDList(MKTAG('X', 'F', 'C', 'N'));
for (auto &iterator : xfcn) {
Resource res = resFork->getResourceDetail(MKTAG('X', 'F', 'C', 'N'), iterator);
debug(0, "Window::probeResources: Detected XFCN '%s'", res.name.c_str());
g_lingo->openXLib(res.name, kXObj, resForkPathName);
}
}
}
delete resFork;
}
}
void DirectorEngine::addArchiveToOpenList(const Common::Path &path) {
// First, remove it if it is present
_allOpenResFiles.remove(path);
// And add it to the list front
_allOpenResFiles.push_front(path);
}
Archive *DirectorEngine::openArchive(const Common::Path &path) {
debug(1, "DirectorEngine::openArchive(\"%s\")", path.toString().c_str());
// If the archive is already open, don't reopen it;
// just init from the existing archive. This prevents errors that
// can happen when trying to load the same archive more than once.
if (_allSeenResFiles.contains(path)) {
addArchiveToOpenList(path);
return _allSeenResFiles.getVal(path);
}
Archive *result = nullptr;
if (getPlatform() == Common::kPlatformWindows) {
result = loadEXE(path);
} else {
result = loadMac(path);
}
if (!result) {
result = createArchive();
if (!result->openFile(path)) {
delete result;
return nullptr;
}
}
result->setPathName(path);
_allSeenResFiles.setVal(path, result);
addArchiveToOpenList(path);
return result;
}
void Window::loadINIStream() {
Common::SeekableReadStream *iniStream = SearchMan.createReadStreamForMember("LINGO.INI");
if (iniStream) {
char *script = (char *)calloc(iniStream->size() + 1, 1);
iniStream->read(script, iniStream->size());
_currentMovie = new Movie(this);
_currentMovie->getMainLingoArch()->addCode(Common::U32String(script, Common::kWindows1252), kMovieScript, 0);
_currentMovie->processEvent(kEventStartUp);
delete _currentMovie;
_currentMovie = nullptr;
free(script);
delete iniStream;
} else {
debugC(1, kDebugLoading, "Window::loadINIStream: No LINGO.INI");
}
}
Archive *DirectorEngine::loadEXE(const Common::Path &movie) {
Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(movie);
if (!exeStream) {
debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to open file '%s'", movie.toString().c_str());
return nullptr;
}
Archive *result = nullptr;
uint32 initialTag = exeStream->readUint32LE();
if (initialTag == MKTAG('R', 'I', 'F', 'X') || initialTag == MKTAG('X', 'F', 'I', 'R')) {
// we've encountered a movie saved from Director, not a projector.
result = loadEXERIFX(exeStream, 0);
// ownership handed to loadEXERIFX
exeStream = nullptr;
} else if (initialTag == MKTAG('R', 'I', 'F', 'F') || initialTag == MKTAG('F', 'F', 'I', 'R')) { // This is just a normal movie
result = new RIFFArchive();
if (!result->openStream(exeStream, 0)) {
debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to load RIFF from '%s'", movie.toString().c_str());
delete result;
return nullptr;
}
// ownership handed to RIFFArchive
exeStream = nullptr;
} else {
Common::WinResources *exe = Common::WinResources::createFromEXE(movie);
if (!exe) {
debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to open EXE '%s'", movie.toString().c_str());
delete exeStream;
return nullptr;
}
const Common::Array<Common::WinResourceID> versions = exe->getIDList(Common::kWinVersion);
for (uint i = 0; i < versions.size(); i++) {
Common::WinResources::VersionInfo *info = exe->getVersionResource(versions[i]);
for (Common::WinResources::VersionHash::const_iterator it = info->hash.begin(); it != info->hash.end(); ++it)
debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): info <%s>: <%s>", it->_key.c_str(), it->_value.encode().c_str());
delete info;
}
Common::Array<Common::WinResourceID> idList = exe->getIDList(Common::kWinGroupCursor);
for (uint i = 0; i < idList.size(); i++) {
Graphics::WinCursorGroup *group = Graphics::WinCursorGroup::createCursorGroup(exe, idList[i]);
g_director->_winCursor.push_back(group);
}
delete exe;
exeStream->seek(-4, SEEK_END);
exeStream->seek(exeStream->readUint32LE());
if (g_director->getVersion() >= 700) {
result = loadEXEv7(exeStream);
} else if (g_director->getVersion() >= 500) {
result = loadEXEv5(exeStream);
} else if (g_director->getVersion() >= 400) {
result = loadEXEv4(exeStream);
} else if (g_director->getVersion() >= 200) {
result = loadEXEv3(exeStream);
} else {
warning("DirectorEngine::loadEXE(): Unhandled Windows EXE version %d", g_director->getVersion());
delete exeStream;
return nullptr;
}
// ownership passed to an EXE loader
exeStream = nullptr;
}
if (result)
result->setPathName(movie);
else if (exeStream)
delete exeStream;
return result;
}
Archive *DirectorEngine::loadEXEv3(Common::SeekableReadStream *stream) {
uint32 mmmSize = 0;
Common::String mmmFileName;
Common::String directoryName;
uint16 entryCount = stream->readUint16LE();
stream->skip(5); // unknown
for (int i = 0; i < entryCount; ++i) {
uint32 mmmSize_ = stream->readUint32LE(); // Main MMM size
Common::String mmmFileName_ = stream->readPascalString();
Common::String directoryName_ = stream->readPascalString();
debugC(1, kDebugLoading, "MMM #%d: '%s'", i, mmmFileName_.c_str());
debugC(1, kDebugLoading, "Directory Name: '%s'", directoryName_.c_str());
debugC(1, kDebugLoading, "MMM size: %d (0x%x)", mmmSize_, mmmSize_);
if (i == 0) {
mmmSize = mmmSize_;
mmmFileName = mmmFileName_;
directoryName = directoryName_;
} else {
if (!SearchMan.hasFile(Common::Path(mmmFileName_, g_director->_dirSeparator)))
warning("DirectorEngine::loadEXEv3(): Failed to find MMM '%s'", mmmFileName_.c_str());
else {
Common::SeekableReadStream *const mmmFile_ = SearchMan.createReadStreamForMember(Common::Path(mmmFileName_, g_director->_dirSeparator));
uint32 mmmFileSize_ = mmmFile_->size();
if (mmmSize_ != mmmFileSize_)
warning("DirectorEngine::loadEXEv3(): File size for '%s' doesn't match. Got %d (0x%x), want %d (0x%x)", mmmFileName_.c_str(), mmmFileSize_, mmmFileSize_, mmmSize_, mmmSize_);
delete mmmFile_;
}
}
// Print a blank line to separate the entries, format a blank string to silence gcc warning
debugC(1, kDebugLoading, "%s", "");
}
Archive *result = nullptr;
if (mmmSize) {
uint32 riffOffset = stream->pos();
debugC(1, kDebugLoading, "RIFF offset: %d (%x)", riffOffset, riffOffset);
if (ConfMan.getBool("dump_scripts")) {
Common::DumpFile out;
byte *buf = (byte *)malloc(mmmSize);
stream->read(buf, mmmSize);
stream->seek(riffOffset);
Common::String fname = Common::String::format("./dumps/%s", encodePathForDump(mmmFileName).c_str());
if (!out.open(fname.c_str(), true)) {
warning("DirectorEngine::loadEXEv3(): Can not open dump file %s", fname.c_str());
} else {
out.write(buf, mmmSize);
out.flush();
out.close();
}
free(buf);
}
result = new RIFFArchive();
if (result->openStream(stream, riffOffset))
return result;
warning("DirectorEngine::loadEXEv3(): Failed to load RIFF from EXE");
// ownership of stream is passed to result, which will clean it up
delete result;
result = nullptr;
}
Common::String fullPathStr = directoryName + mmmFileName;
fullPathStr = convertPath(fullPathStr);
Common::Path fullPath = findMoviePath(fullPathStr);
if (fullPath.empty()) {
warning("DirectorEngine::loadEXEv3(): Could not find '%s'", fullPathStr.c_str());
return nullptr;
}
// The EXE is kicking us to a different movie on startup;
// and we want to treat it as a proper movie change
// (instead of pretending that the EXE is this movie) so that
// elements like the search path are correct.
getCurrentWindow()->setNextMovie(fullPathStr);
// Return an empty archive to avoid "Game data not found".
result = createArchive();
return result;
}
Archive *DirectorEngine::loadEXEv4(Common::SeekableReadStream *stream) {
uint32 ver = stream->readUint32BE();
if (ver != MKTAG('P', 'J', '9', '3')) {
warning("DirectorEngine::loadEXEv4(): Invalid projector tag found in v4 EXE [%s]", tag2str(ver));
delete stream;
return nullptr;
}
uint32 rifxOffset = stream->readUint32LE();
/* uint32 fontMapOffset = */ stream->readUint32LE();
/* uint32 resourceForkOffset1 = */ stream->readUint32LE();
/* uint32 resourceForkOffset2 = */ stream->readUint32LE();
stream->readUint32LE(); // graphics DLL offset
stream->readUint32LE(); // sound DLL offset
/* uint32 rifxOffsetAlt = */ stream->readUint32LE(); // equivalent to rifxOffset
uint32 flags = stream->readUint32LE();
debugC(1, kDebugLoading, "DirectorEngine::loadEXEv4(): PJ93 projector flags: %08x", flags);
return loadEXERIFX(stream, rifxOffset);
}
Archive *DirectorEngine::loadEXEv5(Common::SeekableReadStream *stream) {
uint32 ver = stream->readUint32LE();
if (ver != MKTAG('P', 'J', '9', '5')) {
warning("DirectorEngine::loadEXEv5(): Invalid projector tag found in v5 EXE [%s]", tag2str(ver));
delete stream;
return nullptr;
}
uint32 rifxOffset = stream->readUint32LE();
uint32 pflags = stream->readUint32LE();
uint32 flags = stream->readUint32LE();
stream->readUint16LE(); // x
stream->readUint16LE(); // y
stream->readUint16LE(); // screenWidth
stream->readUint16LE(); // screenHeight
stream->readUint32LE(); // number of components
stream->readUint32LE(); // number of driver files
stream->readUint32LE(); // fontMapOffset
debugC(1, kDebugLoading, "DirectorEngine::loadEXEv5(): PJ95 projector pflags: %08x flags: %08x", pflags, flags);
return loadEXERIFX(stream, rifxOffset);
}
Archive *DirectorEngine::loadEXEv7(Common::SeekableReadStream *stream) {
uint32 ver = stream->readUint32LE();
if (ver != MKTAG('P', 'J', '0', '0') && ver != MKTAG('P', 'J', '0', '1')) {
warning("DirectorEngine::loadEXEv7(): Invalid projector tag found in v7 EXE [%s]", tag2str(ver));
delete stream;
return nullptr;
}
uint32 rifxOffset = stream->readUint32LE();
stream->readUint32LE(); // unknown
stream->readUint32LE(); // unknown
stream->readUint32LE(); // unknown
stream->readUint32LE(); // unknown
stream->readUint32LE(); // some DLL offset
return loadEXERIFX(stream, rifxOffset);
}
Archive *DirectorEngine::loadEXERIFX(Common::SeekableReadStream *stream, uint32 offset) {
Archive *result = new RIFXArchive();
if (!result->openStream(stream, offset)) {
warning("DirectorEngine::loadEXERIFX(): Failed to load RIFX from EXE");
delete result;
result = nullptr;
}
return result;
}
Archive *DirectorEngine::loadMac(const Common::Path &movie) {
Archive *result = nullptr;
if (g_director->getVersion() < 400) {
// The data is part of the resource fork of the executable
result = createArchive();
if (!result->openFile(movie)) {
delete result;
result = nullptr;
debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Could not open '%s'", movie.toString().c_str());
}
} else {
// The RIFX is located in the data fork of the executable
Common::SeekableReadStream *dataFork = Common::MacResManager::openFileOrDataFork(movie);
if (!dataFork) {
debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Failed to open Mac binary '%s'", movie.toString().c_str());
return nullptr;
}
result = new RIFXArchive();
result->setPathName(movie);
// First we need to detect PPC vs. 68k
uint32 tag = dataFork->readUint32BE();
uint32 startOffset;
if (SWAP_BYTES_32(tag) == MKTAG('P', 'J', '9', '3') || tag == MKTAG('P', 'J', '9', '5') || tag == MKTAG('P', 'J', '0', '0')) {
// PPC: The RIFX shares the data fork with the binary
startOffset = dataFork->readUint32BE();
} else {
// 68k: The RIFX is the only thing in the data fork
startOffset = 0;
}
if (!result->openStream(dataFork, startOffset)) {
debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Failed to load RIFX from Mac binary");
delete result;
result = nullptr;
}
}
return result;
}
void Window::loadXtrasFromPath() {
// For D5 and above, Xtras are considered plug and play.
// According to Director Demystified: it considers an
// Xtra installed if it's located in the folder named "Xtras"
// that's on the same level (i.e., in the same folder)
// as the application itself. Xtras can be buried up to
// five layers deep in nested folders within this folder,
// and they'll still be recognized.
Common::ArchiveMemberList targets;
SearchMan.listMatchingMembers(targets, Common::Path("xtras/*"), true);
for (auto &it : targets) {
if (it->isDirectory())
continue;
debugC(5, kDebugLingoExec, "Window::loadXtrasFromPath(): attempting to open Xtra %s", it->getPathInArchive().toString(g_director->_dirSeparator).c_str());
g_lingo->openXLib(it->getFileName(), kXtraObj, it->getPathInArchive());
}
}
void Window::loadStartMovieXLibs() {
if (strcmp(g_director->getGameId(), "warlock") == 0 && g_director->getPlatform() != Common::kPlatformWindows) {
g_lingo->openXLib("FPlayXObj", kXObj, Common::Path());
}
g_lingo->openXLib("SerialPort", kXObj, Common::Path());
}
ProjectorArchive::ProjectorArchive(Common::Path path)
: _path(path), _files() {
// Buffer 100K into memory
Common::SeekableReadStream *stream = createBufferedReadStream();
if (!stream) {
_isLoaded = false;
return;
}
// Build our filemap using the buffered stream
_isLoaded = loadArchive(stream);
delete stream;
}
Common::SeekableReadStream *ProjectorArchive::createBufferedReadStream() {
const uint32 READ_BUFFER_SIZE = 1024 * 100;
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(_path);
if (!stream) {
warning("ProjectorArchive::createBufferedReadStream(): Cannot open %s", _path.toString().c_str());
return nullptr;
}
return Common::wrapBufferedSeekableReadStream(stream, READ_BUFFER_SIZE, DisposeAfterUse::YES);
}
ProjectorArchive::~ProjectorArchive() {
_files.clear();
}
bool ProjectorArchive::loadArchive(Common::SeekableReadStream *stream) {
bool bigEndian = false, found = false;
uint32 off, tag, rifxOffset;
stream->seek(-4, SEEK_END);
off = stream->readUint32LE();
stream->seek(off);
tag = stream->readUint32BE();
// Check whether we got a 'PJ' tag while ignoring the version and accounting for endianness
if (((tag & 0xffff0000) != MKTAG('P','J', 0, 0)) && ((tag & 0x0000ffff) != MKTAG(0, 0, 'J','P'))) {
debugC(5, kDebugLoading, "ProjectorArchive::loadArchive(): Projector Tag not found");
return false;
}
rifxOffset = stream->readUint32LE();
// if value is bigger than the stream size this is most likely a endianness issue.
if (rifxOffset > stream->size()) {
bigEndian = true;
rifxOffset = SWAP_BYTES_32(rifxOffset);
}
stream->seek(rifxOffset);
tag = stream->readUint32BE();
debugC(1, kDebugLoading, "File: %s off: 0x%x (%u), tag: %s rifx: 0x%x (%d)", _path.toString().c_str(), off, off, tag2str(tag), rifxOffset, rifxOffset);
// Try to locate the very next Dict tag(byte-by-byte)
tag = stream->readUint32BE();
found = false;
// This loop has negligible performance impact due to the stream being buffered.
// Furthermore, comparing 4 bytes at a time should be pretty fast on modern systems.
while (!stream->eos()) {
if (tag == MKTAG('D', 'i', 'c', 't') || tag == MKTAG('t', 'c', 'i', 'D')) {
found = true;
break;
}
stream->seek(-3, SEEK_CUR);
tag = stream->readUint32BE();
}
// Return if dict tag is not found
if (!found) {
warning("ProjectorArchive::loadArchive(): Dict Tag not found.");
return false;
}
uint32 size = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
uint32 dictOff = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
// Jump to the actual dict block and parse it
if (!stream->seek(dictOff)) {
warning("BUILDBOT: ProjectorArchive::loadArchive(): Incorrect dict offset (0x%x)", dictOff);
return false;
}
tag = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
size = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
stream->seek(dictOff + 24);
uint32 cnt = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
// Correction for when there is only a single element present according to the dict
uint8 offsetDict = bigEndian && cnt == 1 ? 2 : 0;
bool oBigEndian = bigEndian;
// For 16-bit win projector
if (cnt > 0xFFFF) {
cnt = SWAP_BYTES_32(cnt);
offsetDict = 2;
bigEndian = true;
}
debugC(1, kDebugLoading, "Dict off: 0x%x, Size: 0x%x cnt: %d", dictOff, size, cnt);
uint32 pt = (cnt * 8) + (64 - offsetDict);
Common::StringArray arr(cnt);
for (uint32 i = 0; i < cnt; i++) {
// if this fails it likely means that the dict was invalid.
if (!stream->seek(dictOff + pt, SEEK_SET)) {
warning("ProjectorArchive::loadArchive(): Incorrect entry name offset (0x%x)", dictOff + pt);
return false;
}
uint32 namelen = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
arr[i] = stream->readString(0, namelen);
if (i < cnt - 1) {
int sub = (namelen % 4) ? (namelen % 4) : 4;
pt += 4 + namelen + (4 - sub);
}
}
bigEndian = oBigEndian;
// Jump to the first block which should start right after the dict while making sure size is aligned.
size += (size % 2);
stream->seek(dictOff + size + 8, SEEK_SET);
for (uint32 i = 0; i < cnt; i++) {
tag = stream->readUint32BE();
size = bigEndian ? stream->readUint32BE() : stream->readUint32LE();
Common::Path path = toSafePath(arr[i]);
debugC(1, kDebugLoading, "Entry: %s offset %lX (%ld) tag %s size %d", path.toString().c_str(), long(stream->pos() - 8), long(stream->pos() - 8), tag2str(tag), size);
Entry entry;
// subtract 8 since we want to include tag and size as well
entry.offset = static_cast<uint32>(stream->pos() - 8);
entry.size = size + 8;
_files[path] = entry;
// Align size for the next seek.
size += (size % 2);
// if this fails it suggests something is either wrong with the dict or the file itself.
if (!stream->seek(size, SEEK_CUR)) {
warning("ProjectorArchive::loadArchive(): Could not read next block (0x%x) Prev Block(0x%x : %d)",
entry.offset + entry.size, entry.offset, entry.size);
return false;
}
}
return true;
}
bool ProjectorArchive::hasFile(const Common::Path &path) const {
return (_files.find(path) != _files.end());
}
int ProjectorArchive::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 ProjectorArchive::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *ProjectorArchive::createReadStreamForMember(const Common::Path &path) const {
FileMap::const_iterator fDesc = _files.find(path);
if (fDesc == _files.end())
return nullptr;
debugC(3, kDebugLoading, "ProjectorArchive::createReadStreamForMember(): Opening built-in file '%s' @ 0x%08x, %d bytes",
path.toString().c_str(), fDesc->_value.offset, fDesc->_value.size);
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(_path);
stream->seek(fDesc->_value.offset, SEEK_SET);
byte *data = (byte *)malloc(fDesc->_value.size);
stream->read(data, fDesc->_value.size);
delete stream;
// Now we need to patch the mmap offset
// First, do sanity checking for the file
byte *ptr = data;
uint32 headerTag = READ_UINT32(ptr);
ptr += 4;
bool formatOK = true;
bool isBigEndian = false; // LE
if (headerTag == MKTAG('X', 'F', 'I', 'R')) {
isBigEndian = false;
} else if (headerTag == MKTAG('R', 'I', 'F', 'X')) {
isBigEndian = true;
} else {
formatOK = false;
}
// 4 bytes size
ptr += 4;
uint32 rifxType = isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr);
ptr += 4;
uint32 mmapOffset;
uint32 resCount;
int patchedOffests = 0;
switch (rifxType) {
case MKTAG('M', 'V', '9', '3'):
case MKTAG('M', 'C', '9', '5'):
case MKTAG('A', 'P', 'P', 'L'):
if ((isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr)) != MKTAG('i', 'm', 'a', 'p')) {
formatOK = false;
break;
}
ptr += 4;
// patch it
ptr += 4; // imap length
ptr += 4; // unknown
mmapOffset = isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr);
debugC(8, kDebugLoading, "ProjectorArchive::createReadStreamForMember(): Patched offset at 0x%08x from %u to %u",
(uint32)(ptr - data), mmapOffset, mmapOffset - fDesc->_value.offset);
mmapOffset -= fDesc->_value.offset;
isBigEndian ? WRITE_LE_UINT32(ptr, mmapOffset) : WRITE_BE_UINT32(ptr, mmapOffset);
patchedOffests++;
ptr = data + mmapOffset;
if ((isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr)) != MKTAG('m', 'm', 'a', 'p')) {
formatOK = false;
break;
}
ptr += 4; // mmap length
ptr += 4; // unknown
ptr += 4; // unknown
ptr += 4; // resCount + empty entries
resCount = isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr);
ptr += 4;
ptr += 8; // all 0xFF
ptr += 4; // id of the first free resource, -1 if none.
for (uint32 i = 0; i < resCount; i++) {
ptr += 4; // tag
uint32 size = isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr);
ptr += 4;
uint32 offset = isBigEndian ? READ_LE_UINT32(ptr) : READ_BE_UINT32(ptr);
if (size > 0 || offset >= fDesc->_value.offset) {
debugC(8, kDebugLoading, "ProjectorArchive::createReadStreamForMember(): Patched offset at 0x%08x from %u to %u",
(uint32)(ptr - data), offset, offset - fDesc->_value.offset);
offset -= fDesc->_value.offset;
patchedOffests++;
isBigEndian ? WRITE_LE_UINT32(ptr, offset) : WRITE_BE_UINT32(ptr, offset);
}
ptr += 4;
ptr += 2; // flags
ptr += 2; // unk1
ptr += 4; // nextFreeResourceId
}
break;
default:
formatOK = false;
}
if (!formatOK)
warning("ProjectorArchive::createReadStreamForMember(): File %s has unsupported format", path.toString().c_str());
debugC(3, kDebugLoading, "ProjectorArchive::createReadStreamForMember(): Patched %d offsets", patchedOffests);
return new Common::MemoryReadStream(data, fDesc->_value.size, DisposeAfterUse::YES);
}
} // End of namespace Director