/* 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/file.h" #include "common/savefile.h" #include "common/str.h" #include "common/system.h" #include "gui/filebrowser-dialog.h" #include "video/qt_decoder.h" #include "director/director.h" #include "director/movie.h" #include "director/lingo/lingo.h" #include "director/lingo/lingo-object.h" #include "director/lingo/lingo-utils.h" #include "director/lingo/xlibs/m/mmovie.h" /************************************************** * * USED IN: * Virtual Nightclub * **************************************************/ /* Multi Movie XObject by Mediamation Ltd. Copyright © Trip Media Ltd 1995-1996. --MMovie I mNew X mDispose IS mOpenMMovie, fileName II mCloseMMovie, movieIndex ISSSSS mPlaySegment, segmentName, restoreOpt, abortOpt, purgeOpt, asyncOpt ISSSSS mPlaySegLoop, segmentName, restoreOpt, abortOpt, purgeOpt, asyncOpt I mIdleSegment I mStopSegment IS mSeekSegment, segmentName II mSetSegmentTime, segTime IIIII mSetDisplayBounds, left, top, right, bottom II mGetMovieNormalWidth, movieIndex II mGetMovieNormalHeight, movieIndex II mGetSegCount, movieIndex SII mGetSegName, movieIndex, segmentIndex I mGetMovieRate II mSetMovieRate, ratePercent I mFlushEvents IIIII mInvalidateRect, left, top, right, bottom SSI mReadFile, fileName, scramble SSSI mWriteFile, fileName, data, scramble ISS mCopyFile, sourceFileName, destFileName I mCopyFileCont IS mFreeSpace, driveLetter IS mDeleteFile, fileName S mVolList */ namespace Director { const char *const MMovieXObj::xlibName = "MMovie"; const XlibFileDesc MMovieXObj::fileNames[] = { { "MMovie", nullptr }, { nullptr, nullptr }, }; static const MethodProto xlibMethods[] = { { "Movie", MMovieXObj::m_Movie, 4, 4, 400 }, { "new", MMovieXObj::m_new, 0, 0, 400 }, { "dispose", MMovieXObj::m_dispose, 0, 0, 400 }, { "openMMovie", MMovieXObj::m_openMMovie, 1, 1, 400 }, { "closeMMovie", MMovieXObj::m_closeMMovie, 1, 1, 400 }, { "playSegment", MMovieXObj::m_playSegment, 5, 5, 400 }, { "playSegLoop", MMovieXObj::m_playSegLoop, 5, 5, 400 }, { "idleSegment", MMovieXObj::m_idleSegment, 0, 0, 400 }, { "stopSegment", MMovieXObj::m_stopSegment, 0, 0, 400 }, { "seekSegment", MMovieXObj::m_seekSegment, 1, 1, 400 }, { "setSegmentTime", MMovieXObj::m_setSegmentTime, 1, 1, 400 }, { "setDisplayBounds", MMovieXObj::m_setDisplayBounds, 4, 4, 400 }, { "getMovieNormalWidth", MMovieXObj::m_getMovieNormalWidth, 1, 1, 400 }, { "getMovieNormalHeight", MMovieXObj::m_getMovieNormalHeight, 1, 1, 400 }, { "getSegCount", MMovieXObj::m_getSegCount, 1, 1, 400 }, { "getSegName", MMovieXObj::m_getSegName, 2, 2, 400 }, { "getMovieRate", MMovieXObj::m_getMovieRate, 0, 0, 400 }, { "setMovieRate", MMovieXObj::m_setMovieRate, 1, 1, 400 }, { "flushEvents", MMovieXObj::m_flushEvents, 0, 0, 400 }, { "invalidateRect", MMovieXObj::m_invalidateRect, 4, 4, 400 }, { "readFile", MMovieXObj::m_readFile, 2, 2, 400 }, { "writeFile", MMovieXObj::m_writeFile, 3, 3, 400 }, { "copyFile", MMovieXObj::m_copyFile, 2, 2, 400 }, { "copyFileCont", MMovieXObj::m_copyFileCont, 0, 0, 400 }, { "freeSpace", MMovieXObj::m_freeSpace, 1, 1, 400 }, { "deleteFile", MMovieXObj::m_deleteFile, 1, 1, 400 }, { "volList", MMovieXObj::m_volList, 0, 0, 400 }, { nullptr, nullptr, 0, 0, 0 } }; static const BuiltinProto xlibBuiltins[] = { { nullptr, nullptr, 0, 0, 0, VOIDSYM } }; MMovieXObject::MMovieXObject(ObjectType ObjectType) :Object("MMovie") { _objType = ObjectType; } MMovieXObject::~MMovieXObject() { _lastFrame.free(); for (auto &it : _movies) { if (it._value._video) { delete it._value._video; it._value._video = nullptr; } } } int MMovieXObject::playSegment(int movieIndex, int segIndex, bool looping, bool restore, bool shiftAbort, bool abortOnClick, bool purge, bool async) { int result = MMovieError::MMOVIE_INVALID_MOVIE_INDEX; if (_movies.contains(movieIndex)) { MMovieFile &movie = _movies.getVal(movieIndex); result = MMovieError::MMOVIE_INDEX_OUT_OF_RANGE; if (segIndex <= (int)movie.segments.size() && segIndex > 0) { MMovieSegment &segment = movie.segments[segIndex - 1]; _currentMovieIndex = movieIndex; _currentSegmentIndex = segIndex; _looping = looping; _restore = restore; _shiftAbort = shiftAbort; _abortOnClick = abortOnClick; _purge = purge; _async = async; debugC(5, kDebugXObj, "MMovieXObject::playSegment(): hitting play on movie %s (%d) segment %s (%d) - %d", movie._path.toString().c_str(), movieIndex, segment._name.c_str(), segIndex, segment._start); movie._video->seek(Audio::Timestamp(0, segment._start, movie._video->getTimeScale())); movie._video->start(); result = MMovieError::MMOVIE_NONE; if (!_async) { result = updateScreenBlocking(); } } } return result; } bool MMovieXObject::stopSegment() { if (_currentMovieIndex && _currentSegmentIndex) { MMovieFile &movie = _movies.getVal(_currentMovieIndex); MMovieSegment &seg = movie.segments[_currentSegmentIndex - 1]; debugC(5, kDebugXObj, "MMovieXObject::stopSegment(): hitting stop on movie %s (%d) segment %s (%d) - %d", movie._path.toString().c_str(), _currentMovieIndex, seg._name.c_str(), _currentSegmentIndex, seg._start); if (movie._video) { movie._video->stop(); } _currentMovieIndex = 0; _currentSegmentIndex = 0; return true; } return false; } int MMovieXObject::updateScreenBlocking() { MMovieError result = MMovieError::MMOVIE_PLAYBACK_FINISHED; while (_currentMovieIndex && _currentSegmentIndex) { Common::Event event; bool keepPlaying = true; if (g_director->pollEvent(event)) { switch (event.type) { case Common::EVENT_QUIT: g_director->processEventQUIT(); // fallthrough case Common::EVENT_KEYDOWN: case Common::EVENT_RBUTTONDOWN: case Common::EVENT_LBUTTONDOWN: if (_abortOnClick) { result = MMovieError::MMOVIE_ABORT_DOUBLE_CLICK; keepPlaying = false; } else if (_shiftAbort) { if (event.type == Common::EVENT_RBUTTONDOWN || (event.type == Common::EVENT_LBUTTONDOWN && (g_system->getEventManager()->getModifierState() & Common::KBD_SHIFT))) { result = MMovieError::MMOVIE_ABORT_DOUBLE_CLICK; keepPlaying = false; } } break; default: break; } // pass event through to window manager. // this is required so that e.g. the stillDown is kept up to date g_director->_wm->processEvent(event); } if (!keepPlaying) break; updateScreen(); } return result; } int MMovieXObject::updateScreen() { int result = MMovieError::MMOVIE_CONTINUE_WITHOUT_PLAYING; if (_currentMovieIndex) { MMovieFile &movie = _movies.getVal(_currentMovieIndex); if (_currentSegmentIndex) { MMovieSegment &seg = movie.segments[_currentSegmentIndex - 1]; result = getTicks(); if (movie._video && movie._video->isPlaying() && movie._video->needsUpdate()) { const Graphics::Surface *frame = movie._video->decodeNextFrame(); if (frame && !_bounds.isEmpty()) { debugC(8, kDebugXObj, "MMovieXObject: rendering movie %s (%d), ticks %d", movie._path.toString().c_str(), _currentMovieIndex, getTicks()); Graphics::Surface *temp1 = frame->scale(_bounds.width(), _bounds.height(), false); Graphics::Surface *temp2 = temp1->convertTo(g_director->_pixelformat, movie._video->getPalette()); _lastFrame.copyFrom(*temp2); temp2->free(); delete temp2; temp1->free(); delete temp1; } } if (!_bounds.isEmpty()) g_system->copyRectToScreen(_lastFrame.getPixels(), _lastFrame.pitch, _bounds.left, _bounds.top, _bounds.width(), _bounds.height()); // do a time check uint32 endTime = Audio::Timestamp(0, seg._length + seg._start, movie._video->getTimeScale()).msecs(); debugC(8, kDebugXObj, "MMovieXObject::updateScreen(): time: %d, endTime: %d, ticks: %d, endTicks: %d", movie._video->getTime(), endTime, getTicks(), seg._length + seg._start); if (movie._video->getTime() >= endTime) { if (_looping) { debugC(5, kDebugXObj, "MMovieXObject::updateScreen(): rewinding loop on %s (%d), time: %d, ticks: %d", movie._path.toString().c_str(), _currentMovieIndex, movie._video->getTime(), getTicks()); movie._video->seek(Audio::Timestamp(0, seg._start, movie._video->getTimeScale())); } else { debugC(5, kDebugXObj, "MMovieXObject::updateScreen(): stopping %s (%d), time: %d, ticks: %d", movie._path.toString().c_str(), _currentMovieIndex, movie._video->getTime(), getTicks()); stopSegment(); result = MMovieError::MMOVIE_PLAYBACK_FINISHED; } } } } g_system->updateScreen(); g_director->delayMillis(10); return result; } int MMovieXObject::getTicks() { if (_currentMovieIndex && _currentSegmentIndex) { MMovieFile &movie = _movies.getVal(_currentMovieIndex); MMovieSegment &segment = movie.segments[_currentSegmentIndex - 1]; if (movie._video) { _lastTicks = movie._video->getTime() - Audio::Timestamp(0, segment._start, movie._video->getTimeScale()).msecs(); _lastTicks = _lastTicks * 60 / 1000; } } return _lastTicks; } void MMovieXObj::open(ObjectType type, const Common::Path &path) { MMovieXObject::initMethods(xlibMethods); MMovieXObject *xobj = new MMovieXObject(type); g_lingo->exposeXObject(xlibName, xobj); g_lingo->initBuiltIns(xlibBuiltins); } void MMovieXObj::close(ObjectType type) { MMovieXObject::cleanupMethods(); g_lingo->_globalvars[xlibName] = Datum(); } void MMovieXObj::m_new(int nargs) { g_lingo->printArgs("MMovieXObj::m_new", nargs); g_lingo->dropStack(nargs); g_lingo->push(g_lingo->_state->me); } XOBJSTUB(MMovieXObj::m_Movie, 0) XOBJSTUBNR(MMovieXObj::m_dispose) void MMovieXObj::m_openMMovie(int nargs) { g_lingo->printArgs("MMovieXObj::m_openMMovie", nargs); if (nargs != 1) { g_lingo->dropStack(nargs); g_lingo->push(Datum(-1)); return; } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); Common::String basename = g_lingo->pop().asString(); Common::Path path = findPath(basename); if (path.empty()) { g_lingo->push(MMovieError::MMOVIE_INVALID_OFFSETS_FILE); return; } Common::Path offsetsPath = findPath(basename.substr(0, basename.size()-4) + ".ofs"); if (offsetsPath.empty()) { g_lingo->push(MMovieError::MMOVIE_INVALID_OFFSETS_FILE); return; } if (me->_moviePathMap.contains(basename)) { g_lingo->push(MMovieError::MMOVIE_MOVIE_ALREADY_OPEN); return; } Common::File offsetsFile; if (!offsetsFile.open(offsetsPath)) { g_lingo->push(MMovieError::MMOVIE_INVALID_OFFSETS_FILE); return; } MMovieFile movie(path); movie._video = new Video::QuickTimeDecoder(); if (!movie._video->loadFile(path)) { warning("MMovieXObj::m_openMMovie(): unable to open QT file %s", path.toString().c_str()); delete movie._video; movie._video = nullptr; } uint32 offsetCount = offsetsFile.readUint32BE(); offsetsFile.skip(0x3c); // rest of header should be blank debugC(5, kDebugXObj, "MMovieXObj:m_openMMovie(): opening movie %s (index %d)", path.toString().c_str(), me->_lastIndex); for (uint32 i = 0; i < offsetCount; i++) { Common::String name = offsetsFile.readString(' ', 0x10); uint32 start = offsetsFile.readUint32BE(); uint32 length = offsetsFile.readUint32BE(); debugC(5, kDebugXObj, "MMovieXObj:m_openMMovie(): adding segment %s (index %d): start %d (%dms) length %d (%dms)", name.c_str(), movie.segments.size(), start, Audio::Timestamp(0, start, movie._video->getTimeScale()).msecs(), length, Audio::Timestamp(0, length, movie._video->getTimeScale()).msecs()); movie.segments.push_back(MMovieSegment(name, start, length)); movie.segLookup.setVal(name, movie.segments.size()); } me->_movies.setVal(me->_lastIndex, movie); me->_moviePathMap.setVal(basename, me->_lastIndex); g_lingo->push(me->_lastIndex); me->_lastIndex += 1; } void MMovieXObj::m_closeMMovie(int nargs) { g_lingo->printArgs("MMovieXObj::m_closeMMovie", nargs); if (nargs != 1) { g_lingo->dropStack(nargs); g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_MOVIE_INDEX)); return; } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); int index = g_lingo->pop().asInt(); if (!me->_movies.contains(index)) { warning("MMovieXObj::m_closeMMovie(): movie index %d not found", index); g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_MOVIE_INDEX)); return; } for (auto &it : me->_moviePathMap) { if (it._value == index) { me->_moviePathMap.erase(it._key); break; } } MMovieFile &file = me->_movies.getVal(index); debugC(5, kDebugXObj, "MMovieXObj:m_openMMovie(): closing movie %s (index %d)", file._path.toString().c_str(), me->_lastIndex); if (file._video) { delete file._video; file._video = nullptr; } me->_movies.erase(index); g_lingo->push(Datum(MMovieError::MMOVIE_NONE)); } void MMovieXObj::m_playSegment(int nargs) { g_lingo->printArgs("MMovieXObj::m_playSegment", nargs); if (nargs != 5) { g_lingo->dropStack(nargs); g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_SEGMENT_NAME)); return; } Common::String asyncOpt = g_lingo->pop().asString(); Common::String purgeOpt = g_lingo->pop().asString(); Common::String abortOpt = g_lingo->pop().asString(); Common::String restoreOpt = g_lingo->pop().asString(); Common::String segmentName = g_lingo->pop().asString(); bool restore = restoreOpt.equalsIgnoreCase("restore"); bool shiftAbort = abortOpt.equalsIgnoreCase("shiftAbort"); bool abortOnClick = abortOpt.equalsIgnoreCase("abortOnClick"); bool purge = purgeOpt.equalsIgnoreCase("purge"); bool async = asyncOpt.equalsIgnoreCase("async"); MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); for (auto &it : me->_movies) { if (it._value.segLookup.contains(segmentName)) { int segIndex = it._value.segLookup.getVal(segmentName); int result = me->playSegment(it._key, segIndex, false, restore, shiftAbort, abortOnClick, purge, async); int ticks = me->getTicks(); debugC(5, kDebugXObj, "MMovieXObj::m_playSegment: ticks: %d, result: %d", ticks, result); g_lingo->push(Datum(result)); return; } } g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_SEGMENT_NAME)); return; } void MMovieXObj::m_playSegLoop(int nargs) { g_lingo->printArgs("MMovieXObj::m_playSegLoop", nargs); if (nargs != 5) { g_lingo->dropStack(nargs); g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_SEGMENT_NAME)); return; } Common::String asyncOpt = g_lingo->pop().asString(); Common::String purgeOpt = g_lingo->pop().asString(); Common::String abortOpt = g_lingo->pop().asString(); Common::String restoreOpt = g_lingo->pop().asString(); Common::String segmentName = g_lingo->pop().asString(); bool restore = restoreOpt.equalsIgnoreCase("restore"); bool shiftAbort = abortOpt.equalsIgnoreCase("shiftAbort"); bool abortOnClick = abortOpt.equalsIgnoreCase("abortOnClick"); bool purge = abortOpt.equalsIgnoreCase("purge"); bool async = asyncOpt.equalsIgnoreCase("async"); MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); for (auto &it : me->_movies) { if (it._value.segLookup.contains(segmentName)) { int segIndex = it._value.segLookup.getVal(segmentName); int result = me->playSegment(it._key, segIndex, true, restore, shiftAbort, abortOnClick, purge, async); int ticks = me->getTicks(); debugC(5, kDebugXObj, "MMovieXObj::m_playSegLoop: ticks: %d, result: %d", ticks, result); g_lingo->push(Datum(result)); return; } } g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_SEGMENT_NAME)); return; } void MMovieXObj::m_idleSegment(int nargs) { if (nargs != 0) { g_lingo->dropStack(nargs); } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); int result = me->updateScreen(); int ticks = me->getTicks(); debugC(5, kDebugXObj, "MMovieXObj::m_idleSegment(): ticks: %d, result: %d", ticks, result); g_lingo->push(Datum(result)); } void MMovieXObj::m_stopSegment(int nargs) { g_lingo->printArgs("MMovieXObj::m_stopSegment", nargs); if (nargs != 0) { g_lingo->dropStack(nargs); } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); me->stopSegment(); g_lingo->push(0); } void MMovieXObj::m_seekSegment(int nargs) { g_lingo->printArgs("MMovieXObj::m_seekSegment", nargs); if (nargs != 1) { g_lingo->dropStack(nargs); g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_SEGMENT_NAME)); return; } Common::String segmentName = g_lingo->pop().asString(); MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); for (auto &it : me->_movies) { if (it._value.segLookup.contains(segmentName)) { g_lingo->push(Datum(MMovieError::MMOVIE_NONE)); return; } } g_lingo->push(Datum(MMovieError::MMOVIE_INVALID_SEGMENT_NAME)); } XOBJSTUB(MMovieXObj::m_setSegmentTime, 0) void MMovieXObj::m_setDisplayBounds(int nargs) { g_lingo->printArgs("MMovieXObj::m_setDisplayBounds", nargs); if (nargs != 4) { warning("MMovieXObj::m_setDisplayBounds: expecting 4 arguments!"); g_lingo->dropStack(nargs); g_lingo->push(Datum(0)); return; } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); Datum bottom = g_lingo->pop(); Datum right = g_lingo->pop(); Datum top = g_lingo->pop(); Datum left = g_lingo->pop(); me->_bounds = Common::Rect((int16)left.asInt(), (int16)top.asInt(), (int16)right.asInt(), (int16)bottom.asInt()); me->_lastFrame.free(); me->_lastFrame.create(me->_bounds.width(), me->_bounds.height(), g_director->_pixelformat); Common::Rect screen = g_director->getCurrentMovie()->_movieRect; me->_bounds.clip(Common::Rect(screen.width(), screen.height())); g_lingo->push(Datum(0)); } XOBJSTUB(MMovieXObj::m_getMovieNormalWidth, 0) XOBJSTUB(MMovieXObj::m_getMovieNormalHeight, 0) void MMovieXObj::m_getSegCount(int nargs) { g_lingo->printArgs("MMovieXObj::m_getSegCount", nargs); if (nargs != 1) { g_lingo->dropStack(nargs); g_lingo->push(MMovieError::MMOVIE_INVALID_MOVIE_INDEX); return; } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); int movieIndex = g_lingo->pop().asInt(); if (!me->_movies.contains(movieIndex)) { g_lingo->push(MMovieError::MMOVIE_INVALID_MOVIE_INDEX); return; } g_lingo->push((int)me->_movies.getVal(movieIndex).segments.size()); } void MMovieXObj::m_getSegName(int nargs) { if (nargs != 2) { g_lingo->dropStack(nargs); g_lingo->push(Common::String("")); return; } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); int segmentIndex = g_lingo->pop().asInt(); int movieIndex = g_lingo->pop().asInt(); if (!me->_movies.contains(movieIndex)) { g_lingo->push(Common::String("")); return; } if (segmentIndex > (int)me->_movies.getVal(movieIndex).segments.size() || segmentIndex <= 0) { g_lingo->push(Common::String("")); return; } Common::String result = me->_movies.getVal(movieIndex).segments[segmentIndex - 1]._name; debugC(5, kDebugXObj, "MMovieXObj::m_getSegName(%d, %d): %s", movieIndex, segmentIndex, result.c_str()); g_lingo->push(result); } void MMovieXObj::m_getMovieRate(int nargs) { g_lingo->printArgs("MMovieXObj::m_getMovieRate", nargs); if (nargs != 0) { g_lingo->dropStack(nargs); } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); g_lingo->push(Datum(me->_rate)); } void MMovieXObj::m_setMovieRate(int nargs) { g_lingo->printArgs("MMovieXObj::m_setMovieRate", nargs); if (nargs != 1) { warning("MMovieXObj::m_setMovieRate: expecting 4 arguments"); g_lingo->dropStack(nargs); g_lingo->push(Datum(0)); return; } MMovieXObject *me = static_cast(g_lingo->_state->me.u.obj); me->_rate = g_lingo->pop().asInt(); g_lingo->push(Datum(me->_rate)); } XOBJSTUB(MMovieXObj::m_flushEvents, 0) XOBJSTUB(MMovieXObj::m_invalidateRect, 0) void MMovieXObj::m_readFile(int nargs) { g_lingo->printArgs("MMovieXObj::m_readFile", nargs); if (nargs != 2) { warning("MMovieXObj::m_readFile(): expecting 2 arguments"); g_lingo->dropStack(nargs); g_lingo->push(Datum("")); return; } Common::SaveFileManager *saves = g_system->getSavefileManager(); bool scramble = g_lingo->pop().asInt() != 0; Common::String origPath = g_lingo->pop().asString(); Common::String path = origPath; Common::String prefix = savePrefix(); Common::String result; if (origPath.empty()) { path = getFileNameFromModal(false, Common::String(), Common::String(), "txt"); if (path.empty()) { debugC(5, kDebugXObj, "MMovieXObj::m_readFile(): read cancelled by modal"); g_lingo->push(result); return; } } else { path = lastPathComponent(origPath, g_director->_dirSeparator); if (!path.hasSuffixIgnoreCase(".txt")) path += ".txt"; } if (!path.hasPrefixIgnoreCase(prefix)) { path = prefix + path; } Common::SeekableReadStream *stream = saves->openForLoading(path); if (stream) { debugC(5, kDebugXObj, "MMovieXObj::m_readFile(): opening file %s as %s from the saves dir", origPath.c_str(), path.c_str()); } else { Common::File *f = new Common::File; Common::Path location = findPath(origPath); if (!location.empty() && f->open(location)) { debugC(5, kDebugXObj, "MMovieXObj::m_readFile(): opening file %s from the game dir", origPath.c_str()); stream = (Common::SeekableReadStream *)f; } else { delete f; } } if (stream) { while (!stream->eos() && !stream->err()) { byte ch = stream->readByte(); if (scramble) // remove unbreakable encryption ch ^= 0xa5; result += ch; } delete stream; } else { warning("MMovieXObj::m_readFile(): file %s not found", origPath.c_str()); } g_lingo->push(result); } void MMovieXObj::m_writeFile(int nargs) { g_lingo->printArgs("MMovieXObj::m_writeFile", nargs); if (nargs != 3) { warning("MMovieXObj::m_writeFile(): expecting 3 arguments"); g_lingo->dropStack(nargs); g_lingo->push(Datum("")); return; } Common::SaveFileManager *saves = g_system->getSavefileManager(); bool scramble = g_lingo->pop().asInt() != 0; Common::String data = g_lingo->pop().asString(); Common::String origPath = g_lingo->pop().asString(); Common::String path = origPath; Common::String result; Common::String prefix = savePrefix(); if (origPath.empty()) { path = getFileNameFromModal(true, Common::String(), Common::String(), "txt"); if (path.empty()) { debugC(5, kDebugXObj, "MMovieXObj::m_writeFile(): read cancelled by modal"); g_lingo->push(result); return; } } else { path = lastPathComponent(origPath, g_director->_dirSeparator); // Virtual Nightclub makes autosaves at random intervals. // Intercept and give them a sensible name. if (path.hasSuffixIgnoreCase(".VNC") && path.hasPrefixIgnoreCase("VNC_")) { path = Common::String::format("Autosave.txt"); } if (!path.hasSuffixIgnoreCase(".txt")) path += ".txt"; } if (!path.hasPrefixIgnoreCase(prefix)) { path = prefix + path; } Common::SeekableWriteStream *stream = saves->openForSaving(path, false); if (stream) { debugC(5, kDebugXObj, "MMovieXObj::m_writeFile(): opening file %s as %s from the saves dir", origPath.c_str(), path.c_str()); for (auto &it : data) { byte ch = it; if (scramble) // apply world's greatest encryption ch ^= 0xa5; stream->writeByte(ch); } stream->finalize(); delete stream; } else { warning("MMovieXObj::m_writeFile(): file %s not found", origPath.c_str()); } g_lingo->push(result); } XOBJSTUB(MMovieXObj::m_copyFile, 0) XOBJSTUB(MMovieXObj::m_copyFileCont, 0) XOBJSTUB(MMovieXObj::m_freeSpace, 0) XOBJSTUB(MMovieXObj::m_deleteFile, 0) XOBJSTUB(MMovieXObj::m_volList, "") }