diff options
Diffstat (limited to 'engines/sci/engine')
44 files changed, 6381 insertions, 2629 deletions
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index a993506f7a..e37a1651ef 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -45,6 +45,7 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan) if (!ConfMan.getBool("use_cdaudio")) _usesCdTrack = false; _forceDOSTracks = false; + _pseudoMouseAbility = kPseudoMouseAbilityUninitialized; } reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) { @@ -605,4 +606,50 @@ bool GameFeatures::useAltWinGMSound() { } } +// PseudoMouse was added during SCI1 +// PseudoMouseAbility is about a tiny difference in the keyboard driver, which sets the event type to either +// 40h (old behaviour) or 44h (the keyboard driver actually added 40h to the existing value). +// See engine/kevent.cpp, kMapKeyToDir - also script 933 + +// SCI1EGA: +// Quest for Glory 2 still used the old way. +// +// SCI1EARLY: +// King's Quest 5 0.000.062 uses the old way. +// Leisure Suit Larry 1 demo uses the new way, but no PseudoMouse class. +// Fairy Tales uses the new way. +// X-Mas 1990 uses the old way, no PseudoMouse class. +// Space Quest 4 floppy (1.1) uses the new way. +// Mixed Up Mother Goose uses the old way, no PseudoMouse class. +// +// SCI1MIDDLE: +// Leisure Suit Larry 5 demo uses the new way. +// Conquests of the Longbow demo uses the new way. +// Leisure Suit Larry 1 (2.0) uses the new way. +// Astro Chicken II uses the new way. +PseudoMouseAbilityType GameFeatures::detectPseudoMouseAbility() { + if (_pseudoMouseAbility == kPseudoMouseAbilityUninitialized) { + if (getSciVersion() < SCI_VERSION_1_EARLY) { + // SCI1 EGA or earlier -> pseudo mouse ability is always disabled + _pseudoMouseAbility = kPseudoMouseAbilityFalse; + + } else if (getSciVersion() == SCI_VERSION_1_EARLY) { + // For SCI1 early some games had it enabled, some others didn't. + // We try to find an object called "PseudoMouse". If it's found, we enable the ability otherwise we don't. + reg_t pseudoMouseAddr = _segMan->findObjectByName("PseudoMouse", 0); + + if (pseudoMouseAddr != NULL_REG) { + _pseudoMouseAbility = kPseudoMouseAbilityTrue; + } else { + _pseudoMouseAbility = kPseudoMouseAbilityFalse; + } + + } else { + // SCI1 middle or later -> pseudo mouse ability is always enabled + _pseudoMouseAbility = kPseudoMouseAbilityTrue; + } + } + return _pseudoMouseAbility; +} + } // End of namespace Sci diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 1c410267e6..ee978be033 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -34,6 +34,12 @@ enum MoveCountType { kIncrementMoveCount }; +enum PseudoMouseAbilityType { + kPseudoMouseAbilityUninitialized, + kPseudoMouseAbilityFalse, + kPseudoMouseAbilityTrue +}; + class GameFeatures { public: GameFeatures(SegManager *segMan, Kernel *kernel); @@ -76,6 +82,56 @@ public: * @return Graphics functions type, SCI_VERSION_2 / SCI_VERSION_2_1 */ SciVersion detectSci21KernelType(); + + inline bool usesModifiedAudioAttenuation() const { + switch (g_sci->getGameId()) { + // Assuming MGDX uses modified attenuation since SQ6 does and it was + // released earlier, but not verified (Phar Lap Windows-only release) + case GID_MOTHERGOOSEHIRES: + case GID_PQ4: + case GID_SQ6: + return true; + case GID_KQ7: + case GID_QFG4: + // (1) KQ7 1.51 (SCI2.1early) uses the non-standard attenuation, but + // 2.00b (SCI2.1mid) does not + // (2) QFG4 CD is SCI2.1early; QFG4 floppy is SCI2 and does not use + // the SCI2.1 audio system + return getSciVersion() == SCI_VERSION_2_1_EARLY; + default: + return false; + } + } + + inline bool hasTransparentPicturePlanes() const { + const SciGameId &gid = g_sci->getGameId(); + + // NOTE: MGDX is assumed to not have transparent picture planes since it + // was released before SQ6, but this has not been verified since it + // cannot be disassembled at the moment (Phar Lap Windows-only release) + return getSciVersion() >= SCI_VERSION_2_1_MIDDLE && + gid != GID_SQ6 && + gid != GID_MOTHERGOOSEHIRES; + } + + inline bool hasNewPaletteCode() const { + return getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7; + } + + inline bool VMDOpenStopsAudio() const { + // Of the games that use VMDs: + // Yes: Phant1, Shivers, Torin + // No: SQ6 + // TODO: Optional extra flag to kPlayVMD which defaults to Yes: PQ:SWAT + // TODO: SCI3, GK2 (GK2's VMD code is closer to SCI3 than SCI21) + return getSciVersion() == SCI_VERSION_2_1_MIDDLE && + g_sci->getGameId() != GID_SQ6 && + g_sci->getGameId() != GID_GK2; + } + + inline bool usesAlternateSelectors() const { + return g_sci->getGameId() == GID_PHANTASMAGORIA2; + } #endif /** @@ -110,6 +166,12 @@ public: */ void forceDOSTracks() { _forceDOSTracks = true; } + /** + * Autodetects, if Pseudo Mouse ability is enabled (different behavior in keyboard driver) + * @return kPseudoMouseAbilityTrue or kPseudoMouseAbilityFalse + */ + PseudoMouseAbilityType detectPseudoMouseAbility(); + private: reg_t getDetectionAddr(const Common::String &objName, Selector slc, int methodNum = -1); @@ -130,6 +192,8 @@ private: bool _usesCdTrack; bool _forceDOSTracks; + PseudoMouseAbilityType _pseudoMouseAbility; + SegManager *_segMan; Kernel *_kernel; }; diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp index 0b1001bfda..da1d00f3bf 100644 --- a/engines/sci/engine/file.cpp +++ b/engines/sci/engine/file.cpp @@ -22,6 +22,7 @@ #include "common/savefile.h" #include "common/stream.h" +#include "common/memstream.h" #include "sci/sci.h" #include "sci/engine/file.h" @@ -32,6 +33,118 @@ namespace Sci { +#ifdef ENABLE_SCI32 +/** + * A MemoryWriteStreamDynamic with additional read functionality. + * The read and write functions share a single stream position. + */ +class MemoryDynamicRWStream : public Common::MemoryWriteStreamDynamic, public Common::SeekableReadStream { +protected: + bool _eos; +public: + MemoryDynamicRWStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : MemoryWriteStreamDynamic(disposeMemory), _eos(false) { } + + uint32 read(void *dataPtr, uint32 dataSize); + + bool eos() const { return _eos; } + int32 pos() const { return _pos; } + int32 size() const { return _size; } + void clearErr() { _eos = false; Common::MemoryWriteStreamDynamic::clearErr(); } + bool seek(int32 offs, int whence = SEEK_SET) { return Common::MemoryWriteStreamDynamic::seek(offs, whence); } + +}; + +uint32 MemoryDynamicRWStream::read(void *dataPtr, uint32 dataSize) +{ + // Read at most as many bytes as are still available... + if (dataSize > _size - _pos) { + dataSize = _size - _pos; + _eos = true; + } + memcpy(dataPtr, _ptr, dataSize); + + _ptr += dataSize; + _pos += dataSize; + + return dataSize; +} + +/** + * A MemoryDynamicRWStream intended to re-write a file. + * It reads the contents of `inFile` in the constructor, and writes back + * the changes to `fileName` in the destructor (and when calling commit() ). + */ +class SaveFileRewriteStream : public MemoryDynamicRWStream { +public: + SaveFileRewriteStream(Common::String fileName, + Common::SeekableReadStream *inFile, + kFileOpenMode mode, bool compress); + virtual ~SaveFileRewriteStream(); + + virtual uint32 write(const void *dataPtr, uint32 dataSize) { _changed = true; return MemoryDynamicRWStream::write(dataPtr, dataSize); } + + void commit(); //< Save back to disk + +protected: + Common::String _fileName; + bool _compress; + bool _changed; +}; + +SaveFileRewriteStream::SaveFileRewriteStream(Common::String fileName, + Common::SeekableReadStream *inFile, + kFileOpenMode mode, + bool compress) +: MemoryDynamicRWStream(DisposeAfterUse::YES), + _fileName(fileName), _compress(compress) +{ + const bool truncate = mode == _K_FILE_MODE_CREATE; + const bool seekToEnd = mode == _K_FILE_MODE_OPEN_OR_CREATE; + + if (!truncate && inFile) { + unsigned int s = inFile->size(); + ensureCapacity(s); + inFile->read(_data, s); + if (seekToEnd) { + seek(0, SEEK_END); + } + _changed = false; + } else { + _changed = true; + } +} + +SaveFileRewriteStream::~SaveFileRewriteStream() { + commit(); +} + +void SaveFileRewriteStream::commit() { + // Write contents of buffer back to file + + if (_changed) { + Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName, _compress); + outFile->write(_data, _size); + delete outFile; + _changed = false; + } +} + +#endif + +uint findFreeFileHandle(EngineState *s) { + // Find a free file handle + uint handle = 1; // Ignore _fileHandles[0] + while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen()) + handle++; + + if (handle == s->_fileHandles.size()) { + // Hit size limit => Allocate more space + s->_fileHandles.resize(s->_fileHandles.size() + 1); + } + + return handle; +} + /* * Note on how file I/O is implemented: In ScummVM, one can not create/write * arbitrary data files, simply because many of our target platforms do not @@ -55,7 +168,7 @@ namespace Sci { * for reading only. */ -reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename) { +reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename) { Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH); englishName.toLowercase(); @@ -91,6 +204,31 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u break; } +#ifdef ENABLE_SCI32 + if ((g_sci->getGameId() == GID_PHANTASMAGORIA && (filename == "phantsg.dir" || filename == "chase.dat" || filename == "tmp.dat")) || + (g_sci->getGameId() == GID_PQSWAT && filename == "swat.dat")) { + debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str()); + + inFile = saveFileMan->openForLoading(wrappedName); + // If no matching savestate exists: fall back to reading from a regular + // file + if (!inFile) + inFile = SearchMan.createReadStreamForMember(englishName); + + if (mode == _K_FILE_MODE_OPEN_OR_FAIL && !inFile) { + debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str()); + return SIGNAL_REG; + } + + SaveFileRewriteStream *stream; + stream = new SaveFileRewriteStream(wrappedName, inFile, mode, isCompressed); + + delete inFile; + + inFile = stream; + outFile = stream; + } else +#endif if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { // Try to open file, abort if not possible inFile = saveFileMan->openForLoading(wrappedName); @@ -126,15 +264,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u return SIGNAL_REG; } - // Find a free file handle - uint handle = 1; // Ignore _fileHandles[0] - while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen()) - handle++; - - if (handle == s->_fileHandles.size()) { - // Hit size limit => Allocate more space - s->_fileHandles.resize(s->_fileHandles.size() + 1); - } + uint handle = findFreeFileHandle(s); s->_fileHandles[handle]._in = inFile; s->_fileHandles[handle]._out = outFile; @@ -191,43 +321,65 @@ static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) return (l.time > r.time); } -// Create a sorted array containing all found savedgames -void listSavegames(Common::Array<SavegameDesc> &saves) { +bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc) { Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + Common::SeekableReadStream *in; + if ((in = saveFileMan->openForLoading(filename)) == nullptr) { + return false; + } + + SavegameMetadata meta; + if (!get_savegame_metadata(in, &meta) || meta.name.empty()) { + // invalid + delete in; + return false; + } + delete in; + + const int id = strtol(filename.end() - 3, NULL, 10); + desc->id = id; + desc->date = meta.saveDate; + // We need to fix date in here, because we save DDMMYYYY instead of + // YYYYMMDD, so sorting wouldn't work + desc->date = ((desc->date & 0xFFFF) << 16) | ((desc->date & 0xFF0000) >> 8) | ((desc->date & 0xFF000000) >> 24); + desc->time = meta.saveTime; + desc->version = meta.version; + desc->gameVersion = meta.gameVersion; +#ifdef ENABLE_SCI32 + if (g_sci->getGameId() == GID_SHIVERS) { + desc->lowScore = meta.lowScore; + desc->highScore = meta.highScore; + } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + desc->avatarId = meta.avatarId; + } +#endif + + if (meta.name.lastChar() == '\n') + meta.name.deleteLastChar(); + + Common::strlcpy(desc->name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH); - // Load all saves + return desc; +} + +// Create an array containing all found savedgames, sorted by creation date +void listSavegames(Common::Array<SavegameDesc> &saves) { + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern()); for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) { - Common::String filename = *iter; - Common::SeekableReadStream *in; - if ((in = saveFileMan->openForLoading(filename))) { - SavegameMetadata meta; - if (!get_savegame_metadata(in, &meta) || meta.name.empty()) { - // invalid - delete in; - continue; - } - delete in; - - SavegameDesc desc; - desc.id = strtol(filename.end() - 3, NULL, 10); - desc.date = meta.saveDate; - // We need to fix date in here, because we save DDMMYYYY instead of - // YYYYMMDD, so sorting wouldn't work - desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24); - desc.time = meta.saveTime; - desc.version = meta.version; - - if (meta.name.lastChar() == '\n') - meta.name.deleteLastChar(); - - Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH); - - debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id); - - saves.push_back(desc); + const Common::String &filename = *iter; + +#ifdef ENABLE_SCI32 + const int id = strtol(filename.end() - 3, NULL, 10); + if (id == kNewGameId || id == kAutoSaveId) { + continue; } +#endif + + SavegameDesc desc; + fillSavegameDesc(filename, &desc); + saves.push_back(desc); } // Sort the list by creation date of the saves @@ -252,8 +404,12 @@ FileHandle::~FileHandle() { } void FileHandle::close() { - delete _in; - delete _out; + // NB: It is possible _in and _out are both non-null, but + // then they point to the same object. + if (_in) + delete _in; + else + delete _out; _in = 0; _out = 0; _name.clear(); @@ -365,119 +521,4 @@ reg_t DirSeeker::nextFile(SegManager *segMan) { return _outbuffer; } - -#ifdef ENABLE_SCI32 - -VirtualIndexFile::VirtualIndexFile(Common::String fileName) : _fileName(fileName), _changed(false) { - Common::SeekableReadStream *inFile = g_sci->getSaveFileManager()->openForLoading(fileName); - - _bufferSize = inFile->size(); - _buffer = new char[_bufferSize]; - inFile->read(_buffer, _bufferSize); - _ptr = _buffer; - delete inFile; -} - -VirtualIndexFile::VirtualIndexFile(uint32 initialSize) : _changed(false) { - _bufferSize = initialSize; - _buffer = new char[_bufferSize]; - _ptr = _buffer; -} - -VirtualIndexFile::~VirtualIndexFile() { - close(); - - _bufferSize = 0; - delete[] _buffer; - _buffer = 0; -} - -uint32 VirtualIndexFile::read(char *buffer, uint32 size) { - uint32 curPos = _ptr - _buffer; - uint32 finalSize = MIN<uint32>(size, _bufferSize - curPos); - char *localPtr = buffer; - - for (uint32 i = 0; i < finalSize; i++) - *localPtr++ = *_ptr++; - - return finalSize; -} - -uint32 VirtualIndexFile::write(const char *buffer, uint32 size) { - _changed = true; - uint32 curPos = _ptr - _buffer; - - // Check if the buffer needs to be resized - if (curPos + size >= _bufferSize) { - _bufferSize = curPos + size + 1; - char *tmp = _buffer; - _buffer = new char[_bufferSize]; - _ptr = _buffer + curPos; - memcpy(_buffer, tmp, _bufferSize); - delete[] tmp; - } - - for (uint32 i = 0; i < size; i++) - *_ptr++ = *buffer++; - - return size; -} - -uint32 VirtualIndexFile::readLine(char *buffer, uint32 size) { - uint32 startPos = _ptr - _buffer; - uint32 bytesRead = 0; - char *localPtr = buffer; - - // This is not a full-blown implementation of readLine, but it - // suffices for Phantasmagoria - while (startPos + bytesRead < size) { - bytesRead++; - - if (*_ptr == 0 || *_ptr == 0x0A) { - _ptr++; - *localPtr = 0; - return bytesRead; - } else { - *localPtr++ = *_ptr++; - } - } - - return bytesRead; -} - -bool VirtualIndexFile::seek(int32 offset, int whence) { - uint32 startPos = _ptr - _buffer; - assert(offset >= 0); - - switch (whence) { - case SEEK_CUR: - assert(startPos + offset < _bufferSize); - _ptr += offset; - break; - case SEEK_SET: - assert(offset < (int32)_bufferSize); - _ptr = _buffer + offset; - break; - case SEEK_END: - assert((int32)_bufferSize - offset >= 0); - _ptr = _buffer + (_bufferSize - offset); - break; - } - - return true; -} - -void VirtualIndexFile::close() { - if (_changed && !_fileName.empty()) { - Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName); - outFile->write(_buffer, _bufferSize); - delete outFile; - } - - // Maintain the buffer, and seek to the beginning of it - _ptr = _buffer; -} - -#endif - } // End of namespace Sci diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h index 54627d5228..1c9a092063 100644 --- a/engines/sci/engine/file.h +++ b/engines/sci/engine/file.h @@ -28,22 +28,31 @@ namespace Sci { -enum { +enum kFileOpenMode { _K_FILE_MODE_OPEN_OR_CREATE = 0, _K_FILE_MODE_OPEN_OR_FAIL = 1, _K_FILE_MODE_CREATE = 2 }; -/* Maximum length of a savegame name (including terminator character). */ -#define SCI_MAX_SAVENAME_LENGTH 0x24 +enum { + SCI_MAX_SAVENAME_LENGTH = 36, ///< Maximum length of a savegame name (including terminator character). + MAX_SAVEGAME_NR = 20 ///< Maximum number of savegames +}; +#ifdef ENABLE_SCI32 enum { - MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ + kAutoSaveId = 0, ///< The save game slot number for autosaves + kNewGameId = 999, ///< The save game slot number for a "new game" save + + // SCI engine expects game IDs to start at 0, but slot 0 in ScummVM is + // reserved for autosave, so non-autosave games get their IDs shifted up + // when saving or restoring, and shifted down when enumerating save games + kSaveIdShift = 1 }; +#endif #define VIRTUALFILE_HANDLE_START 32000 #define VIRTUALFILE_HANDLE_SCI32SAVE 32100 -#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir" #define VIRTUALFILE_HANDLE_SCIAUDIO 32300 #define VIRTUALFILE_HANDLE_END 32300 @@ -54,6 +63,14 @@ struct SavegameDesc { int time; int version; char name[SCI_MAX_SAVENAME_LENGTH]; + Common::String gameVersion; +#ifdef ENABLE_SCI32 + // Used by Shivers 1 + uint16 lowScore; + uint16 highScore; + // Used by MGDX + uint8 avatarId; +#endif }; class FileHandle { @@ -93,50 +110,7 @@ private: void addAsVirtualFiles(Common::String title, Common::String fileMask); }; - -#ifdef ENABLE_SCI32 - -/** - * An implementation of a virtual file that supports basic read and write - * operations simultaneously. - * - * This class has been initially implemented for Phantasmagoria, which has its - * own custom save/load code. The load code keeps checking for the existence - * of the save index file and keeps closing and reopening it for each save - * slot. This is notoriously slow and clumsy, and introduces noticeable delays, - * especially for non-desktop systems. Also, its game scripts request to open - * the index file for reading and writing with the same parameters - * (SaveManager::setCurrentSave and SaveManager::getCurrentSave). Moreover, - * the game scripts reopen the index file for writing in order to update it - * and seek within it. We do not support seeking in writeable streams, and the - * fact that our saved games are ZIP files makes this operation even more - * expensive. Finally, the savegame index file is supposed to be expanded when - * a new save slot is added. - * For the aforementioned reasons, this class has been implemented, which offers - * the basic functionality needed by the game scripts in Phantasmagoria. - */ -class VirtualIndexFile { -public: - VirtualIndexFile(Common::String fileName); - VirtualIndexFile(uint32 initialSize); - ~VirtualIndexFile(); - - uint32 read(char *buffer, uint32 size); - uint32 readLine(char *buffer, uint32 size); - uint32 write(const char *buffer, uint32 size); - bool seek(int32 offset, int whence); - void close(); - -private: - char *_buffer; - uint32 _bufferSize; - char *_ptr; - - Common::String _fileName; - bool _changed; -}; - -#endif +uint findFreeFileHandle(EngineState *s); } // End of namespace Sci diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp index 70c8c52bf0..50f7709baf 100644 --- a/engines/sci/engine/gc.cpp +++ b/engines/sci/engine/gc.cpp @@ -24,6 +24,10 @@ #include "common/array.h" #include "sci/graphics/ports.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/controls32.h" +#endif + namespace Sci { //#define GC_DEBUG_CODE @@ -42,7 +46,7 @@ const char *segmentTypeNames[] = { "dynmem", // 9 "obsolete", // 10: obsolete string fragments "array", // 11: SCI32 arrays - "string" // 12: SCI32 strings + "obsolete" // 12: obsolete SCI32 strings }; #endif @@ -139,14 +143,29 @@ AddrSet *findAllActiveReferences(EngineState *s) { const Common::Array<SegmentObj *> &heap = s->_segMan->getSegments(); uint heapSize = heap.size(); - // Init: Explicitly loaded scripts for (uint i = 1; i < heapSize; i++) { - if (heap[i] && heap[i]->getType() == SEG_TYPE_SCRIPT) { - Script *script = (Script *)heap[i]; + if (heap[i]) { + // Init: Explicitly loaded scripts + if (heap[i]->getType() == SEG_TYPE_SCRIPT) { + Script *script = (Script *)heap[i]; + + if (script->getLockers()) { // Explicitly loaded? + wm.pushArray(script->listObjectReferences()); + } + } + +#ifdef ENABLE_SCI32 + // Init: Explicitly opted-out bitmaps + else if (heap[i]->getType() == SEG_TYPE_BITMAP) { + BitmapTable *bt = static_cast<BitmapTable *>(heap[i]); - if (script->getLockers()) { // Explicitly loaded? - wm.pushArray(script->listObjectReferences()); + for (uint j = 0; j < bt->_table.size(); j++) { + if (bt->_table[j].data && bt->_table[j].data->getShouldGC() == false) { + wm.push(make_reg(i, j)); + } + } } +#endif } } diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index 0df4701334..85cad99226 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -84,13 +84,19 @@ uint Kernel::getKernelNamesSize() const { } const Common::String &Kernel::getKernelName(uint number) const { - // FIXME: The following check is a temporary workaround for an issue - // leading to crashes when using the debugger's backtrace command. - if (number >= _kernelNames.size()) - return _invalid; + assert(number < _kernelFuncs.size()); return _kernelNames[number]; } +Common::String Kernel::getKernelName(uint number, uint subFunction) const { + assert(number < _kernelFuncs.size()); + const KernelFunction &kernelCall = _kernelFuncs[number]; + + assert(subFunction < kernelCall.subFunctionCount); + return kernelCall.subFunctions[subFunction].name; +} + + int Kernel::findKernelFuncPos(Common::String kernelFuncName) { for (uint32 i = 0; i < _kernelNames.size(); i++) if (_kernelNames[i] == kernelFuncName) @@ -165,8 +171,11 @@ void Kernel::loadSelectorNames() { // (io) -> optionally integer AND an object // (i) -> optional integer // . -> any type -// i* -> optional multiple integers -// .* -> any parameters afterwards (or none) +// i* -> at least one integer, more integers may follow after that +// (i*) -> optional multiple integers +// .* -> at least one parameter of any type and more parameters of any type may follow +// (.*) -> any parameters afterwards (or none) +// * -> means "more of the last parameter may follow (or none at all)", must be at the end of a signature. Is not valid anywhere else. static uint16 *parseKernelSignature(const char *kernelName, const char *writtenSig) { const char *curPos; char curChar; @@ -401,7 +410,7 @@ uint16 Kernel::findRegType(reg_t reg) { case SEG_TYPE_HUNK: #ifdef ENABLE_SCI32 case SEG_TYPE_ARRAY: - case SEG_TYPE_STRING: + case SEG_TYPE_BITMAP: #endif result |= SIG_TYPE_REFERENCE; break; @@ -853,7 +862,7 @@ void Kernel::loadKernelNames(GameFeatures *features) { _kernelNames[0x26] = "Portrait"; else if (g_sci->getPlatform() == Common::kPlatformMacintosh) _kernelNames[0x84] = "ShowMovie"; - } else if (g_sci->getGameId() == GID_QFG4 && g_sci->isDemo()) { + } else if (g_sci->getGameId() == GID_QFG4DEMO) { _kernelNames[0x7b] = "RemapColors"; // QFG4 Demo has this SCI2 function instead of StrSplit } @@ -879,8 +888,8 @@ void Kernel::loadKernelNames(GameFeatures *features) { // how kDoSound is called from Sound::play(). // Known games that use this: // GK2 demo - // KQ7 1.4 - // PQ4 SWAT demo + // KQ7 1.4/1.51 + // PQ:SWAT demo // LSL6 // PQ4CD // QFG4CD @@ -891,7 +900,7 @@ void Kernel::loadKernelNames(GameFeatures *features) { _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo); // OnMe is IsOnMe here, but they should be compatible - _kernelNames[0x23] = "Robot"; // Graph in SCI2 + _kernelNames[0x23] = g_sci->getGameId() == GID_LSL6HIRES ? "Empty" : "Robot"; // Graph in SCI2 _kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2 } else { // Normal SCI2.1 kernel table diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 92916ecc68..5fb1ab6c66 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -158,6 +158,7 @@ public: uint getKernelNamesSize() const; const Common::String &getKernelName(uint number) const; + Common::String getKernelName(uint number, uint subFunction) const; /** * Determines the selector ID of a selector by its name. @@ -420,55 +421,167 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv); #ifdef ENABLE_SCI32 // SCI2 Kernel Functions +reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv); +reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv); +reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv); +reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv); +reg_t kPlatform32(EngineState *s, int argc, reg_t *argv); +reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv); +reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv); + +reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioDistort(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv); + +reg_t kRobot(EngineState *s, int argc, reg_t *argv); +reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv); +reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv); +reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv); +reg_t kRobotClose(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv); +reg_t kRobotPause(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv); +reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv); + +reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv); + +reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv); + +reg_t kSave(EngineState *s, int argc, reg_t *argv); +reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv); +reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv); +reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv); +reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv); +reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv); +reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv); + +reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv); reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv); -reg_t kArray(EngineState *s, int argc, reg_t *argv); reg_t kListAt(EngineState *s, int argc, reg_t *argv); -reg_t kString(EngineState *s, int argc, reg_t *argv); +reg_t kArray(EngineState *s, int argc, reg_t *argv); +reg_t kArrayNew(EngineState *s, int argc, reg_t *argv); +reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv); +reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv); +reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv); +reg_t kArrayFree(EngineState *s, int argc, reg_t *argv); +reg_t kArrayFill(EngineState *s, int argc, reg_t *argv); +reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv); +reg_t kArrayCompare(EngineState *s, int argc, reg_t *argv); +reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv); +reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv); +reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv); + +reg_t kString(EngineState *s, int argc, reg_t *argv); reg_t kStringNew(EngineState *s, int argc, reg_t *argv); -reg_t kStringSize(EngineState *s, int argc, reg_t *argv); -reg_t kStringAt(EngineState *s, int argc, reg_t *argv); -reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv); +reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv); reg_t kStringFree(EngineState *s, int argc, reg_t *argv); -reg_t kStringFill(EngineState *s, int argc, reg_t *argv); -reg_t kStringCopy(EngineState *s, int argc, reg_t *argv); reg_t kStringCompare(EngineState *s, int argc, reg_t *argv); -reg_t kStringDup(EngineState *s, int argc, reg_t *argv); reg_t kStringGetData(EngineState *s, int argc, reg_t *argv); -reg_t kStringLen(EngineState *s, int argc, reg_t *argv); -reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv); -reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv); -reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv); +reg_t kStringLength(EngineState *s, int argc, reg_t *argv); +reg_t kStringFormat(EngineState *s, int argc, reg_t *argv); +reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv); +reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv); reg_t kStringTrim(EngineState *s, int argc, reg_t *argv); -reg_t kStringUpper(EngineState *s, int argc, reg_t *argv); -reg_t kStringLower(EngineState *s, int argc, reg_t *argv); -reg_t kStringTrn(EngineState *s, int argc, reg_t *argv); -reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv); +reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv); +reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv); +reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv); +reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv); reg_t kMulDiv(EngineState *s, int argc, reg_t *argv); -reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv); + reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv); reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv); -reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv); +reg_t kBitmap(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapSetOrigin(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv); reg_t kAddPlane(EngineState *s, int argc, reg_t *argv); reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv); reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv); +reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv); reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv); reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv); reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv); reg_t kFrameOut(EngineState *s, int argc, reg_t *argv); +reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv); +reg_t kCelWide32(EngineState *s, int argc, reg_t *argv); reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1 reg_t kInPolygon(EngineState *s, int argc, reg_t *argv); @@ -478,14 +591,23 @@ reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv); reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv); reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv); reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv); +reg_t kListSort(EngineState *s, int argc, reg_t *argv); reg_t kEditText(EngineState *s, int argc, reg_t *argv); -reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv); -reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv); reg_t kSetScroll(EngineState *s, int argc, reg_t *argv); -reg_t kPalCycle(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv); reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv); + +reg_t kPalCycle(EngineState *s, int argc, reg_t *argv); +reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv); +reg_t kPalCycleDoCycle(EngineState *s, int argc, reg_t *argv); +reg_t kPalCyclePause(EngineState *s, int argc, reg_t *argv); +reg_t kPalCycleOn(EngineState *s, int argc, reg_t *argv); +reg_t kPalCycleOff(EngineState *s, int argc, reg_t *argv); + reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv); reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv); reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv); @@ -499,26 +621,28 @@ reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv); // SCI2.1 Kernel Functions reg_t kMorphOn(EngineState *s, int argc, reg_t *argv); reg_t kText(EngineState *s, int argc, reg_t *argv); -reg_t kSave(EngineState *s, int argc, reg_t *argv); -reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); +reg_t kTextSize32(EngineState *s, int argc, reg_t *argv); +reg_t kTextWidth(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); -reg_t kRobot(EngineState *s, int argc, reg_t *argv); -reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); reg_t kCD(EngineState *s, int argc, reg_t *argv); +reg_t kCheckCD(EngineState *s, int argc, reg_t *argv); +reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv); reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv); reg_t kAddBefore(EngineState *s, int argc, reg_t *argv); reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv); reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv); reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv); reg_t kWinHelp(EngineState *s, int argc, reg_t *argv); +reg_t kMessageBox(EngineState *s, int argc, reg_t *argv); reg_t kGetConfig(EngineState *s, int argc, reg_t *argv); reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv); +reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv); reg_t kCelInfo(EngineState *s, int argc, reg_t *argv); reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv); +reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv); reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv); reg_t kFont(EngineState *s, int argc, reg_t *argv); -reg_t kBitmap(EngineState *s, int argc, reg_t *argv); reg_t kAddLine(EngineState *s, int argc, reg_t *argv); reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv); reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv); @@ -532,7 +656,6 @@ reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv); -reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv); @@ -547,7 +670,6 @@ reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv); -reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv); reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv); @@ -600,7 +722,7 @@ reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv); reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv); reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv); reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv); -reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv); +reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv); reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv); #endif diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index fbd0b13c88..2db3c59e64 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -60,15 +60,19 @@ struct SciKernelMapSubEntry { #define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL } -#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE -#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 -#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE -#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 -#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE -#define SIG_SCI21EARLY_ONLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY -#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 -#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE -#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3 +#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE +#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 +#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE +#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 +#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE +#define SIG_SCI2 SCI_VERSION_2, SCI_VERSION_2 +#define SIG_SCI21EARLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY +#define SIG_THRU_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY +#define SIG_THRU_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE +#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 +#define SIG_SINCE_SCI21MID SCI_VERSION_2_1_MIDDLE, SCI_VERSION_3 +#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3 +#define SIG_SCI3 SCI_VERSION_3, SCI_VERSION_3 #define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1 #define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE @@ -98,7 +102,7 @@ struct SciKernelMapSubEntry { static const SciKernelMapSubEntry kDoSound_subops[] = { { SIG_SOUNDSCI0, 0, MAP_CALL(DoSoundInit), "o", NULL }, { SIG_SOUNDSCI0, 1, MAP_CALL(DoSoundPlay), "o", NULL }, - { SIG_SOUNDSCI0, 2, MAP_CALL(DoSoundRestore), "(o)", NULL }, + { SIG_SOUNDSCI0, 2, MAP_EMPTY(DoSoundRestore), "(o)", NULL }, { SIG_SOUNDSCI0, 3, MAP_CALL(DoSoundDispose), "o", NULL }, { SIG_SOUNDSCI0, 4, MAP_CALL(DoSoundMute), "(i)", NULL }, { SIG_SOUNDSCI0, 5, MAP_CALL(DoSoundStop), "o", NULL }, @@ -111,7 +115,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { { SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL }, { SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, { SIG_SOUNDSCI1EARLY, 1, MAP_CALL(DoSoundMute), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 2, MAP_CALL(DoSoundRestore), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 2, MAP_EMPTY(DoSoundRestore), NULL, NULL }, { SIG_SOUNDSCI1EARLY, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, { SIG_SOUNDSCI1EARLY, 4, MAP_CALL(DoSoundUpdate), NULL, NULL }, { SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL }, @@ -124,11 +128,11 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { { SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi), "oiii", NULL }, { SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundGlobalReverb), "(i)", NULL }, { SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold), "oi", NULL }, - { SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy), "", NULL }, + { SIG_SOUNDSCI1EARLY, 15, MAP_EMPTY(DoSoundDummy), "", NULL }, // ^^ Longbow demo { SIG_SOUNDSCI1LATE, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, { SIG_SOUNDSCI1LATE, 1, MAP_CALL(DoSoundMute), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 2, MAP_CALL(DoSoundRestore), "", NULL }, + { SIG_SOUNDSCI1LATE, 2, MAP_EMPTY(DoSoundRestore), "", NULL }, { SIG_SOUNDSCI1LATE, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, { SIG_SOUNDSCI1LATE, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL }, { SIG_SOUNDSCI1LATE, 5, MAP_CALL(DoSoundSuspend), "i", NULL }, @@ -139,7 +143,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { { SIG_SOUNDSCI1LATE, 10, MAP_CALL(DoSoundPause), NULL, NULL }, { SIG_SOUNDSCI1LATE, 11, MAP_CALL(DoSoundFade), "oiiii(i)", kDoSoundFade_workarounds }, { SIG_SOUNDSCI1LATE, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 13, MAP_EMPTY(DoSoundDummy), NULL, NULL }, { SIG_SOUNDSCI1LATE, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL }, { SIG_SOUNDSCI1LATE, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL }, { SIG_SOUNDSCI1LATE, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL }, @@ -148,39 +152,103 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { { SIG_SOUNDSCI1LATE, 19, MAP_CALL(DoSoundGlobalReverb), NULL, NULL }, { SIG_SOUNDSCI1LATE, 20, MAP_CALL(DoSoundUpdate), NULL, NULL }, #ifdef ENABLE_SCI32 - { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, - { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), NULL, NULL }, - { SIG_SOUNDSCI21, 2, MAP_CALL(DoSoundRestore), NULL, NULL }, - { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, - { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), NULL, NULL }, - { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), NULL, NULL }, - { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), NULL, NULL }, - { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), NULL, NULL }, - { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o(i)", NULL }, + { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), "(i)", NULL }, + { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), "(i)", NULL }, + { SIG_SOUNDSCI21, 2, MAP_EMPTY(DoSoundRestore), NULL, NULL }, + { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), "", NULL }, + { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL }, + { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), "i", NULL }, + { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), "o", NULL }, + { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), "o", NULL }, + { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o", kDoSoundPlay_workarounds }, // ^^ TODO: if this is really the only change between SCI1LATE AND SCI21, we could rename the // SIG_SOUNDSCI1LATE #define to SIG_SINCE_SOUNDSCI1LATE and make it being SCI1LATE+. Although // I guess there are many more changes somewhere // TODO: Quest for Glory 4 (SCI2.1) uses the old scheme, we need to detect it accordingly // signature for SCI21 should be "o" - { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), NULL, NULL }, - { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), NULL, NULL }, - { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, kDoSoundFade_workarounds }, - { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, - { SIG_SOUNDSCI21, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, - { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), NULL, NULL }, - { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), NULL, NULL }, - { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), NULL, NULL }, - { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL }, - { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), NULL, NULL }, - { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundGlobalReverb), NULL, NULL }, - { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), NULL, NULL }, + { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), "o", NULL }, + { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), "[o0]i", NULL }, + { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), "oiiii", kDoSoundFade_workarounds }, + { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), "oi", NULL }, + { SIG_SOUNDSCI21, 13, MAP_EMPTY(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL }, + { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL }, + { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL }, + { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), "o", NULL }, + { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), "oiiii", NULL }, + { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundGlobalReverb), "(i)", NULL }, + { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), "o", NULL }, #endif SCI_SUBOPENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +// NOTE: In SSCI, some 'unused' kDoAudio subops are actually called indirectly +// by kDoSound: +// +// kDoSoundGetAudioCapability -> kDoAudioGetCapability +// kDoSoundPlay -> kDoAudioPlay, kDoAudioStop +// kDoSoundPause -> kDoAudioPause, kDoAudioResume +// kDoSoundFade -> kDoAudioFade +// kDoSoundSetVolume -> kDoAudioVolume +// kDoSoundSetLoop -> kDoAudioSetLoop +// kDoSoundUpdateCues -> kDoAudioPosition +// +// In ScummVM, logic inside these kernel functions has been moved to methods of +// Audio32, and direct calls to Audio32 are made from kDoSound instead. +// +// Some kDoAudio methods are esoteric and appear to be used only by one or two +// games: +// +// - kDoAudioMixing: Phantasmagoria (other games call this function, but only +// to disable the feature) +// - kDoAudioHasSignal: SQ6 TalkRandCycle +// - kDoAudioPan: Rama RegionSFX::pan method +// - kDoAudioCritical: Phantasmagoria, chapter 3, nursery (room 14200), during +// the "ghost lullaby" event. It is used to make the +// lullaby sound exclusive, but it really doesn't make any +// major difference. Returning 0 means "non-critical", i.e. +// normal audio behavior. +// +// Finally, there is a split in SCI2.1mid audio code. QFG4CD & SQ6 do not have +// opcodes 18 and 19, but they exist in GK2, KQ7 2.00b, Phantasmagoria 1, +// PQ:SWAT, and Torin. It is unknown if they exist in MUMG Deluxe or Shivers 1; +// they are not used in either of these games. + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kDoAudio_subops[] = { + { SIG_SCI32, 0, MAP_CALL(DoAudioInit), "", NULL }, + // SCI2 includes a Sync script that would call + // kDoAudioWaitForPlay, but SSCI has no opcode 1 until + // SCI2.1early + { SIG_SINCE_SCI21, 1, MAP_CALL(DoAudioWaitForPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(DoAudioPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 3, MAP_CALL(DoAudioStop), "(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 4, MAP_CALL(DoAudioPause), "(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 5, MAP_CALL(DoAudioResume), "(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 6, MAP_CALL(DoAudioPosition), "(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 7, MAP_CALL(DoAudioRate), "(i)", NULL }, + { SIG_SCI32, 8, MAP_CALL(DoAudioVolume), "(i)(i)(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 9, MAP_CALL(DoAudioGetCapability), "", NULL }, + { SIG_SCI32, 10, MAP_CALL(DoAudioBitDepth), "(i)", NULL }, + { SIG_SCI32, 11, MAP_DUMMY(DoAudioDistort), "(i)", NULL }, + { SIG_SCI32, 12, MAP_CALL(DoAudioMixing), "(i)", NULL }, + { SIG_SCI32, 13, MAP_CALL(DoAudioChannels), "(i)", NULL }, + { SIG_SCI32, 14, MAP_CALL(DoAudioPreload), "(i)", NULL }, + { SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade), "(iiii)(i)(i)", NULL }, + { SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36), "iiiii(iii)(i)", NULL }, + { SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal), "", NULL }, + { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "(i)", NULL }, + { SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop), "iii(o)", NULL }, + { SIG_SCI3, 20, MAP_DUMMY(DoAudioPan), "", NULL }, + { SIG_SCI3, 21, MAP_DUMMY(DoAudioPanOff), "", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; +#endif + // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kGraph_subops[] = { - { SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start + // 1 - load bits { SIG_SCIALL, 2, MAP_CALL(GraphGetColorCount), "", NULL }, // 3 - set palette via resource { SIG_SCIALL, 4, MAP_CALL(GraphDrawLine), "iiiii(i)(i)", kGraphDrawLine_workarounds }, @@ -210,8 +278,8 @@ static const SciKernelMapSubEntry kPalVary_subops[] = { { SIG_SCI16, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, { SIG_SCI16, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, #ifdef ENABLE_SCI32 - { SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(ii)", NULL }, - { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", NULL }, + { SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(i)(i)", NULL }, + { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", kPalVarySetPercent_workarounds }, { SIG_SCI32, 2, MAP_CALL(PalVaryGetPercent), "", NULL }, { SIG_SCI32, 3, MAP_CALL(PalVaryOff), "", NULL }, { SIG_SCI32, 4, MAP_CALL(PalVaryMergeTarget), "i", NULL }, @@ -226,24 +294,26 @@ static const SciKernelMapSubEntry kPalVary_subops[] = { // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kPalette_subops[] = { - { SIG_SCIALL, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, + { SIG_SCI16, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, { SIG_SCI16, 2, MAP_CALL(PaletteSetFlag), "iii", NULL }, { SIG_SCI16, 3, MAP_CALL(PaletteUnsetFlag), "iii", kPaletteUnsetFlag_workarounds }, -#ifdef ENABLE_SCI32 - { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL }, - { SIG_SCI32, 3, MAP_CALL(PaletteFindColor), "iii", NULL }, -#endif { SIG_SCI16, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL }, { SIG_SCI16, 5, MAP_CALL(PaletteFindColor), "iii", NULL }, { SIG_SCI16, 6, MAP_CALL(PaletteAnimate), "i*", NULL }, { SIG_SCI16, 7, MAP_CALL(PaletteSave), "", NULL }, { SIG_SCI16, 8, MAP_CALL(PaletteRestore), "[r0]", NULL }, +#ifdef ENABLE_SCI32 + { SIG_SCI32, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL }, + { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "iii", NULL }, + { SIG_SCI3, 4, MAP_CALL(PaletteSetGamma), "i", NULL }, +#endif SCI_SUBOPENTRY_TERMINATOR }; +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kFileIO_subops[] = { - { SIG_SCI32, 0, MAP_CALL(FileIOOpen), "r(i)", NULL }, - { SIG_SCIALL, 0, MAP_CALL(FileIOOpen), "ri", NULL }, + { SIG_SCIALL, 0, MAP_CALL(FileIOOpen), "ri", kFileIOOpen_workarounds }, { SIG_SCIALL, 1, MAP_CALL(FileIOClose), "i", NULL }, { SIG_SCIALL, 2, MAP_CALL(FileIOReadRaw), "iri", NULL }, { SIG_SCIALL, 3, MAP_CALL(FileIOWriteRaw), "iri", NULL }, @@ -256,13 +326,13 @@ static const SciKernelMapSubEntry kFileIO_subops[] = { { SIG_SCIALL, 10, MAP_CALL(FileIOExists), "r", NULL }, { SIG_SINCE_SCI11, 11, MAP_CALL(FileIORename), "rr", NULL }, #ifdef ENABLE_SCI32 - { SIG_SCI32, 13, MAP_CALL(FileIOReadByte), "i", NULL }, - { SIG_SCI32, 14, MAP_CALL(FileIOWriteByte), "ii", NULL }, - { SIG_SCI32, 15, MAP_CALL(FileIOReadWord), "i", NULL }, - { SIG_SCI32, 16, MAP_CALL(FileIOWriteWord), "ii", NULL }, - { SIG_SCI32, 17, MAP_CALL(FileIOCreateSaveSlot), "ir", NULL }, - { SIG_SCI32, 18, MAP_EMPTY(FileIOChangeDirectory), "r", NULL }, // for SQ6, when changing the savegame directory in the save/load dialog - { SIG_SCI32, 19, MAP_CALL(FileIOIsValidDirectory), "r", NULL }, // for Torin / Torin demo + { SIG_SINCE_SCI21MID, 13, MAP_CALL(FileIOReadByte), "i", NULL }, + { SIG_SINCE_SCI21MID, 14, MAP_CALL(FileIOWriteByte), "ii", NULL }, + { SIG_SINCE_SCI21MID, 15, MAP_CALL(FileIOReadWord), "i", NULL }, + { SIG_SINCE_SCI21MID, 16, MAP_CALL(FileIOWriteWord), "ii", NULL }, + { SIG_SINCE_SCI21MID, 17, "FileIOCheckFreeSpace", kCheckFreeSpace, "i(r)", NULL }, + { SIG_SINCE_SCI21MID, 18, MAP_CALL(FileIOGetCWD), "r", NULL }, + { SIG_SINCE_SCI21MID, 19, MAP_CALL(FileIOIsValidDirectory), "r", NULL }, #endif SCI_SUBOPENTRY_TERMINATOR }; @@ -270,16 +340,73 @@ static const SciKernelMapSubEntry kFileIO_subops[] = { #ifdef ENABLE_SCI32 // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kPalCycle_subops[] = { + { SIG_SCI32, 0, MAP_CALL(PalCycleSetCycle), "iii(i)", NULL }, + { SIG_SCI32, 1, MAP_CALL(PalCycleDoCycle), "i(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(PalCyclePause), "(i)", NULL }, + { SIG_SCI32, 3, MAP_CALL(PalCycleOn), "(i)", NULL }, + { SIG_SCI32, 4, MAP_CALL(PalCycleOff), "(i)", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kSave_subops[] = { - { SIG_SCI32, 0, MAP_CALL(SaveGame), "[r0]i[r0](r0)", NULL }, - { SIG_SCI32, 1, MAP_CALL(RestoreGame), "[r0]i[r0]", NULL }, - { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r*)", NULL }, - { SIG_SCI32, 3, MAP_CALL(CheckSaveGame), ".*", NULL }, + { SIG_SCI32, 0, MAP_CALL(SaveGame32), "[r0]i[r0][r0]", NULL }, + { SIG_SCI32, 1, MAP_CALL(RestoreGame32), "[r0]i[r0]", NULL }, + // System script 64994 in several SCI2.1mid games (KQ7 2.00b, Phant1, + // PQ:SWAT, SQ6, Torin) calls GetSaveDir with an extra unused argument, and + // it is easier to just handle it here than to bother with creating + // workarounds + { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r)", NULL }, + { SIG_SCI32, 3, MAP_CALL(CheckSaveGame32), "ri[r0]", NULL }, // Subop 4 hasn't been encountered yet - { SIG_SCI32, 5, MAP_CALL(GetSaveFiles), "rrr", NULL }, + { SIG_SCI32, 5, MAP_CALL(GetSaveFiles32), "rrr", NULL }, { SIG_SCI32, 6, MAP_CALL(MakeSaveCatName), "rr", NULL }, { SIG_SCI32, 7, MAP_CALL(MakeSaveFileName), "rri", NULL }, - { SIG_SCI32, 8, MAP_CALL(AutoSave), "[o0]", NULL }, + { SIG_SCI32, 8, MAP_EMPTY(GameIsRestarting), "(.*)", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kFont_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(SetFontHeight), "i", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(SetFontRes), "ii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kText_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(TextSize32), "r[r0]i(i)(i)", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(TextWidth), "ri", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kBitmap_subops[] = { + { SIG_SINCE_SCI21, 0, MAP_CALL(BitmapCreate), "iiii(i)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "[r!]", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(BitmapDrawLine), "riiiii(i)(i)", NULL }, + { SIG_SINCE_SCI21, 3, MAP_CALL(BitmapDrawView), "riii(i)(i)(0)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 4, MAP_CALL(BitmapDrawText), "rriiiiiiiiiii", NULL }, + { SIG_SINCE_SCI21, 5, MAP_CALL(BitmapDrawColor), "riiiii", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(BitmapDrawBitmap), "rr(i)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 7, MAP_CALL(BitmapInvert), "riiiiii", NULL }, + { SIG_SINCE_SCI21MID, 8, MAP_CALL(BitmapSetOrigin), "rii", NULL }, + { SIG_SINCE_SCI21MID, 9, MAP_CALL(BitmapCreateFromView), "iii(i)(i)(i)([r0])", NULL }, + { SIG_SINCE_SCI21MID, 10, MAP_CALL(BitmapCopyPixels), "rr", NULL }, + { SIG_SINCE_SCI21MID, 11, MAP_CALL(BitmapClone), "r", NULL }, + { SIG_SINCE_SCI21MID, 12, MAP_CALL(BitmapGetInfo), "r(i)(i)", NULL }, + { SIG_SINCE_SCI21LATE,13, MAP_CALL(BitmapScale), "r...ii", NULL }, + { SIG_SCI3, 14, MAP_CALL(BitmapCreateFromUnknown), "......", NULL }, + { SIG_SCI3, 15, MAP_EMPTY(Bitmap), "(.*)", NULL }, + { SIG_SCI3, 16, MAP_EMPTY(Bitmap), "(.*)", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kCD_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(CheckCD), "(i)", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(GetSavedCD), "", NULL }, SCI_SUBOPENTRY_TERMINATOR }; @@ -294,87 +421,171 @@ static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 6, MAP_CALL(NextNode), "n", NULL }, { SIG_SINCE_SCI21, 7, MAP_CALL(PrevNode), "n", NULL }, { SIG_SINCE_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL }, - { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL }, - { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL }, + { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn(.)", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln(.)", NULL }, { SIG_SINCE_SCI21, 11, MAP_CALL(AddToEnd), "ln(.)", NULL }, { SIG_SINCE_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL }, { SIG_SINCE_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL }, { SIG_SINCE_SCI21, 14, MAP_CALL(MoveToEnd), "ln", NULL }, { SIG_SINCE_SCI21, 15, MAP_CALL(FindKey), "l.", NULL }, { SIG_SINCE_SCI21, 16, MAP_CALL(DeleteKey), "l.", NULL }, - { SIG_SINCE_SCI21, 17, MAP_CALL(ListAt), "li", NULL }, + { SIG_SINCE_SCI21, 17, MAP_CALL(ListAt), "li", kListAt_workarounds }, { SIG_SINCE_SCI21, 18, MAP_CALL(ListIndexOf) , "l[io]", NULL }, { SIG_SINCE_SCI21, 19, MAP_CALL(ListEachElementDo), "li(.*)", NULL }, { SIG_SINCE_SCI21, 20, MAP_CALL(ListFirstTrue), "li(.*)", NULL }, { SIG_SINCE_SCI21, 21, MAP_CALL(ListAllTrue), "li(.*)", NULL }, - { SIG_SINCE_SCI21, 22, MAP_CALL(Sort), "ooo", NULL }, + { SIG_SINCE_SCI21, 22, MAP_CALL(ListSort), "li(i)", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kShowMovieWin_subops[] = { + { SIG_SCI2, 0, MAP_CALL(ShowMovieWinOpen), "r", NULL }, + { SIG_SCI2, 1, MAP_CALL(ShowMovieWinInit), "ii(ii)", NULL }, + { SIG_SCI2, 2, MAP_CALL(ShowMovieWinPlay), "i", NULL }, + { SIG_SCI2, 6, MAP_CALL(ShowMovieWinClose), "", NULL }, + { SIG_SINCE_SCI21, 0, MAP_CALL(ShowMovieWinOpen), "ir", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(ShowMovieWinInit), "iii(ii)", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(ShowMovieWinPlay), "i(ii)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(ShowMovieWinClose), "i", NULL }, + // Since movies are rendered within the graphics engine in ScummVM, + // it is not necessary to copy the palette from SCI to MCI, so this + // can be a no-op + { SIG_SINCE_SCI21, 7, MAP_EMPTY(ShowMovieWinSetPalette), "i", NULL }, + { SIG_SINCE_SCI21, 8, MAP_CALL(ShowMovieWinGetDuration), "i", NULL }, + { SIG_SINCE_SCI21, 11, MAP_CALL(ShowMovieWinCue), "ii", NULL }, + { SIG_SINCE_SCI21, 14, MAP_CALL(ShowMovieWinPlayUntilEvent), "i(i)", NULL }, + { SIG_SINCE_SCI21, 15, MAP_CALL(ShowMovieWinInitDouble), "iii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// There are a lot of subops to PlayVMD, but only a few of them are ever +// actually used by games +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kPlayVMD_subops[] = { + { SIG_SINCE_SCI21, 0, MAP_CALL(PlayVMDOpen), "r(i)(i)", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(PlayVMDInit), "ii(i)(i)(ii)", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(PlayVMDClose), "", NULL }, + { SIG_SINCE_SCI21, 7, MAP_CALL(PlayVMDIgnorePalettes), "", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(PlayVMDGetStatus), "", NULL }, + { SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL }, + { SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL }, + { SIG_SINCE_SCI21, 17, MAP_DUMMY(PlayVMDStartBlob), "", NULL }, + { SIG_SINCE_SCI21, 18, MAP_DUMMY(PlayVMDStopBlobs), "", NULL }, + { SIG_SINCE_SCI21, 21, MAP_CALL(PlayVMDSetBlackoutArea), "iiii", NULL }, + { SIG_SINCE_SCI21, 23, MAP_CALL(PlayVMDRestrictPalette), "ii", NULL }, + { SIG_SCI3, 28, MAP_EMPTY(PlayVMDSetPreload), "i", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kRobot_subops[] = { + { SIG_SINCE_SCI21, 0, MAP_CALL(RobotOpen), "ioiii(i)", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(RobotShowFrame), "i(ii)", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(RobotGetFrameSize), "r", NULL }, + { SIG_SINCE_SCI21, 4, MAP_CALL(RobotPlay), "", NULL }, + { SIG_SINCE_SCI21, 5, MAP_CALL(RobotGetIsFinished), "", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(RobotGetIsPlaying), "", NULL }, + { SIG_SINCE_SCI21, 7, MAP_CALL(RobotClose), "", NULL }, + { SIG_SINCE_SCI21, 8, MAP_CALL(RobotGetCue), "o", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(RobotPause), "", NULL }, + { SIG_SINCE_SCI21, 11, MAP_CALL(RobotGetFrameNo), "", NULL }, + { SIG_SINCE_SCI21, 12, MAP_CALL(RobotSetPriority), "i", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kRemapColors_subops[] = { + { SIG_SCI32, 0, MAP_CALL(RemapColorsOff), "(i)", NULL }, + { SIG_SCI32, 1, MAP_CALL(RemapColorsByRange), "iiii(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(RemapColorsByPercent), "ii(i)", NULL }, + { SIG_SCI32, 3, MAP_CALL(RemapColorsToGray), "ii(i)", NULL }, + { SIG_SCI32, 4, MAP_CALL(RemapColorsToPercentGray), "iii(i)", NULL }, + { SIG_SCI32, 5, MAP_CALL(RemapColorsBlockRange), "ii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kArray_subops[] = { + { SIG_SCI32, 0, MAP_CALL(ArrayNew), "ii", NULL }, + { SIG_SCI32, 1, MAP_CALL(ArrayGetSize), "r", NULL }, + { SIG_SCI32, 2, MAP_CALL(ArrayGetElement), "ri", NULL }, + { SIG_SCI32, 3, MAP_CALL(ArraySetElements), "ri(.*)", kArraySetElements_workarounds }, + { SIG_SCI32, 4, MAP_CALL(ArrayFree), "r", NULL }, + { SIG_SCI32, 5, MAP_CALL(ArrayFill), "riii", NULL }, + { SIG_SCI32, 6, MAP_CALL(ArrayCopy), "ririi", NULL }, + // there is no subop 7 + { SIG_SCI32, 8, MAP_CALL(ArrayDuplicate), "r", NULL }, + { SIG_SCI32, 9, MAP_CALL(ArrayGetData), "[or]", NULL }, + { SIG_SCI3, 10, MAP_CALL(ArrayByteCopy), "ririi", NULL }, SCI_SUBOPENTRY_TERMINATOR }; // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kString_subops[] = { - { SIG_SCI32, 0, MAP_CALL(StringNew), "i(i)", NULL }, - { SIG_SCI32, 1, MAP_CALL(StringSize), "[or]", NULL }, - { SIG_SCI32, 2, MAP_CALL(StringAt), "[or]i", NULL }, - { SIG_SCI32, 3, MAP_CALL(StringPutAt), "[or]i(i*)", NULL }, - // StringFree accepts invalid references - { SIG_SCI32, 4, MAP_CALL(StringFree), "[or0!]", NULL }, - { SIG_SCI32, 5, MAP_CALL(StringFill), "[or]ii", NULL }, - { SIG_SCI32, 6, MAP_CALL(StringCopy), "[or]i[or]ii", NULL }, - { SIG_SCI32, 7, MAP_CALL(StringCompare), "[or][or](i)", NULL }, - - // =SCI2, SCI2.1 Early and SCI2.1 Middle= - { SIG_UNTIL_SCI21MID, 8, MAP_CALL(StringDup), "[or]", NULL }, - // TODO: This gets called with null references in Torin. Check if this is correct, or it's - // caused by missing functionality - { SIG_UNTIL_SCI21MID, 9, MAP_CALL(StringGetData), "[or0]", NULL }, - { SIG_UNTIL_SCI21MID, 10, MAP_CALL(StringLen), "[or]", NULL }, - { SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringPrintf), "[or](.*)", NULL }, - { SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringPrintfBuf), "[or](.*)", NULL }, - { SIG_UNTIL_SCI21MID, 13, MAP_CALL(StringAtoi), "[or]", NULL }, - { SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim), "[or]i", NULL }, - { SIG_UNTIL_SCI21MID, 15, MAP_CALL(StringUpper), "[or]", NULL }, - { SIG_UNTIL_SCI21MID, 16, MAP_CALL(StringLower), "[or]", NULL }, - // the following 2 are unknown atm (happen in Phantasmagoria) - // possibly translate? - { SIG_UNTIL_SCI21MID, 17, MAP_CALL(StringTrn), "[or]", NULL }, - { SIG_UNTIL_SCI21MID, 18, MAP_CALL(StringTrnExclude), "[or]", NULL }, - - // SCI2.1 Late + SCI3 - kStringDup + kStringGetData were removed - { SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLen), "[or]", NULL }, - { SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringPrintf), "[or](.*)", NULL }, - { SIG_SINCE_SCI21LATE,10, MAP_CALL(StringPrintfBuf), "[or](.*)", NULL }, - { SIG_SINCE_SCI21LATE,11, MAP_CALL(StringAtoi), "[or]", NULL }, - { SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim), "[or]i", NULL }, - { SIG_SINCE_SCI21LATE,13, MAP_CALL(StringUpper), "[or]", NULL }, - { SIG_SINCE_SCI21LATE,14, MAP_CALL(StringLower), "[or]", NULL }, - { SIG_SINCE_SCI21LATE,15, MAP_CALL(StringTrn), "[or]", NULL }, - { SIG_SINCE_SCI21LATE,16, MAP_CALL(StringTrnExclude), "[or]", NULL }, + // every single copy of script 64918 in SCI2 through 2.1mid calls StringNew + // with a second type argument which is unused (new strings are always type + // 3) + { SIG_THRU_SCI21MID, 0, MAP_CALL(StringNew), "i(i)", NULL }, + { SIG_THRU_SCI21MID, 1, MAP_CALL(ArrayGetSize), "r", NULL }, + { SIG_THRU_SCI21MID, 2, MAP_CALL(StringGetChar), "ri", NULL }, + { SIG_THRU_SCI21MID, 3, MAP_CALL(ArraySetElements), "ri(i*)", kArraySetElements_workarounds }, + { SIG_THRU_SCI21MID, 4, MAP_CALL(StringFree), "[0r]", NULL }, + { SIG_THRU_SCI21MID, 5, MAP_CALL(ArrayFill), "rii", NULL }, + { SIG_THRU_SCI21MID, 6, MAP_CALL(ArrayCopy), "ririi", NULL }, + { SIG_SCI32, 7, MAP_CALL(StringCompare), "rr(i)", NULL }, + + { SIG_THRU_SCI21MID, 8, MAP_CALL(ArrayDuplicate), "r", NULL }, + { SIG_THRU_SCI21MID, 9, MAP_CALL(StringGetData), "[0or]", NULL }, + { SIG_THRU_SCI21MID, 10, MAP_CALL(StringLength), "r", NULL }, + { SIG_THRU_SCI21MID, 11, MAP_CALL(StringFormat), "r(.*)", NULL }, + { SIG_THRU_SCI21MID, 12, MAP_CALL(StringFormatAt), "r[ro](.*)", NULL }, + { SIG_THRU_SCI21MID, 13, MAP_CALL(StringToInteger), "r", NULL }, + { SIG_THRU_SCI21MID, 14, MAP_CALL(StringTrim), "ri(i)", NULL }, + { SIG_THRU_SCI21MID, 15, MAP_CALL(StringToUpperCase), "r", NULL }, + { SIG_THRU_SCI21MID, 16, MAP_CALL(StringToLowerCase), "r", NULL }, + { SIG_THRU_SCI21MID, 17, MAP_CALL(StringReplaceSubstring), "rrrr", NULL }, + { SIG_THRU_SCI21MID, 18, MAP_CALL(StringReplaceSubstringEx), "rrrr", NULL }, + + { SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLength), "r", NULL }, + { SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringFormat), "r(.*)", NULL }, + { SIG_SINCE_SCI21LATE,10, MAP_CALL(StringFormatAt), "rr(.*)", NULL }, + { SIG_SINCE_SCI21LATE,11, MAP_CALL(StringToInteger), "r", NULL }, + { SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim), "ri(i)", NULL }, + { SIG_SINCE_SCI21LATE,13, MAP_CALL(StringToUpperCase), "r", NULL }, + { SIG_SINCE_SCI21LATE,14, MAP_CALL(StringToLowerCase), "r", NULL }, + { SIG_SINCE_SCI21LATE,15, MAP_CALL(StringReplaceSubstring), "rrrr", NULL }, + { SIG_SINCE_SCI21LATE,16, MAP_CALL(StringReplaceSubstringEx), "rrrr", NULL }, SCI_SUBOPENTRY_TERMINATOR }; // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kScrollWindow_subops[] = { { SIG_SCI32, 0, MAP_CALL(ScrollWindowCreate), "oi", NULL }, - { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "o.ii.(.)", NULL }, - { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "o", NULL }, - { SIG_SCI32, 3, MAP_DUMMY(ScrollWindowPageUp), "o", NULL }, - { SIG_SCI32, 4, MAP_DUMMY(ScrollWindowPageDown), "o", NULL }, - { SIG_SCI32, 5, MAP_DUMMY(ScrollWindowUpArrow), "o", NULL }, - { SIG_SCI32, 6, MAP_DUMMY(ScrollWindowDownArrow), "o", NULL }, - { SIG_SCI32, 7, MAP_DUMMY(ScrollWindowHome), "o", NULL }, - { SIG_SCI32, 8, MAP_DUMMY(ScrollWindowEnd), "o", NULL }, - { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "o.", NULL }, - { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "oi", NULL }, - { SIG_SCI32, 11, MAP_DUMMY(ScrollWindowGo), "o..", NULL }, - { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "o.....", NULL }, - { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "o.", NULL }, - { SIG_SCI32, 14, MAP_DUMMY(ScrollWindowModify), "o.....(.)", NULL }, - { SIG_SCI32, 15, MAP_DUMMY(ScrollWindowHide), "o", NULL }, - { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "o", NULL }, - { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "o", NULL }, - { SIG_SCI32, 18, MAP_DUMMY(ScrollWindowText), "o", NULL }, - { SIG_SCI32, 19, MAP_DUMMY(ScrollWindowReconstruct), "o.", NULL }, + { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "iriii(i)", kScrollWindowAdd_workarounds }, + { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "i", NULL }, + { SIG_SCI32, 3, MAP_CALL(ScrollWindowPageUp), "i", NULL }, + { SIG_SCI32, 4, MAP_CALL(ScrollWindowPageDown), "i", NULL }, + { SIG_SCI32, 5, MAP_CALL(ScrollWindowUpArrow), "i", NULL }, + { SIG_SCI32, 6, MAP_CALL(ScrollWindowDownArrow), "i", NULL }, + { SIG_SCI32, 7, MAP_CALL(ScrollWindowHome), "i", NULL }, + { SIG_SCI32, 8, MAP_CALL(ScrollWindowEnd), "i", NULL }, + { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "i.", NULL }, + { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "ii", NULL }, + { SIG_SCI32, 11, MAP_CALL(ScrollWindowGo), "i..", NULL }, + { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "i.....", NULL }, + { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "i.", NULL }, + { SIG_SCI32, 14, MAP_CALL(ScrollWindowModify), "iiriii(i)", NULL }, + { SIG_SCI32, 15, MAP_CALL(ScrollWindowHide), "i", NULL }, + { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "i", NULL }, + { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "i", NULL }, + // LSL6hires uses kScrollWindowText and kScrollWindowReconstruct to try to save + // and restore the content of the game's subtitle window, but this feature did not + // use the normal save/load functionality of the engine and was actually broken + // (all text formatting was missing on restore). Since there is no real reason to + // save the subtitle scrollback anyway, we just ignore calls to these two functions. + { SIG_SCI32, 18, MAP_EMPTY(ScrollWindowText), "i", NULL }, + { SIG_SCI32, 19, MAP_EMPTY(ScrollWindowReconstruct), "i.", NULL }, SCI_SUBOPENTRY_TERMINATOR }; @@ -404,18 +615,28 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL }, { MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, - { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(BaseSetter), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "BaseSetter", kBaseSetter32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, +#endif { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, + { MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL }, #ifdef ENABLE_SCI32 - { "CantBeHere", kCantBeHere32, SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL }, + { MAP_CALL(CantBeHere), SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL }, #endif - { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, - { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, kCelHigh_workarounds }, - { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, kCelWide_workarounds }, - { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL }, + { MAP_CALL(CelHigh), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelHigh_workarounds }, + { MAP_CALL(CelWide), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelWide_workarounds }, +#ifdef ENABLE_SCI32 + { "CelHigh", kCelHigh32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL }, + { "CelWide", kCelWide32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, kCelWide_workarounds }, +#endif + { MAP_CALL(CheckFreeSpace), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "r(i)", NULL, NULL }, { MAP_CALL(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL }, - { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(CheckSaveGame), SIG_EVERYWHERE, ".*", NULL, NULL }, + { MAP_CALL(CheckFreeSpace), SIG_SCI16, SIGFOR_ALL, "r", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "CheckSaveGame", kCheckSaveGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri[r0]", NULL, NULL }, +#endif + { MAP_CALL(CheckSaveGame), SIG_SCI16, SIGFOR_ALL, ".*", NULL, NULL }, { MAP_CALL(Clone), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(CoordPri), SIG_EVERYWHERE, "i(i)", NULL, NULL }, { MAP_CALL(CosDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, @@ -429,7 +650,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL }, { MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds }, { MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL }, - { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(DoAudio), SCI_VERSION_NONE, SCI_VERSION_2, SIGFOR_ALL, "i(.*)", NULL, NULL }, // subop +#ifdef ENABLE_SCI32 + { "DoAudio", kDoAudio32, SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kDoAudio_subops, NULL }, +#endif { MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o(i)", NULL, NULL }, { MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(DoSound), SIG_EVERYWHERE, "i(.*)", kDoSound_subops, NULL }, @@ -462,13 +686,23 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(GetMenu), SIG_EVERYWHERE, "i.", NULL, NULL }, { MAP_CALL(GetMessage), SIG_EVERYWHERE, "iiir", NULL, NULL }, { MAP_CALL(GetPort), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(GetSaveDir), SIG_SCI32, SIGFOR_ALL, "(r*)", NULL, NULL }, - { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, +#ifdef ENABLE_SCI32 + { MAP_CALL(GetSaveDir), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(r)", NULL, NULL }, +#endif + { MAP_CALL(GetSaveDir), SIG_SCI16, SIGFOR_ALL, "", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "GetSaveFiles", kGetSaveFiles32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rrr", NULL, NULL }, +#endif { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, + { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "GlobalToLocal", kGlobalToLocal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, +#endif + { MAP_CALL(Graph), SIG_SCI16, SIGFOR_ALL, NULL, kGraph_subops, NULL }, +#ifdef ENABLE_SCI32 + { MAP_EMPTY(Graph), SIG_SCI32, SIGFOR_ALL, "(.*)", NULL, NULL }, +#endif { MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(InitBresen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, @@ -478,8 +712,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop { MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL }, { MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "LocalToGlobal", kLocalToGlobal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, +#endif { MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, { MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, kMemory_workarounds }, // subop @@ -504,37 +740,59 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL }, { MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL }, { MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Platform), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "Platform", kPlatform32, SIG_SCI32, SIGFOR_MAC, "(.*)", NULL, NULL }, + { "Platform", kPlatform32, SIG_SCI32, SIGFOR_ALL, "(i)", NULL, kPlatform32_workarounds }, +#endif { MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop { MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL }, { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL }, + { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, kRandom_workarounds }, { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, kReadNumber_workarounds }, { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL }, #ifdef ENABLE_SCI32 - { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", NULL, NULL }, + { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", kRemapColors_subops, NULL }, #endif - { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, + { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, kResCheck_workarounds }, { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, { MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "RestoreGame", kRestoreGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0]", NULL, NULL }, +#endif { MAP_CALL(RestoreGame), SIG_EVERYWHERE, "[r0]i[r0]", NULL, NULL }, { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, - { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "SaveGame", kSaveGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0][r0]", NULL, NULL }, +#endif + { MAP_CALL(SaveGame), SIG_SCI16, SIGFOR_ALL, "[r0]i[r0](r0)", NULL, NULL }, { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_SINCE_SCI21, SIGFOR_ALL, "i(i)([io])(i*)", NULL, NULL }, - // TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why { MAP_CALL(SetCursor), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, + { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, +#ifdef ENABLE_SCI32 + { "SetCursor", kSetCursor32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)", NULL, kSetCursor_workarounds }, +#endif { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, - { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "SetNowSeen", kSetNowSeen32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, +#endif { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds }, { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, - { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(ShakeScreen), SIG_SCI16, SIGFOR_ALL, "(i)(i)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "ShakeScreen", kShakeScreen32, SIG_SCI32, SIGFOR_ALL, "i(i)", NULL, NULL }, +#endif + { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL }, + { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_MAC, "ri(i)(i)", NULL, NULL }, + { "ShowMovie", kShowMovieWin, SIG_SCI32, SIGFOR_WIN, "(.*)", kShowMovieWin_subops, NULL }, +#endif { MAP_CALL(Show), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL }, @@ -546,10 +804,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, kStrLen_workarounds }, { MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL }, - { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_SCIALL, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL }, + { MAP_CALL(TextColors), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL }, + { MAP_CALL(TextFonts), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_ALL, "r[r0]i(i)(r0)", NULL, NULL }, { MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL }, { "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL }, @@ -581,13 +839,17 @@ static SciKernelMapEntry s_kernelMap[] = { #ifdef ENABLE_SCI32 // SCI2 Kernel Functions // TODO: whoever knows his way through those calls, fix the signatures. + { "TextSize", kTextSize32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL }, + { MAP_DUMMY(TextColors), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_DUMMY(TextFonts), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Array), SIG_EVERYWHERE, "i(.*)", kArray_subops, NULL }, { MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DisposeTextBitmap), SIG_EVERYWHERE, "r", NULL, NULL }, + { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "[r!]", NULL, NULL }, { MAP_CALL(FrameOut), SIG_EVERYWHERE, "(i)", NULL, NULL }, { MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL }, @@ -597,6 +859,8 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(ListEachElementDo), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, { MAP_CALL(ListFirstTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, { MAP_CALL(ListIndexOf), SIG_EVERYWHERE, "l[o0]", NULL, NULL }, + // kMessageBox is used only by KQ7 1.51 + { MAP_CALL(MessageBox), SIG_SCI32, SIGFOR_ALL, "rri", NULL, NULL }, { "OnMe", kIsOnMe, SIG_EVERYWHERE, "iioi", NULL, NULL }, // Purge is used by the memory manager in SSCI to ensure that X number of bytes (the so called "unmovable // memory") are available when the current room changes. This is similar to the SCI0-SCI1.1 FlushResources @@ -610,10 +874,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(ObjectIntersect), SIG_EVERYWHERE, "oo", NULL, NULL }, { MAP_CALL(EditText), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(MakeSaveCatName), SIG_EVERYWHERE, "rr", NULL, NULL }, - { MAP_CALL(MakeSaveFileName), SIG_EVERYWHERE, "rri", NULL, NULL }, - { MAP_CALL(SetScroll), SIG_EVERYWHERE, "oiiiii(i)", NULL, NULL }, - { MAP_CALL(PalCycle), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, + { MAP_CALL(MakeSaveCatName), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rr", NULL, NULL }, + { MAP_CALL(MakeSaveFileName), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rri", NULL, NULL }, + { MAP_CALL(SetScroll), SIG_EVERYWHERE, "oiiii(i)(i)", NULL, NULL }, + { MAP_CALL(PalCycle), SIG_EVERYWHERE, "(.*)", kPalCycle_subops, NULL }, // SCI2 Empty functions @@ -649,34 +913,37 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_DUMMY(MarkMemory), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(GetHighItemPri), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(ShowStylePercent), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_DUMMY(InvertRect), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_DUMMY(InvertRect), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, { MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(TextWidth), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL }, { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions - { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(CD), SIG_SINCE_SCI21MID, SIGFOR_ALL, "(.*)", kCD_subops, NULL }, { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL }, { MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL }, { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, - { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL }, + { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", kRobot_subops, NULL }, { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, - { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL }, + { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL }, + { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL }, { MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(GetConfig), SIG_EVERYWHERE, "ro", NULL, NULL }, { MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL }, - { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL }, - { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(CelInfo), SIG_SINCE_SCI21MID, SIGFOR_ALL, "iiiiii", NULL, NULL }, + { MAP_CALL(SetLanguage), SIG_SINCE_SCI21MID, SIGFOR_ALL, "r", NULL, NULL }, { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "i(.*)", kScrollWindow_subops, NULL }, - { MAP_CALL(SetFontRes), SIG_SCI21EARLY_ONLY, SIGFOR_ALL, "ii", NULL, NULL }, - { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, - { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL }, - { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL }, - { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "[r0]o", NULL, NULL }, + { MAP_CALL(SetFontRes), SIG_SCI21EARLY, SIGFOR_ALL, "ii", NULL, NULL }, + { MAP_CALL(Font), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kFont_subops, NULL }, + { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", kBitmap_subops, NULL }, + { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiii(iiiii)", NULL, NULL }, + // The first argument is a ScreenItem instance ID that is created by the + // engine, not the VM; as a result, in ScummVM, this argument looks like + // an integer and not an object, although it is an object reference. + { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "ioiiii(iiiii)", NULL, NULL }, + { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "io", NULL, NULL }, // SCI2.1 Empty Functions @@ -684,14 +951,14 @@ static SciKernelMapEntry s_kernelMap[] = { // stub in the original interpreters, but it gets called by the game scripts. // Usually, it gets called with a string (which is the output format) and a // variable number of parameters - { MAP_EMPTY(PrintDebug), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(PrintDebug), SIG_SCI32, SIGFOR_ALL, "r(.*)", NULL, NULL }, // SetWindowsOption is used to set Windows specific options, like for example the title bar visibility of // the game window in Phantasmagoria 2. We ignore these settings completely. - { MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE, "ii", NULL, NULL }, // Debug function called whenever the current room changes - { MAP_EMPTY(NewRoom), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_EMPTY(NewRoom), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // Unused / debug SCI2.1 unused functions, always mapped to kDummy @@ -724,10 +991,11 @@ static SciKernelMapEntry s_kernelMap[] = { // SetHotRectangles - used by Phantasmagoria 1, script 64981 (used in the chase scene) // <lskovlun> The idea, if I understand correctly, is that the engine generates events // of a special HotRect type continuously when the mouse is on that rectangle + { MAP_CALL(SetHotRectangles), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(r)", NULL, NULL }, + + // Used by SQ6 to scroll through the inventory via the up/down buttons + { MAP_CALL(MovePlaneItems), SIG_SINCE_SCI21, SIGFOR_ALL, "oii(i)", NULL, NULL }, - // MovePlaneItems - used by SQ6 to scroll through the inventory via the up/down buttons - // SetPalStyleRange - 2 integer parameters, start and end. All styles from start-end - // (inclusive) are set to 0 { MAP_CALL(SetPalStyleRange), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(MorphOn), SIG_EVERYWHERE, "", NULL, NULL }, @@ -1030,7 +1298,6 @@ static const char *const sci2_default_knames[] = { /*0x89*/ "TextWidth", // for debugging(?), only in SCI2, not used in any SCI2 game /*0x8a*/ "PointSize", // for debugging(?), only in SCI2, not used in any SCI2 game - // GK2 Demo (and similar) only kernel functions /*0x8b*/ "AddLine", /*0x8c*/ "DeleteLine", /*0x8d*/ "UpdateLine", diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index bb595e9960..a00630622e 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -34,6 +34,9 @@ #include "sci/graphics/coordadjuster.h" #include "sci/graphics/cursor.h" #include "sci/graphics/maciconbar.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif namespace Sci { @@ -58,10 +61,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { if (g_debug_simulated_key && (mask & SCI_EVENT_KEYBOARD)) { // In case we use a simulated event we query the current mouse position mousePos = g_sci->_gfxCursor->getPosition(); -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) - g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); -#endif + // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); @@ -73,7 +73,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { g_debug_simulated_key = 0; return make_reg(0, 1); } - + curEvent = g_sci->getEventManager()->getSciEvent(mask); if (s->_delayedRestoreGame) { @@ -83,13 +83,17 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { } // For a real event we use its associated mouse position - mousePos = curEvent.mousePos; #ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) - g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); + if (getSciVersion() >= SCI_VERSION_2) + mousePos = curEvent.mousePosSci; + else { +#endif + mousePos = curEvent.mousePos; + // Limit the mouse cursor position, if necessary + g_sci->_gfxCursor->refreshPosition(); +#ifdef ENABLE_SCI32 + } #endif - // Limit the mouse cursor position, if necessary - g_sci->_gfxCursor->refreshPosition(); if (g_sci->getVocabulary()) g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event @@ -101,7 +105,25 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { // question. Check GfxCursor::setPosition(), for a more detailed // explanation and a list of cursor position workarounds. if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) { - s->_cursorWorkaroundActive = false; + // For OpenPandora and possibly other platforms, that support analog-stick control + touch screen + // control at the same time: in case the cursor is currently at the coordinate set by the scripts, + // we will count down instead of immediately disabling the workaround. + // On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the + // touch screen. In this case we would sometimes disable the workaround, simply because the touch + // screen hasn't yet overwritten the position and thus the workaround would not work anymore. + // On OpenPandora it would sometimes work and sometimes not without this. + if (s->_cursorWorkaroundPoint == mousePos) { + // Cursor is still at the same spot as set by the scripts + if (s->_cursorWorkaroundPosCount > 0) { + s->_cursorWorkaroundPosCount--; + } else { + // Was for quite a bit of time at that spot, so disable workaround now + s->_cursorWorkaroundActive = false; + } + } else { + // Cursor has moved, but is within the rect -> disable workaround immediately + s->_cursorWorkaroundActive = false; + } } else { mousePos.x = s->_cursorWorkaroundPoint.x; mousePos.y = s->_cursorWorkaroundPoint.y; @@ -214,7 +236,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { // check bugs #3058865 and #3127824 if (s->_gameIsBenchmarking) { // Game is benchmarking, don't add a delay - } else { + } else if (getSciVersion() < SCI_VERSION_2) { g_system->delayMillis(10); } @@ -239,11 +261,12 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { if (readSelectorValue(segMan, obj, SELECTOR(type)) == SCI_EVENT_KEYBOARD) { // Keyboard uint16 message = readSelectorValue(segMan, obj, SELECTOR(message)); uint16 eventType = SCI_EVENT_DIRECTION; - // Check if the game is using cursor views. These games allowed control - // of the mouse cursor via the keyboard controls (the so called - // "PseudoMouse" functionality in script 933). - if (g_sci->_features->detectSetCursorType() == SCI_VERSION_1_1) + // It seems with SCI1 Sierra started to add the SCI_EVENT_DIRECTION bit instead of setting it directly. + // It was done inside the keyboard driver and is required for the PseudoMouse functionality and class + // to work (script 933). + if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) { eventType |= SCI_EVENT_KEYBOARD; + } for (int i = 0; i < 9; i++) { if (keyToDirMap[i].key == message) { @@ -261,14 +284,13 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; - reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); - g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y, planeObject); + g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y); writeSelectorValue(segMan, obj, SELECTOR(x), x); writeSelectorValue(segMan, obj, SELECTOR(y), y); @@ -280,14 +302,13 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; - reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); - g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y, planeObject); + g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y); writeSelectorValue(segMan, obj, SELECTOR(x), x); writeSelectorValue(segMan, obj, SELECTOR(y), y); @@ -302,4 +323,76 @@ reg_t kJoystick(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 +reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) { + const reg_t result = argv[0]; + const reg_t planeObj = argv[1]; + + bool visible = true; + Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (plane == nullptr) { + plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + visible = false; + } + if (plane == nullptr) { + error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj)); + } + + const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left; + const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top; + + writeSelectorValue(s->_segMan, result, SELECTOR(x), x); + writeSelectorValue(s->_segMan, result, SELECTOR(y), y); + + return make_reg(0, visible); +} + +reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) { + const reg_t result = argv[0]; + const reg_t planeObj = argv[1]; + + bool visible = true; + Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (plane == nullptr) { + plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + visible = false; + } + if (plane == nullptr) { + error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj)); + } + + const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left; + const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top; + + writeSelectorValue(s->_segMan, result, SELECTOR(x), x); + writeSelectorValue(s->_segMan, result, SELECTOR(y), y); + + return make_reg(0, visible); +} + +reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv) { + if (argc == 1) { + g_sci->getEventManager()->setHotRectanglesActive((bool)argv[0].toUint16()); + return s->r_acc; + } + + const int16 numRects = argv[0].toSint16(); + SciArray &hotRects = *s->_segMan->lookupArray(argv[1]); + + Common::Array<Common::Rect> rects; + rects.resize(numRects); + + for (int16 i = 0; i < numRects; ++i) { + rects[i].left = hotRects.getAsInt16(i * 4); + rects[i].top = hotRects.getAsInt16(i * 4 + 1); + rects[i].right = hotRects.getAsInt16(i * 4 + 2) + 1; + rects[i].bottom = hotRects.getAsInt16(i * 4 + 3) + 1; + } + + g_sci->getEventManager()->setHotRectanglesActive(true); + g_sci->getEventManager()->setHotRectangles(rects); + return s->r_acc; +} +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 335763a35f..796eb08450 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -29,6 +29,7 @@ #include "common/savefile.h" #include "common/system.h" #include "common/translation.h" +#include "common/memstream.h" #include "gui/saveload.h" @@ -39,14 +40,18 @@ #include "sci/engine/savegame.h" #include "sci/sound/audio.h" #include "sci/console.h" +#ifdef ENABLE_SCI32 +#include "sci/resource.h" +#endif namespace Sci { -extern reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename); +extern reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename); extern FileHandle *getFileFromHandle(EngineState *s, uint handle); extern int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle); extern void listSavegames(Common::Array<SavegameDesc> &saves); extern int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId); +extern bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc); /** * Writes the cwd to the supplied address and returns the address in acc. @@ -154,33 +159,37 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) { } reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { - if (argc > 1) { - // SCI1.1/SCI32 - // TODO: don't know if those are right for SCI32 as well - // Please note that sierra sci supported both calls either w/ or w/o opcode in SCI1.1 - switch (argv[1].toUint16()) { - case 0: // return saved game size - return make_reg(0, 0); // we return 0 - - case 1: // return free harddisc space (shifted right somehow) - return make_reg(0, 0x7fff); // we return maximum - - case 2: // same as call w/o opcode - break; - return make_reg(0, 1); - - default: - error("kCheckFreeSpace: called with unknown sub-op %d", argv[1].toUint16()); - } + // A file path to test is also passed to this function as a separate + // argument, but we do not actually check anything, so it is unused + + enum { + kSaveGameSize = 0, + kFreeDiskSpace = 1, + kEnoughSpaceToSave = 2 + }; + + int16 subop; + // In SCI2.1mid, the call is moved into kFileIO and the arguments are + // flipped + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { + subop = argc > 0 ? argv[0].toSint16() : 2; + } else { + subop = argc > 1 ? argv[1].toSint16() : 2; } - Common::String path = s->_segMan->getString(argv[0]); + switch (subop) { + case kSaveGameSize: + return make_reg(0, 0); - debug(3, "kCheckFreeSpace(%s)", path.c_str()); - // We simply always pretend that there is enough space. The alternative - // would be to write a big test file, which is not nice on systems where - // doing so is very slow. - return make_reg(0, 1); + case kFreeDiskSpace: // in KiB; up to 32MiB maximum + return make_reg(0, 0x7fff); + + case kEnoughSpaceToSave: + return make_reg(0, 1); + + default: + error("kCheckFreeSpace: called with unknown sub-op %d", subop); + } } reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { @@ -195,32 +204,70 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 reg_t kCD(EngineState *s, int argc, reg_t *argv) { - // TODO: Stub - switch (argv[0].toUint16()) { - case 0: - if (argc == 1) { - // Check if a disc is in the drive - return TRUE_REG; - } else { - // Check if the specified disc is in the drive - // and return the current disc number. We just - // return the requested disc number. - return argv[1]; - } - case 1: - // Return the current CD number - return make_reg(0, 1); - default: - warning("CD(%d)", argv[0].toUint16()); + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kCheckCD(EngineState *s, int argc, reg_t *argv) { + const int16 cdNo = argc > 0 ? argv[0].toSint16() : 0; + + if (cdNo) { + g_sci->getResMan()->findDisc(cdNo); } - return NULL_REG; + return make_reg(0, g_sci->getResMan()->getCurrentDiscNo()); +} + +reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) { + // TODO: This is wrong, CD number needs to be available prior to + // the save game being loaded + return make_reg(0, g_sci->getResMan()->getCurrentDiscNo()); } #endif // ---- FileIO operations ----------------------------------------------------- +#ifdef ENABLE_SCI32 +static bool isSaveCatalogue(const Common::String &name) { + return name == "autosave.cat" || name.hasSuffix("sg.cat"); +} + +// SCI32 save game scripts check for, and write directly to, the save game +// catalogue. Since ScummVM does not use these catalogues, when looking for a +// catalogue, we instead check for save games within ScummVM that are logically +// equivalent to the behaviour of SSCI. +static bool saveCatalogueExists(const Common::String &name) { + bool exists = false; + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + + // There will always be one save game in some games, the "new game" + // game, which should be ignored when deciding if there are any save + // games available + uint numPermanentSaves; + switch (g_sci->getGameId()) { + case GID_TORIN: + case GID_LSL7: + case GID_LIGHTHOUSE: + numPermanentSaves = 1; + break; + default: + numPermanentSaves = 0; + break; + } + + // Torin uses autosave.cat; LSL7 uses autosvsg.cat + if (name == "autosave.cat" || name == "autosvsg.cat") { + exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty(); + } else { + exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > numPermanentSaves; + } + + return exists; +} +#endif + reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { if (!s) return make_reg(0, getSciVersion()); @@ -230,9 +277,13 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); - // SCI32 can call K_FILEIO_OPEN with only one argument. It seems to - // just be checking if it exists. - int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16(); + if (name.empty()) { + // Happens many times during KQ1 (e.g. when typing something) + debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); + return SIGNAL_REG; + } + + kFileOpenMode mode = (kFileOpenMode)argv[1].toUint16(); bool unwrapFilename = true; // SQ4 floppy prepends /\ to the filenames @@ -250,85 +301,158 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { return SIGNAL_REG; } - if (name.empty()) { - // Happens many times during KQ1 (e.g. when typing something) - debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); - return SIGNAL_REG; - } - debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); - - if (name.hasPrefix("sciAudio\\")) { - // fan-made sciAudio extension, don't create those files and instead return a virtual handle - return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO); - } - #ifdef ENABLE_SCI32 - if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { - if (s->_virtualIndexFile) { - return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); - } else { - Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH); - Common::String wrappedName = g_sci->wrapFilename(englishName); - if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) { - s->_virtualIndexFile = new VirtualIndexFile(wrappedName); - return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); - } + // GK1, GK2, KQ7, LSL6hires, Phant1, PQ4, PQ:SWAT, and SQ6 read in + // their game version from the VERSION file + if (name.compareToIgnoreCase("version") == 0) { + unwrapFilename = false; + + // LSL6hires version is in a file with an empty extension + if (Common::File::exists(name + ".")) { + name += "."; } } - // Shivers is trying to store savegame descriptions and current spots in - // separate .SG files, which are hardcoded in the scripts. - // Essentially, there is a normal save file, created by the executable - // and an extra hardcoded save file, created by the game scripts, probably - // because they didn't want to modify the save/load code to add the extra - // information. - // Each slot in the book then has two strings, the save description and a - // description of the current spot that the player is at. Currently, the - // spot strings are always empty (probably related to the unimplemented - // kString subop 14, which gets called right before this call). - // For now, we don't allow the creation of these files, which means that - // all the spot descriptions next to each slot description will be empty - // (they are empty anyway). Until a viable solution is found to handle these - // extra files and until the spot description strings are initialized - // correctly, we resort to virtual files in order to make the load screen - // useable. Without this code it is unusable, as the extra information is - // always saved to 0.SG for some reason, but on restore the correct file is - // used. Perhaps the virtual ID is not taken into account when saving. - // - // Future TODO: maintain spot descriptions and show them too, ideally without - // having to return to this logic of extra hardcoded files. if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) { + // Shivers stores the name and score of save games in separate %d.SG + // files, which are used by the save/load screen if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { - // Game scripts are trying to create a file with the save - // description, stop them here + // Suppress creation of the SG file, since it is not necessary debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); return SIGNAL_REG; } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { // Create a virtual file containing the save game description // and slot number, as the game scripts expect. - int slotNumber; - sscanf(name.c_str(), "%d.SG", &slotNumber); + int saveNo; + sscanf(name.c_str(), "%d.SG", &saveNo); + saveNo += kSaveIdShift; - Common::Array<SavegameDesc> saves; - listSavegames(saves); - int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START); + SavegameDesc save; + fillSavegameDesc(g_sci->getSavegameName(saveNo), &save); - if (!s->_virtualIndexFile) { - // Make the virtual file buffer big enough to avoid having it grow dynamically. - // 50 bytes should be more than enough. - s->_virtualIndexFile = new VirtualIndexFile(50); + Common::String score; + if (!save.highScore) { + score = Common::String::format("%u", save.lowScore); + } else { + score = Common::String::format("%u%03u", save.highScore, save.lowScore); } - s->_virtualIndexFile->seek(0, SEEK_SET); - s->_virtualIndexFile->write(saves[savegameNr].name, strlen(saves[savegameNr].name)); - s->_virtualIndexFile->write("\0", 1); - s->_virtualIndexFile->write("\0", 1); // Spot description (empty) - s->_virtualIndexFile->seek(0, SEEK_SET); + const uint nameLength = strlen(save.name); + const uint size = nameLength + /* \r\n */ 2 + score.size(); + char *buffer = (char *)malloc(size); + memcpy(buffer, save.name, nameLength); + buffer[nameLength] = '\r'; + buffer[nameLength + 1] = '\n'; + memcpy(buffer + nameLength + 2, score.c_str(), score.size()); + + const uint handle = findFreeFileHandle(s); + + s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES); + s->_fileHandles[handle]._out = nullptr; + s->_fileHandles[handle]._name = ""; + + return make_reg(0, handle); + } + } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES && name.hasSuffix(".DTA")) { + // MGDX stores the name and avatar ID in separate %d.DTA files, which + // are used by the save/load screen + if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { + // Suppress creation of the DTA file, since it is not necessary + debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); + return SIGNAL_REG; + } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { + // Create a virtual file containing the save game description + // and slot number, as the game scripts expect. + int saveNo; + sscanf(name.c_str(), "%d.DTA", &saveNo); + saveNo += kSaveIdShift; + + SavegameDesc save; + fillSavegameDesc(g_sci->getSavegameName(saveNo), &save); + + const Common::String avatarId = Common::String::format("%02d", save.avatarId); + const uint nameLength = strlen(save.name); + const uint size = nameLength + /* \r\n */ 2 + avatarId.size() + 1; + char *buffer = (char *)malloc(size); + memcpy(buffer, save.name, nameLength); + buffer[nameLength] = '\r'; + buffer[nameLength + 1] = '\n'; + memcpy(buffer + nameLength + 2, avatarId.c_str(), avatarId.size() + 1); + + const uint handle = findFreeFileHandle(s); + + s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES); + s->_fileHandles[handle]._out = nullptr; + s->_fileHandles[handle]._name = ""; + + return make_reg(0, handle); + } + } else if (g_sci->getGameId() == GID_KQ7) { + // KQ7 creates a temp.tmp file to perform an atomic rewrite of the + // catalogue, but since we do not create catalogues for most SCI32 + // games, ignore the write + if (name == "temp.tmp") { + return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); + } + + // KQ7 tries to read out game information from catalogues directly + // instead of using the standard kSaveGetFiles function + if (name == "kq7cdsg.cat") { + if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { + // Suppress creation of the catalogue file, since it is not necessary + debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); + return SIGNAL_REG; + } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { + Common::Array<SavegameDesc> saves; + listSavegames(saves); + + const uint recordSize = sizeof(int16) + SCI_MAX_SAVENAME_LENGTH; + const uint numSaves = MIN<uint>(saves.size(), 10); + const uint size = numSaves * recordSize + /* terminator */ 2; + byte *const buffer = (byte *)malloc(size); + + byte *out = buffer; + for (uint i = 0; i < numSaves; ++i) { + WRITE_UINT16(out, saves[i].id - kSaveIdShift); + Common::strlcpy((char *)out + sizeof(int16), saves[i].name, SCI_MAX_SAVENAME_LENGTH); + out += recordSize; + } + WRITE_UINT16(out, 0xFFFF); + + const uint handle = findFreeFileHandle(s); + s->_fileHandles[handle]._in = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); + s->_fileHandles[handle]._out = nullptr; + s->_fileHandles[handle]._name = ""; + + return make_reg(0, handle); + } + } + } + + // See kMakeSaveCatName + if (name == "fake.cat") { + return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); + } + + if (isSaveCatalogue(name)) { + const bool exists = saveCatalogueExists(name); + if (exists) { + // Dummy handle is used to represent the catalogue and ignore any + // direct game script writes return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); + } else { + return SIGNAL_REG; } } #endif + debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); + + if (name.hasPrefix("sciAudio\\")) { + // fan-made sciAudio extension, don't create those files and instead return a virtual handle + return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO); + } + // QFG import rooms get a virtual filelisting instead of an actual one if (g_sci->inQfGImportRoom()) { // We need to find out what the user actually selected, "savedHeroes" is @@ -349,16 +473,9 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { uint16 handle = argv[0].toUint16(); -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) { - s->_virtualIndexFile->close(); - return SIGNAL_REG; - } -#endif - if (handle >= VIRTUALFILE_HANDLE_START) { // it's a virtual handle? ignore it - return SIGNAL_REG; + return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG; } FileHandle *f = getFileFromHandle(s, handle); @@ -366,7 +483,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { f->close(); if (getSciVersion() <= SCI_VERSION_0_LATE) return s->r_acc; // SCI0 semantics: no value returned - return SIGNAL_REG; + return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG; } if (getSciVersion() <= SCI_VERSION_0_LATE) @@ -381,17 +498,9 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) { char *buf = new char[size]; debugC(kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size); -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) { - bytesRead = s->_virtualIndexFile->read(buf, size); - } else { -#endif - FileHandle *f = getFileFromHandle(s, handle); - if (f) - bytesRead = f->_in->read(buf, size); -#ifdef ENABLE_SCI32 - } -#endif + FileHandle *f = getFileFromHandle(s, handle); + if (f) + bytesRead = f->_in->read(buf, size); // TODO: What happens if less bytes are read than what has // been requested? (i.e. if bytesRead is non-zero, but still @@ -406,27 +515,37 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { uint16 handle = argv[0].toUint16(); uint16 size = argv[2].toUint16(); + +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) { + return make_reg(0, size); + } +#endif + char *buf = new char[size]; + uint bytesWritten = 0; bool success = false; s->_segMan->memcpy((byte *)buf, argv[1], size); debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size); + FileHandle *f = getFileFromHandle(s, handle); + if (f) { + bytesWritten = f->_out->write(buf, size); + success = !f->_out->err(); + } + + delete[] buf; + #ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) { - s->_virtualIndexFile->write(buf, size); - success = true; - } else { -#endif - FileHandle *f = getFileFromHandle(s, handle); - if (f) { - f->_out->write(buf, size); - success = true; + if (getSciVersion() >= SCI_VERSION_2) { + if (!success) { + return SIGNAL_REG; } -#ifdef ENABLE_SCI32 + + return make_reg(0, bytesWritten); } #endif - delete[] buf; if (success) return NULL_REG; return make_reg(0, 6); // DOS - invalid handle @@ -456,19 +575,21 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { int savedir_nr = saves[slotNum].id; name = g_sci->getSavegameName(savedir_nr); result = saveFileMan->removeSavefile(name); +#ifdef ENABLE_SCI32 } else if (getSciVersion() >= SCI_VERSION_2) { + // Special case for KQ7, basically identical to the SQ4 case above, + // where the game hardcodes its save game names + if (name.hasPrefix("kq7cdsg.")) { + int saveNo = atoi(name.c_str() + name.size() - 3); + name = g_sci->getSavegameName(saveNo + kSaveIdShift); + } + // The file name may be already wrapped, so check both cases result = saveFileMan->removeSavefile(name); if (!result) { const Common::String wrappedName = g_sci->wrapFilename(name); result = saveFileMan->removeSavefile(wrappedName); } - -#ifdef ENABLE_SCI32 - if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { - delete s->_virtualIndexFile; - s->_virtualIndexFile = 0; - } #endif } else { const Common::String wrappedName = g_sci->wrapFilename(name); @@ -476,6 +597,13 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { } debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return make_reg(0, result); + } +#endif + if (result) return NULL_REG; return make_reg(0, 2); // DOS - file not found error code @@ -488,12 +616,7 @@ reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize); uint32 bytesRead; -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) - bytesRead = s->_virtualIndexFile->readLine(buf, maxsize); - else -#endif - bytesRead = fgets_wrapper(s, buf, maxsize, handle); + bytesRead = fgets_wrapper(s, buf, maxsize, handle); s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); delete[] buf; @@ -520,16 +643,9 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) { - s->_virtualIndexFile->write(str.c_str(), str.size()); - return NULL_REG; - } -#endif - FileHandle *f = getFileFromHandle(s, handle); - if (f) { + if (f && f->_out) { f->_out->write(str.c_str(), str.size()); if (getSciVersion() <= SCI_VERSION_0_LATE) return s->r_acc; // SCI0 semantics: no value returned @@ -543,27 +659,21 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) { uint16 handle = argv[0].toUint16(); - uint16 offset = ABS<int16>(argv[1].toSint16()); // can be negative + int16 offset = argv[1].toSint16(); uint16 whence = argv[2].toUint16(); debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) - return make_reg(0, s->_virtualIndexFile->seek(offset, whence)); -#endif - FileHandle *f = getFileFromHandle(s, handle); if (f && f->_in) { - // Backward seeking isn't supported in zip file streams, thus adapt the - // parameters accordingly if games ask for such a seek mode. A known - // case where this is requested is the save file manager in Phantasmagoria - if (whence == SEEK_END) { - whence = SEEK_SET; - offset = f->_in->size() - offset; + const bool success = f->_in->seek(offset, whence); + if (getSciVersion() >= SCI_VERSION_2) { + if (success) { + return make_reg(0, f->_in->pos()); + } + return SIGNAL_REG; } - - return make_reg(0, f->_in->seek(offset, whence)); + return make_reg(0, success); } else if (f && f->_out) { error("kFileIOSeek: Unsupported seek operation on a writeable stream (offset: %d, whence: %d)", offset, whence); } @@ -591,14 +701,6 @@ reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); -#ifdef ENABLE_SCI32 - // Cache the file existence result for the Phantasmagoria - // save index file, as the game scripts keep checking for - // its existence. - if (name == PHANTASMAGORIA_SAVEGAME_INDEX && s->_virtualIndexFile) - return TRUE_REG; -#endif - bool exists = false; if (g_sci->getGameId() == GID_PEPPER) { @@ -611,6 +713,15 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 + if (isSaveCatalogue(name)) { + return saveCatalogueExists(name) ? TRUE_REG : NULL_REG; + } +#endif + + // TODO: It may apparently be worth caching the existence of + // phantsg.dir, and possibly even keeping it open persistently + // Check for regular file exists = Common::File::exists(name); @@ -673,6 +784,15 @@ reg_t kFileIORename(EngineState *s, int argc, reg_t *argv) { Common::String oldName = s->_segMan->getString(argv[0]); Common::String newName = s->_segMan->getString(argv[1]); + // We don't fully implement all cases that could occur here, and + // assume the file to be renamed is a wrapped filename. + // Known usage: In Phant1 and KQ7 while deleting savegames. + // The scripts rewrite the dir file as a temporary file, and then + // rename it to the actual dir file. + + oldName = g_sci->wrapFilename(oldName); + newName = g_sci->wrapFilename(newName); + // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 // returns -1 on fail. We just return -1 for all versions. if (g_sci->getSaveFileManager()->renameSavefile(oldName, newName)) @@ -694,7 +814,7 @@ reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) { FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); if (f) f->_out->writeByte(argv[1].toUint16() & 0xff); - return s->r_acc; // FIXME: does this really not return anything? + return s->r_acc; } reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) { @@ -708,27 +828,13 @@ reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) { FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); if (f) f->_out->writeUint16LE(argv[1].toUint16()); - return s->r_acc; // FIXME: does this really not return anything? + return s->r_acc; } -reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) { - // Used in Shivers when the user enters his name on the guest book - // in the beginning to start the game. - - // Creates a new save slot, and returns if the operation was successful - - // Argument 0 denotes the save slot as a negative integer, 2 means "0" - // Argument 1 is a string, with the file name, obtained from kSave(5). - // The interpreter checks if it can be written to (by checking for free - // disk space and write permissions) - - // We don't really use or need any of this... - - uint16 saveSlot = argv[0].toUint16(); - char* fileName = s->_segMan->lookupString(argv[1])->getRawData(); - warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName); - - return TRUE_REG; // slot creation was successful +reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv) { + SciArray &fileName = *s->_segMan->lookupArray(argv[0]); + fileName.fromString("C:\\SIERRA\\"); + return argv[0]; } reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv) { @@ -753,7 +859,14 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) { #endif reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { - Common::String game_id; + // slot 0 is the ScummVM auto-save slot, which is not used by us, but is + // still reserved + enum { + SAVEGAMESLOT_FIRST = 1, + SAVEGAMESLOT_LAST = 99 + }; + + Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : ""; int16 virtualId = argv[1].toSint16(); int16 savegameId = -1; Common::String game_description; @@ -786,10 +899,8 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { g_sci->_soundCmd->pauseAll(false); // unpause music (we can't have it paused during save) if (savegameId < 0) return NULL_REG; - } else { // Real call from script - game_id = s->_segMan->getString(argv[0]); if (argv[2].isNull()) error("kSaveGame: called with description being NULL"); game_description = s->_segMan->getString(argv[2]); @@ -806,25 +917,47 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) { // virtualId is low, we assume that scripts expect us to create new slot - if (g_sci->getGameId() == GID_JONES) { + switch (g_sci->getGameId()) { + case GID_JONES: // Jones has one save slot only savegameId = 0; - } else if (virtualId == s->_lastSaveVirtualId) { - // if last virtual id is the same as this one, we assume that caller wants to overwrite last save - savegameId = s->_lastSaveNewId; - } else { - uint savegameNr; - // savegameId is in lower range, scripts expect us to create a new slot - for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) { - for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) { - if (savegameId == saves[savegameNr].id) + break; + case GID_FANMADE: { + // Fanmade game, try to identify the game + const char *gameName = g_sci->getGameObjectName(); + + if (strcmp(gameName, "CascadeQuest") == 0) { + // Cascade Quest calls us directly to auto-save and uses slot 99, + // put that save into slot 0 (ScummVM auto-save slot) see bug #7007 + if (virtualId == (SAVEGAMEID_OFFICIALRANGE_START - 1)) { + savegameId = 0; + } + } + break; + } + default: + break; + } + + if (savegameId < 0) { + // savegameId not set yet + if (virtualId == s->_lastSaveVirtualId) { + // if last virtual id is the same as this one, we assume that caller wants to overwrite last save + savegameId = s->_lastSaveNewId; + } else { + uint savegameNr; + // savegameId is in lower range, scripts expect us to create a new slot + for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) { + for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) { + if (savegameId == saves[savegameNr].id) + break; + } + if (savegameNr == saves.size()) // Slot not found, seems to be good to go break; } - if (savegameNr == saves.size()) // Slot not found, seems to be good to go - break; + if (savegameId > SAVEGAMESLOT_LAST) + error("kSavegame: no more savegame slots available"); } - if (savegameId > SAVEGAMESLOT_LAST) - error("kSavegame: no more savegame slots available"); } } else { error("kSaveGame: invalid savegameId used"); @@ -982,10 +1115,6 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { } reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { - Common::String game_id = s->_segMan->getString(argv[0]); - - debug(3, "kGetSaveFiles(%s)", game_id.c_str()); - // Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really // mean new slot instead of overwriting the old one s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START; @@ -994,6 +1123,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { listSavegames(saves); uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR); + Common::String game_id = s->_segMan->getString(argv[0]); + + debug(3, "kGetSaveFiles(%s)", game_id.c_str()); + reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves); if (!slot) { @@ -1021,46 +1154,205 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 -reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) { - // Normally, this creates the name of the save catalogue/directory to save into. - // First parameter is the string to save the result into. Second is a string - // with game parameters. We don't have a use for this at all, as we have our own - // savegame directory management, thus we always return an empty string. - return argv[0]; +reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) { + const bool isScummVMSave = argv[0].isNull(); + Common::String gameName = ""; + int16 saveNo; + Common::String saveDescription; + Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]); + + if (isScummVMSave) { + // ScummVM call, from a patched Game::save + g_sci->_soundCmd->pauseAll(true); + GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true); + saveNo = dialog.runModalWithCurrentTarget(); + g_sci->_soundCmd->pauseAll(false); + + if (saveNo < 0) { + // User cancelled save + return NULL_REG; + } + + saveDescription = dialog.getResultString(); + if (saveDescription.empty()) { + saveDescription = dialog.createDefaultSaveDescription(saveNo); + } + } else { + // Native script call + gameName = s->_segMan->getString(argv[0]); + saveNo = argv[1].toSint16(); + saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); + } + + debugC(kDebugLevelFile, "Game name %s save %d desc %s ver %s", gameName.c_str(), saveNo, saveDescription.c_str(), gameVersion.c_str()); + + // Auto-save system used by Torin and LSL7 + if (gameName == "Autosave" || gameName == "Autosv") { + if (saveNo == 0) { + // Autosave slot 0 is the autosave + } else { + // Autosave slot 1 is a "new game" save + saveNo = kNewGameId; + } + } else if (!isScummVMSave) { + // ScummVM save screen will give a pre-corrected save number, but native + // save-load will not + saveNo += kSaveIdShift; + } + + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + const Common::String filename = g_sci->getSavegameName(saveNo); + Common::OutSaveFile *saveStream = saveFileMan->openForSaving(filename); + + if (saveStream == nullptr) { + warning("Error opening savegame \"%s\" for writing", filename.c_str()); + return NULL_REG; + } + + if (!gamestate_save(s, saveStream, saveDescription, gameVersion)) { + warning("Saving the game failed"); + saveStream->finalize(); + delete saveStream; + return NULL_REG; + } + + saveStream->finalize(); + if (saveStream->err()) { + warning("Writing the savegame failed"); + delete saveStream; + return NULL_REG; + } + + delete saveStream; + return TRUE_REG; } -reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) { - // Creates a savegame name from a slot number. Used when deleting saved games. - // Param 0: the output buffer (same as in kMakeSaveCatName) - // Param 1: a string with game parameters, ignored - // Param 2: the selected slot +reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) { + const bool isScummVMRestore = argv[0].isNull(); + Common::String gameName = ""; + int16 saveNo = argv[1].toSint16(); + const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); + + if (isScummVMRestore && saveNo == -1) { + // ScummVM call, either from lancher or a patched Game::restore + g_sci->_soundCmd->pauseAll(true); + GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false); + saveNo = dialog.runModalWithCurrentTarget(); + g_sci->_soundCmd->pauseAll(false); + + if (saveNo < 0) { + // User cancelled restore + return s->r_acc; + } + } else { + gameName = s->_segMan->getString(argv[0]); + } + + if (gameName == "Autosave" || gameName == "Autosv") { + if (saveNo == 0) { + // Autosave slot 0 is the autosave + } else { + // Autosave slot 1 is a "new game" save + saveNo = kNewGameId; + } + } else if (!isScummVMRestore) { + // ScummVM save screen will give a pre-corrected save number, but native + // save-load will not + saveNo += kSaveIdShift; + } + + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + const Common::String filename = g_sci->getSavegameName(saveNo); + Common::SeekableReadStream *saveStream = saveFileMan->openForLoading(filename); + + if (saveStream == nullptr) { + warning("Savegame #%d not found", saveNo); + return NULL_REG; + } + + gamestate_restore(s, saveStream); + delete saveStream; - SciString *resultString = s->_segMan->lookupString(argv[0]); - uint16 virtualId = argv[2].toUint16(); - if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) - error("kMakeSaveFileName: invalid savegame ID specified"); - uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START; + gamestate_afterRestoreFixUp(s, saveNo); + return TRUE_REG; +} + +reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) { + const Common::String gameName = s->_segMan->getString(argv[0]); + int16 saveNo = argv[1].toSint16(); + const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); Common::Array<SavegameDesc> saves; listSavegames(saves); - Common::String filename = g_sci->getSavegameName(saveSlot); - resultString->fromString(filename); + if (gameName == "Autosave" || gameName == "Autosv") { + if (saveNo == 1) { + saveNo = kNewGameId; + } + } else { + saveNo += kSaveIdShift; + } - return argv[0]; -} + SavegameDesc save; + if (!fillSavegameDesc(g_sci->getSavegameName(saveNo), &save)) { + return NULL_REG; + } -reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) { - // TODO - // This is a timer callback, with 1 parameter: the timer object - // (e.g. "timers"). - // It's used for auto-saving (i.e. save every X minutes, by checking - // the elapsed time from the timer object) + if (save.version < MINIMUM_SAVEGAME_VERSION || + save.version > CURRENT_SAVEGAME_VERSION || + save.gameVersion != gameVersion) { + + return NULL_REG; + } - // This function has to return something other than 0 to proceed return TRUE_REG; } +reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) { + // argv[0] is gameName, used in SSCI as the name of the save game catalogue + // but unused here since ScummVM does not support multiple catalogues + SciArray &descriptions = *s->_segMan->lookupArray(argv[1]); + SciArray &saveIds = *s->_segMan->lookupArray(argv[2]); + + Common::Array<SavegameDesc> saves; + listSavegames(saves); + + // Normally SSCI limits to 20 games per directory, but ScummVM allows more + // than that with games that use the standard save-load dialogue + descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true); + saveIds.resize(saves.size() + 1, true); + + for (uint i = 0; i < saves.size(); ++i) { + const SavegameDesc &save = saves[i]; + char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i); + Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH); + saveIds.setFromInt16(i, save.id - kSaveIdShift); + } + + descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0'; + saveIds.setFromInt16(saves.size(), 0); + + return make_reg(0, saves.size()); +} + +reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) { + // ScummVM does not use SCI catalogues for save games, but game scripts try + // to write out catalogues manually after a save game is deleted, so we need + // to be able to identify and ignore these IO operations by always giving + // back a fixed catalogue name and then ignoring it in kFileIO + SciArray &outCatName = *s->_segMan->lookupArray(argv[0]); + outCatName.fromString("fake.cat"); + return argv[0]; +} + +reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) { + SciArray &outFileName = *s->_segMan->lookupArray(argv[0]); + // argv[1] is the game name, which is not used by ScummVM + const int16 saveNo = argv[2].toSint16(); + outFileName.fromString(g_sci->getSavegameName(saveNo + kSaveIdShift)); + return argv[0]; +} + #endif } // End of namespace Sci diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 91d241fb79..c605ba29e1 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -45,6 +45,7 @@ #include "sci/graphics/paint16.h" #include "sci/graphics/picture.h" #include "sci/graphics/ports.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/view.h" @@ -355,16 +356,11 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { } textWidth = dest[3].toUint16(); textHeight = dest[2].toUint16(); - + uint16 languageSplitter = 0; Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, sep); -#ifdef ENABLE_SCI32 - if (g_sci->_gfxText32) - g_sci->_gfxText32->kernelTextSize(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight); - else -#endif - g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); // One of the game texts in LB2 German contains loads of spaces in // its end. We trim the text here, otherwise the graphics code will @@ -445,8 +441,15 @@ reg_t kCantBeHere(EngineState *s, int argc, reg_t *argv) { reg_t curObject = argv[0]; reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; - reg_t canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); - return canBeHere; +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return g_sci->_gfxCompare->kernelCantBeHere32(curObject, listReference); + } else { +#endif + return g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); +#ifdef ENABLE_SCI32 + } +#endif } reg_t kIsItSkip(EngineState *s, int argc, reg_t *argv) { @@ -577,7 +580,6 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) { reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); - return s->r_acc; } @@ -830,7 +832,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { uint16 languageSplitter = 0; Common::String splitText; - + switch (type) { case SCI_CONTROLS_TYPE_BUTTON: case SCI_CONTROLS_TYPE_TEXTEDIT: @@ -1189,7 +1191,7 @@ reg_t kDisplay(EngineState *s, int argc, reg_t *argv) { argc--; argc--; argv++; argv++; text = g_sci->getKernel()->lookupText(textp, index); } - + uint16 languageSplitter = 0; Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter); @@ -1253,16 +1255,16 @@ reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { switch (operation) { case 0: { // remap by percent uint16 percent = argv[1].toUint16(); - g_sci->_gfxPalette16->resetRemapping(); - g_sci->_gfxPalette16->setRemappingPercent(254, percent); + g_sci->_gfxRemap16->resetRemapping(); + g_sci->_gfxRemap16->setRemappingPercent(254, percent); } break; case 1: { // remap by range uint16 from = argv[1].toUint16(); uint16 to = argv[2].toUint16(); uint16 base = argv[3].toUint16(); - g_sci->_gfxPalette16->resetRemapping(); - g_sci->_gfxPalette16->setRemappingRange(254, from, to, base); + g_sci->_gfxRemap16->resetRemapping(); + g_sci->_gfxRemap16->setRemappingRange(254, from, to, base); } break; case 2: // turn remapping off (unused) diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 4d48ae4e99..880ea0f559 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -26,8 +26,6 @@ #include "graphics/cursorman.h" #include "graphics/surface.h" -#include "gui/message.h" - #include "sci/sci.h" #include "sci/event.h" #include "sci/resource.h" @@ -39,21 +37,25 @@ #include "sci/graphics/cache.h" #include "sci/graphics/compare.h" #include "sci/graphics/controls16.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/cursor.h" #include "sci/graphics/palette.h" #include "sci/graphics/paint16.h" #include "sci/graphics/picture.h" #include "sci/graphics/ports.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/view.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/palette32.h" +#include "sci/graphics/cursor32.h" +#include "sci/graphics/celobj32.h" #include "sci/graphics/controls32.h" #include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class -#include "sci/graphics/text32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/paint32.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" +#include "sci/graphics/text32.h" +#include "sci/graphics/transitions32.h" #endif namespace Sci { @@ -61,69 +63,168 @@ namespace Sci { extern void showScummVMDialog(const Common::String &message); -reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { - // Returns 0 if the screen width or height is less than 640 or 400, - // respectively. - if (g_system->getWidth() < 640 || g_system->getHeight() < 400) - return make_reg(0, 0); +reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv) { + reg_t object = argv[0]; - return make_reg(0, 1); + const GuiResourceId viewId = readSelectorValue(s->_segMan, object, SELECTOR(view)); + const int16 loopNo = readSelectorValue(s->_segMan, object, SELECTOR(loop)); + const int16 celNo = readSelectorValue(s->_segMan, object, SELECTOR(cel)); + const int16 x = readSelectorValue(s->_segMan, object, SELECTOR(x)); + const int16 y = readSelectorValue(s->_segMan, object, SELECTOR(y)); + + CelObjView celObj(viewId, loopNo, celNo); + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const Ratio scaleX(scriptWidth, celObj._xResolution); + + int16 brLeft; + + if (celObj._mirrorX) { + brLeft = x - ((celObj._width - celObj._origin.x) * scaleX).toInt(); + } else { + brLeft = x - (celObj._origin.x * scaleX).toInt(); + } + + const int16 brRight = brLeft + (celObj._width * scaleX).toInt() - 1; + + writeSelectorValue(s->_segMan, object, SELECTOR(brLeft), brLeft); + writeSelectorValue(s->_segMan, object, SELECTOR(brRight), brRight); + writeSelectorValue(s->_segMan, object, SELECTOR(brBottom), y + 1); + writeSelectorValue(s->_segMan, object, SELECTOR(brTop), y + 1 - readSelectorValue(s->_segMan, object, SELECTOR(yStep))); + + return s->r_acc; } -// SCI32 variant, can't work like sci16 variants -reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) { - // TODO -// reg_t curObject = argv[0]; -// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; +reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv) { + const bool found = g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); + + // NOTE: MGDX is assumed to use the older kSetNowSeen since it was + // released before SQ6, but this has not been verified since it cannot be + // disassembled at the moment (Phar Lap Windows-only release) + if (getSciVersion() <= SCI_VERSION_2_1_EARLY || + g_sci->getGameId() == GID_SQ6 || + g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + + if (!found) { + error("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0])); + } + return s->r_acc; + } + + if (!found) { + warning("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0])); + } - return NULL_REG; + return make_reg(0, found); +} + +reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv) { + switch (argc) { + case 1: { + if (argv[0].toSint16() == -2) { + g_sci->_gfxCursor32->clearRestrictedArea(); + } else { + if (argv[0].isNull()) { + g_sci->_gfxCursor32->hide(); + } else { + g_sci->_gfxCursor32->show(); + } + } + break; + } + case 2: { + const Common::Point position(argv[0].toSint16(), argv[1].toSint16()); + g_sci->_gfxCursor32->setPosition(position); + break; + } + case 3: { + g_sci->_gfxCursor32->setView(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16()); + break; + } + case 4: { + const Common::Rect restrictRect(argv[0].toSint16(), + argv[1].toSint16(), + argv[2].toSint16() + 1, + argv[3].toSint16() + 1); + g_sci->_gfxCursor32->setRestrictedArea(restrictRect); + break; + } + default: + error("kSetCursor: Invalid number of arguments (%d)", argc); + } + + return s->r_acc; +} + +reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->shakeScreen(argv[0].toSint16(), (ShakeDirection)argv[1].toSint16()); + return s->r_acc; +} + +reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { + const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer(); + if (buffer.screenWidth < 640 || buffer.screenHeight < 400) + return make_reg(0, 0); + + return make_reg(0, 1); } reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { - debugC(6, kDebugLevelGraphics, "kAddScreenItem %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0])); + debugC(6, kDebugLevelGraphics, "kAddScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); - return NULL_REG; + return s->r_acc; } reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { - debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0])); + debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); - return NULL_REG; + return s->r_acc; } reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { - debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0])); + debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]); - return NULL_REG; + return s->r_acc; } reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { - debugC(6, kDebugLevelGraphics, "kAddPlane %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0])); + debugC(6, kDebugLevelGraphics, "kAddPlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelAddPlane(argv[0]); return s->r_acc; } reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { - debugC(7, kDebugLevelGraphics, "kUpdatePlane %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0])); + debugC(7, kDebugLevelGraphics, "kUpdatePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]); return s->r_acc; } reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) { - debugC(6, kDebugLevelGraphics, "kDeletePlane %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0])); + debugC(6, kDebugLevelGraphics, "kDeletePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelDeletePlane(argv[0]); return s->r_acc; } +reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv) { + const reg_t plane = argv[0]; + const int16 deltaX = argv[1].toSint16(); + const int16 deltaY = argv[2].toSint16(); + const bool scrollPics = argc > 3 ? argv[3].toUint16() : false; + + g_sci->_gfxFrameout->kernelMovePlaneItems(plane, deltaX, deltaY, scrollPics); + return s->r_acc; +} + reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) { reg_t planeObj = argv[0]; GuiResourceId pictureId = argv[1].toUint16(); int16 x = argv[2].toSint16(); int16 y = argv[3].toSint16(); bool mirrorX = argc > 4 ? argv[4].toSint16() : false; + bool deleteDuplicate = argc > 5 ? argv[5].toSint16() : true; - g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX); - return NULL_REG; + g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX, deleteDuplicate); + return s->r_acc; } reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { @@ -132,15 +233,13 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { bool showBits = argc > 0 ? argv[0].toUint16() : true; - g_sci->_gfxFrameout->kernelFrameout(showBits); - s->speedThrottler(16); - s->_throttleTrigger = true; - return NULL_REG; + g_sci->_gfxFrameout->kernelFrameOut(showBits); + return s->r_acc; } reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16()); - return NULL_REG; + g_sci->_gfxTransitions32->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16()); + return s->r_acc; } reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { @@ -149,14 +248,13 @@ reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { return make_reg(0, objRect1.intersects(objRect2)); } -// Tests if the coordinate is on the passed object reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { - uint16 x = argv[0].toUint16(); - uint16 y = argv[1].toUint16(); - reg_t targetObject = argv[2]; - uint16 checkPixels = argv[3].getOffset(); + int16 x = argv[0].toSint16(); + int16 y = argv[1].toSint16(); + reg_t object = argv[2]; + bool checkPixel = argv[3].toSint16(); - return make_reg(0, g_sci->_gfxFrameout->kernelIsOnMe(x, y, checkPixels, targetObject)); + return g_sci->_gfxFrameout->kernelIsOnMe(object, Common::Point(x, y), checkPixel); } reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { @@ -196,24 +294,53 @@ reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { if (subop == 0) { TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode)); - reg_t out; - return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, 1, &out); + return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, true, true); } else { CelInfo32 celInfo; celInfo.type = kCelTypeView; celInfo.resourceId = readSelectorValue(segMan, object, SELECTOR(view)); celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop)); celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel)); - reg_t out; - return g_sci->_gfxText32->createTitledFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed, &out); + return g_sci->_gfxText32->createFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed, true); } } -reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxText32->disposeTextBitmap(argv[0]); +reg_t kText(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->setFont(argv[2].toUint16()); + + SciArray *rect = s->_segMan->lookupArray(argv[0]); + if (rect == nullptr) { + error("kTextSize: %04x:%04x cannot be dereferenced", PRINT_REG(argv[0])); + } + + Common::String text = s->_segMan->getString(argv[1]); + int16 maxWidth = argc > 3 ? argv[3].toSint16() : 0; + bool doScaling = argc > 4 ? argv[4].toSint16() : true; + + Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling); + + reg_t value[4] = { + make_reg(0, textRect.left), + make_reg(0, textRect.top), + make_reg(0, textRect.right - 1), + make_reg(0, textRect.bottom - 1) }; + + rect->setElements(0, 4, value); return s->r_acc; } +reg_t kTextWidth(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->setFont(argv[1].toUint16()); + Common::String text = s->_segMan->getString(argv[0]); + return make_reg(0, g_sci->_gfxText32->getStringWidth(text)); +} + reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 1: @@ -231,12 +358,16 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) { + return g_sci->_gfxControls32->kernelMessageBox(s->_segMan->getString(argv[0]), s->_segMan->getString(argv[1]), argv[2].toUint16()); +} + /** * Causes an immediate plane transition with an optional transition * effect */ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - ShowStyleType type = (ShowStyleType)argv[0].toUint16(); + const uint16 type = argv[0].toUint16(); reg_t planeObj = argv[1]; int16 seconds = argv[2].toSint16(); // NOTE: This value seems to indicate whether the transition is an @@ -271,6 +402,10 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { divisions = argc > 9 ? argv[9].toSint16() : -1; } + if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) { + error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); + } + // TODO: Reuse later for SCI2 and SCI3 implementation and then discard // warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, " // "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, " @@ -282,33 +417,52 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { // NOTE: The order of planeObj and showStyle are reversed // because this is how SCI3 called the corresponding method // on the KernelMgr - g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); + g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, (ShowStyleType)type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); + + return s->r_acc; +} + +reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv) { + GuiResourceId resourceId = argv[0].toUint16(); + int16 loopNo = argv[1].toSint16(); + int16 celNo = argv[2].toSint16(); + CelObjView celObj(resourceId, loopNo, celNo); + return make_reg(0, mulru(celObj._height, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, celObj._yResolution))); +} - return NULL_REG; +reg_t kCelWide32(EngineState *s, int argc, reg_t *argv) { + GuiResourceId resourceId = argv[0].toUint16(); + int16 loopNo = argv[1].toSint16(); + int16 celNo = argv[2].toSint16(); + CelObjView celObj(resourceId, loopNo, celNo); + return make_reg(0, mulru(celObj._width, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, celObj._xResolution))); } reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board // are occupied by pieces already - switch (argv[0].toUint16()) { // subops 0 - 4 - // 0 - return the view - // 1 - return the loop - // 2, 3 - nop - case 4: { - GuiResourceId viewId = argv[1].toSint16(); - int16 loopNo = argv[2].toSint16(); - int16 celNo = argv[3].toSint16(); - int16 x = argv[4].toUint16(); - int16 y = argv[5].toUint16(); - byte color = g_sci->_gfxCache->kernelViewGetColorAtCoordinate(viewId, loopNo, celNo, x, y); - return make_reg(0, color); - } - default: { - kStub(s, argc, argv); - return s->r_acc; - } + CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16()); + + int16 result = 0; + + switch (argv[0].toUint16()) { + case 0: + result = view._origin.x; + break; + case 1: + result = view._origin.y; + break; + case 2: + case 3: + // null operation + break; + case 4: + result = view.readPixel(argv[4].toSint16(), argv[5].toSint16(), view._mirrorX); + break; } + + return make_reg(0, result); } reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { @@ -318,439 +472,515 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { } reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) { - debug("kScrollWindowCreate"); - kStub(s, argc, argv); - return argv[0]; + const reg_t object = argv[0]; + const uint16 maxNumEntries = argv[1].toUint16(); + + SegManager *segMan = s->_segMan; + const int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor)); + const TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode)); + const GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font)); + const int16 backColor = readSelectorValue(segMan, object, SELECTOR(back)); + const int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore)); + const reg_t plane = readSelector(segMan, object, SELECTOR(plane)); + + Common::Rect rect; + + if (g_sci->_features->usesAlternateSelectors()) { + rect.left = readSelectorValue(segMan, object, SELECTOR(left)); + rect.top = readSelectorValue(segMan, object, SELECTOR(top)); + rect.right = readSelectorValue(segMan, object, SELECTOR(right)) + 1; + rect.bottom = readSelectorValue(segMan, object, SELECTOR(bottom)) + 1; + } else { + rect.left = readSelectorValue(segMan, object, SELECTOR(nsLeft)); + rect.top = readSelectorValue(segMan, object, SELECTOR(nsTop)); + rect.right = readSelectorValue(segMan, object, SELECTOR(nsRight)) + 1; + rect.bottom = readSelectorValue(segMan, object, SELECTOR(nsBottom)) + 1; + } + const Common::Point position(rect.left, rect.top); + + return g_sci->_gfxControls32->makeScrollWindow(rect, position, plane, foreColor, backColor, fontId, alignment, borderColor, maxNumEntries); } reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) { - debug("kScrollWindowAdd"); - return kStubNull(s, argc, argv); + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + const Common::String text = s->_segMan->getString(argv[1]); + const GuiResourceId fontId = argv[2].toSint16(); + const int16 color = argv[3].toSint16(); + const TextAlign alignment = (TextAlign)argv[4].toSint16(); + const bool scrollTo = argc > 5 ? (bool)argv[5].toUint16() : true; + + return scrollWindow->add(text, fontId, color, alignment, scrollTo); } reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) { - debug("kScrollWindowWhere"); - return kStubNull(s, argc, argv); + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + const uint16 where = (argv[1].toUint16() * scrollWindow->where()).toInt(); + + return make_reg(0, where); +} + +reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + const Ratio scrollTop(argv[1].toSint16(), argv[2].toSint16()); + scrollWindow->go(scrollTop); + + return s->r_acc; +} + +reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + const reg_t entryId = argv[1]; + const Common::String newText = s->_segMan->getString(argv[2]); + const GuiResourceId fontId = argv[3].toSint16(); + const int16 color = argv[4].toSint16(); + const TextAlign alignment = (TextAlign)argv[5].toSint16(); + const bool scrollTo = argc > 6 ? (bool)argv[6].toUint16() : true; + + return scrollWindow->modify(entryId, newText, fontId, color, alignment, scrollTo); +} + +reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->hide(); + + return s->r_acc; } reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) { - debug("kScrollWindowShow"); - return kStubNull(s, argc, argv); + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->show(); + + return s->r_acc; } -reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) { - debug("kScrollWindowDestroy"); - return kStubNull(s, argc, argv); +reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->pageUp(); + + return s->r_acc; } -#if 0 -reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { - // Used by SQ6 and LSL6 hires for the text area in the bottom of the - // screen. The relevant scripts also exist in Phantasmagoria 1, but they're - // unused. This is always called by scripts 64906 (ScrollerWindow) and - // 64907 (ScrollableWindow). - - reg_t kWindow = argv[1]; - uint16 op = argv[0].toUint16(); - switch (op) { - case 0: // Init - // TODO: Init reads the nsLeft, nsTop, nsRight, nsBottom, - // borderColor, fore, back, mode, font, plane selectors - // from the window in argv[1]. - g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems - g_sci->_gfxFrameout->clearScrollTexts(); - return argv[1]; // kWindow - case 1: // Show message, called by ScrollableWindow::addString - case 14: // Modify message, called by ScrollableWindow::modifyString - // TODO: The parameters in Modify are shifted by one: the first - // argument is the handle of the text to modify. The others - // are as Add. - { - Common::String text = s->_segMan->getString(argv[2]); - uint16 x = 0; - uint16 y = 0; - // TODO: argv[3] is font - // TODO: argv[4] is color - // TODO: argv[5] is alignment (0 = left, 1 = center, 2 = right) - // font,color,alignment may also be -1. (Maybe same as previous?) - // TODO: argv[6] is an optional bool, defaulting to true if not present. - // If true, the old contents are scrolled out of view. - // TODO: Return a handle of the inserted text. (Used for modify/insert) - // This handle looks like it should also be usable by kString. - g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14)); - } - break; - case 2: // Clear, called by ScrollableWindow::erase - g_sci->_gfxFrameout->clearScrollTexts(); - break; - case 3: // Page up, called by ScrollableWindow::scrollTo - // TODO - kStub(s, argc, argv); - break; - case 4: // Page down, called by ScrollableWindow::scrollTo - // TODO - kStub(s, argc, argv); - break; - case 5: // Up arrow, called by ScrollableWindow::scrollTo - g_sci->_gfxFrameout->prevScrollText(); - break; - case 6: // Down arrow, called by ScrollableWindow::scrollTo - g_sci->_gfxFrameout->nextScrollText(); - break; - case 7: // Home, called by ScrollableWindow::scrollTo - g_sci->_gfxFrameout->firstScrollText(); - break; - case 8: // End, called by ScrollableWindow::scrollTo - g_sci->_gfxFrameout->lastScrollText(); - break; - case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize - // TODO: This reads the nsLeft, nsTop, nsRight, nsBottom - // selectors from the SCI object passed in argv[2]. - kStub(s, argc, argv); - break; - case 10: // Where, called by ScrollableWindow::where - // TODO: - // Gives the current relative scroll location as a fraction - // with argv[2] as the denominator. (Return value is the numerator.) - // Silenced the warnings because of the high amount of console spam - //kStub(s, argc, argv); - break; - case 11: // Go, called by ScrollableWindow::scrollTo - // TODO: - // Two arguments provide a fraction: argv[2] is num., argv[3] is denom. - // Scrolls to the relative location given by the fraction. - kStub(s, argc, argv); - break; - case 12: // Insert, called by ScrollableWindow::insertString - // 5 extra parameters here: - // handle of insert location (new string takes that position). - // text, font, color, alignment - // TODO - kStub(s, argc, argv); - break; - // case 13 (Delete) is handled below - // case 14 (Modify) is handled above - case 15: // Hide, called by ScrollableWindow::hide - g_sci->_gfxFrameout->toggleScrollText(false); - break; - case 16: // Show, called by ScrollableWindow::show - g_sci->_gfxFrameout->toggleScrollText(true); - break; - case 17: // Destroy, called by ScrollableWindow::dispose - g_sci->_gfxFrameout->clearScrollTexts(); - break; - case 13: // Delete, unused - case 18: // Text, unused - case 19: // Reconstruct, unused - error("kScrollWindow: Unused subop %d invoked", op); - break; - default: - error("kScrollWindow: unknown subop %d", op); - break; - } +reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->pageDown(); + + return s->r_acc; +} + +reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->upArrow(); + + return s->r_acc; +} + +reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->downArrow(); + + return s->r_acc; +} + +reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->home(); + + return s->r_acc; +} + +reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->end(); + + return s->r_acc; +} + +reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxControls32->destroyScrollWindow(argv[0]); return s->r_acc; } -#endif reg_t kFont(EngineState *s, int argc, reg_t *argv) { - // TODO: Handle font settings for SCI2.1 + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - switch (argv[0].toUint16()) { - case 1: - g_sci->_gfxText32->_scaledWidth = argv[1].toUint16(); - g_sci->_gfxText32->_scaledHeight = argv[2].toUint16(); - return NULL_REG; - default: - error("kFont: unknown subop %d", argv[0].toUint16()); +reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv) { + // TODO: Setting font may have just been for side effect + // of setting the fontHeight on the font manager, in + // which case we could just get the font directly ourselves. + g_sci->_gfxText32->setFont(argv[0].toUint16()); + g_sci->_gfxText32->_yResolution = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_yResolution - 1) / g_sci->_gfxText32->_yResolution; + return make_reg(0, g_sci->_gfxText32->_yResolution); +} + +reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->_xResolution = argv[0].toUint16(); + g_sci->_gfxText32->_yResolution = argv[1].toUint16(); + return s->r_acc; +} + +reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) { + int16 width = argv[0].toSint16(); + int16 height = argv[1].toSint16(); + int16 skipColor = argv[2].toSint16(); + int16 backColor = argv[3].toSint16(); + int16 xResolution = argc > 4 ? argv[4].toSint16() : g_sci->_gfxText32->_xResolution; + int16 yResolution = argc > 5 ? argv[5].toSint16() : g_sci->_gfxText32->_yResolution; + bool useRemap = argc > 6 ? argv[6].toSint16() : false; + + reg_t bitmapId; + SciBitmap &bitmap = *s->_segMan->allocateBitmap(&bitmapId, width, height, skipColor, 0, 0, xResolution, yResolution, 0, useRemap, true); + memset(bitmap.getPixels(), backColor, width * height); + return bitmapId; +} + +reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) { + const reg_t &addr = argv[0]; + const SegmentObj *const segment = s->_segMan->getSegmentObj(addr.getSegment()); + + if (segment != nullptr && + segment->getType() == SEG_TYPE_BITMAP && + segment->isValidOffset(addr.getOffset())) { + + s->_segMan->freeBitmap(addr); } return s->r_acc; } -reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxText32->_scaledWidth = argv[0].toUint16(); - g_sci->_gfxText32->_scaledHeight = argv[1].toUint16(); - return NULL_REG; +reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv) { + // bitmapMemId, (x1, y1, x2, y2) OR (x2, y2, x1, y1), line color, unknown int, unknown int + return kStubNull(s, argc + 1, argv - 1); } -// TODO: Eventually, all of the kBitmap operations should be put -// in a separate class +reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) { + SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); + CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16()); -// NOTE: This size is correct only for SCI2.1mid; the size for -// SCI2/2.1early is 36 -#define BITMAP_HEADER_SIZE 46 + const int16 x = argc > 4 ? argv[4].toSint16() : 0; + const int16 y = argc > 5 ? argv[5].toSint16() : 0; + const int16 alignX = argc > 7 ? argv[7].toSint16() : -1; + const int16 alignY = argc > 8 ? argv[8].toSint16() : -1; -reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { - // Used for bitmap operations in SCI2.1 and SCI3. - // This is the SCI2.1 version, the functionality seems to have changed in SCI3. + Common::Point position( + x == -1 ? bitmap.getOrigin().x : x, + y == -1 ? bitmap.getOrigin().y : y + ); - switch (argv[0].toUint16()) { - case 0: // init bitmap surface - { - // 6 params, called e.g. from TextView::init() in Torin's Passage, - // script 64890 and TransView::init() in script 64884 - uint16 width = argv[1].toUint16(); - uint16 height = argv[2].toUint16(); - //uint16 skip = argv[3].toUint16(); - uint16 back = argv[4].toUint16(); // usually equals skip - //uint16 width2 = (argc >= 6) ? argv[5].toUint16() : 0; - //uint16 height2 = (argc >= 7) ? argv[6].toUint16() : 0; - //uint16 transparentFlag = (argc >= 8) ? argv[7].toUint16() : 0; - - // TODO: skip, width2, height2, transparentFlag - // (used for transparent bitmaps) - int entrySize = width * height + BITMAP_HEADER_SIZE; - reg_t memoryId = s->_segMan->allocateHunkEntry("Bitmap()", entrySize); - byte *memoryPtr = s->_segMan->getHunkPointer(memoryId); - memset(memoryPtr, 0, BITMAP_HEADER_SIZE); // zero out the bitmap header - memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height); - // Save totalWidth, totalHeight - // TODO: Save the whole bitmap header, like SSCI does - WRITE_SCI11ENDIAN_UINT16(memoryPtr, width); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 2, height); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 4, 0); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 6, 0); - memoryPtr[8] = 0; - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 10, 0); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 20, BITMAP_HEADER_SIZE); - WRITE_SCI11ENDIAN_UINT32(memoryPtr + 28, 46); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 36, width); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 38, width); - return memoryId; - } - break; - case 1: // dispose text bitmap surface - return kDisposeTextBitmap(s, argc - 1, argv + 1); - case 2: // dispose bitmap surface, with extra param - // 2 params, called e.g. from MenuItem::dispose in Torin's Passage, - // script 64893 - warning("kBitmap(2), unk1 %d, bitmap ptr %04x:%04x", argv[1].toUint16(), PRINT_REG(argv[2])); - break; - case 3: // tiled surface - { - // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage, - // script 64869 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - // The tiled view seems to always have 2 loops. - // These loops need to have 1 cel in loop 0 and 8 cels in loop 1. - uint16 viewNum = argv[2].toUint16(); // vTiles selector - uint16 loop = argv[3].toUint16(); - uint16 cel = argv[4].toUint16(); - uint16 x = argv[5].toUint16(); - uint16 y = argv[6].toUint16(); - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - GfxView *view = g_sci->_gfxCache->getView(viewNum); - uint16 tileWidth = view->getWidth(loop, cel); - uint16 tileHeight = view->getHeight(loop, cel); - const byte *tileBitmap = view->getBitmap(loop, cel); - uint16 width = MIN<uint16>(totalWidth - x, tileWidth); - uint16 height = MIN<uint16>(totalHeight - y, tileHeight); - - for (uint16 curY = 0; curY < height; curY++) { - for (uint16 curX = 0; curX < width; curX++) { - bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX]; - } - } + position.x -= alignX == -1 ? view._origin.x : alignX; + position.y -= alignY == -1 ? view._origin.y : alignY; - } - break; - case 4: // add text to bitmap - { - // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage, - // script 64894 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - Common::String text = s->_segMan->getString(argv[2]); - uint16 textX = argv[3].toUint16(); - uint16 textY = argv[4].toUint16(); - //reg_t unk5 = argv[5]; - //reg_t unk6 = argv[6]; - //reg_t unk7 = argv[7]; // skip? - //reg_t unk8 = argv[8]; // back? - //reg_t unk9 = argv[9]; - uint16 fontId = argv[10].toUint16(); - //uint16 mode = argv[11].toUint16(); - uint16 dimmed = argv[12].toUint16(); - //warning("kBitmap(4): bitmap ptr %04x:%04x, font %d, mode %d, dimmed %d - text: \"%s\"", - // PRINT_REG(bitmapPtr), font, mode, dimmed, text.c_str()); - uint16 foreColor = 255; // TODO - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - GfxFont *font = g_sci->_gfxCache->getFont(fontId); - - int16 charCount = 0; - uint16 curX = textX, curY = textY; - const char *txt = text.c_str(); - - while (*txt) { - charCount = g_sci->_gfxText32->GetLongest(txt, totalWidth, font); - if (charCount == 0) - break; - - for (int i = 0; i < charCount; i++) { - unsigned char curChar = txt[i]; - font->drawToBuffer(curChar, curY, curX, foreColor, dimmed, bitmap, totalWidth, totalHeight); - curX += font->getCharWidth(curChar); - } + Common::Rect drawRect( + position.x, + position.y, + position.x + view._width, + position.y + view._height + ); + drawRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight())); + view.draw(bitmap.getBuffer(), drawRect, position, view._mirrorX); + return s->r_acc; +} - curX = textX; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces - } +reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) { + // called e.g. from TextButton::createBitmap() in Torin's Passage, script 64894 - } - break; - case 5: // fill with color - { - // 6 params, called e.g. from TextView::init() and TextView::draw() - // in Torin's Passage, script 64890 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - uint16 x = argv[2].toUint16(); - uint16 y = argv[3].toUint16(); - uint16 fillWidth = argv[4].toUint16(); // width - 1 - uint16 fillHeight = argv[5].toUint16(); // height - 1 - uint16 back = argv[6].toUint16(); - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - uint16 width = MIN<uint16>(totalWidth - x, fillWidth); - uint16 height = MIN<uint16>(totalHeight - y, fillHeight); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - for (uint16 curY = 0; curY < height; curY++) { - for (uint16 curX = 0; curX < width; curX++) { - bitmap[(curY + y) * totalWidth + (curX + x)] = back; - } - } + SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); + Common::String text = s->_segMan->getString(argv[1]); + Common::Rect textRect( + argv[2].toSint16(), + argv[3].toSint16(), + argv[4].toSint16() + 1, + argv[5].toSint16() + 1 + ); + int16 foreColor = argv[6].toSint16(); + int16 backColor = argv[7].toSint16(); + int16 skipColor = argv[8].toSint16(); + GuiResourceId fontId = (GuiResourceId)argv[9].toUint16(); + TextAlign alignment = (TextAlign)argv[10].toSint16(); + int16 borderColor = argv[11].toSint16(); + bool dimmed = argv[12].toUint16(); + + // NOTE: Technically the engine checks these things: + // textRect.bottom > 0 + // textRect.right > 0 + // textRect.left < bitmap.width + // textRect.top < bitmap.height + // Then clips. But this seems stupid. + textRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight())); + + reg_t textBitmapObject = g_sci->_gfxText32->createFontBitmap(textRect.width(), textRect.height(), Common::Rect(textRect.width(), textRect.height()), text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, false, false); + CelObjMem textCel(textBitmapObject); + textCel.draw(bitmap.getBuffer(), textRect, Common::Point(textRect.left, textRect.top), false); + s->_segMan->freeBitmap(textBitmapObject); + + return s->r_acc; +} + +reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv) { + // called e.g. from TextView::init() and TextView::draw() in Torin's Passage, script 64890 + SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); + Common::Rect fillRect( + argv[1].toSint16(), + argv[2].toSint16(), + argv[3].toSint16() + 1, + argv[4].toSint16() + 1 + ); + + bitmap.getBuffer().fillRect(fillRect, argv[5].toSint16()); + return s->r_acc; +} + +reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv) { + // target bitmap, source bitmap, x, y, unknown boolean + + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv) { + // bitmap, left, top, right, bottom, foreColor, backColor + + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapSetOrigin(EngineState *s, int argc, reg_t *argv) { + SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); + bitmap.setOrigin(Common::Point(argv[1].toSint16(), argv[2].toSint16())); + return s->r_acc; +} + +reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) { + CelObjView view(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16()); + const uint8 skipColor = argc > 3 && argv[3].toSint16() != -1 ? argv[3].toSint16() : view._skipColor; + const uint8 backColor = argc > 4 && argv[4].toSint16() != -1 ? argv[4].toSint16() : view._skipColor; + const bool useRemap = argc > 5 ? (bool)argv[5].toSint16() : false; + + reg_t bitmapId; + SciBitmap &bitmap = *s->_segMan->allocateBitmap(&bitmapId, view._width, view._height, skipColor, 0, 0, view._xResolution, view._yResolution, 0, useRemap, true); + Buffer &buffer = bitmap.getBuffer(); + + const Common::Rect viewRect(view._width, view._height); + buffer.fillRect(viewRect, backColor); + view.draw(buffer, viewRect, Common::Point(0, 0), view._mirrorX); + + if (argc > 6 && !argv[6].isNull()) { + reg_t clutHandle = argv[6]; + if (s->_segMan->isObject(clutHandle)) { + clutHandle = readSelector(s->_segMan, clutHandle, SELECTOR(data)); } - break; - default: - kStub(s, argc, argv); - break; + + SciArray &clut = *s->_segMan->lookupArray(clutHandle); + bitmap.applyRemap(clut); } - return s->r_acc; + return bitmapId; } -// Used for edit boxes in save/load dialogs. It's a rewritten version of kEditControl, -// but it handles events on its own, using an internal loop, instead of using SCI -// scripts for event management like kEditControl does. Called by script 64914, -// DEdit::hilite(). -reg_t kEditText(EngineState *s, int argc, reg_t *argv) { - reg_t controlObject = argv[0]; +reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) { + // target bitmap, source bitmap - if (!controlObject.isNull()) { - g_sci->_gfxControls32->kernelTexteditChange(controlObject); + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv) { + // bitmap + + return kStub(s, argc + 1, argv - 1); +} + +reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv) { + SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); + + if (argc == 1) { + return make_reg(0, bitmap.getWidth()); } - return s->r_acc; + int32 offset; + if (argc == 2) { + offset = argv[1].toUint16(); + } else { + const int16 x = argv[1].toSint16(); + const int16 y = argv[2].toSint16(); + offset = y * bitmap.getWidth() + x; + } + + assert(offset >= 0 && offset < bitmap.getWidth() * bitmap.getHeight()); + const uint8 color = bitmap.getPixels()[offset]; + return make_reg(0, color); +} + +reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv) { + // TODO: SCI3 + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv) { + // TODO: SCI3 + return kStub(s, argc + 1, argv - 1); +} + +reg_t kEditText(EngineState *s, int argc, reg_t *argv) { + return g_sci->_gfxControls32->kernelEditText(argv[0]); } reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { - return kStubNull(s, argc, argv); // return 0:0 for now, so that follow up calls won't create signature mismatches -#if 0 - reg_t plane = argv[0]; - Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16()); - Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16()); - // argv[5] is unknown (a number, usually 200) - byte color = (byte)argv[6].toUint16(); - byte priority = (byte)argv[7].toUint16(); - byte control = (byte)argv[8].toUint16(); - // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps? -// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control); - return s->r_acc; -#endif + const reg_t plane = argv[0]; + const Common::Point startPoint(argv[1].toSint16(), argv[2].toSint16()); + const Common::Point endPoint(argv[3].toSint16(), argv[4].toSint16()); + + int16 priority; + uint8 color; + LineStyle style; + uint16 pattern; + uint8 thickness; + + if (argc == 10) { + priority = argv[5].toSint16(); + color = (uint8)argv[6].toUint16(); + style = (LineStyle)argv[7].toSint16(); + pattern = argv[8].toUint16(); + thickness = (uint8)argv[9].toUint16(); + } else { + priority = 1000; + color = 255; + style = kLineStyleSolid; + pattern = 0; + thickness = 1; + } + + return g_sci->_gfxPaint32->kernelAddLine(plane, startPoint, endPoint, priority, color, style, pattern, thickness); } reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { - return kStub(s, argc, argv); - -#if 0 - reg_t hunkId = argv[0]; - reg_t plane = argv[1]; - Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16()); - Common::Point endPoint(argv[4].toUint16(), argv[5].toUint16()); - // argv[6] is unknown (a number, usually 200) - byte color = (byte)argv[7].toUint16(); - byte priority = (byte)argv[8].toUint16(); - byte control = (byte)argv[9].toUint16(); - // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps? -// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control); + const reg_t screenItemObject = argv[0]; + const reg_t planeObject = argv[1]; + const Common::Point startPoint(argv[2].toSint16(), argv[3].toSint16()); + const Common::Point endPoint(argv[4].toSint16(), argv[5].toSint16()); + + int16 priority; + uint8 color; + LineStyle style; + uint16 pattern; + uint8 thickness; + + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); + if (plane == nullptr) { + error("kUpdateLine: Plane %04x:%04x not found", PRINT_REG(planeObject)); + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); + if (screenItem == nullptr) { + error("kUpdateLine: Screen item %04x:%04x not found", PRINT_REG(screenItemObject)); + } + + if (argc == 11) { + priority = argv[6].toSint16(); + color = (uint8)argv[7].toUint16(); + style = (LineStyle)argv[8].toSint16(); + pattern = argv[9].toUint16(); + thickness = (uint8)argv[10].toUint16(); + } else { + priority = screenItem->_priority; + color = screenItem->_celInfo.color; + style = kLineStyleSolid; + pattern = 0; + thickness = 1; + } + + g_sci->_gfxPaint32->kernelUpdateLine(screenItem, plane, startPoint, endPoint, priority, color, style, pattern, thickness); + return s->r_acc; -#endif } + reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) { - return kStub(s, argc, argv); -#if 0 - reg_t hunkId = argv[0]; - reg_t plane = argv[1]; -// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); + g_sci->_gfxPaint32->kernelDeleteLine(argv[0], argv[1]); return s->r_acc; -#endif } +// Used by LSL6hires intro (room 110) reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { - // Called in the intro of LSL6 hires (room 110) - // The end effect of this is the same as the old screen scroll transition - - // 7 parameters - reg_t planeObject = argv[0]; - //int16 x = argv[1].toSint16(); - //int16 y = argv[2].toSint16(); - uint16 pictureId = argv[3].toUint16(); - // param 4: int (0 in LSL6, probably scroll direction? The picture in LSL6 scrolls down) - // param 5: int (first call is 1, then the subsequent one is 0 in LSL6) - // param 6: optional int (0 in LSL6) - - // Set the new picture directly for now - //writeSelectorValue(s->_segMan, planeObject, SELECTOR(left), x); - //writeSelectorValue(s->_segMan, planeObject, SELECTOR(top), y); - writeSelectorValue(s->_segMan, planeObject, SELECTOR(picture), pictureId); - // and update our draw list - g_sci->_gfxFrameout->kernelUpdatePlane(planeObject); - - // TODO - return kStub(s, argc, argv); + const reg_t plane = argv[0]; + const int16 deltaX = argv[1].toSint16(); + const int16 deltaY = argv[2].toSint16(); + const GuiResourceId pictureId = argv[3].toUint16(); + const bool animate = argv[4].toUint16(); + // NOTE: speed was accepted as an argument, but then never actually used + // const int16 speed = argc > 5 ? (bool)argv[5].toSint16() : -1; + const bool mirrorX = argc > 6 ? (bool)argv[6].toUint16() : false; + + g_sci->_gfxTransitions32->kernelSetScroll(plane, deltaX, deltaY, pictureId, animate, mirrorX); + return s->r_acc; } // Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270) reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxFrameout->_palMorphIsOn = true; - return NULL_REG; + return s->r_acc; +} + +reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv) { + const GuiResourceId paletteId = argv[0].toUint16(); + g_sci->_gfxPalette32->loadPalette(paletteId); + return s->r_acc; +} + +reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv) { + const uint8 r = argv[0].toUint16(); + const uint8 g = argv[1].toUint16(); + const uint8 b = argv[2].toUint16(); + return make_reg(0, g_sci->_gfxPalette32->matchColor(r, g, b)); +} + +/* + * Used in SCI3. SCI3 contains 6 gamma look-up tables, with the first + * table (gamma = 0) being the default one. + */ +reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) { + const uint8 gamma = argv[0].toUint16(); + assert(gamma <= 6); + + warning("TODO: kPaletteSetGamma(%d)", gamma); + + return s->r_acc; } reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) { - uint16 fromColor = argv[0].toUint16(); - uint16 toColor = argv[1].toUint16(); - uint16 percent = argv[2].toUint16(); + const uint16 fromColor = argv[0].toUint16(); + const uint16 toColor = argv[1].toUint16(); + const uint16 percent = argv[2].toUint16(); g_sci->_gfxPalette32->setFade(percent, fromColor, toColor); - return NULL_REG; + return s->r_acc; } reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) { - GuiResourceId paletteId = argv[0].toUint16(); - int time = argc > 1 ? argv[1].toSint16() * 60 : 0; - int16 percent = argc > 2 ? argv[2].toSint16() : 100; + const GuiResourceId paletteId = argv[0].toUint16(); + const int32 time = argc > 1 ? argv[1].toSint16() * 60 : 0; + const int16 percent = argc > 2 ? argv[2].toSint16() : 100; int16 fromColor; int16 toColor; - if (argc > 4) { + if (g_sci->_features->hasNewPaletteCode() && argc > 4) { fromColor = argv[3].toSint16(); toColor = argv[4].toSint16(); } else { @@ -758,14 +988,14 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) { } g_sci->_gfxPalette32->kernelPalVarySet(paletteId, percent, time, fromColor, toColor); - return NULL_REG; + return s->r_acc; } reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv) { - int time = argc > 0 ? argv[0].toSint16() * 60 : 0; - int16 percent = argc > 1 ? argv[1].toSint16() : 0; - g_sci->_gfxPalette32->setVaryPercent(percent, time, -1, -1); - return NULL_REG; + const int32 time = argc > 0 ? argv[0].toSint16() * 60 : 0; + const int16 percent = argc > 1 ? argv[1].toSint16() : 0; + g_sci->_gfxPalette32->setVaryPercent(percent, time); + return s->r_acc; } reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) { @@ -774,173 +1004,154 @@ reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) { reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxPalette32->varyOff(); - return NULL_REG; + return s->r_acc; } reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) { - GuiResourceId paletteId = argv[0].toUint16(); + const GuiResourceId paletteId = argv[0].toUint16(); g_sci->_gfxPalette32->kernelPalVaryMergeTarget(paletteId); return make_reg(0, g_sci->_gfxPalette32->getVaryPercent()); } reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv) { - int time = argv[0].toSint16() * 60; + const int32 time = argv[0].toSint16() * 60; g_sci->_gfxPalette32->setVaryTime(time); - return NULL_REG; + return s->r_acc; } reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv) { - GuiResourceId paletteId = argv[0].toUint16(); + const GuiResourceId paletteId = argv[0].toUint16(); g_sci->_gfxPalette32->kernelPalVarySetTarget(paletteId); return make_reg(0, g_sci->_gfxPalette32->getVaryPercent()); } reg_t kPalVarySetStart(EngineState *s, int argc, reg_t *argv) { - GuiResourceId paletteId = argv[0].toUint16(); + const GuiResourceId paletteId = argv[0].toUint16(); g_sci->_gfxPalette32->kernelPalVarySetStart(paletteId); return make_reg(0, g_sci->_gfxPalette32->getVaryPercent()); } reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv) { - GuiResourceId paletteId = argv[0].toUint16(); + const GuiResourceId paletteId = argv[0].toUint16(); g_sci->_gfxPalette32->kernelPalVaryMergeStart(paletteId); return make_reg(0, g_sci->_gfxPalette32->getVaryPercent()); } -enum { - kSetCycle = 0, - kDoCycle = 1, - kCyclePause = 2, - kCycleOn = 3, - kCycleOff = 4 -}; - reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) { - // Examples: GK1 room 480 (Bayou ritual), LSL6 room 100 (title screen) + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - switch (argv[0].toUint16()) { - case kSetCycle: { - uint16 fromColor = argv[1].toUint16(); - uint16 toColor = argv[2].toUint16(); - int16 direction = argv[3].toSint16(); - uint16 delay = (argc == 4 ? 0 : argv[4].toUint16()); +reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv) { + const uint16 fromColor = argv[0].toUint16(); + const uint16 toColor = argv[1].toUint16(); + const int16 direction = argv[2].toSint16(); + const uint16 delay = argc > 3 ? argv[3].toUint16() : 0; + g_sci->_gfxPalette32->setCycle(fromColor, toColor, direction, delay); + return s->r_acc; +} - g_sci->_gfxPalette32->setCycle(fromColor, toColor, direction, delay); - } - break; - case kDoCycle: { - uint16 fromColor = argv[1].toUint16(); - int16 speed = (argc == 2) ? 1 : argv[2].toSint16(); - g_sci->_gfxPalette32->doCycle(fromColor, speed); - } - break; - case kCyclePause: { - if (argc == 1) { - g_sci->_gfxPalette32->cycleAllPause(); - } else { - uint16 fromColor = argv[1].toUint16(); - g_sci->_gfxPalette32->cyclePause(fromColor); - } - } - break; - case kCycleOn: { - if (argc == 1) { - g_sci->_gfxPalette32->cycleAllOn(); - } else { - uint16 fromColor = argv[1].toUint16(); - g_sci->_gfxPalette32->cycleOn(fromColor); - } - } - break; - case kCycleOff: { - if (argc == 1) { - g_sci->_gfxPalette32->cycleAllOff(); - } else { - uint16 fromColor = argv[1].toUint16(); - g_sci->_gfxPalette32->cycleOff(fromColor); - } - break; - } - default: - // In SCI2.1 there are no values above 4, so should never get here; - // SCI just returns early if this ever happens. - assert(false); - break; +reg_t kPalCycleDoCycle(EngineState *s, int argc, reg_t *argv) { + const uint16 fromColor = argv[0].toUint16(); + const int16 speed = argc > 1 ? argv[1].toSint16() : 1; + g_sci->_gfxPalette32->doCycle(fromColor, speed); + return s->r_acc; +} + +reg_t kPalCyclePause(EngineState *s, int argc, reg_t *argv) { + if (argc == 0) { + g_sci->_gfxPalette32->cycleAllPause(); + } else { + const uint16 fromColor = argv[0].toUint16(); + g_sci->_gfxPalette32->cyclePause(fromColor); + } + return s->r_acc; +} + +reg_t kPalCycleOn(EngineState *s, int argc, reg_t *argv) { + if (argc == 0) { + g_sci->_gfxPalette32->cycleAllOn(); + } else { + const uint16 fromColor = argv[0].toUint16(); + g_sci->_gfxPalette32->cycleOn(fromColor); } + return s->r_acc; +} +reg_t kPalCycleOff(EngineState *s, int argc, reg_t *argv) { + if (argc == 0) { + g_sci->_gfxPalette32->cycleAllOff(); + } else { + const uint16 fromColor = argv[0].toUint16(); + g_sci->_gfxPalette32->cycleOff(fromColor); + } return s->r_acc; } reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) { - uint16 operation = argv[0].toUint16(); - - switch (operation) { - case 0: { // turn remapping off - // WORKAROUND: Game scripts in QFG4 erroneously turn remapping off in room - // 140 (the character point allocation screen) and never turn it back on, - // even if it's clearly used in that screen. - if (g_sci->getGameId() == GID_QFG4 && s->currentRoomNumber() == 140) - return s->r_acc; - - int16 base = (argc >= 2) ? argv[1].toSint16() : 0; - if (base > 0) - warning("kRemapColors(0) called with base %d", base); - g_sci->_gfxPalette32->resetRemapping(); - } - break; - case 1: { // remap by range - uint16 color = argv[1].toUint16(); - uint16 from = argv[2].toUint16(); - uint16 to = argv[3].toUint16(); - uint16 base = argv[4].toUint16(); - uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0; - if (unk5 > 0) - warning("kRemapColors(1) called with 6 parameters, unknown parameter is %d", unk5); - g_sci->_gfxPalette32->setRemappingRange(color, from, to, base); - } - break; - case 2: { // remap by percent - uint16 color = argv[1].toUint16(); - uint16 percent = argv[2].toUint16(); // 0 - 100 - if (argc >= 4) - warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); - g_sci->_gfxPalette32->setRemappingPercent(color, percent); - } - break; - case 3: { // remap to gray - // Example call: QFG4 room 490 (Baba Yaga's hut) - params are color 253, 75% and 0. - // In this room, it's used for the cloud before Baba Yaga appears. - int16 color = argv[1].toSint16(); - int16 percent = argv[2].toSint16(); // 0 - 100 - if (argc >= 4) - warning("RemapToGray called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); - g_sci->_gfxPalette32->setRemappingPercentGray(color, percent); - } - break; - case 4: { // remap to percent gray - // Example call: QFG4 rooms 530/535 (swamp) - params are 253, 100%, 200 - int16 color = argv[1].toSint16(); - int16 percent = argv[2].toSint16(); // 0 - 100 - // argv[3] is unknown (a number, e.g. 200) - start color, perhaps? - if (argc >= 5) - warning("RemapToGrayPercent called with 5 parameters, unknown parameter is %d", argv[4].toUint16()); - g_sci->_gfxPalette32->setRemappingPercentGray(color, percent); - } - break; - case 5: { // don't map to range - //int16 mapping = argv[1].toSint16(); - uint16 intensity = argv[2].toUint16(); - // HACK for PQ4 - if (g_sci->getGameId() == GID_PQ4) - g_sci->_gfxPalette32->kernelSetIntensity(0, 255, intensity, true); - - kStub(s, argc, argv); - } - break; - default: - break; + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv) { + if (argc == 0) { + g_sci->_gfxRemap32->remapAllOff(); + } else { + const uint8 color = argv[0].toUint16(); + g_sci->_gfxRemap32->remapOff(color); } + return s->r_acc; +} + +reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv) { + const uint8 color = argv[0].toUint16(); + const int16 from = argv[1].toSint16(); + const int16 to = argv[2].toSint16(); + const int16 base = argv[3].toSint16(); + // NOTE: There is an optional last parameter after `base` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapByRange(color, from, to, base); + return s->r_acc; +} + +reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv) { + const uint8 color = argv[0].toUint16(); + const int16 percent = argv[1].toSint16(); + // NOTE: There is an optional last parameter after `percent` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapByPercent(color, percent); + return s->r_acc; +} + +reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv) { + const uint8 color = argv[0].toUint16(); + const int16 gray = argv[1].toSint16(); + // NOTE: There is an optional last parameter after `gray` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapToGray(color, gray); + return s->r_acc; +} + +reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv) { + const uint8 color = argv[0].toUint16(); + const int16 gray = argv[1].toSint16(); + const int16 percent = argv[2].toSint16(); + // NOTE: There is an optional last parameter after `percent` + // which was only used by the priority map debugger, which + // does not exist in release versions of SSCI + g_sci->_gfxRemap32->remapToPercentGray(color, gray, percent); + return s->r_acc; +} +reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv) { + const uint8 from = argv[0].toUint16(); + const uint8 count = argv[1].toUint16(); + g_sci->_gfxRemap32->blockRange(from, count); return s->r_acc; } diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp index c0da2daaeb..de5d7f1536 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -302,14 +302,14 @@ reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv) { reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); - Node *firstnode = argv[1].isNull() ? NULL : s->_segMan->lookupNode(argv[1]); - Node *newnode = s->_segMan->lookupNode(argv[2]); + Node *firstNode = s->_segMan->lookupNode(argv[1]); + Node *newNode = s->_segMan->lookupNode(argv[2]); #ifdef CHECK_LISTS checkListPointer(s->_segMan, argv[0]); #endif - if (!newnode) { + if (!newNode) { error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2])); return NULL_REG; } @@ -320,22 +320,64 @@ reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) { } if (argc == 4) - newnode->key = argv[3]; + newNode->key = argv[3]; - if (firstnode) { // We're really appending after - reg_t oldnext = firstnode->succ; + if (firstNode) { // We're really appending after + const reg_t oldNext = firstNode->succ; - newnode->pred = argv[1]; - firstnode->succ = argv[2]; - newnode->succ = oldnext; + newNode->pred = argv[1]; + firstNode->succ = argv[2]; + newNode->succ = oldNext; - if (oldnext.isNull()) // Appended after last node? + if (oldNext.isNull()) // Appended after last node? // Set new node as last list node list->last = argv[2]; else - s->_segMan->lookupNode(oldnext)->pred = argv[2]; + s->_segMan->lookupNode(oldNext)->pred = argv[2]; - } else { // !firstnode + } else { + addToFront(s, argv[0], argv[2]); // Set as initial list node + } + + return s->r_acc; +} + +reg_t kAddBefore(EngineState *s, int argc, reg_t *argv) { + List *list = s->_segMan->lookupList(argv[0]); + Node *firstNode = s->_segMan->lookupNode(argv[1]); + Node *newNode = s->_segMan->lookupNode(argv[2]); + +#ifdef CHECK_LISTS + checkListPointer(s->_segMan, argv[0]); +#endif + + if (!newNode) { + error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2])); + return NULL_REG; + } + + if (argc != 3 && argc != 4) { + error("kAddBefore: Haven't got 3 or 4 arguments, aborting"); + return NULL_REG; + } + + if (argc == 4) + newNode->key = argv[3]; + + if (firstNode) { // We're really appending before + const reg_t oldPred = firstNode->pred; + + newNode->succ = argv[1]; + firstNode->pred = argv[2]; + newNode->pred = oldPred; + + if (oldPred.isNull()) // Appended before first node? + // Set new node as first list node + list->first = argv[2]; + else + s->_segMan->lookupNode(oldPred)->succ = argv[2]; + + } else { addToFront(s, argv[0], argv[2]); // Set as initial list node } @@ -374,13 +416,21 @@ reg_t kFindKey(EngineState *s, int argc, reg_t *argv) { reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) { reg_t node_pos = kFindKey(s, 2, argv); - Node *n; List *list = s->_segMan->lookupList(argv[0]); if (node_pos.isNull()) return NULL_REG; // Signal failure - n = s->_segMan->lookupNode(node_pos); + Node *n = s->_segMan->lookupNode(node_pos); + +#ifdef ENABLE_SCI32 + for (int i = 1; i <= list->numRecursions; ++i) { + if (list->nextNodes[i] == node_pos) { + list->nextNodes[i] = n->succ; + } + } +#endif + if (list->first == node_pos) list->first = n->succ; if (list->last == node_pos) @@ -486,7 +536,7 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; if (list->first.isNull()) { - error("kListAt tried to reference empty list (%04x:%04x)", PRINT_REG(argv[0])); + // Happens in Torin when examining Di's locket in chapter 3 return NULL_REG; } Node *curNode = s->_segMan->lookupNode(curAddress); @@ -539,15 +589,23 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); Node *curNode = s->_segMan->lookupNode(list->first); - reg_t curObject; Selector slc = argv[1].toUint16(); ObjVarRef address; + ++list->numRecursions; + + if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) { + error("Too much recursion in kListEachElementDo"); + } + while (curNode) { - // We get the next node here as the current node might be gone after the invoke - reg_t nextNode = curNode->succ; - curObject = curNode->value; + // We get the next node here as the current node might be deleted by the + // invoke. In the case that the next node is also deleted, kDeleteKey + // needs to be able to adjust the location of the next node, which is + // why it is stored on the list instead of on the stack + list->nextNodes[list->numRecursions] = curNode->succ; + reg_t curObject = curNode->value; // First, check if the target selector is a variable if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { @@ -559,11 +617,18 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { } } else { invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); + // Check if the call above leads to a game restore, in which case + // the segment manager will be reset, and the original list will + // be invalidated + if (s->abortScriptProcessing == kAbortLoadGame) + return s->r_acc; } - curNode = s->_segMan->lookupNode(nextNode); + curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]); } + --list->numRecursions; + return s->r_acc; } @@ -571,37 +636,51 @@ reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); Node *curNode = s->_segMan->lookupNode(list->first); - reg_t curObject; Selector slc = argv[1].toUint16(); ObjVarRef address; - s->r_acc = NULL_REG; // reset the accumulator + s->r_acc = NULL_REG; + + ++list->numRecursions; + + if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) { + error("Too much recursion in kListFirstTrue"); + } while (curNode) { - reg_t nextNode = curNode->succ; - curObject = curNode->value; + // We get the next node here as the current node might be deleted by the + // invoke. In the case that the next node is also deleted, kDeleteKey + // needs to be able to adjust the location of the next node, which is + // why it is stored on the list instead of on the stack + list->nextNodes[list->numRecursions] = curNode->succ; + reg_t curObject = curNode->value; // First, check if the target selector is a variable if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { // If it's a variable selector, check its value. // Example: script 64893 in Torin, MenuHandler::isHilited checks // all children for variable selector 0x03ba (bHilited). - if (!readSelector(s->_segMan, curObject, slc).isNull()) - return curObject; + if (!readSelector(s->_segMan, curObject, slc).isNull()) { + s->r_acc = curObject; + break; + } } else { invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); // Check if the result is true - if (!s->r_acc.isNull()) - return curObject; + if (!s->r_acc.isNull()) { + s->r_acc = curObject; + break; + } } - curNode = s->_segMan->lookupNode(nextNode); + curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]); } - // No selector returned true - return NULL_REG; + --list->numRecursions; + + return s->r_acc; } reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) { @@ -613,10 +692,20 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) { ObjVarRef address; - s->r_acc = make_reg(0, 1); // reset the accumulator + s->r_acc = TRUE_REG; + + ++list->numRecursions; + + if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) { + error("Too much recursion in kListAllTrue"); + } while (curNode) { - reg_t nextNode = curNode->succ; + // We get the next node here as the current node might be deleted by the + // invoke. In the case that the next node is also deleted, kDeleteKey + // needs to be able to adjust the location of the next node, which is + // why it is stored on the list instead of on the stack + list->nextNodes[list->numRecursions] = curNode->succ; curObject = curNode->value; // First, check if the target selector is a variable @@ -631,7 +720,53 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) { if (s->r_acc.isNull()) break; - curNode = s->_segMan->lookupNode(nextNode); + curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]); + } + + --list->numRecursions; + + return s->r_acc; +} + +reg_t kListSort(EngineState *s, int argc, reg_t *argv) { + List *list = s->_segMan->lookupList(argv[0]); + const int16 selector = argv[1].toSint16(); + const bool isDescending = argc > 2 ? (bool)argv[2].toUint16() : false; + + reg_t firstNode = list->first; + for (reg_t node = firstNode; node != NULL_REG; node = s->_segMan->lookupNode(firstNode)->succ) { + + reg_t a; + if (selector == -1) { + a = s->_segMan->lookupNode(node)->value; + } else { + a = readSelector(s->_segMan, s->_segMan->lookupNode(node)->value, selector); + } + + firstNode = node; + for (reg_t newNode = s->_segMan->lookupNode(node)->succ; newNode != NULL_REG; newNode = s->_segMan->lookupNode(newNode)->succ) { + reg_t b; + if (selector == -1) { + b = s->_segMan->lookupNode(newNode)->value; + } else { + b = readSelector(s->_segMan, s->_segMan->lookupNode(newNode)->value, selector); + } + + if ((!isDescending && b < a) || (isDescending && a < b)) { + firstNode = newNode; + a = b; + } + } + + if (firstNode != node) { + reg_t buf[4] = { argv[0], s->_segMan->lookupNode(firstNode)->key }; + kDeleteKey(s, 2, buf); + + buf[1] = node; + buf[2] = firstNode; + buf[3] = s->_segMan->lookupNode(firstNode)->value; + kAddBefore(s, 4, buf); + } } return s->r_acc; @@ -643,11 +778,6 @@ reg_t kList(EngineState *s, int argc, reg_t *argv) { error("not supposed to call this"); } -reg_t kAddBefore(EngineState *s, int argc, reg_t *argv) { - error("Unimplemented function kAddBefore called"); - return s->r_acc; -} - reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv) { error("Unimplemented function kMoveToFront called"); return s->r_acc; @@ -659,197 +789,105 @@ reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv) { } reg_t kArray(EngineState *s, int argc, reg_t *argv) { - uint16 op = argv[0].toUint16(); - - // Use kString when accessing strings - // This is possible, as strings inherit from arrays - // and in this case (type 3) arrays are of type char *. - // kString is almost exactly the same as kArray, so - // this call is possible - // TODO: we need to either merge SCI2 strings and - // arrays together, and in the future merge them with - // the SCI1 strings and arrays in the segment manager - bool callStringFunc = false; - if (op == 0) { - // New, check if the target type is 3 (string) - if (argv[2].toUint16() == 3) - callStringFunc = true; - } else { - if (s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_STRING || - s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_SCRIPT) { - callStringFunc = true; - } - -#if 0 - if (op == 6) { - if (s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_STRING || - s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_SCRIPT) { - callStringFunc = true; - } - } -#endif - } - - if (callStringFunc) { - Kernel *kernel = g_sci->getKernel(); - uint16 kernelStringFuncId = kernel->_kernelFunc_StringId; - if (kernelStringFuncId) { - const KernelFunction *kernelStringFunc = &kernel->_kernelFuncs[kernelStringFuncId]; + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - if (op < kernelStringFunc->subFunctionCount) { - // subfunction-id is valid - const KernelSubFunction *kernelStringSubCall = &kernelStringFunc->subFunctions[op]; - argc--; - argv++; // remove subfunction-id from arguments - // and call the kString subfunction - return kernelStringSubCall->function(s, argc, argv); - } - } - } +reg_t kArrayNew(EngineState *s, int argc, reg_t *argv) { + uint16 size = argv[0].toUint16(); + const SciArrayType type = (SciArrayType)argv[1].toUint16(); - switch (op) { - case 0: { // New - reg_t arrayHandle; - SciArray<reg_t> *array = s->_segMan->allocateArray(&arrayHandle); - array->setType(argv[2].toUint16()); - array->setSize(argv[1].toUint16()); - return arrayHandle; - } - case 1: { // Size - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - return make_reg(0, array->getSize()); - } - case 2: { // At (return value at an index) - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { - // HACK: Phantasmagoria 2 keeps trying to access past the end of an - // array when it starts. I'm assuming it's trying to see where the - // array ends, or tries to resize it. Adjust the array size - // accordingly, and return NULL for now. - if (array->getSize() == argv[2].toUint16()) { - array->setSize(argv[2].toUint16()); - return NULL_REG; - } - } - return array->getValue(argv[2].toUint16()); + if (type == kArrayTypeString) { + ++size; } - case 3: { // Atput (put value at an index) - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - - uint32 index = argv[2].toUint16(); - uint32 count = argc - 3; - if (index + count > 65535) - break; + reg_t arrayHandle; + s->_segMan->allocateArray(type, size, &arrayHandle); + return arrayHandle; +} - if (array->getSize() < index + count) - array->setSize(index + count); +reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv) { + const SciArray &array = *s->_segMan->lookupArray(argv[0]); + return make_reg(0, array.size()); +} - for (uint16 i = 0; i < count; i++) - array->setValue(i + index, argv[i + 3]); +reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv) { + SciArray &array = *s->_segMan->lookupArray(argv[0]); + return array.getAsID(argv[1].toUint16()); +} - return argv[1]; // We also have to return the handle - } - case 4: // Free - // Freeing of arrays is handled by the garbage collector - return s->r_acc; - case 5: { // Fill - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - uint16 index = argv[2].toUint16(); +reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv) { + SciArray &array = *s->_segMan->lookupArray(argv[0]); + array.setElements(argv[1].toUint16(), argc - 2, argv + 2); + return argv[0]; +} - // A count of -1 means fill the rest of the array - uint16 count = argv[3].toSint16() == -1 ? array->getSize() - index : argv[3].toUint16(); - uint16 arraySize = array->getSize(); +reg_t kArrayFree(EngineState *s, int argc, reg_t *argv) { + s->_segMan->freeArray(argv[0]); + return s->r_acc; +} - if (arraySize < index + count) - array->setSize(index + count); +reg_t kArrayFill(EngineState *s, int argc, reg_t *argv) { + SciArray &array = *s->_segMan->lookupArray(argv[0]); + array.fill(argv[1].toUint16(), argv[2].toUint16(), argv[3]); + return argv[0]; +} - for (uint16 i = 0; i < count; i++) - array->setValue(i + index, argv[4]); +reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv) { + SciArray &target = *s->_segMan->lookupArray(argv[0]); + const uint16 targetIndex = argv[1].toUint16(); - return argv[1]; + SciArray source; + // String copies may be made from static script data + if (!s->_segMan->isArray(argv[2])) { + source.setType(kArrayTypeString); + source.fromString(s->_segMan->getString(argv[2])); + } else { + source = *s->_segMan->lookupArray(argv[2]); } - case 6: { // Cpy - if (argv[1].isNull() || argv[3].isNull()) { - if (getSciVersion() == SCI_VERSION_3) { - // FIXME: Happens in SCI3, probably because of a missing kernel function. - warning("kArray(Cpy): Request to copy from or to a null pointer"); - return NULL_REG; - } else { - // SCI2-2.1: error out - error("kArray(Cpy): Request to copy from or to a null pointer"); - } - } - - reg_t arrayHandle = argv[1]; - SciArray<reg_t> *array1 = s->_segMan->lookupArray(argv[1]); - SciArray<reg_t> *array2 = s->_segMan->lookupArray(argv[3]); - uint32 index1 = argv[2].toUint16(); - uint32 index2 = argv[4].toUint16(); - - // The original engine ignores bad copies too - if (index2 > array2->getSize()) - break; - - // A count of -1 means fill the rest of the array - uint32 count = argv[5].toSint16() == -1 ? array2->getSize() - index2 : argv[5].toUint16(); + const uint16 sourceIndex = argv[3].toUint16(); + const uint16 count = argv[4].toUint16(); - if (array1->getSize() < index1 + count) - array1->setSize(index1 + count); + target.copy(source, sourceIndex, targetIndex, count); + return argv[0]; +} - for (uint16 i = 0; i < count; i++) - array1->setValue(i + index1, array2->getValue(i + index2)); +reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv) { + reg_t targetHandle; - return arrayHandle; + // String duplicates may be made from static script data + if (!s->_segMan->isArray(argv[0])) { + const Common::String source = s->_segMan->getString(argv[0]); + SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, source.size(), &targetHandle); + target.fromString(source); + } else { + SciArray &source = *s->_segMan->lookupArray(argv[0]); + SciArray &target = *s->_segMan->allocateArray(source.getType(), source.size(), &targetHandle); + target = source; } - case 7: // Cmp - // Not implemented in SSCI - warning("kArray(Cmp) called"); - return s->r_acc; - case 8: { // Dup - if (argv[1].isNull()) { - warning("kArray(Dup): Request to duplicate a null pointer"); -#if 0 - // Allocate an array anyway - reg_t arrayHandle; - SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle); - dupArray->setType(3); - dupArray->setSize(0); - return arrayHandle; -#endif - return NULL_REG; - } - SegmentObj *sobj = s->_segMan->getSegmentObj(argv[1].getSegment()); - if (!sobj || sobj->getType() != SEG_TYPE_ARRAY) - error("kArray(Dup): Request to duplicate a segment which isn't an array"); - reg_t arrayHandle; - SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle); - // This must occur after allocateArray, as inserting a new object - // in the heap object list might invalidate this pointer. Also refer - // to the same issue in kClone() - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - - dupArray->setType(array->getType()); - dupArray->setSize(array->getSize()); - - for (uint32 i = 0; i < array->getSize(); i++) - dupArray->setValue(i, array->getValue(i)); - - return arrayHandle; - } - case 9: // Getdata - if (!s->_segMan->isHeapObject(argv[1])) - return argv[1]; + return targetHandle; +} - return readSelector(s->_segMan, argv[1], SELECTOR(data)); - default: - error("Unknown kArray subop %d", op); +reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv) { + if (s->_segMan->isObject(argv[0])) { + return readSelector(s->_segMan, argv[0], SELECTOR(data)); } - return NULL_REG; + return argv[0]; } +reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv) { + SciArray &target = *s->_segMan->lookupArray(argv[0]); + const uint16 targetOffset = argv[1].toUint16(); + const SciArray &source = *s->_segMan->lookupArray(argv[2]); + const uint16 sourceOffset = argv[3].toUint16(); + const uint16 count = argv[4].toUint16(); + + target.byteCopy(source, sourceOffset, targetOffset, count); + return argv[0]; +} #endif } // End of namespace Sci diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index f4bb4ff85b..9aa03a4760 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -20,6 +20,7 @@ * */ +#include "common/config-manager.h" #include "common/system.h" #include "sci/sci.h" @@ -29,6 +30,9 @@ #include "sci/engine/kernel.h" #include "sci/engine/gc.h" #include "sci/graphics/cursor.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" +#endif #include "sci/graphics/maciconbar.h" #include "sci/console.h" @@ -243,10 +247,18 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelTime, "GetTime(24h) returns %d", retval); break; case KGETTIME_DATE : - // Year since 1980 (0 = 1980, 1 = 1981, etc.) - retval = loc_time.tm_mday | ((loc_time.tm_mon + 1) << 5) | (((loc_time.tm_year - 80) & 0x7f) << 9); + { + // SCI0 late: Year since 1920 (0 = 1920, 1 = 1921, etc) + // SCI01 and newer: Year since 1980 (0 = 1980, 1 = 1981, etc) + // Atari ST SCI0 late versions use the newer base year. + int baseYear = 80; + if (getSciVersion() == SCI_VERSION_0_LATE && g_sci->getPlatform() == Common::kPlatformDOS) { + baseYear = 20; + } + retval = loc_time.tm_mday | ((loc_time.tm_mon + 1) << 5) | (((loc_time.tm_year - baseYear) & 0x7f) << 9); debugC(kDebugLevelTime, "GetTime(date) returns %d", retval); break; + } default: error("Attempt to use unknown GetTime mode %d", mode); break; @@ -401,6 +413,15 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) { } else if (setting == "startroom") { // Debug setting in LSL7, specifies the room to start from. s->_segMan->strcpy(data, ""); + } else if (setting == "game") { + // Hoyle 5 startup, specifies the number of the game to start. + s->_segMan->strcpy(data, ""); + } else if (setting == "laptop") { + // Hoyle 5 startup. + s->_segMan->strcpy(data, ""); + } else if (setting == "jumpto") { + // Hoyle 5 startup. + s->_segMan->strcpy(data, ""); } else { error("GetConfig: Unknown configuration setting %s", setting.c_str()); } @@ -438,6 +459,14 @@ reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv) { } } +extern Common::String format(const Common::String &source, int argc, const reg_t *argv); + +reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv) { + const Common::String debugString = s->_segMan->getString(argv[0]); + debugC(kDebugLevelGame, "%s", format(debugString, argc - 1, argv + 1).c_str()); + return s->r_acc; +} + #endif // kIconBar is really a subop of kMacPlatform for SCI1.1 Mac @@ -493,9 +522,12 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) { // In SCI1, its usage is still unknown // In SCI1.1, it's NOP // In SCI32, it's used for remapping cursor ID's +#ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2_1_EARLY) // Set Mac cursor remap - g_sci->_gfxCursor->setMacCursorRemapList(argc - 1, argv + 1); - else if (getSciVersion() != SCI_VERSION_1_1) + g_sci->_gfxCursor32->setMacCursorRemapList(argc - 1, argv + 1); + else +#endif + if (getSciVersion() != SCI_VERSION_1_1) warning("Unknown SCI1 kMacPlatform(0) call"); break; case 4: // Handle icon bar code @@ -518,31 +550,28 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) { } enum kSciPlatforms { + kSciPlatformMacintosh = 0, kSciPlatformDOS = 1, kSciPlatformWindows = 2 }; -enum kPlatformOps { - kPlatformUnk0 = 0, - kPlatformCDSpeed = 1, - kPlatformUnk2 = 2, - kPlatformCDCheck = 3, - kPlatformGetPlatform = 4, - kPlatformUnk5 = 5, - kPlatformIsHiRes = 6, - kPlatformIsItWindows = 7 -}; - reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { + enum Operation { + kPlatformUnknown = 0, + kPlatformGetPlatform = 4, + kPlatformUnknown5 = 5, + kPlatformIsHiRes = 6, + kPlatformWin311OrHigher = 7 + }; + bool isWindows = g_sci->getPlatform() == Common::kPlatformWindows; - if (argc == 0 && getSciVersion() < SCI_VERSION_2) { + if (argc == 0) { // This is called in KQ5CD with no parameters, where it seems to do some // graphics driver check. This kernel function didn't have subfunctions // then. If 0 is returned, the game functions normally, otherwise all // the animations show up like a slideshow (e.g. in the intro). So we - // return 0. However, the behavior changed for kPlatform with no - // parameters in SCI32. + // return 0. return NULL_REG; } @@ -554,30 +583,23 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { uint16 operation = (argc == 0) ? 0 : argv[0].toUint16(); switch (operation) { - case kPlatformCDSpeed: - // TODO: Returns CD Speed? - warning("STUB: kPlatform(CDSpeed)"); - break; - case kPlatformUnk2: - // Always returns 2 - return make_reg(0, 2); - case kPlatformCDCheck: - // TODO: Some sort of CD check? - warning("STUB: kPlatform(CDCheck)"); - break; - case kPlatformUnk0: + case kPlatformUnknown: // For Mac versions, kPlatform(0) with other args has more functionality if (g_sci->getPlatform() == Common::kPlatformMacintosh && argc > 1) return kMacPlatform(s, argc - 1, argv + 1); // Otherwise, fall through case kPlatformGetPlatform: - return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS); - case kPlatformUnk5: + if (isWindows) + return make_reg(0, kSciPlatformWindows); + else if (g_sci->getPlatform() == Common::kPlatformMacintosh) + return make_reg(0, kSciPlatformMacintosh); + else + return make_reg(0, kSciPlatformDOS); + case kPlatformUnknown5: // This case needs to return the opposite of case 6 to get hires graphics return make_reg(0, !isWindows); case kPlatformIsHiRes: - return make_reg(0, isWindows); - case kPlatformIsItWindows: + case kPlatformWin311OrHigher: return make_reg(0, isWindows); default: error("Unsupported kPlatform operation %d", operation); @@ -586,6 +608,43 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 +reg_t kPlatform32(EngineState *s, int argc, reg_t *argv) { + enum Operation { + kGetPlatform = 0, + kGetCDSpeed = 1, + kGetColorDepth = 2, + kGetCDDrive = 3 + }; + + const Operation operation = argc > 0 ? (Operation)argv[0].toSint16() : kGetPlatform; + + switch (operation) { + case kGetPlatform: + switch (g_sci->getPlatform()) { + case Common::kPlatformDOS: + return make_reg(0, kSciPlatformDOS); + case Common::kPlatformWindows: + return make_reg(0, kSciPlatformWindows); + case Common::kPlatformMacintosh: + // For Mac versions, kPlatform(0) with other args has more functionality + if (argc > 1) + return kMacPlatform(s, argc - 1, argv + 1); + else + return make_reg(0, kSciPlatformMacintosh); + default: + error("Unknown platform %d", g_sci->getPlatform()); + } + case kGetColorDepth: + return make_reg(0, /* 256 color */ 2); + case kGetCDSpeed: + case kGetCDDrive: + default: + return make_reg(0, 0); + } +} +#endif + reg_t kEmpty(EngineState *s, int argc, reg_t *argv) { // Placeholder for empty kernel functions which are still called from the // engine scripts (like the empty kSetSynonyms function in SCI1.1). This @@ -597,18 +656,28 @@ reg_t kEmpty(EngineState *s, int argc, reg_t *argv) { reg_t kStub(EngineState *s, int argc, reg_t *argv) { Kernel *kernel = g_sci->getKernel(); int kernelCallNr = -1; + int kernelSubCallNr = -1; Common::List<ExecStack>::const_iterator callIterator = s->_executionStack.end(); if (callIterator != s->_executionStack.begin()) { callIterator--; ExecStack lastCall = *callIterator; - kernelCallNr = lastCall.debugSelector; + kernelCallNr = lastCall.debugKernelFunction; + kernelSubCallNr = lastCall.debugKernelSubFunction; + } + + Common::String warningMsg; + if (kernelSubCallNr == -1) { + warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) + + Common::String::format("[%x]", kernelCallNr); + } else { + warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr, kernelSubCallNr) + + Common::String::format("[%x:%x]", kernelCallNr, kernelSubCallNr); + } - Common::String warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) + - Common::String::format("[%x]", kernelCallNr) + - " invoked. Params: " + - Common::String::format("%d", argc) + " ("; + warningMsg += " invoked. Params: " + + Common::String::format("%d", argc) + " ("; for (int i = 0; i < argc; i++) { warningMsg += Common::String::format("%04x:%04x", PRINT_REG(argv[i])); diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 5b2245e84d..937b1cfc2f 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -326,7 +326,7 @@ static void draw_line(EngineState *s, Common::Point p1, Common::Point p2, int ty p2.y = CLIP<int16>(p2.y, 0, height - 1); assert(type >= 0 && type <= 3); - g_sci->_gfxPaint->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255); + g_sci->_gfxPaint16->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255); } static void draw_point(EngineState *s, Common::Point p, int start, int width, int height) { @@ -1397,10 +1397,8 @@ static reg_t allocateOutputArray(SegManager *segMan, int size) { #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { - SciArray<reg_t> *array = segMan->allocateArray(&addr); + SciArray *array = segMan->allocateArray(kArrayTypeInt16, size * 2, &addr); assert(array); - array->setType(0); - array->setSize(size * 2); return addr; } #endif @@ -1943,14 +1941,14 @@ static int liesBefore(const Vertex *v, const Common::Point &p1, const Common::Po // indexp1/vertexp1 on the polygon being merged. // It ends with the point intersection2, being the analogous intersection. struct Patch { - unsigned int indexw1; - unsigned int indexp1; + uint32 indexw1; + uint32 indexp1; const Vertex *vertexw1; const Vertex *vertexp1; Common::Point intersection1; - unsigned int indexw2; - unsigned int indexp2; + uint32 indexw2; + uint32 indexp2; const Vertex *vertexw2; const Vertex *vertexp2; Common::Point intersection2; @@ -1960,7 +1958,7 @@ struct Patch { // Check if the given vertex on the work polygon is bypassed by this patch. -static bool isVertexCovered(const Patch &p, unsigned int wi) { +static bool isVertexCovered(const Patch &p, uint32 wi) { // / v (outside) // ---w1--1----p----w2--2---- @@ -2402,7 +2400,7 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) { // Copy work.vertices into arrayRef Vertex *vertex; - unsigned int n = 0; + uint32 n = 0; CLIST_FOREACH(vertex, &work.vertices) { if (vertex == work.vertices._head || vertex->v != vertex->_prev->v) writePoint(arrayRef, n++, vertex->v); diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index 303de079aa..0e29ccf783 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -238,8 +238,8 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { // initialized to 0, whereas it's 6 in other versions. Thus, we assign it // to 6 here, fixing the speed of the introduction. Refer to bug #3102071. if (g_sci->getGameId() == GID_PQ2 && script == 200 && - s->variables[VAR_GLOBAL][3].isNull()) { - s->variables[VAR_GLOBAL][3] = make_reg(0, 6); + s->variables[VAR_GLOBAL][kGlobalVarSpeed].isNull()) { + s->variables[VAR_GLOBAL][kGlobalVarSpeed] = make_reg(0, 6); } return make_reg(scriptSeg, address); @@ -260,9 +260,6 @@ reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv) { if (argc != 2) { return s->r_acc; } else { - // This exists in the KQ5CD and GK1 interpreter. We know it is used - // when GK1 starts up, before the Sierra logo. - warning("kDisposeScript called with 2 parameters, still untested"); return argv[1]; } } diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 398a623286..ed53b8d52f 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -26,7 +26,11 @@ #include "sci/engine/kernel.h" #include "sci/engine/vm.h" // for Object #include "sci/sound/audio.h" +#ifdef ENABLE_SCI32 +#include "sci/sound/audio32.h" +#endif #include "sci/sound/soundcmd.h" +#include "sci/sound/sync.h" #include "audio/mixer.h" #include "common/system.h" @@ -46,7 +50,6 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) { CREATE_DOSOUND_FORWARD(DoSoundInit) CREATE_DOSOUND_FORWARD(DoSoundPlay) -CREATE_DOSOUND_FORWARD(DoSoundRestore) CREATE_DOSOUND_FORWARD(DoSoundDispose) CREATE_DOSOUND_FORWARD(DoSoundMute) CREATE_DOSOUND_FORWARD(DoSoundStop) @@ -61,13 +64,41 @@ CREATE_DOSOUND_FORWARD(DoSoundUpdateCues) CREATE_DOSOUND_FORWARD(DoSoundSendMidi) CREATE_DOSOUND_FORWARD(DoSoundGlobalReverb) CREATE_DOSOUND_FORWARD(DoSoundSetHold) -CREATE_DOSOUND_FORWARD(DoSoundDummy) CREATE_DOSOUND_FORWARD(DoSoundGetAudioCapability) CREATE_DOSOUND_FORWARD(DoSoundSuspend) CREATE_DOSOUND_FORWARD(DoSoundSetVolume) CREATE_DOSOUND_FORWARD(DoSoundSetPriority) CREATE_DOSOUND_FORWARD(DoSoundSetLoop) +#ifdef ENABLE_SCI32 +reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) { + // Phantasmagoria Mac (and seemingly no other game (!)) uses this + // cutdown version of kDoSound. + + switch (argv[0].toUint16()) { + case 0: + return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc); + case 2: + return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc); + case 3: + return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc); + case 4: + return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc); + case 5: + return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc); + case 8: + return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc); + case 9: + return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc); + case 10: + return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc); + } + + error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16()); + return s->r_acc; +} +#endif + reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case kSciAudioPlay: { @@ -113,7 +144,8 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) { } /** - * Used for speech playback and digital soundtracks in CD games + * Used for speech playback and digital soundtracks in CD games. + * This is the SCI16 version; SCI32 is handled separately. */ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { // JonesCD uses different functions based on the cdaudio.map file @@ -184,14 +216,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { int16 volume = argv[1].toUint16(); volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX); debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume); -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { - int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2; - volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX); - mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2); - return make_reg(0, volumePrev); - } else -#endif mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2); break; } @@ -232,12 +256,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { if (getSciVersion() <= SCI_VERSION_1_1) { debugC(kDebugLevelSound, "kDoAudio: CD audio subop"); return kDoCdAudio(s, argc - 1, argv + 1); -#ifdef ENABLE_SCI32 - } else { - // TODO: This isn't CD Audio in SCI32 anymore - warning("kDoAudio: Unhandled case 10, %d extra arguments passed", argc - 1); - break; -#endif } // 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C @@ -286,14 +304,12 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { } reg_t kDoSync(EngineState *s, int argc, reg_t *argv) { - SegManager *segMan = s->_segMan; switch (argv[0].toUint16()) { case kSciAudioSyncStart: { ResourceId id; - g_sci->_audio->stopSoundSync(); + g_sci->_sync->stop(); - // Load sound sync resource and lock it if (argc == 3) { id = ResourceId(kResourceTypeSync, argv[2].toUint16()); } else if (argc == 7) { @@ -304,14 +320,14 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } - g_sci->_audio->setSoundSync(id, argv[1], segMan); + g_sci->_sync->start(id, argv[1]); break; } case kSciAudioSyncNext: - g_sci->_audio->doSoundSync(argv[1], segMan); + g_sci->_sync->next(argv[1]); break; case kSciAudioSyncStop: - g_sci->_audio->stopSoundSync(); + g_sci->_sync->stop(); break; default: error("DoSync: Unhandled subfunction %d", argv[0].toUint16()); @@ -321,6 +337,155 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 +reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, 0); +} + +reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv) { + return g_sci->_audio32->kernelPlay(false, argc, argv); +} + +reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv) { + return g_sci->_audio32->kernelPlay(true, argc, argv); +} + +reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv) { + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); + return make_reg(0, g_sci->_audio32->stop(channelIndex)); +} + +reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv) { + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); + return make_reg(0, g_sci->_audio32->pause(channelIndex)); +} + +reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv) { + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); + return make_reg(0, g_sci->_audio32->resume(channelIndex)); +} + +reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv) { + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); + return make_reg(0, g_sci->_audio32->getPosition(channelIndex)); +} + +reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv) { + // NOTE: In the original engine this would set the hardware + // DSP sampling rate; ScummVM mixer does not need this, so + // we only store the value to satisfy engine compatibility. + + if (argc > 0) { + const uint16 sampleRate = argv[0].toUint16(); + if (sampleRate != 0) { + g_sci->_audio32->setSampleRate(sampleRate); + } + } + + return make_reg(0, g_sci->_audio32->getSampleRate()); +} + +reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv) { + const int16 volume = argc > 0 ? argv[0].toSint16() : -1; + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 1, argc > 2 ? argv[2] : NULL_REG); + + if (volume != -1) { + g_sci->_audio32->setVolume(channelIndex, volume); + } + + return make_reg(0, g_sci->_audio32->getVolume(channelIndex)); +} + +reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, 1); +} + +reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv) { + // NOTE: In the original engine this would set the hardware + // DSP bit depth; ScummVM mixer does not need this, so + // we only store the value to satisfy engine compatibility. + + if (argc > 0) { + const uint16 bitDepth = argv[0].toUint16(); + if (bitDepth != 0) { + g_sci->_audio32->setBitDepth(bitDepth); + } + } + + return make_reg(0, g_sci->_audio32->getBitDepth()); +} + +reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv) { + if (argc > 0) { + g_sci->_audio32->setAttenuatedMixing(argv[0].toUint16()); + } + + return make_reg(0, g_sci->_audio32->getAttenuatedMixing()); +} + +reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv) { + // NOTE: In the original engine this would set the hardware + // DSP stereo output; ScummVM mixer does not need this, so + // we only store the value to satisfy engine compatibility. + + if (argc > 0) { + const int16 numChannels = argv[0].toSint16(); + if (numChannels != 0) { + g_sci->_audio32->setNumOutputChannels(numChannels); + } + } + + return make_reg(0, g_sci->_audio32->getNumOutputChannels()); +} + +reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv) { + // NOTE: In the original engine this would cause audio + // data for new channels to be preloaded to memory when + // the channel was initialized; we do not need this, so + // we only store the value to satisfy engine compatibility. + + if (argc > 0) { + g_sci->_audio32->setPreload(argv[0].toUint16()); + } + + return make_reg(0, g_sci->_audio32->getPreload()); +} + +reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv) { + if (argc < 4) { + return make_reg(0, 0); + } + + // NOTE: Sierra did a nightmarish hack here, temporarily replacing + // the argc of the kernel arguments with 2 and then restoring it + // after findChannelByArgs was called. + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(2, argv, 0, argc > 5 ? argv[5] : NULL_REG); + + const int16 volume = argv[1].toSint16(); + const int16 speed = argv[2].toSint16(); + const int16 steps = argv[3].toSint16(); + const bool stopAfterFade = argc > 4 ? (bool)argv[4].toUint16() : false; + + return make_reg(0, g_sci->_audio32->fadeChannel(channelIndex, volume, speed, steps, stopAfterFade)); +} + +reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_audio32->hasSignal()); +} + +reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv) { + const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc == 3 ? argv[2] : NULL_REG); + + const bool loop = argv[0].toSint16() != 0 && argv[0].toSint16() != 1; + + g_sci->_audio32->setLoop(channelIndex, loop); + return s->r_acc; +} reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) { // This is used by script 90 of MUMG Deluxe from the main menu to toggle @@ -335,33 +500,6 @@ reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) { - // Phantasmagoria Mac (and seemingly no other game (!)) uses this - // cutdown version of kDoSound. - - switch (argv[0].toUint16()) { - case 0: - return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc); - case 2: - return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc); - case 3: - return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc); - case 4: - return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc); - case 5: - return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc); - case 8: - return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc); - case 9: - return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc); - case 10: - return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc); - } - - error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16()); - return s->r_acc; -} - #endif } // End of namespace Sci diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 310e38dbd1..ab1f0210e7 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -202,11 +202,6 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { } -#define ALIGN_NONE 0 -#define ALIGN_RIGHT 1 -#define ALIGN_LEFT -1 -#define ALIGN_CENTER 2 - /* Format(targ_address, textresnr, index_inside_res, ...) ** or ** Format(targ_address, heap_text_addr, ...) @@ -214,6 +209,13 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { ** the supplied parameters and writes it to the targ_address. */ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { + enum { + ALIGN_NONE = 0, + ALIGN_RIGHT = 1, + ALIGN_LEFT = -1, + ALIGN_CENTER = 2 + }; + uint16 *arguments; reg_t dest = argv[0]; int maxsize = 4096; /* Arbitrary... */ @@ -301,12 +303,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { case 's': { /* Copy string */ reg_t reg = argv[startarg + paramindex]; -#ifdef ENABLE_SCI32 - // If the string is a string object, get to the actual string in the data selector - if (s->_segMan->isObject(reg)) - reg = readSelector(s->_segMan, reg, SELECTOR(data)); -#endif - Common::String tempsource = g_sci->getKernel()->lookupText(reg, arguments[paramindex + 1]); int slen = strlen(tempsource.c_str()); @@ -379,12 +375,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { case 'u': unsignedVar = true; case 'd': { /* Copy decimal */ - // In the new SCI2 kString function, %d is used for unsigned - // integers. An example is script 962 in Shivers - it uses %d - // to create file names. - if (getSciVersion() >= SCI_VERSION_2) - unsignedVar = true; - /* int templen; -- unused atm */ const char *format_string = "%d"; @@ -437,14 +427,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { *target = 0; /* Terminate string */ -#ifdef ENABLE_SCI32 - // Resize SCI32 strings if necessary - if (getSciVersion() >= SCI_VERSION_2) { - SciString *string = s->_segMan->lookupString(dest); - string->setSize(strlen(targetbuf) + 1); - } -#endif - s->_segMan->strcpy(dest, targetbuf); return dest; /* Return target addr */ @@ -456,31 +438,15 @@ reg_t kStrLen(EngineState *s, int argc, reg_t *argv) { reg_t kGetFarText(EngineState *s, int argc, reg_t *argv) { - Resource *textres = g_sci->getResMan()->findResource(ResourceId(kResourceTypeText, argv[0].toUint16()), 0); - char *seeker; - int counter = argv[1].toUint16(); - - if (!textres) { - error("text.%d does not exist", argv[0].toUint16()); - return NULL_REG; - } - - seeker = (char *)textres->data; - - // The second parameter (counter) determines the number of the string - // inside the text resource. - while (counter--) { - while (*seeker++) - ; - } + const Common::String text = g_sci->getKernel()->lookupText(make_reg(0, argv[0].toUint16()), argv[1].toUint16()); // If the third argument is NULL, allocate memory for the destination. This // occurs in SCI1 Mac games. The memory will later be freed by the game's // scripts. if (argv[2] == NULL_REG) - s->_segMan->allocDynmem(strlen(seeker) + 1, "Mac FarText", &argv[2]); + s->_segMan->allocDynmem(text.size() + 1, "Mac FarText", &argv[2]); - s->_segMan->strcpy(argv[2], seeker); // Copy the string and get return value + s->_segMan->strcpy(argv[2], text.c_str()); // Copy the string and get return value return argv[2]; } @@ -661,248 +627,240 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 -reg_t kText(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: - return kTextSize(s, argc - 1, argv + 1); - default: - // TODO: Other subops here too, perhaps kTextColors and kTextFonts - warning("kText(%d)", argv[0].toUint16()); - break; - } - - return s->r_acc; +reg_t kString(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); } -// TODO: there is an unused second argument, happens at least in LSL6 right during the intro reg_t kStringNew(EngineState *s, int argc, reg_t *argv) { reg_t stringHandle; - SciString *string = s->_segMan->allocateString(&stringHandle); - string->setSize(argv[0].toUint16()); - - // Make sure the first character is a null character - if (string->getSize() > 0) - string->setValue(0, 0); - + const uint16 size = argv[0].toUint16(); + s->_segMan->allocateArray(kArrayTypeString, size, &stringHandle); return stringHandle; } -reg_t kStringSize(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, s->_segMan->getString(argv[0]).size()); -} +reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv) { + const uint16 index = argv[1].toUint16(); -// At (return value at an index) -reg_t kStringAt(EngineState *s, int argc, reg_t *argv) { - // Note that values are put in bytes to avoid sign extension - if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) { - SciString *string = s->_segMan->lookupString(argv[0]); - byte val = string->getRawData()[argv[1].toUint16()]; - return make_reg(0, val); - } else { - Common::String string = s->_segMan->getString(argv[0]); - byte val = string[argv[1].toUint16()]; - return make_reg(0, val); - } -} - -// Atput (put value at an index) -reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv) { - SciString *string = s->_segMan->lookupString(argv[0]); - - uint32 index = argv[1].toUint16(); - uint32 count = argc - 2; + // Game scripts may contain static raw string data + if (!s->_segMan->isArray(argv[0])) { + const Common::String string = s->_segMan->getString(argv[0]); + if (index >= string.size()) { + return make_reg(0, 0); + } - if (index + count > 65535) - return NULL_REG; + return make_reg(0, (byte)string[index]); + } - if (string->getSize() < index + count) - string->setSize(index + count); + SciArray &array = *s->_segMan->lookupArray(argv[0]); - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[i + 2].toUint16()); + if (index >= array.size()) { + return make_reg(0, 0); + } - return argv[0]; // We also have to return the handle + return array.getAsID(index); } reg_t kStringFree(EngineState *s, int argc, reg_t *argv) { - // Freeing of strings is handled by the garbage collector + if (!argv[0].isNull()) { + s->_segMan->freeArray(argv[0]); + } return s->r_acc; } -reg_t kStringFill(EngineState *s, int argc, reg_t *argv) { - SciString *string = s->_segMan->lookupString(argv[0]); - uint16 index = argv[1].toUint16(); +reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) { + const Common::String string1 = s->_segMan->getString(argv[0]); + const Common::String string2 = s->_segMan->getString(argv[1]); - // A count of -1 means fill the rest of the array - uint16 count = argv[2].toSint16() == -1 ? string->getSize() - index : argv[2].toUint16(); - uint16 stringSize = string->getSize(); + int result; + if (argc == 3) { + result = strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16()); + } else { + result = strcmp(string1.c_str(), string2.c_str()); + } - if (stringSize < index + count) - string->setSize(index + count); + return make_reg(0, (result > 0) - (result < 0)); +} - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[3].toUint16()); +reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) { + if (s->_segMan->isObject(argv[0])) { + return readSelector(s->_segMan, argv[0], SELECTOR(data)); + } return argv[0]; } -reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) { - const char *string2 = 0; - uint32 string2Size = 0; - Common::String string; +reg_t kStringLength(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, s->_segMan->getString(argv[0]).size()); +} - if (argv[2].getSegment() == s->_segMan->getStringSegmentId()) { - SciString *sstr; - sstr = s->_segMan->lookupString(argv[2]); - string2 = sstr->getRawData(); - string2Size = sstr->getSize(); - } else { - string = s->_segMan->getString(argv[2]); - string2 = string.c_str(); - string2Size = string.size() + 1; +namespace { + bool isFlag(const char c) { + return strchr("-+ 0#", c); } - uint32 index1 = argv[1].toUint16(); - uint32 index2 = argv[3].toUint16(); - - if (argv[0] == argv[2]) { - // source and destination string are one and the same - if (index1 == index2) { - // even same index? ignore this call - // Happens in KQ7, when starting a chapter - return argv[0]; - } - // TODO: this will crash, when setSize() is triggered later - // we need to exactly replicate original interpreter behavior - warning("kString(Copy): source is the same as destination string"); + bool isPrecision(const char c) { + return strchr(".0123456789*", c); } - // The original engine ignores bad copies too - if (index2 > string2Size) - return NULL_REG; - - // A count of -1 means fill the rest of the array - uint32 count = argv[4].toSint16() == -1 ? string2Size - index2 + 1 : argv[4].toUint16(); -// reg_t strAddress = argv[0]; + bool isWidth(const char c) { + return strchr("0123456789*", c); + } - SciString *string1 = s->_segMan->lookupString(argv[0]); - //SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress); + bool isLength(const char c) { + return strchr("hjlLtz", c); + } - if (string1->getSize() < index1 + count) - string1->setSize(index1 + count); + bool isType(const char c) { + return strchr("dsuxXaAceEfFgGinop", c); + } - // Note: We're accessing from c_str() here because the - // string's size ignores the trailing 0 and therefore - // triggers an assert when doing string2[i + index2]. - for (uint16 i = 0; i < count; i++) - string1->setValue(i + index1, string2[i + index2]); + bool isSignedType(const char type) { + // For whatever reason, %d ends up being treated as unsigned in SSCI + return type == 'i'; + } - return argv[0]; -} + bool isUnsignedType(const char type) { + return strchr("duxXoc", type); + } -reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) { - Common::String string1 = argv[0].isNull() ? "" : s->_segMan->getString(argv[0]); - Common::String string2 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]); + bool isStringType(const char type) { + return type == 's'; + } - if (argc == 3) // Strncmp - return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16())); - else // Strcmp - return make_reg(0, strcmp(string1.c_str(), string2.c_str())); -} + Common::String readPlaceholder(const char *&in, reg_t arg) { + const char *const start = in; -// was removed for SCI2.1 Late+ -reg_t kStringDup(EngineState *s, int argc, reg_t *argv) { - reg_t stringHandle; + assert(*in == '%'); + ++in; - SciString *dupString = s->_segMan->allocateString(&stringHandle); + while (isFlag(*in)) { + ++in; + } + while (isWidth(*in)) { + ++in; + } + while (isPrecision(*in)) { + ++in; + } + while (isLength(*in)) { + ++in; + } - if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) { - *dupString = *s->_segMan->lookupString(argv[0]); - } else { - dupString->fromString(s->_segMan->getString(argv[0])); + char format[64]; + format[0] = '\0'; + const char type = *in++; + Common::strlcpy(format, start, MIN<size_t>(64, in - start + 1)); + + if (isType(type)) { + if (isSignedType(type)) { + const int value = arg.toSint16(); + return Common::String::format(format, value); + } else if (isUnsignedType(type)) { + const uint value = arg.toUint16(); + return Common::String::format(format, value); + } else if (isStringType(type)) { + Common::String value; + SegManager *segMan = g_sci->getEngineState()->_segMan; + if (segMan->isObject(arg)) { + value = segMan->getString(readSelector(segMan, arg, SELECTOR(data))); + } else { + value = segMan->getString(arg); + } + return Common::String::format(format, value.c_str()); + } else { + error("Unsupported format type %c", type); + } + } else { + return Common::String::format("%s", format); + } } - - return stringHandle; } -// was removed for SCI2.1 Late+ -reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) { - if (!s->_segMan->isHeapObject(argv[0])) - return argv[0]; +Common::String format(const Common::String &source, int argc, const reg_t *argv) { + Common::String out; + const char *in = source.c_str(); + int argIndex = 0; + while (*in != '\0') { + if (*in == '%') { + if (in[1] == '%') { + in += 2; + out += "%"; + continue; + } - return readSelector(s->_segMan, argv[0], SELECTOR(data)); -} + if (argIndex < argc) { + out += readPlaceholder(in, argv[argIndex++]); + } else { + out += readPlaceholder(in, NULL_REG); + } + } else { + out += *in++; + } + } -reg_t kStringLen(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, s->_segMan->strlen(argv[0])); + return out; } -reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv) { +reg_t kStringFormat(EngineState *s, int argc, reg_t *argv) { reg_t stringHandle; - s->_segMan->allocateString(&stringHandle); - - reg_t *adjustedArgs = new reg_t[argc + 1]; - adjustedArgs[0] = stringHandle; - memcpy(&adjustedArgs[1], argv, argc * sizeof(reg_t)); - - kFormat(s, argc + 1, adjustedArgs); - delete[] adjustedArgs; + SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, 0, &stringHandle); + reg_t source = argv[0]; + // Str objects may be passed in place of direct references to string data + if (s->_segMan->isObject(argv[0])) { + source = readSelector(s->_segMan, argv[0], SELECTOR(data)); + } + target.fromString(format(s->_segMan->getString(source), argc - 1, argv + 1)); return stringHandle; } -reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv) { - return kFormat(s, argc, argv); +reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv) { + SciArray &target = *s->_segMan->lookupArray(argv[0]); + reg_t source = argv[1]; + // Str objects may be passed in place of direct references to string data + if (s->_segMan->isObject(argv[1])) { + source = readSelector(s->_segMan, argv[1], SELECTOR(data)); + } + target.fromString(format(s->_segMan->getString(source), argc - 2, argv + 2)); + return argv[0]; } -reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv) { - Common::String string = s->_segMan->getString(argv[0]); - return make_reg(0, (uint16)atoi(string.c_str())); +reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, (uint16)s->_segMan->getString(argv[0]).asUint64()); } reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) { - Common::String string = s->_segMan->getString(argv[0]); - - string.trim(); - // TODO: Second parameter (bitfield, trim from left, right, center) - warning("kStringTrim (%d)", argv[1].getOffset()); - s->_segMan->strcpy(argv[0], string.c_str()); - return NULL_REG; + SciArray &array = *s->_segMan->lookupArray(argv[0]); + const int8 flags = argv[1].toSint16(); + const char showChar = argc > 2 ? argv[2].toSint16() : '\0'; + array.trim(flags, showChar); + return s->r_acc; } -reg_t kStringUpper(EngineState *s, int argc, reg_t *argv) { +reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv) { Common::String string = s->_segMan->getString(argv[0]); - string.toUppercase(); s->_segMan->strcpy(argv[0], string.c_str()); - return NULL_REG; + return argv[0]; } -reg_t kStringLower(EngineState *s, int argc, reg_t *argv) { +reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv) { Common::String string = s->_segMan->getString(argv[0]); - string.toLowercase(); s->_segMan->strcpy(argv[0], string.c_str()); - return NULL_REG; -} - -// Possibly kStringTranslate? -reg_t kStringTrn(EngineState *s, int argc, reg_t *argv) { - warning("kStringTrn (argc = %d)", argc); - return NULL_REG; + return argv[0]; } -// Possibly kStringTranslateExclude? -reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv) { - warning("kStringTrnExclude (argc = %d)", argc); - return NULL_REG; +reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv) { + error("TODO: kStringReplaceSubstring not implemented"); + return argv[3]; } -reg_t kString(EngineState *s, int argc, reg_t *argv) { - if (!s) - return make_reg(0, getSciVersion()); - error("not supposed to call this"); +reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv) { + error("TODO: kStringReplaceSubstringEx not implemented"); + return argv[3]; } - #endif } // End of namespace Sci diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index 8db0c542eb..11378d7647 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -40,7 +40,8 @@ #include "video/qt_decoder.h" #include "sci/video/seq_decoder.h" #ifdef ENABLE_SCI32 -#include "video/coktel_decoder.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/video32.h" #include "sci/video/robot_decoder.h" #endif @@ -60,40 +61,21 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth(); uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight(); - videoState.fileName.toLowercase(); - bool isVMD = videoState.fileName.hasSuffix(".vmd"); - - if (screenWidth == 640 && width <= 320 && height <= 240 && ((videoState.flags & kDoubled) || !isVMD)) { + if (screenWidth == 640 && width <= 320 && height <= 240) { width *= 2; height *= 2; pitch *= 2; scaleBuffer = new byte[width * height * bytesPerPixel]; } - uint16 x, y; - - // Sanity check... - if (videoState.x > 0 && videoState.y > 0 && isVMD) { - x = videoState.x; - y = videoState.y; - - if (x + width > screenWidth || y + height > screenHeight) { - // Happens in the Lighthouse demo - warning("VMD video won't fit on screen, centering it instead"); - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - } - } else { - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - } + uint16 x = (screenWidth - width) / 2; + uint16 y = (screenHeight - height) / 2; bool skipVideo = false; - EngineState *s = g_sci->getEngineState(); if (videoDecoder->hasDirtyPalette()) { - const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; - g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + const byte *palette = videoDecoder->getPalette(); + g_system->getPaletteManager()->setPalette(palette, 0, 255); } while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { @@ -102,7 +84,7 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { if (frame) { if (scaleBuffer) { - // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows + // TODO: Probably should do aspect ratio correction in KQ6 g_sci->_gfxScreen->scale2x((const byte *)frame->getPixels(), scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight(), bytesPerPixel); g_system->copyRectToScreen(scaleBuffer, pitch, x, y, width, height); } else { @@ -110,8 +92,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { } if (videoDecoder->hasDirtyPalette()) { - const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; - g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + const byte *palette = videoDecoder->getPalette(); + g_system->getPaletteManager()->setPalette(palette, 0, 255); } g_system->updateScreen(); @@ -180,20 +162,9 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { // TODO: This appears to be some sort of subop. case 0 contains the string // for the video, so we'll just play it from there for now. -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { - // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to - // follow SCI1.1/2. - if (argv[0].toUint16() != 1) - error("SCI2.1 kShowMovie argv[0] not 1"); - argv++; - argc--; - } -#endif switch (argv[0].toUint16()) { case 0: { Common::String filename = s->_segMan->getString(argv[1]); - videoDecoder = new Video::AVIDecoder(); if (filename.equalsIgnoreCase("gk2a.avi")) { // HACK: Switch to 16bpp graphics for Indeo3. @@ -208,6 +179,8 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { } } + videoDecoder = new Video::AVIDecoder(); + if (!videoDecoder->loadFile(filename.c_str())) { warning("Failed to open movie file %s", filename.c_str()); delete videoDecoder; @@ -229,7 +202,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { // We also won't be copying the screen to the SCI screen... if (g_system->getScreenFormat().bytesPerPixel != 1) initGraphics(screenWidth, screenHeight, screenWidth > 320); - else { + else if (getSciVersion() < SCI_VERSION_2) { g_sci->_gfxScreen->kernelSyncWithFramebuffer(); g_sci->_gfxPalette16->kernelSyncScreenPalette(); } @@ -242,160 +215,255 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 +reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) { + Common::String fileName = s->_segMan->getString(argv[0]); + const int16 numTicks = argv[1].toSint16(); + const int16 x = argc > 3 ? argv[2].toSint16() : 0; + const int16 y = argc > 3 ? argv[3].toSint16() : 0; + + g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y); + + return s->r_acc; +} reg_t kRobot(EngineState *s, int argc, reg_t *argv) { - int16 subop = argv[0].toUint16(); - - switch (subop) { - case 0: { // init - int id = argv[1].toUint16(); - reg_t obj = argv[2]; - int16 flag = argv[3].toSint16(); - int16 x = argv[4].toUint16(); - int16 y = argv[5].toUint16(); - warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y); - g_sci->_robotDecoder->load(id); - g_sci->_robotDecoder->start(); - g_sci->_robotDecoder->setPos(x, y); - } - break; - case 1: // LSL6 hires (startup) - // TODO - return NULL_REG; // an integer is expected - case 4: { // start - we don't really have a use for this one - //int id = argv[1].toUint16(); - //warning("kRobot(start), id %d", id); - } - break; - case 7: // unknown, called e.g. by Phantasmagoria - warning("kRobot(%d)", subop); - break; - case 8: // sync - //if (true) { // debug: automatically skip all robot videos - if (g_sci->_robotDecoder->endOfVideo()) { - g_sci->_robotDecoder->close(); - // Signal the engine scripts that the video is done - writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG); - } else { - writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG); - } - break; - default: - warning("kRobot(%d)", subop); - break; - } + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} +reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv) { + const GuiResourceId robotId = argv[0].toUint16(); + const reg_t plane = argv[1]; + const int16 priority = argv[2].toSint16(); + const int16 x = argv[3].toSint16(); + const int16 y = argv[4].toSint16(); + const int16 scale = argc > 5 ? argv[5].toSint16() : 128; + g_sci->_video32->getRobotPlayer().open(robotId, plane, priority, x, y, scale); + return make_reg(0, 0); +} +reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv) { + const uint16 frameNo = argv[0].toUint16(); + const uint16 newX = argc > 1 ? argv[1].toUint16() : (uint16)RobotDecoder::kUnspecified; + const uint16 newY = argc > 1 ? argv[2].toUint16() : (uint16)RobotDecoder::kUnspecified; + g_sci->_video32->getRobotPlayer().showFrame(frameNo, newX, newY, RobotDecoder::kUnspecified); return s->r_acc; } -reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { - uint16 operation = argv[0].toUint16(); - Video::VideoDecoder *videoDecoder = 0; - bool reshowCursor = g_sci->_gfxCursor->isVisible(); - Common::String warningMsg; +reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) { + Common::Rect frameRect; + const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect); - switch (operation) { - case 0: // init - s->_videoState.reset(); - s->_videoState.fileName = s->_segMan->derefString(argv[1]); + SciArray *outRect = s->_segMan->lookupArray(argv[0]); + reg_t values[4] = { + make_reg(0, frameRect.left), + make_reg(0, frameRect.top), + make_reg(0, frameRect.right - 1), + make_reg(0, frameRect.bottom - 1) }; + outRect->setElements(0, 4, values); - if (argc > 2 && argv[2] != NULL_REG) - warning("kPlayVMD: third parameter isn't 0 (it's %04x:%04x - %s)", PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2])); - break; - case 1: - { - // Set VMD parameters. Called with a maximum of 6 parameters: - // - // x, y, flags, gammaBoost, gammaFirst, gammaLast - // - // gammaBoost boosts palette colors in the range gammaFirst to - // gammaLast, but only if bit 4 in flags is set. Percent value such that - // 0% = no amplification These three parameters are optional if bit 4 is - // clear. Also note that the x, y parameters play subtle games if used - // with subfx 21. The subtleness has to do with creation of temporary - // planes and positioning relative to such planes. - - uint16 flags = argv[3].getOffset(); - Common::String flagspec; - - if (argc > 3) { - if (flags & kDoubled) - flagspec += "doubled "; - if (flags & kDropFrames) - flagspec += "dropframes "; - if (flags & kBlackLines) - flagspec += "blacklines "; - if (flags & kUnkBit3) - flagspec += "bit3 "; - if (flags & kGammaBoost) - flagspec += "gammaboost "; - if (flags & kHoldBlackFrame) - flagspec += "holdblack "; - if (flags & kHoldLastFrame) - flagspec += "holdlast "; - if (flags & kUnkBit7) - flagspec += "bit7 "; - if (flags & kStretch) - flagspec += "stretch"; - - warning("VMDFlags: %s", flagspec.c_str()); - - s->_videoState.flags = flags; - } + return make_reg(0, numFramesTotal); +} - warning("x, y: %d, %d", argv[1].getOffset(), argv[2].getOffset()); - s->_videoState.x = argv[1].getOffset(); - s->_videoState.y = argv[2].getOffset(); +reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().resume(); + return s->r_acc; +} - if (argc > 4 && flags & 16) - warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].getOffset(), argv[5].getOffset(), argv[6].getOffset()); - break; +reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusEnd); +} + +reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusPlaying); +} + +reg_t kRobotClose(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().close(); + return s->r_acc; +} + +reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv) { + writeSelectorValue(s->_segMan, argv[0], SELECTOR(signal), g_sci->_video32->getRobotPlayer().getCue()); + return s->r_acc; +} + +reg_t kRobotPause(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().pause(); + return s->r_acc; +} + +reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getFrameNo()); +} + +reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().setPriority(argv[0].toSint16()); + return s->r_acc; +} + +reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; } - case 6: // Play - videoDecoder = new Video::AdvancedVMDDecoder(); - if (s->_videoState.fileName.empty()) { - // Happens in Lighthouse - warning("kPlayVMD: Empty filename passed"); - return s->r_acc; - } + const Common::String fileName = s->_segMan->getString(argv[0]); + return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName)); +} - if (!videoDecoder->loadFile(s->_videoState.fileName)) { - warning("Could not open VMD %s", s->_videoState.fileName.c_str()); - break; - } +reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } - if (reshowCursor) - g_sci->_gfxCursor->kernelHide(); + const int16 x = argv[0].toSint16(); + const int16 y = argv[1].toSint16(); + const int16 width = argc > 3 ? argv[2].toSint16() : 0; + const int16 height = argc > 3 ? argv[3].toSint16() : 0; + return make_reg(0, g_sci->_video32->getAVIPlayer().init1x(x, y, width, height)); +} - playVideo(videoDecoder, s->_videoState); +reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) { + if (getSciVersion() == SCI_VERSION_2) { + AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags)); + } else { + // argv[0] is a broken movie ID + const int16 from = argc > 2 ? argv[1].toSint16() : 0; + const int16 to = argc > 2 ? argv[2].toSint16() : 0; + const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0; + const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false; + return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue)); + } +} - if (reshowCursor) - g_sci->_gfxCursor->kernelShow(); - break; - case 23: // set video palette range - s->_vmdPalStart = argv[1].toUint16(); - s->_vmdPalEnd = argv[2].toUint16(); - break; - case 14: - // Takes an additional integer parameter (e.g. 3) - case 16: - // Takes an additional parameter, usually 0 - case 21: - // Looks to be setting the video size and position. Called with 4 extra integer - // parameters (e.g. 86, 41, 235, 106) - default: - warningMsg = Common::String::format("PlayVMD - unsupported subop %d. Params: %d (", operation, argc); +reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getAVIPlayer().close()); +} - for (int i = 0; i < argc; i++) { - warningMsg += Common::String::format("%04x:%04x", PRINT_REG(argv[i])); - warningMsg += (i == argc - 1 ? ")" : ", "); - } +reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration()); +} - warning("%s", warningMsg.c_str()); - break; +reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; } + const uint16 frameNo = argv[0].toUint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo)); +} + +reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) { + const int defaultFlags = + AVIPlayer::kEventFlagEnd | + AVIPlayer::kEventFlagEscapeKey; + + // argv[0] is the movie number, which is not used by this method + const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags); + + return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags)); +} + +reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) { + // argv[0] is a broken movie ID + const int16 x = argv[1].toSint16(); + const int16 y = argv[2].toSint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().init2x(x, y)); +} + +reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv) { + const Common::String fileName = s->_segMan->getString(argv[0]); + // argv[1] is an optional cache size argument which we do not use + // const uint16 cacheSize = argc > 1 ? CLIP<int16>(argv[1].toSint16(), 16, 1024) : 0; + const VMDPlayer::OpenFlags flags = argc > 2 ? (VMDPlayer::OpenFlags)argv[2].toUint16() : VMDPlayer::kOpenFlagNone; + + return make_reg(0, g_sci->_video32->getVMDPlayer().open(fileName, flags)); +} + +reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv) { + const int16 x = argv[0].toSint16(); + const int16 y = argv[1].toSint16(); + const VMDPlayer::PlayFlags flags = argc > 2 ? (VMDPlayer::PlayFlags)argv[2].toUint16() : VMDPlayer::kPlayFlagNone; + int16 boostPercent; + int16 boostStartColor; + int16 boostEndColor; + if (argc > 5 && (flags & VMDPlayer::kPlayFlagBoost)) { + boostPercent = argv[3].toSint16(); + boostStartColor = argv[4].toSint16(); + boostEndColor = argv[5].toSint16(); + } else { + boostPercent = 0; + boostStartColor = -1; + boostEndColor = -1; + } + + g_sci->_video32->getVMDPlayer().init(x, y, flags, boostPercent, boostStartColor, boostEndColor); + + return make_reg(0, 0); +} + +reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getVMDPlayer().close()); +} + +reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getVMDPlayer().ignorePalettes(); + return s->r_acc; +} + +reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getVMDPlayer().getStatus()); +} + +reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) { + const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16(); + const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1; + const int16 yieldInterval = argc > 2 ? argv[2].toSint16() : -1; + return make_reg(0, g_sci->_video32->getVMDPlayer().kernelPlayUntilEvent(flags, lastFrameNo, yieldInterval)); +} + +reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getVMDPlayer().setShowCursor((bool)argv[0].toUint16()); + return s->r_acc; +} + +reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + Common::Rect blackoutArea; + blackoutArea.left = MAX<int16>(0, argv[0].toSint16()); + blackoutArea.top = MAX<int16>(0, argv[1].toSint16()); + blackoutArea.right = MIN<int16>(scriptWidth, argv[2].toSint16() + 1); + blackoutArea.bottom = MIN<int16>(scriptHeight, argv[3].toSint16() + 1); + g_sci->_video32->getVMDPlayer().setBlackoutArea(blackoutArea); + return s->r_acc; +} + +reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getVMDPlayer().restrictPalette(argv[0].toUint16(), argv[1].toUint16()); return s->r_acc; } diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index 5300b72b71..26ab9b47a5 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -333,12 +333,14 @@ void MessageState::popCursorStack() { error("Message: attempt to pop from empty stack"); } -int MessageState::hexDigitToInt(char h) { +int MessageState::hexDigitToWrongInt(char h) { + // Hex digits above 9 are incorrectly interpreted by SSCI as 11-16 instead + // of 10-15 because of a never-fixed typo if ((h >= 'A') && (h <= 'F')) - return h - 'A' + 10; + return h - 'A' + 11; if ((h >= 'a') && (h <= 'f')) - return h - 'a' + 10; + return h - 'a' + 11; if ((h >= '0') && (h <= '9')) return h - '0'; @@ -355,8 +357,8 @@ bool MessageState::stringHex(Common::String &outStr, const Common::String &inStr if (index + 2 >= inStr.size()) return false; - int digit1 = hexDigitToInt(inStr[index + 1]); - int digit2 = hexDigitToInt(inStr[index + 2]); + int digit1 = hexDigitToWrongInt(inStr[index + 1]); + int digit2 = hexDigitToWrongInt(inStr[index + 2]); // Check for hex if ((digit1 == -1) || (digit2 == -1)) @@ -439,21 +441,8 @@ Common::String MessageState::processString(const char *s) { void MessageState::outputString(reg_t buf, const Common::String &str) { #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { - if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_STRING) { - SciString *sciString = _segMan->lookupString(buf); - sciString->setSize(str.size() + 1); - for (uint32 i = 0; i < str.size(); i++) - sciString->setValue(i, str.c_str()[i]); - sciString->setValue(str.size(), 0); - } else if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_ARRAY) { - // Happens in the intro of LSL6, we are asked to write the string - // into an array - SciArray<reg_t> *sciString = _segMan->lookupArray(buf); - sciString->setSize(str.size() + 1); - for (uint32 i = 0; i < str.size(); i++) - sciString->setValue(i, make_reg(0, str.c_str()[i])); - sciString->setValue(str.size(), NULL_REG); - } + SciArray *sciString = _segMan->lookupArray(buf); + sciString->fromString(str); } else { #endif SegmentRef buffer_r = _segMan->dereference(buf); diff --git a/engines/sci/engine/message.h b/engines/sci/engine/message.h index ff76534d2d..5847e4767e 100644 --- a/engines/sci/engine/message.h +++ b/engines/sci/engine/message.h @@ -73,7 +73,7 @@ private: bool getRecord(CursorStack &stack, bool recurse, MessageRecord &record); void outputString(reg_t buf, const Common::String &str); Common::String processString(const char *s); - int hexDigitToInt(char h); + int hexDigitToWrongInt(char h); bool stringHex(Common::String &outStr, const Common::String &inStr, uint &index); bool stringLit(Common::String &outStr, const Common::String &inStr, uint &index); bool stringStage(Common::String &outStr, const Common::String &inStr, uint &index); diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp index 0626c084c1..0566d6955f 100644 --- a/engines/sci/engine/object.cpp +++ b/engines/sci/engine/object.cpp @@ -255,6 +255,8 @@ void Object::initSelectorsSci3(const byte *buf) { if (g_sci->getKernel()->getSelectorNamesSize() % 32) ++groups; + _mustSetViewVisible.resize(groups); + methods = properties = 0; // Selectors are divided into groups of 32, of which the first @@ -270,7 +272,9 @@ void Object::initSelectorsSci3(const byte *buf) { // This object actually has selectors belonging to this group int typeMask = READ_SCI11ENDIAN_UINT32(seeker); - for (int bit = 2; bit < 32; ++bit) { + _mustSetViewVisible[groupNr] = (typeMask & 1); + + for (int bit = 2; bit < 32; ++bit) { int value = READ_SCI11ENDIAN_UINT16(seeker + bit * 2); if (typeMask & (1 << bit)) { // Property ++properties; @@ -281,7 +285,8 @@ void Object::initSelectorsSci3(const byte *buf) { } } - } + } else + _mustSetViewVisible[groupNr] = false; } _variables.resize(properties); diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index cc9f5ebb52..74a908a810 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -47,10 +47,10 @@ enum infoSelectorFlags { * When set, indicates to game scripts that a screen * item can be updated. */ - kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ? + kInfoFlagViewVisible = 0x0008, /** - * When set, the object has an associated screen item in + * When set, the VM object has an associated ScreenItem in * the rendering tree. */ kInfoFlagViewInserted = 0x0010, @@ -262,6 +262,8 @@ public: bool initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass = true); void syncBaseObject(const byte *ptr) { _baseObj = ptr; } + bool mustSetViewVisibleSci3(int selector) const { return _mustSetViewVisible[selector/32]; } + private: void initSelectorsSci3(const byte *buf); @@ -278,6 +280,7 @@ private: reg_t _superClassPosSci3; /**< reg_t pointing to superclass for SCI3 */ reg_t _speciesSelectorSci3; /**< reg_t containing species "selector" for SCI3 */ reg_t _infoSelectorSci3; /**< reg_t containing info "selector" for SCI3 */ + Common::Array<bool> _mustSetViewVisible; /** cached bit of info to make lookup fast, SCI3 only */ }; diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 18cee3321f..720f6783ee 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -48,8 +48,10 @@ #include "sci/sound/music.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/palette32.h" +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" #endif namespace Sci { @@ -60,24 +62,87 @@ namespace Sci { #pragma mark - -// Experimental hack: Use syncWithSerializer to sync. By default, this assume -// the object to be synced is a subclass of Serializable and thus tries to invoke -// the saveLoadWithSerializer() method. But it is possible to specialize this -// template function to handle stuff that is not implementing that interface. -template<typename T> -void syncWithSerializer(Common::Serializer &s, T &obj) { +// These are serialization functions for various objects. + +void syncWithSerializer(Common::Serializer &s, Common::Serializable &obj) { + obj.saveLoadWithSerializer(s); +} + +// FIXME: Object could implement Serializable to make use of the function +// above. +void syncWithSerializer(Common::Serializer &s, Object &obj) { obj.saveLoadWithSerializer(s); } +void syncWithSerializer(Common::Serializer &s, reg_t &obj) { + // Segment and offset are accessed directly here + s.syncAsUint16LE(obj._segment); + s.syncAsUint16LE(obj._offset); +} + +void syncWithSerializer(Common::Serializer &s, synonym_t &obj) { + s.syncAsUint16LE(obj.replaceant); + s.syncAsUint16LE(obj.replacement); +} + +void syncWithSerializer(Common::Serializer &s, Class &obj) { + s.syncAsSint32LE(obj.script); + syncWithSerializer(s, obj.reg); +} + +void syncWithSerializer(Common::Serializer &s, List &obj) { + syncWithSerializer(s, obj.first); + syncWithSerializer(s, obj.last); +} + +void syncWithSerializer(Common::Serializer &s, Node &obj) { + syncWithSerializer(s, obj.pred); + syncWithSerializer(s, obj.succ); + syncWithSerializer(s, obj.key); + syncWithSerializer(s, obj.value); +} + +#pragma mark - + // By default, sync using syncWithSerializer, which in turn can easily be overloaded. template<typename T> struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> { - void operator()(Common::Serializer &s, T &obj) const { - //obj.saveLoadWithSerializer(s); + void operator()(Common::Serializer &s, T &obj, int) const { syncWithSerializer(s, obj); } }; +// Syncer for entries in a segment obj table +template<typename T> +struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> { + void operator()(Common::Serializer &s, typename T::Entry &entry, int index) const { + s.syncAsSint32LE(entry.next_free); + + bool hasData; + if (s.getVersion() >= 37) { + if (s.isSaving()) { + hasData = entry.data != nullptr; + } + s.syncAsByte(hasData); + } else { + hasData = (entry.next_free == index); + } + + if (hasData) { + if (s.isLoading()) { + entry.data = new typename T::value_type; + } + syncWithSerializer(s, *entry.data); + } else if (s.isLoading()) { + if (s.getVersion() < 37) { + typename T::value_type dummy; + syncWithSerializer(s, dummy); + } + entry.data = nullptr; + } + } +}; + /** * Sync a Common::Array using a Common::Serializer. * When saving, this writes the length of the array, then syncs (writes) all entries. @@ -102,9 +167,8 @@ struct ArraySyncer : Common::BinaryFunction<Common::Serializer, T, void> { if (s.isLoading()) arr.resize(len); - typename Common::Array<T>::iterator i; - for (i = arr.begin(); i != arr.end(); ++i) { - sync(s, *i); + for (uint i = 0; i < len; ++i) { + sync(s, arr[i], i); } } }; @@ -116,18 +180,10 @@ void syncArray(Common::Serializer &s, Common::Array<T> &arr) { sync(s, arr); } - -template<> -void syncWithSerializer(Common::Serializer &s, reg_t &obj) { - // Segment and offset are accessed directly here - s.syncAsUint16LE(obj._segment); - s.syncAsUint16LE(obj._offset); -} - -template<> -void syncWithSerializer(Common::Serializer &s, synonym_t &obj) { - s.syncAsUint16LE(obj.replaceant); - s.syncAsUint16LE(obj.replacement); +template<typename T, class Syncer> +void syncArray(Common::Serializer &s, Common::Array<T> &arr) { + ArraySyncer<T, Syncer> sync; + sync(s, arr); } void SegManager::saveLoadWithSerializer(Common::Serializer &s) { @@ -176,9 +232,8 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { } else if (type == SEG_TYPE_ARRAY) { // Set the correct segment for SCI32 arrays _arraysSegId = i; - } else if (type == SEG_TYPE_STRING) { - // Set the correct segment for SCI32 strings - _stringSegId = i; + } else if (s.getVersion() >= 36 && type == SEG_TYPE_BITMAP) { + _bitmapSegId = i; #endif } @@ -247,12 +302,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { } -template<> -void syncWithSerializer(Common::Serializer &s, Class &obj) { - s.syncAsSint32LE(obj.script); - syncWithSerializer(s, obj.reg); -} - static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) { s.syncString(obj.name); s.syncVersion(CURRENT_SAVEGAME_VERSION); @@ -281,6 +330,28 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) } s.syncAsUint32LE(obj.playTime); } + + // Some games require additional metadata to display their restore screens + // correctly + if (s.getVersion() >= 39) { + if (s.isSaving()) { + const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL]; + if (g_sci->getGameId() == GID_SHIVERS) { + obj.lowScore = globals[kGlobalVarScore].toUint16(); + obj.highScore = globals[kGlobalVarShivers1Score].toUint16(); + obj.avatarId = 0; + } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + obj.lowScore = obj.highScore = 0; + obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kGlobalVarEgo], SELECTOR(view)); + } else { + obj.lowScore = obj.highScore = obj.avatarId = 0; + } + } + + s.syncAsUint16LE(obj.lowScore); + s.syncAsUint16LE(obj.highScore); + s.syncAsByte(obj.avatarId); + } } void EngineState::saveLoadWithSerializer(Common::Serializer &s) { @@ -310,8 +381,15 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { _segMan->saveLoadWithSerializer(s); g_sci->_soundCmd->syncPlayList(s); - // NOTE: This will be GfxPalette32 for SCI32 engine games - g_sci->_gfxPalette16->saveLoadWithSerializer(s); + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + g_sci->_gfxPalette32->saveLoadWithSerializer(s); + g_sci->_gfxRemap32->saveLoadWithSerializer(s); + g_sci->_gfxCursor32->saveLoadWithSerializer(s); + } else +#endif + g_sci->_gfxPalette16->saveLoadWithSerializer(s); } void Vocabulary::saveLoadWithSerializer(Common::Serializer &s) { @@ -331,102 +409,13 @@ void Object::saveLoadWithSerializer(Common::Serializer &s) { syncArray<reg_t>(s, _variables); } -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Clone>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer<Object>(s, obj); -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<List>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer(s, obj.first); - syncWithSerializer(s, obj.last); -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Node>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer(s, obj.pred); - syncWithSerializer(s, obj.succ); - syncWithSerializer(s, obj.key); - syncWithSerializer(s, obj.value); -} - -#ifdef ENABLE_SCI32 -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciArray<reg_t> >::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - byte type = 0; - uint32 size = 0; - - if (s.isSaving()) { - type = (byte)obj.getType(); - size = obj.getSize(); - } - s.syncAsByte(type); - s.syncAsUint32LE(size); - if (s.isLoading()) { - obj.setType((int8)type); - - // HACK: Skip arrays that have a negative type - if ((int8)type < 0) - return; - - obj.setSize(size); - } - - for (uint32 i = 0; i < size; i++) { - reg_t value; - - if (s.isSaving()) - value = obj.getValue(i); - - syncWithSerializer(s, value); - - if (s.isLoading()) - obj.setValue(i, value); - } -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciString>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - uint32 size = 0; - - if (s.isSaving()) { - size = obj.getSize(); - s.syncAsUint32LE(size); - } else { - s.syncAsUint32LE(size); - obj.setSize(size); - } - - for (uint32 i = 0; i < size; i++) { - char value = 0; - - if (s.isSaving()) - value = obj.getValue(i); - - s.syncAsByte(value); - - if (s.isLoading()) - obj.setValue(i, value); - } -} -#endif template<typename T> void sync_Table(Common::Serializer &s, T &obj) { s.syncAsSint32LE(obj.first_free); s.syncAsSint32LE(obj.entries_used); - syncArray<typename T::Entry>(s, obj._table); + syncArray<typename T::Entry, SegmentObjTableEntrySyncer<T> >(s, obj._table); } void CloneTable::saveLoadWithSerializer(Common::Serializer &s) { @@ -643,28 +632,44 @@ void SoundCommandParser::syncPlayList(Common::Serializer &s) { } void SoundCommandParser::reconstructPlayList() { - Common::StackLock lock(_music->_mutex); + _music->_mutex.lock(); // We store all songs here because starting songs may re-shuffle their order MusicList songs; for (MusicList::iterator i = _music->getPlayListStart(); i != _music->getPlayListEnd(); ++i) songs.push_back(*i); + // Done with main playlist, so release lock + _music->_mutex.unlock(); + for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) { - initSoundResource(*i); + MusicEntry *entry = *i; + initSoundResource(entry); + +#ifdef ENABLE_SCI32 + if (_soundVersion >= SCI_VERSION_2_1_EARLY && entry->isSample) { + const reg_t &soundObj = entry->soundObj; + + if ((int)readSelectorValue(_segMan, soundObj, SELECTOR(loop)) != -1 && + readSelector(_segMan, soundObj, SELECTOR(handle)) != NULL_REG) { - if ((*i)->status == kSoundPlaying) { + writeSelector(_segMan, soundObj, SELECTOR(handle), NULL_REG); + processPlaySound(soundObj, entry->playBed); + } + } else +#endif + if (entry->status == kSoundPlaying) { // WORKAROUND: PQ3 (German?) scripts can set volume negative in the // sound object directly without going through DoSound. // Since we re-read this selector when re-playing the sound after loading, // this will lead to unexpected behaviour. As a workaround we // sync the sound object's selectors here. (See bug #5501) - writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(loop), (*i)->loop); - writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(priority), (*i)->priority); + writeSelectorValue(_segMan, entry->soundObj, SELECTOR(loop), entry->loop); + writeSelectorValue(_segMan, entry->soundObj, SELECTOR(priority), entry->priority); if (_soundVersion >= SCI_VERSION_1_EARLY) - writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol), (*i)->volume); + writeSelectorValue(_segMan, entry->soundObj, SELECTOR(vol), entry->volume); - processPlaySound((*i)->soundObj, (*i)->playBed); + processPlaySound(entry->soundObj, entry->playBed); } } } @@ -677,11 +682,60 @@ void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) { sync_Table<ArrayTable>(ser, *this); } -void StringTable::saveLoadWithSerializer(Common::Serializer &ser) { - if (ser.getVersion() < 18) +void SciArray::saveLoadWithSerializer(Common::Serializer &s) { + uint16 savedSize; + + if (s.isSaving()) { + savedSize = _size; + } + + s.syncAsByte(_type); + s.syncAsByte(_elementSize); + s.syncAsUint16LE(savedSize); + + if (s.isLoading()) { + resize(savedSize); + } + + switch (_type) { + case kArrayTypeInt16: + case kArrayTypeID: + for (int i = 0; i < savedSize; ++i) { + syncWithSerializer(s, ((reg_t *)_data)[i]); + } + break; + case kArrayTypeByte: + case kArrayTypeString: + s.syncBytes((byte *)_data, savedSize); + break; + default: + error("Attempt to sync invalid SciArray type %d", _type); + } +} + +void BitmapTable::saveLoadWithSerializer(Common::Serializer &ser) { + if (ser.getVersion() < 36) { + return; + } + + sync_Table(ser, *this); +} + +void SciBitmap::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 36) { return; + } + + s.syncAsByte(_gc); + s.syncAsUint32LE(_dataSize); + if (s.isLoading()) { + _data = (byte *)malloc(_dataSize); + } + s.syncBytes(_data, _dataSize); - sync_Table<StringTable>(ser, *this); + if (s.isLoading()) { + _buffer = Buffer(getWidth(), getHeight(), getPixels()); + } } #endif @@ -729,7 +783,7 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) { } #ifdef ENABLE_SCI32 -void saveLoadPalette32(Common::Serializer &s, Palette *const palette) { +static void saveLoadPalette32(Common::Serializer &s, Palette *const palette) { s.syncAsUint32LE(palette->timestamp); for (int i = 0; i < ARRAYSIZE(palette->colors); ++i) { s.syncAsByte(palette->colors[i].used); @@ -739,7 +793,7 @@ void saveLoadPalette32(Common::Serializer &s, Palette *const palette) { } } -void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) { +static void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) { bool hasPalette; if (s.isSaving()) { hasPalette = (*palette != nullptr); @@ -760,6 +814,16 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { if (s.isLoading()) { ++_version; + + for (int i = 0; i < kNumCyclers; ++i) { + delete _cyclers[i]; + _cyclers[i] = nullptr; + } + + delete _varyTargetPalette; + _varyTargetPalette = nullptr; + delete _varyStartPalette; + _varyStartPalette = nullptr; } s.syncAsSint16LE(_varyDirection); @@ -768,7 +832,7 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint16LE(_varyFromColor); s.syncAsSint16LE(_varyToColor); s.syncAsUint16LE(_varyNumTimesPaused); - s.syncAsByte(_versionUpdated); + s.syncAsByte(_needsUpdate); s.syncAsSint32LE(_varyTime); s.syncAsUint32LE(_varyLastTick); @@ -808,8 +872,62 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsUint16LE(cycler->numTimesPaused); } } +} + +void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 35) { + return; + } + + s.syncAsByte(_numActiveRemaps); + s.syncAsByte(_blockedRangeStart); + s.syncAsSint16LE(_blockedRangeCount); + + for (uint i = 0; i < _remaps.size(); ++i) { + SingleRemap &singleRemap = _remaps[i]; + s.syncAsByte(singleRemap._type); + if (s.isLoading() && singleRemap._type != kRemapNone) { + singleRemap.reset(); + } + s.syncAsByte(singleRemap._from); + s.syncAsByte(singleRemap._to); + s.syncAsByte(singleRemap._delta); + s.syncAsByte(singleRemap._percent); + s.syncAsByte(singleRemap._gray); + } - // TODO: _clutTable + if (s.isLoading()) { + _needsUpdate = true; + } +} + +void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 38) { + return; + } + + int32 hideCount; + if (s.isSaving()) { + hideCount = _hideCount; + } + s.syncAsSint32LE(hideCount); + s.syncAsSint16LE(_restrictedArea.left); + s.syncAsSint16LE(_restrictedArea.top); + s.syncAsSint16LE(_restrictedArea.right); + s.syncAsSint16LE(_restrictedArea.bottom); + s.syncAsUint16LE(_cursorInfo.resourceId); + s.syncAsUint16LE(_cursorInfo.loopNo); + s.syncAsUint16LE(_cursorInfo.celNo); + + if (s.isLoading()) { + hide(); + setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo); + if (!hideCount) { + show(); + } else { + _hideCount = hideCount; + } + } } #endif @@ -903,7 +1021,7 @@ void SegManager::reconstructClones() { if (!isUsed) continue; - CloneTable::Entry &seeker = ct->_table[j]; + CloneTable::value_type &seeker = ct->at(j); const Object *baseObj = getObject(seeker.getSpeciesSelector()); seeker.cloneFromObject(baseObj); if (!baseObj) { @@ -935,10 +1053,11 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin meta.gameObjectOffset = g_sci->getGameObject().getOffset(); // Checking here again - if (s->executionStackBase) { - warning("Cannot save from below kernel function"); - return false; - } +// TODO: This breaks Torin autosave, is there actually any reason for it? +// if (s->executionStackBase) { +// warning("Cannot save from below kernel function"); +// return false; +// } Common::Serializer ser(0, fh); sync_SavegameMetadata(ser, meta); @@ -1012,6 +1131,26 @@ void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) { g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals break; + case GID_KQ6: + if (g_sci->isCD()) { + // WORKAROUND: + // For the CD version of King's Quest 6, set global depending on current hires/lowres state + // The game sets a global at the start depending on it and some things check that global + // instead of checking platform like for example the game action menu. + // This never happened in the original interpreter, because the original DOS interpreter + // was only capable of lowres graphics and the original Windows 3.11 interpreter was only capable + // of hires graphics. Saved games were not compatible between those two. + // Which means saving during lowres mode, then going into hires mode and restoring that saved game, + // will result in some graphics being incorrect (lowres). + // That's why we are setting the global after restoring a saved game depending on hires/lowres state. + // The CD demo of KQ6 does the same and uses the exact same global. + if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) { + s->variables[VAR_GLOBAL][0xA9].setOffset(1); + } else { + s->variables[VAR_GLOBAL][0xA9].setOffset(0); + } + } + break; case GID_PQ2: // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875). // It gets disabled in the game's death screen. @@ -1081,8 +1220,13 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { // We MUST NOT delete all planes/screen items. At least Space Quest 6 has a few in memory like for example // the options plane, which are not re-added and are in memory all the time right from the start of the // game. Sierra SCI32 did not clear planes, only scripts cleared the ones inside planes::elements. - if (getSciVersion() >= SCI_VERSION_2) - g_sci->_gfxFrameout->syncWithScripts(false); + if (getSciVersion() >= SCI_VERSION_2) { + if (!s->_delayedRestoreFromLauncher) { + // Only do it, when we are restoring regulary and not from launcher + // As it could result in option planes etc. on the screen (happens in gk1) + g_sci->_gfxFrameout->syncWithScripts(false); + } + } #endif s->reset(true); @@ -1131,6 +1275,8 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { // signal restored game to game scripts s->gameIsRestarting = GAMEISRESTARTING_RESTORE; + + s->_delayedRestoreFromLauncher = false; } bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 459e992e24..873394aebb 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,6 +37,11 @@ struct EngineState; * * Version - new/changed feature * ============================= + * 39 - Accurate SCI32 arrays/strings, score metadata, avatar metadata + * 38 - SCI32 cursor + * 37 - Segment entry data changed to pointers + * 36 - SCI32 bitmap segment + * 35 - SCI32 remap * 34 - SCI32 palettes, and store play time in ticks * 33 - new overridePriority flag in MusicEntry * 32 - new playBed flag in MusicEntry @@ -59,7 +64,7 @@ struct EngineState; */ enum { - CURRENT_SAVEGAME_VERSION = 34, + CURRENT_SAVEGAME_VERSION = 39, MINIMUM_SAVEGAME_VERSION = 14 }; @@ -73,6 +78,13 @@ struct SavegameMetadata { uint32 playTime; uint16 gameObjectOffset; uint16 script0Size; + + // Used by Shivers 1 + uint16 lowScore; + uint16 highScore; + + // Used by MGDX + uint8 avatarId; }; /** diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 26a7ff5718..8a973bd217 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -72,6 +72,11 @@ void Script::freeScript() { _offsetLookupSaidCount = 0; } +enum { + kSci11NumExportsOffset = 6, + kSci11ExportTableOffset = 8 +}; + void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptPatcher) { freeScript(); @@ -172,10 +177,11 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP _localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1; // half block size } } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { - if (READ_LE_UINT16(_buf + 1 + 5) > 0) { // does the script have an export table? - _exportTable = (const uint16 *)(_buf + 1 + 5 + 2); - _numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1); + _numExports = READ_SCI11ENDIAN_UINT16(_buf + kSci11NumExportsOffset); + if (_numExports) { + _exportTable = (const uint16 *)(_buf + kSci11ExportTableOffset); } + _localsOffset = _scriptSize + 4; _localsCount = READ_SCI11ENDIAN_UINT16(_buf + _localsOffset - 2); } else if (getSciVersion() == SCI_VERSION_3) { @@ -234,6 +240,7 @@ void Script::identifyOffsets() { _offsetLookupObjectCount = 0; _offsetLookupStringCount = 0; _offsetLookupSaidCount = 0; + _codeOffset = 0; if (getSciVersion() < SCI_VERSION_1_1) { // SCI0 + SCI1 @@ -392,6 +399,16 @@ void Script::identifyOffsets() { scriptDataPtr = _heapStart; scriptDataLeft = _heapSize; + enum { + kExportSize = 2, + kPropertySize = 2, + kNumMethodsSize = 2, + kPropDictEntrySize = 2, + kMethDictEntrySize = 4 + }; + + const byte *hunkPtr = _buf + kSci11ExportTableOffset + _numExports * kExportSize; + if (scriptDataLeft < 4) error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); @@ -431,11 +448,22 @@ void Script::identifyOffsets() { if (scriptDataLeft < 2) error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); - blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr) * 2; + const uint16 numProperties = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + blockSize = numProperties * kPropertySize; if (blockSize < 4) error("Script::identifyOffsets(): invalid block size in script %d", _nr); scriptDataPtr += 2; scriptDataLeft -= 2; + + const uint16 scriptNum = READ_SCI11ENDIAN_UINT16(scriptDataPtr + 6); + + if (scriptNum != 0xFFFF) { + hunkPtr += numProperties * kPropDictEntrySize; + } + + const uint16 numMethods = READ_SCI11ENDIAN_UINT16(hunkPtr); + hunkPtr += kNumMethodsSize + numMethods * kMethDictEntrySize; + blockSize -= 4; // blocksize contains UINT16 type and UINT16 size if (scriptDataLeft < blockSize) error("Script::identifyOffsets(): invalid block size in script %d", _nr); @@ -444,6 +472,8 @@ void Script::identifyOffsets() { scriptDataLeft -= blockSize; } while (1); + _codeOffset = hunkPtr - _buf; + // now scriptDataPtr points to right at the start of the strings if (scriptDataPtr > endOfStringPtr) error("Script::identifyOffsets(): string block / end-of-string block mismatch in script %d", _nr); @@ -674,7 +704,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme return true; } -int Script::relocateOffsetSci3(uint32 offset) { +int Script::relocateOffsetSci3(uint32 offset) const { int relocStart = READ_LE_UINT32(_buf + 8); int relocCount = READ_LE_UINT16(_buf + 18); const byte *seeker = _buf + relocStart; @@ -820,9 +850,10 @@ uint32 Script::validateExportFunc(int pubfunct, bool relocSci3) { } } - // Note that it's perfectly normal to return a zero offset, especially in - // SCI1.1 and newer games. Examples include script 64036 in Torin's Passage, - // script 64908 in the demo of RAMA and script 1013 in KQ6 floppy. + // TODO: Check if this should be done for SCI1.1 games as well + if (getSciVersion() >= SCI_VERSION_2 && offset == 0) { + offset = _codeOffset; + } if (offset >= _bufSize) error("Invalid export function pointer"); @@ -1058,11 +1089,17 @@ void Script::initializeObjectsSci11(SegManager *segMan, SegmentId segmentId) { obj->setSuperClassSelector( segMan->getClassAddress(obj->getSuperClassSelector().getOffset(), SCRIPT_GET_LOCK, 0)); - // If object is instance, get -propDict- from class and set it for this - // object. This is needed for ::isMemberOf() to work. + // -propDict- is used by Obj::isMemberOf to determine if an object + // is an instance of a class. For classes, we therefore relocate + // -propDict- to the script's segment. For instances, we copy + // -propDict- from its class. // Example test case - room 381 of sq4cd - if isMemberOf() doesn't work, - // talk-clicks on the robot will act like clicking on ego - if (!obj->isClass()) { + // talk-clicks on the robot will act like clicking on ego. + if (obj->isClass()) { + reg_t propDict = obj->getPropDictSelector(); + propDict.setSegment(segmentId); + obj->setPropDictSelector(propDict); + } else { reg_t classObject = obj->getSuperClassSelector(); const Object *classObj = segMan->getObject(classObject); obj->setPropDictSelector(classObj->getPropDictSelector()); diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h index 755e2f3698..677b367051 100644 --- a/engines/sci/engine/script.h +++ b/engines/sci/engine/script.h @@ -81,6 +81,8 @@ private: const byte *_synonyms; /**< Synonyms block or 0 if not present */ uint16 _numSynonyms; /**< Number of entries in the synonyms block */ + int _codeOffset; /**< The absolute offset of the VM code block */ + int _localsOffset; uint16 _localsCount; @@ -264,7 +266,7 @@ public: * Resolve a relocation in an SCI3 script * @param offset The offset to relocate from */ - int relocateOffsetSci3(uint32 offset); + int relocateOffsetSci3(uint32 offset) const; /** * Gets an offset to the beginning of the code block in a SCI3 script diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index fc0dca5123..0c4f0da959 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -103,6 +103,9 @@ static const char *const selectorNameTable[] = { "modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "cycler", // Space Quest 4 / system selector "setLoop", // Laura Bow 1 Colonel's Bequest +#ifdef ENABLE_SCI32 + "newWith", // SCI2 array script +#endif NULL }; @@ -132,8 +135,86 @@ enum ScriptPatcherSelectors { SELECTOR_modNum, SELECTOR_cycler, SELECTOR_setLoop +#ifdef ENABLE_SCI32 + , + SELECTOR_newWith +#endif +}; + +#ifdef ENABLE_SCI32 +// It is not possible to change the directory for ScummVM save games, so disable +// the "change directory" button in the standard save dialogue +static const uint16 sci2ChangeDirSignature[] = { + 0x72, SIG_ADDTOOFFSET(+2), // lofsa changeDirI + 0x4a, SIG_UINT16(0x04), // send 4 + SIG_MAGICDWORD, + 0x36, // push + 0x35, 0xF7, // ldi $f7 + 0x12, // and + 0x36, // push + SIG_END +}; + +static const uint16 sci2ChangeDirPatch[] = { + PATCH_ADDTOOFFSET(+3), // lofsa changeDirI + PATCH_ADDTOOFFSET(+3), // send 4 + PATCH_ADDTOOFFSET(+1), // push + 0x35, 0x00, // ldi 0 + PATCH_END +}; + +// Save game script hardcodes the maximum number of save games to 20, but +// this is an artificial constraint that does not apply to ScummVM +static const uint16 sci2NumSavesSignature1[] = { + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x14, // ldi 20 + 0x22, // lt? + SIG_END +}; + +static const uint16 sci2NumSavesPatch1[] = { + PATCH_ADDTOOFFSET(+2), // lsl local[2] + 0x35, 0x63, // ldi 99 + PATCH_END +}; + +static const uint16 sci2NumSavesSignature2[] = { + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x14, // ldi 20 + 0x1a, // eq? + SIG_END +}; + +static const uint16 sci2NumSavesPatch2[] = { + PATCH_ADDTOOFFSET(+2), // lsl local[2] + 0x35, 0x63, // ldi 99 + PATCH_END }; +// Phantasmagoria & SQ6 try to initialize the first entry of an int16 array +// using an empty string, which is not valid (it should be a number) +static const uint16 sci21IntArraySignature[] = { + 0x38, SIG_SELECTOR16(newWith), // pushi newWith + 0x7a, // push2 + 0x39, 0x04, // pushi $4 + 0x72, SIG_ADDTOOFFSET(+2), // lofsa string "" + SIG_MAGICDWORD, + 0x36, // push + 0x51, 0x0b, // class IntArray + 0x4a, 0x8, // send $8 + SIG_END +}; + +static const uint16 sci21IntArrayPatch[] = { + PATCH_ADDTOOFFSET(+6), // push $b9; push2; pushi $4 + 0x76, // push0 + 0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes) + PATCH_END +}; +#endif + // =========================================================================== // Conquests of Camelot // At the bazaar in Jerusalem, it's possible to see a girl taking a shower. @@ -379,12 +460,40 @@ static const SciScriptPatcherEntry ecoquest2Signatures[] = { }; // =========================================================================== +// Fan-made games +// Attention: Try to make script patches as specific as possible + +// CascadeQuest::autosave in script 994 is called various times to auto-save the game. +// The script use a fixed slot "999" for this purpose. This doesn't work in ScummVM, because we do not let +// scripts save directly into specific slots, but instead use virtual slots / detect scripts wanting to +// create a new slot. +// +// For this game we patch the code to use slot 99 instead. kSaveGame also checks for Cascade Quest, +// will then check, if slot 99 is asked for and will then use the actual slot 0, which is the official +// ScummVM auto-save slot. +// +// Responsible method: CascadeQuest::autosave +// Fixes bug: #7007 +static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x03e7), // pushi 3E7 (999d) -> save game slot 999 + 0x74, SIG_UINT16(0x06f8), // lofss "AutoSave" + 0x89, 0x1e, // lsg global[1E] + 0x43, 0x2d, 0x08, // callk SaveGame + SIG_END +}; + +static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = { + 0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot + PATCH_END +}; + // EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the // wrong address when an incorrect word is typed, therefore leading to an // infinite loop. This script bug was not apparent in SSCI, probably because // event handling was slightly different there, so it was never discovered. // Fixes bug: #5120 -static const uint16 fanmadeSignatureInfiniteLoop[] = { +static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = { 0x38, SIG_UINT16(0x004c), // pushi 004c 0x39, 0x00, // pushi 00 0x87, 0x01, // lap 01 @@ -395,15 +504,16 @@ static const uint16 fanmadeSignatureInfiniteLoop[] = { SIG_END }; -static const uint16 fanmadePatchInfiniteLoop[] = { +static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = { PATCH_ADDTOOFFSET(+10), - 0x30, SIG_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c + 0x30, PATCH_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c PATCH_END }; -// script, description, signature patch +// script, description, signature patch static const SciScriptPatcherEntry fanmadeSignatures[] = { - { true, 999, "infinite loop on typo", 1, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop }, + { true, 994, "Cascade Quest: fix auto-saving", 1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving }, + { true, 999, "Demo Quest: infinite loop on typo", 1, fanmadeSignatureDemoQuestInfiniteLoop, fanmadePatchDemoQuestInfiniteLoop }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -585,6 +695,10 @@ static const SciScriptPatcherEntry freddypharkasSignatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Gabriel Knight 1 + // =========================================================================== // daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220. // this is not enough time to get to the door, so we patch that to 23 seconds @@ -634,6 +748,68 @@ static const uint16 gk1PatchDay6PoliceSleep[] = { PATCH_END }; +// At the start of day 5, there is like always some dialogue with Grace. +// +// The dialogue script code about the drum book + veve newspaper clip is a bit broken. +// +// In case the player already has the veve, but is supposed to get the drum book, then the drum book +// dialogue is repeated twice and the veve newspaper dialogue is also repeated (although it was played on day 4 +// in such case already). +// +// Drum book dialogue is called twice. +// Once via GetTheVeve::changeState(0) and a second time via GetTheVeve::changeState(11). +// +// GetTheVeve::changeState(0) would also play the first line of the veve pattern newspaper and that's skipped, +// when the player is supposed to get the drum book. +// GetTheVeve::changeState(1) up to state 10 will do the dialogue about the veve newspaper. +// At the start of state 1 though, the player will get the drum book in case he ask for research. +// Right after that the scripts check, if the player has the drum book and then go the veve newspaper route. +// +// We fix this by skipping the drum book check in case the player just got the drum book. +// The scripts will then skip to state 12, skipping over the second drum book dialogue call. +// +// More notes: The veve newspaper item is inventory 9. The drum book is inventory 14. +// The flag for veve research is 36, the flag for drum research is 73. +// +// This bug of course also occurs, when using the original interpreter. +// +// Special thanks, credits and kudos to sluicebox on IRC, who did a ton of research on this and even found this game bug originally. +// +// Applies to at least: English PC-CD, German PC-CD +// Responsible method: getTheVeve::changeState(1) - script 212 +static const uint16 gk1SignatureDay5DrumBookDialogue[] = { + 0x31, 0x0b, // bnt [skip giving player drum book code] + 0x38, SIG_UINT16(0x0200), // pushi 0200h + 0x78, // push1 + SIG_MAGICDWORD, + 0x39, 0x0e, // pushi 0Eh + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, 0x00, // send 06 - GKEgo::get(0Eh) + // end of giving player drum book code + 0x38, SIG_UINT16(0x0202), // pushi 0202h + 0x78, // push1 + 0x39, 0x0e, // pushi 0Eh + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, 0x00, // send 06 - GKEgo::has(0Eh) + 0x18, // not + 0x30, SIG_UINT16(0x0025), // bnt [veve newspaper code] + SIG_END +}; + +static const uint16 gk1PatchDay5DrumBookDialogue[] = { + 0x31, 0x0d, // bnt [skip giving player drum book code] adjusted + PATCH_ADDTOOFFSET(+11), // skip give player drum book original code + 0x33, 0x0D, // jmp [over the check inventory for drum book code] + // check inventory for drum book + 0x38, SIG_UINT16(0x0202), // pushi 0202h + 0x78, // push1 + 0x39, 0x0e, // pushi 0Eh + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, 0x00, // send 06 - GKEgo::has(0Eh) + 0x2f, 0x23, // bt [veve newspaper code] (adjusted, saves 2 bytes) + PATCH_END +}; + // startOfDay5::changeState (20h) - when gabriel goes to the phone the script will hang // Applies to at least: English PC-CD, German PC-CD, English Mac // Responsible method: startOfDay5::changeState @@ -700,7 +876,7 @@ static const uint16 gk1PatchInterrogationBug[] = { 0x76, // push0 0x4a, 0x04, 0x00, // send 0004 0xa5, 0x00, // sat 00 - 0x38, SIG_SELECTOR16(dispose), // pushi dispose + 0x38, PATCH_SELECTOR16(dispose), // pushi dispose 0x76, // push0 0x63, 0x50, // pToa 50 0x4a, 0x04, 0x00, // send 0004 @@ -717,15 +893,135 @@ static const uint16 gk1PatchInterrogationBug[] = { PATCH_END }; -// script, description, signature patch +// On day 10 nearly at the end of the game, Gabriel Knight dresses up and right after that +// someone will be at the door. Gabriel turns around to see what's going on. +// +// In ScummVM Gabriel turning around plays endlessly. This is caused by the loop of Gabriel +// being kept at 1, but view + cel were changed accordingly. The view used - which is view 859 - +// does not have a loop 1. kNumCels is called on that, BUT kNumCels in SSCI is broken in that +// regard. It checks for loop > count and not loop >= count and will return basically random data +// in case loop == count. +// +// In SSCI this simply worked by accident. kNumCels returned 0x53 in this case, but later script code +// fixed that up somehow, so it worked out in the end. +// +// The setup for this is done in SDJEnters::changeState(0). The cycler will never reach the goal +// because the goal will be cel -1, so it loops endlessly. +// +// We fix this by adding a setLoop(0). +// +// Applies to at least: English PC-CD, German PC-CD +// Responsible method: sDJEnters::changeState +static const uint16 gk1SignatureDay10GabrielDressUp[] = { + 0x87, 0x01, // lap param[1] + 0x65, 0x14, // aTop state + 0x36, // push + 0x3c, // dup + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x30, SIG_UINT16(0x006f), // bnt [next state 1] + SIG_ADDTOOFFSET(+84), + 0x39, 0x0e, // pushi 0Eh (view) + 0x78, // push1 + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x035B), // pushi 035Bh (859d) + 0x38, SIG_UINT16(0x0141), // pushi 0141h (setCel) + 0x78, // push1 + 0x76, // push0 + 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle) + 0x7a, // push2 + 0x51, 0x18, // class End + 0x36, // push + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + 0x4a, 0x14, 0x00, // send 14h + // GKEgo::view(859) + // GKEgo::setCel(0) + // GKEgo::setCycle(End, sDJEnters) + 0x32, SIG_UINT16(0x0233), // jmp [ret] + // next state + 0x3c, // dup + 0x35, 0x01, // ldi 01 + 0x1a, // eq? + 0x31, 0x07, // bnt [next state 2] + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x0226), // jmp [ret] + // next state + 0x3c, // dup + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x2a, // bnt [next state 3] + 0x78, // push1 + SIG_ADDTOOFFSET(+34), + // part of state 2 code, delays for 1 cycle + 0x35, 0x01, // ldi 1 + 0x65, 0x1a, // aTop cycles + SIG_END +}; + +static const uint16 gk1PatchDay10GabrielDressUp[] = { + PATCH_ADDTOOFFSET(+9), + 0x30, SIG_UINT16(0x0073), // bnt [next state 1] - offset adjusted + SIG_ADDTOOFFSET(+84 + 11), + // added by us: setting loop to 0 (5 bytes needed) + 0x38, SIG_UINT16(0x00FB), // pushi 00FBh (setLoop) + 0x78, // push1 + 0x76, // push0 + // original code, but offset changed + 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle) + 0x7a, // push2 + 0x51, 0x18, // class End + 0x36, // push + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + 0x4a, 0x1a, 0x00, // send 1Ah - adjusted + // GKEgo::view(859) + // GKEgo::setCel(0) + // GKEgo::setLoop(0) <-- new, by us + // GKEgo::setCycle(End, sDJEnters) + // end of original code + 0x3a, // toss + 0x48, // ret (saves 1 byte) + // state 1 code + 0x3c, // dup + 0x34, SIG_UINT16(0x0001), // ldi 0001 (waste 1 byte) + 0x1a, // eq? + 0x31, 2, // bnt [next state 2] + 0x33, 41, // jmp to state 2 delay code + SIG_ADDTOOFFSET(+41), + // wait 2 cycles instead of only 1 + 0x35, 0x02, // ldi 2 + PATCH_END +}; + +// script, description, signature patch static const SciScriptPatcherEntry gk1Signatures[] = { - { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug }, - { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, - { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, - { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug }, + { true, 212, "day 5 drum book dialogue error", 1, gk1SignatureDay5DrumBookDialogue, gk1PatchDay5DrumBookDialogue }, + { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, + { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, + { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { true, 808, "day 10 gabriel dress up infinite turning", 1, gk1SignatureDay10GabrielDressUp, gk1PatchDay10GabrielDressUp }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, SCI_SIGNATUREENTRY_TERMINATOR }; +#pragma mark - +#pragma mark Gabriel Knight 2 + +// script, description, signature patch +static const SciScriptPatcherEntry gk2Signatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // at least during harpy scene export 29 of script 0 is called in kq5cd and // has an issue for those calls, where temp 3 won't get inititialized, but @@ -813,6 +1109,32 @@ static const uint16 kq5PatchWitchCageInit[] = { PATCH_END }; +// The multilingual releases of KQ5 hang right at the end during the magic battle with Mordack. +// It seems additional code was added to wait for signals, but the signals are never set and thus +// the game hangs. We disable that code, so that the battle works again. +// This also happened in the original interpreter. +// We must not change similar code, that happens before. + +// Applies to at least: French PC floppy, German PC floppy, Spanish PC floppy +// Responsible method: stingScript::changeState, dragonScript::changeState, snakeScript::changeState +static const uint16 kq5SignatureMultilingualEndingGlitch[] = { + SIG_MAGICDWORD, + 0x89, 0x57, // lsg global[57h] + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x18, // not + 0x30, SIG_UINT16(0x0011), // bnt [skip signal check] + SIG_ADDTOOFFSET(+8), // skip globalSound::prevSignal get code + 0x36, // push + 0x35, 0x0a, // ldi 0Ah + SIG_END +}; + +static const uint16 kq5PatchMultilingualEndingGlitch[] = { + PATCH_ADDTOOFFSET(+6), + 0x32, // change BNT into JMP + PATCH_END +}; // In the final battle, the DOS version uses signals in the music to handle // timing, while in the Windows version another method is used and the GM @@ -843,9 +1165,10 @@ static const uint16 kq5PatchWinGMSignals[] = { // script, description, signature patch static const SciScriptPatcherEntry kq5Signatures[] = { - { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, - { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit }, - { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals }, + { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, + { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit }, + { true, 124, "Multilingual: Ending glitching out", 3, kq5SignatureMultilingualEndingGlitch, kq5PatchMultilingualEndingGlitch }, + { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1165,6 +1488,7 @@ static const uint16 kq6CDPatchAudioTextSupport2[] = { // Additional patch specifically for King's Quest 6 // Fixes special windows, used for example in the Pawn shop (room 280), // when the man in a robe complains about no more mints. +// Or also in room 300 at the cliffs (aka copy protection), when Alexander falls down the cliffs. // We have to change even more code, because the game uses PODialog class for // text windows and myDialog class for audio. Both are saved to KQ6Print::dialog // Sadly PODialog is created during KQ6Print::addText, myDialog is set during @@ -1191,13 +1515,34 @@ static const uint16 kq6CDSignatureAudioTextSupport3[] = { }; static const uint16 kq6CDPatchAudioTextSupport3[] = { - 0x31, 0x5c, // adjust jump to reuse audio mode addText-calling code - PATCH_ADDTOOFFSET(102), - 0x48, // ret - 0x48, // ret (waste byte) + 0x31, 0x68, // adjust jump to reuse audio mode addText-calling code + PATCH_ADDTOOFFSET(+85), // right at the MAGIC_DWORD + // check, if text is supposed to be shown. If yes, skip the follow-up check (param[1]) + 0x89, 0x5a, // lsg global[5Ah] + 0x35, 0x01, // ldi 01 + 0x12, // and + 0x2f, 0x07, // bt [skip over param check] + // original code, checks param[1] + 0x8f, 0x01, // lsp param[1] + 0x35, 0x01, // ldi 01 + 0x1a, // eq? + 0x31, 0x10, // bnt [code to set property repressText to 1], adjusted + // use myDialog class, so that text box automatically disappears (this is not done for text only mode, like in the original) 0x72, 0x0e, 0x00, // lofsa myDialog 0x65, 0x12, // aTop dialog - 0x33, 0xed, // jump back to audio mode addText-calling code + // followed by original addText-calling code + 0x38, + PATCH_GETORIGINALBYTE(+95), + PATCH_GETORIGINALBYTE(+96), // pushi addText + 0x78, // push1 + 0x8f, 0x02, // lsp param[2] + 0x59, 0x03, // &rest 03 + 0x54, 0x06, // self 06 + 0x48, // ret + + 0x35, 0x01, // ldi 01 + 0x65, 0x2e, // aTop repressText + 0x48, // ret PATCH_END }; @@ -1396,6 +1741,173 @@ static const SciScriptPatcherEntry kq6Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Kings Quest 7 + +// =========================================================================== + +// King's Quest 7 has really weird subtitles. It seems as if the subtitles were +// not fully finished. +// +// Method kqMessager::findTalker in script 0 tries to figure out, which class to use for +// displaying subtitles. It uses the "talker" data of the given message to do that. +// Strangely this "talker" data seems to be quite broken. +// For example chapter 2 starts with a cutscene. +// Troll king: "Welcome, most beautiful of princesses!" - talker 6 +// Which is followed by the princess going +// "Hmm?" - which is set to talker 99, normally the princess is talker 7. +// +// Talker 99 is seen as unknown and thus treated as "narrator", which makes +// the scripts put the text at the top of the game screen and even use a +// different font. +// +// In other cases, when the player character thinks to himself talker 99 +// is also used. In such situations it may make somewhat sense to do so, +// but putting the text at the top of the screen is also irritating to the player. +// It's really weird. +// +// The scripts also put the regular text in the middle of the screen, blocking +// animations. +// +// And for certain rooms, the subtitle box may use another color +// like for example pink/purple at the start of chapter 5. +// +// We fix all of that (hopefully - lots of testing is required). +// We put the text at the bottom of the play screen. +// We also make the scripts use the regular KQTalker instead of KQNarrator. +// And we also make the subtitle box use color 255, which is fixed white. +// +// Applies to at least: PC CD 1.4 English, 1.51 English, 1.51 German, 2.00 English +// Patched method: KQNarrator::init (script 31) +static const uint16 kq7SignatureSubtitleFix1[] = { + SIG_MAGICDWORD, + 0x39, 0x25, // pushi 25h (fore) + 0x78, // push1 + 0x39, 0x06, // pushi 06 - sets back to 6 + 0x39, 0x26, // pushi 26 (back) + 0x78, // push1 + 0x78, // push1 - sets back to 1 + 0x39, 0x2a, // pushi 2Ah (font) + 0x78, // push1 + 0x89, 0x16, // lsg global[16h] - sets font to global[16h] + 0x7a, // push2 (y) + 0x78, // push1 + 0x76, // push0 - sets y to 0 + 0x54, SIG_UINT16(0x0018), // self 18h + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix1[] = { + 0x33, 0x12, // jmp [skip special init code] + PATCH_END +}; + +// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English +// Patched method: Narrator::init (script 64928) +static const uint16 kq7SignatureSubtitleFix2[] = { + SIG_MAGICDWORD, + 0x89, 0x5a, // lsg global[5a] + 0x35, 0x02, // ldi 02 + 0x12, // and + 0x31, 0x1e, // bnt [skip audio volume code] + 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51) + 0x76, // push0 + 0x81, 0x01, // lag global[1] + 0x4a, 0x04, 0x00, // send 04 + 0x65, 0x32, // aTop curVolume + 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51) + 0x78, // push1 + 0x67, 0x32, // pTos curVolume + 0x35, 0x02, // ldi 02 + 0x06, // mul + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x08, // div + 0x36, // push + 0x81, 0x01, // lag global[1] + 0x4a, 0x06, 0x00, // send 06 + // end of volume code + 0x35, 0x01, // ldi 01 + 0x65, 0x28, // aTop initialized + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix2[] = { + PATCH_ADDTOOFFSET(+5), // skip to bnt + 0x31, 0x1b, // bnt [skip audio volume code] + PATCH_ADDTOOFFSET(+15), // right after "aTop curVolume / pushi masterVolume / push1" + 0x7a, // push2 + 0x06, // mul (saves 3 bytes in total) + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x08, // div + 0x36, // push + 0x81, 0x01, // lag global[1] + 0x4a, 0x06, 0x00, // send 06 + // end of volume code + 0x35, 118, // ldi 118d + 0x65, 0x16, // aTop y + 0x78, // push1 (saves 1 byte) + 0x69, 0x28, // sTop initialized + PATCH_END +}; + +// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English +// Patched method: Narrator::say (script 64928) +static const uint16 kq7SignatureSubtitleFix3[] = { + SIG_MAGICDWORD, + 0x63, 0x28, // pToa initialized + 0x18, // not + 0x31, 0x07, // bnt [skip init code] + 0x38, SIG_ADDTOOFFSET(+2), // pushi init (008Eh for 2.00, 0093h for 1.51) + 0x76, // push0 + 0x54, SIG_UINT16(0x0004), // self 04 + // end of init code + 0x8f, 0x00, // lsp param[0] + 0x35, 0x01, // ldi 01 + 0x1e, // gt? + 0x31, 0x08, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x31, 0x04, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x33, 0x02, // jmp [over set acc to 0 code] + 0x35, 0x00, // ldi 00 + 0x65, 0x18, // aTop caller + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix3[] = { + PATCH_ADDTOOFFSET(+2), // skip over "pToa initialized code" + 0x2f, 0x0c, // bt [skip init code] - saved 1 byte + 0x38, + PATCH_GETORIGINALBYTE(+6), + PATCH_GETORIGINALBYTE(+7), // pushi (init) + 0x76, // push0 + 0x54, PATCH_UINT16(0x0004), // self 04 + // additionally set background color here (5 bytes) + 0x34, PATCH_UINT16(255), // pushi 255d + 0x65, 0x2e, // aTop back + // end of init code + 0x8f, 0x00, // lsp param[0] + 0x35, 0x01, // ldi 01 - this may get optimized to get another byte + 0x1e, // gt? + 0x31, 0x04, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x2f, 0x02, // bt [over set acc to 0 code] + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry kq7Signatures[] = { + { true, 31, "subtitle fix 1/3", 1, kq7SignatureSubtitleFix1, kq7PatchSubtitleFix1 }, + { true, 64928, "subtitle fix 2/3", 1, kq7SignatureSubtitleFix2, kq7PatchSubtitleFix2 }, + { true, 64928, "subtitle fix 3/3", 1, kq7SignatureSubtitleFix3, kq7PatchSubtitleFix3 }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // Script 210 in the German version of Longbow handles the case where Robin // hands out the scroll to Marion and then types his name using the hand code. @@ -1436,9 +1948,109 @@ static const uint16 longbowPatchShowHandCode[] = { PATCH_END }; +// When walking through the forest, arithmetic errors may occur at "random". +// The scripts try to add a value and a pointer to the object "berryBush". +// +// This is caused by a local variable overflow. +// +// The scripts create berry bush objects dynamically. The array storage for +// those bushes may hold a total of 8 bushes. But sometimes 10 bushes +// are created. This overwrites 2 additional locals in script 225 and +// those locals are used normally for value lookups. +// +// Changing the total of bushes could cause all sorts of other issues, +// that's why I rather patched the code, that uses the locals for a lookup. +// Which means it doesn't matter anymore when those locals are overwritten. +// +// Applies to at least: English PC floppy, German PC floppy, English Amiga floppy +// Responsible method: export 2 of script 225 +// Fixes bug: #6751 +static const uint16 longbowSignatureBerryBushFix[] = { + 0x89, 0x70, // lsg global[70h] + 0x35, 0x03, // ldi 03h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x002d), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x04, // ldi 04h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0025), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x05, // ldi 05h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x001d), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x06, // ldi 06h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0015), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x18, // ldi 18h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x000d), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x19, // ldi 19h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0005), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x1a, // ldi 1Ah + 0x1a, // eq? + // jump location for the "bt" instructions + 0x30, SIG_UINT16(0x0011), // bnt [skip over follow up code, to offset 0c35] + // 55 bytes until here + 0x85, 00, // lat temp[0] + SIG_MAGICDWORD, + 0x9a, SIG_UINT16(0x0110), // lsli local[110h] -> 110h points normally to 110h / 2Bh + // 5 bytes + 0x7a, // push2 + SIG_END +}; + +static const uint16 longbowPatchBerryBushFix[] = { + PATCH_ADDTOOFFSET(+4), // keep: lsg global[70h], ldi 03h + 0x22, // lt? (global < 03h) + 0x2f, 0x42, // bt [skip over all the code directly] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x06, // ldi 06h + 0x24, // le? (global <= 06h) + 0x2f, 0x0e, // bt [to kRandom code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x18, // ldi 18h + 0x22, // lt? (global < 18h) + 0x2f, 0x34, // bt [skip over all the code directly] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x1a, // ldi 1Ah + 0x24, // le? (global <= 1Ah) + 0x31, 0x2d, // bnt [skip over all the code directly] + // 28 bytes, 27 bytes saved + // kRandom code + 0x85, 0x00, // lat temp[0] + 0x2f, 0x05, // bt [skip over case 0] + // temp[0] == 0 + 0x38, SIG_UINT16(0x0110), // pushi 0110h - that's what's normally at local[110h] + 0x33, 0x18, // jmp [kRandom call] + // check temp[0] further + 0x78, // push1 + 0x1a, // eq? + 0x31, 0x05, // bt [skip over case 1] + // temp[0] == 1 + 0x38, SIG_UINT16(0x002b), // pushi 002Bh - that's what's normally at local[111h] + 0x33, 0x0F, // jmp [kRandom call] + // temp[0] >= 2 + 0x8d, 00, // lst temp[0] + 0x35, 0x02, // ldi 02 + 0x04, // sub + 0x9a, SIG_UINT16(0x0112), // lsli local[112h] -> look up value in 2nd table + // this may not be needed at all and was just added for safety reasons + // waste 9 spare bytes + 0x35, 0x00, // ldi 00 + 0x35, 0x00, // ldi 00 + 0x34, PATCH_UINT16(0x0000), // ldi 0000 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry longbowSignatures[] = { { true, 210, "hand code crash", 5, longbowSignatureShowHandCode, longbowPatchShowHandCode }, + { true, 225, "arithmetic berry bush fix", 1, longbowSignatureBerryBushFix, longbowPatchBerryBushFix }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1596,6 +2208,39 @@ static const SciScriptPatcherEntry larry6Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Leisure Suit Larry 6 Hires + +// When entering room 270 (diving board) from room 230, a typo in the game +// script means that `setScale` is called accidentally instead of `setScaler`. +// In SSCI this did not do much because the first argument happened to be +// smaller than the y-position of `ego`, but in ScummVM the first argument is +// larger and so a debug message "y value less than vanishingY" is displayed. +static const uint16 larry6HiresSignatureSetScale[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x14b), // pushi 014b (setScale) + 0x38, SIG_UINT16(0x05), // pushi 0005 + 0x51, 0x2c, // class 2c (Scaler) + SIG_END +}; + +static const uint16 larry6HiresPatchSetScale[] = { + 0x38, SIG_UINT16(0x14f), // pushi 014f (setScaler) + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry larry6HiresSignatures[] = { + { true, 270, "fix incorrect setScale call", 1, larry6HiresSignatureSetScale, larry6HiresPatchSetScale }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // Laura Bow 1 - Colonel's Bequest // @@ -1627,9 +2272,160 @@ static const uint16 laurabow1PatchEasterEggViewFix[] = { PATCH_END }; +// When oiling the armor or opening the visor of the armor, the scripts +// first check if Laura/ego is near the armor and if she is not, they will move her +// to the armor. After that further code is executed. +// +// The current location is checked by a ego::inRect() call. +// +// The given rect for the inRect call inside openVisor::changeState was made larger for Atari ST/Amiga versions. +// We change the PC version to use the same rect. +// +// Additionally the coordinate, that Laura is moved to, is 152, 107 and may not be reachable depending on where +// Laura/ego was, when "use oil on helmet of armor" / "open visor of armor" got entered. +// Bad coordinates are for example 82, 110, which then cause collisions and effectively an endless loop. +// Game will effectively "freeze" and the user is only able to restore a previous game. +// This also happened, when using the original interpreter. +// We change the destination coordinate to 152, 110, which seems to be reachable all the time. +// +// The following patch fixes the rect for the PC version of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: openVisor::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorOpenVisorFix[] = { + 0x39, 0x04, // pushi 04 + SIG_MAGICDWORD, + 0x39, 0x6a, // pushi 6a (106d) + 0x38, SIG_UINT16(0x96), // pushi 0096 (150d) + 0x39, 0x6c, // pushi 6c (108d) + 0x38, SIG_UINT16(0x98), // pushi 0098 (152d) + SIG_END +}; + +static const uint16 laurabow1PatchArmorOpenVisorFix[] = { + PATCH_ADDTOOFFSET(+2), + 0x39, 0x68, // pushi 68 (104d) (-2) + 0x38, SIG_UINT16(0x94), // pushi 0094 (148d) (-2) + 0x39, 0x6f, // pushi 6f (111d) (+3) + 0x38, SIG_UINT16(0x9a), // pushi 009a (154d) (+2) + PATCH_END +}; + +// This here fixes the destination coordinate (exact details are above). +// +// Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy +// Responsible method: openVisor::changeState, oiling::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorMoveToFix[] = { + SIG_MAGICDWORD, + 0x36, // push + 0x39, 0x6b, // pushi 6B (107d) + 0x38, SIG_UINT16(0x0098), // pushi 98 (152d) + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + SIG_END +}; + +static const uint16 laurabow1PatchArmorMoveToFix[] = { + PATCH_ADDTOOFFSET(+1), + 0x39, 0x6e, // pushi 6E (110d) - adjust x, so that no collision can occur anymore + PATCH_END +}; + +// In some cases like for example when the player oils the arm of the armor, command input stays +// disabled, even when the player exits fast enough, so that Laura doesn't die. +// +// This is caused by the scripts only enabling control (directional movement), but do not enable command input as well. +// +// This bug also happens, when using the original interpreter. +// And it was fixed for the Atari ST + Amiga versions of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7) +// Fixes bug: #7154 +static const uint16 laurabow1SignatureArmorOilingArmFix[] = { + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + SIG_MAGICDWORD, + 0x72, SIG_UINT16(0x1a5c), // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x19a1), // lofsa "Visor" + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x194a), // lofsa "note" + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x18f3), // lofsa "valve" + 0x4a, 0x04, // send 04 + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x02, // ldi 02 + 0x1c, // ne? + 0x30, SIG_UINT16(0x0014), // bnt [to ret] + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x05, // ldi 05 + 0x1c, // ne? + 0x30, SIG_UINT16(0x000c), // bnt [to ret] + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x06, // ldi 06 + 0x1c, // ne? + 0x30, SIG_UINT16(0x0004), // bnt [to ret] + // followed by code to call script 0 export to re-enable controls and call setMotion + SIG_END +}; + +static const uint16 laurabow1PatchArmorOilingArmFix[] = { + PATCH_ADDTOOFFSET(+3), // skip over pushi 89h + 0x3c, // dup + 0x3c, // dup + 0x3c, // dup + // saves a total of 6 bytes + 0x76, // push0 + 0x72, SIG_UINT16(0x1a59), // lofsa "Can" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x19a1), // lofsa "Visor" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x194d), // lofsa "note" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x18f9), // lofsa "valve" 18f3 + 0x4a, 0x04, // send 04 + // new code to enable input as well, needs 9 spare bytes + 0x38, SIG_UINT16(0x00e2), // canInput + 0x78, // push1 + 0x78, // push1 + 0x51, 0x2b, // class User + 0x4a, 0x06, // send 06 -> call User::canInput(1) + // original code, but changed a bit to save some more bytes + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x02, // ldi 02 + 0x04, // sub + 0x31, 0x12, // bnt [to ret] + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x04, // sub + 0x31, 0x0c, // bnt [to ret] + 0x78, // push1 + 0x1a, // eq? + 0x2f, 0x08, // bt [to ret] + // saves 7 bytes, we only need 3, so waste 4 bytes + 0x35, 0x00, // ldi 0 + 0x35, 0x00, // ldi 0 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry laurabow1Signatures[] = { - { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, + { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, + { true, 37, "armor open visor fix", 1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix }, + { true, 37, "armor move to fix", 2, laurabow1SignatureArmorMoveToFix, laurabow1PatchArmorMoveToFix }, + { true, 37, "allowing input, after oiling arm", 1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2066,6 +2862,18 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Phantasmagoria + +// script, description, signature patch +static const SciScriptPatcherEntry phantasmagoriaSignatures[] = { + { true, 901, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // Police Quest 1 VGA @@ -2207,6 +3015,20 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Police Quest 4 + +// script, description, signature patch +static const SciScriptPatcherEntry pq4Signatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // At the healer's house there is a bird's nest up on the tree. // The player can throw rocks at it until it falls to the ground. @@ -2547,10 +3369,41 @@ static const uint16 qfg1vgaPatchWhiteStagDagger[] = { PATCH_END }; +// The dagger range has a script bug that can freeze the game or cause Brutus to kill you even after you've killed him. +// This is a bug in the original game. +// +// When Bruno leaves, a 300 tick countdown starts. If you kill Brutus or leave room 73 within those 300 ticks then +// the game is left in a broken state. For the rest of the game, if you ever return to the dagger range from the +// east or west during the first half of the day then the game will freeze or Brutus will come back to life +// and kill you, even if you already killed him. +// +// Special thanks, credits and kudos to sluicebox, who did a ton of research on this and even found this game bug originally. +// +// Applies to at least: English floppy, Mac floppy +// Responsible method: brutusWaits::changeState +// Fixes bug #9558 +static const uint16 qfg1vgaSignatureBrutusScriptFreeze[] = { + 0x78, // push1 + 0x38, SIG_UINT16(0x144), // pushi 144h (324d) + 0x45, 0x05, 0x02, // call export 5 of script 0 + SIG_MAGICDWORD, + 0x34, SIG_UINT16(0x12c), // ldi 12Ch (300d) + 0x65, 0x20, // aTop ticks + SIG_END +}; + +static const uint16 qfg1vgaPatchBrutusScriptFreeze[] = { + 0x34, PATCH_UINT16(0), // ldi 0 (waste 7 bytes) + 0x35, 0x00, // ldi 0 + 0x35, 0x00, // ldi 0 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg1vgaSignatures[] = { { true, 41, "moving to castle gate", 1, qfg1vgaSignatureMoveToCastleGate, qfg1vgaPatchMoveToCastleGate }, { true, 55, "healer's hut, no delay for buy/steal", 1, qfg1vgaSignatureHealerHutNoDelay, qfg1vgaPatchHealerHutNoDelay }, + { true, 73, "brutus script freeze glitch", 1, qfg1vgaSignatureBrutusScriptFreeze, qfg1vgaPatchBrutusScriptFreeze }, { true, 77, "white stag dagger throw animation glitch", 1, qfg1vgaSignatureWhiteStagDagger, qfg1vgaPatchWhiteStagDagger }, { true, 96, "funny room script bug fixed", 1, qfg1vgaSignatureFunnyRoomFix, qfg1vgaPatchFunnyRoomFix }, { true, 210, "cheetaur description fixed", 1, qfg1vgaSignatureCheetaurDescription, qfg1vgaPatchCheetaurDescription }, @@ -2734,7 +3587,7 @@ static const uint16 qfg3PatchImportDialog[] = { // Teller::doChild. We jump to this call of hero::solvePuzzle to get that same // behaviour. // Applies to at least: English, German, Italian, French, Spanish Floppy -// Responsible method: unknown +// Responsible method: uhuraTell::doChild // Fixes bug: #5172 static const uint16 qfg3SignatureWooDialog[] = { SIG_MAGICDWORD, @@ -2907,6 +3760,84 @@ static const uint16 qfg3PatchChiefPriority[] = { PATCH_END }; +// There are 3 points that can't be achieved in the game. They should've been +// awarded for telling Rakeesh and Kreesha (room 285) about the Simabni +// initiation. +// However the array of posibble messages the hero can tell in that room +// (local 156) is missing the "Tell about Initiation" message (#31) which +// awards these points. +// This patch adds the message to that array, thus allowing the hero to tell +// that message (after completing the initiation) and gain the 3 points. +// A side effect of increasing the local156 array is that the next local +// array is shifted and shrinks in size from 4 words to 3. The patch changes +// the 2 locations in the script that reference that array, to point to the new +// location ($aa --> $ab). It is safe to shrink the 2nd array to 3 words +// because only the first element in it is ever used. +// +// Note: You have to re-enter the room in case a saved game was loaded from a +// previous version of ScummVM and that saved game was made inside that room. +// +// Applies to: English, French, German, Italian, Spanish and the GOG release. +// Responsible method: heap in script 285 +// Fixes bug #7086 +static const uint16 qfg3SignatureMissingPoints1[] = { + // local[$9c] = [0 -41 -76 1 -30 -77 -33 -34 -35 -36 -37 -42 -80 999] + // local[$aa] = [0 0 0 0] + SIG_UINT16(0x0000), // 0 START MARKER + SIG_MAGICDWORD, + SIG_UINT16(0xFFD7), // -41 "Greet" + SIG_UINT16(0xFFB4), // -76 "Say Good-bye" + SIG_UINT16(0x0001), // 1 "Tell about Tarna" + SIG_UINT16(0xFFE2), // -30 "Tell about Simani" + SIG_UINT16(0xFFB3), // -77 "Tell about Prisoner" + SIG_UINT16(0xFFDF), // -33 "Dispelled Leopard Lady" + SIG_UINT16(0xFFDE), // -34 "Tell about Leopard Lady" + SIG_UINT16(0xFFDD), // -35 "Tell about Leopard Lady" + SIG_UINT16(0xFFDC), // -36 "Tell about Leopard Lady" + SIG_UINT16(0xFFDB), // -37 "Tell about Village" + SIG_UINT16(0xFFD6), // -42 "Greet" + SIG_UINT16(0xFFB0), // -80 "Say Good-bye" + SIG_UINT16(0x03E7), // 999 END MARKER + SIG_ADDTOOFFSET(+2), // local[$aa][0] + SIG_END +}; + +static const uint16 qfg3PatchMissingPoints1[] = { + PATCH_ADDTOOFFSET(+14), + PATCH_UINT16(0xFFE1), // -31 "Tell about Initiation" + PATCH_UINT16(0xFFDE), // -34 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDD), // -35 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDC), // -36 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDB), // -37 "Tell about Village" + PATCH_UINT16(0xFFD6), // -42 "Greet" + PATCH_UINT16(0xFFB0), // -80 "Say Good-bye" + PATCH_UINT16(0x03E7), // 999 END MARKER + PATCH_GETORIGINALBYTE(+28), // local[$aa][0].low + PATCH_GETORIGINALBYTE(+29), // local[$aa][0].high + PATCH_END +}; + +static const uint16 qfg3SignatureMissingPoints2a[] = { + SIG_MAGICDWORD, + 0x35, 0x00, // ldi 0 + 0xb3, 0xaa, // sali local[$aa] + SIG_END +}; + +static const uint16 qfg3SignatureMissingPoints2b[] = { + SIG_MAGICDWORD, + 0x36, // push + 0x5b, 0x02, 0xaa, // lea local[$aa] + SIG_END +}; + +static const uint16 qfg3PatchMissingPoints2[] = { + PATCH_ADDTOOFFSET(+3), + 0xab, // local[$aa] ==> local[$ab] + PATCH_END +}; + + // Partly WORKAROUND: // During combat, the game is not properly throttled. That's because the game uses // an inner loop for combat and does not iterate through the main loop. @@ -2966,19 +3897,119 @@ static const uint16 qfg3PatchCombatSpeedThrottling2[] = { PATCH_END }; +// In room #750, when the hero enters from the top east path (room #755), it +// could go out of the contained-access polygon bounds, and be able to travel +// freely in the room. +// The reason is that the cutoff y value (42) that determines whether the hero +// enters from the top or bottom path is inaccurate: it's possible to enter the +// top path from as low as y=45. +// This patch changes the cutoff to be 50 which should be low enough. +// It also changes the position in which the hero enters from the top east path +// as the current location is hidden behind the tree. +// +// Applies to: English, French, German, Italian, Spanish and the GOG release. +// Responsible method: enterEast::changeState (script 750) +// Fixes bug #6693 +static const uint16 qfg3SignatureRoom750Bounds1[] = { + // (if (< (ego y?) 42) + 0x76, // push0 ("y") + 0x76, // push0 + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x04, // send 4 + SIG_MAGICDWORD, + 0x36, // push + 0x35, 42, // ldi 42 <-- comparing ego.y with 42 + 0x22, // lt? + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds1[] = { + // (if (< (ego y?) 50) + PATCH_ADDTOOFFSET(+8), + 50, // 42 --> 50 + PATCH_END +}; + +static const uint16 qfg3SignatureRoom750Bounds2[] = { + // (ego x: 294 y: 39) + 0x78, // push1 ("x") + 0x78, // push1 + 0x38, SIG_UINT16(294), // pushi 294 + 0x76, // push0 ("y") + 0x78, // push1 + SIG_MAGICDWORD, + 0x39, 29, // pushi 29 + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x0c, // send 12 + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds2[] = { + // (ego x: 320 y: 39) + PATCH_ADDTOOFFSET(+3), + PATCH_UINT16(320), // 294 --> 320 + PATCH_ADDTOOFFSET(+3), + 39, // 29 --> 39 + PATCH_END +}; + +static const uint16 qfg3SignatureRoom750Bounds3[] = { + // (ego setMotion: MoveTo 282 29 self) + 0x38, SIG_SELECTOR16(setMotion), // pushi "setMotion" 0x133 in QfG3 + 0x39, 0x04, // pushi 4 + 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo + 0x36, // push + 0x38, SIG_UINT16(282), // pushi 282 + SIG_MAGICDWORD, + 0x39, 29, // pushi 29 + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x0c, // send 12 + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds3[] = { + // (ego setMotion: MoveTo 309 35 self) + PATCH_ADDTOOFFSET(+9), + PATCH_UINT16(309), // 282 --> 309 + PATCH_ADDTOOFFSET(+1), + 35, // 29 --> 35 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg3Signatures[] = { - { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt }, - { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar }, - { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char }, - { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority }, - { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 }, - { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 }, + { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, + { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, + { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt }, + { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar }, + { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char }, + { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority }, + { true, 285, "missing points for telling about initiation heap", 1, qfg3SignatureMissingPoints1, qfg3PatchMissingPoints1 }, + { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2a, qfg3PatchMissingPoints2 }, + { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2b, qfg3PatchMissingPoints2 }, + { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 }, + { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 }, + { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds1, qfg3PatchRoom750Bounds1 }, + { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds2, qfg3PatchRoom750Bounds2 }, + { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds3, qfg3PatchRoom750Bounds3 }, SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Quest for Glory 4 + +// script, description, signature patch +static const SciScriptPatcherEntry qfg4Signatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // script 298 of sq4/floppy has an issue. object "nest" uses another property // which isn't included in property count. We return 0 in that case, the game @@ -3406,24 +4437,35 @@ static const uint16 sq1vgaSignatureSpiderDroidTiming[] = { 0x30, SIG_UINT16(0x0005), // bnt [further method code] 0x35, 0x00, // ldi 00 0x32, SIG_UINT16(0x0052), // jmp [super-call] - 0x89, 0xa6, // lsg global[a6] + 0x89, 0xa6, // lsg global[a6] <-- flag gets set to 1 when ego went up the skeleton tail, when going down it's set to 2 0x35, 0x01, // ldi 01 0x1a, // eq? - 0x30, SIG_UINT16(0x0012), // bnt [2nd code], in case global A6 <> 1 + 0x30, SIG_UINT16(0x0012), // bnt [PChase set code], in case global A6 <> 1 0x81, 0xb5, // lag global[b5] - 0x30, SIG_UINT16(0x000d), // bnt [2nd code], in case global B5 == 0 + 0x30, SIG_UINT16(0x000d), // bnt [PChase set code], in case global B5 == 0 0x38, SIG_UINT16(0x008c), // pushi 008c 0x78, // push1 0x72, SIG_UINT16(0x1cb6), // lofsa 1CB6 (moveToPath) 0x36, // push 0x54, 0x06, // self 06 0x32, SIG_UINT16(0x0038), // jmp [super-call] + // PChase set call 0x81, 0xb5, // lag global[B5] 0x18, // not 0x30, SIG_UINT16(0x0032), // bnt [super-call], in case global B5 <> 0 + // followed by: + // is spider in current room + // is global A6h == 2? -> set PChase SIG_END }; // 58 bytes) +// Global A6h <> 1 (did NOT went up the skeleton) +// Global B5h = 0 -> set PChase +// Global B5h <> 0 -> do not do anything +// Global A6h = 1 (did went up the skeleton) +// Global B5h = 0 -> set PChase +// Global B5h <> 0 -> set moveToPath + static const uint16 sq1vgaPatchSpiderDroidTiming[] = { 0x63, 0x4e, // pToa script 0x2f, 0x68, // bt [super-call] @@ -3448,8 +4490,8 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = { 0x65, 0x4c, // aTop cycleSpeed 0x65, 0x5e, // aTop moveSpeed // new code end - 0x89, 0xb5, // lsg global[B5] - 0x31, 0x13, // bnt [2nd code chunk] + 0x81, 0xb5, // lag global[B5] + 0x31, 0x13, // bnt [PChase code chunk] 0x89, 0xa6, // lsg global[A6] 0x35, 0x01, // ldi 01 0x1a, // eq? @@ -3531,6 +4573,103 @@ static const SciScriptPatcherEntry sq5Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Shivers + +// In room 35170, there is a CCTV control station with a joystick that must be +// clicked and dragged to pan the camera. In order to enable dragging, on +// mousedown, the vJoystick::handleEvent method calls vJoystick::doVerb(1), +// which enables the drag functionality of the joystick. However, +// vJoystick::handleEvent then makes a super call to ShiversProp::handleEvent, +// which calls vJoystick::doVerb(). This second call, which fails to pass an +// argument, causes an uninitialized read off the stack for the first parameter. +// In SSCI, this happens to work because the uninitialized value on the stack +// happens to be 1. Disabling the super call avoids the bad doVerb call without +// any apparent ill effect. +static const uint16 shiversSignatureJoystickFix[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0xa5), // pushi handleEvent + 0x78, // push1 + 0x8f, 0x01, // lsp 1 + 0x59, 0x02, // &rest 2 + 0x57, 0x7f, SIG_UINT16(6), // super ShiversProp[7f], 6 + SIG_END +}; + +static const uint16 shiversPatchJoystickFix[] = { + 0x48, // ret + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry shiversSignatures[] = { + { true, 35170, "fix CCTV joystick interaction", 1, shiversSignatureJoystickFix, shiversPatchJoystickFix }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#pragma mark - +#pragma mark Space Quest 6 + +// After the explosion in the Quarters of Deepship 86, the game tries to perform +// a dramatic long fade, but does this with an unreasonably large number of +// divisions which takes tens of seconds to finish (because transitions are not +// CPU-dependent in ScummVM). +static const uint16 sq6SlowTransitionSignature1[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x578), // pushi $0578 + 0x51, 0x33, // class Styler + SIG_END +}; + +static const uint16 sq6SlowTransitionPatch1[] = { + 0x38, SIG_UINT16(500), // pushi 500 + PATCH_END +}; + +// For whatever reason, SQ6 sets the default number of transition divisions to +// be a much larger value at startup (200 vs 30) if it thinks it is running in +// Windows. Room 410 (eulogy room) also unconditionally resets divisions to the +// larger value. +static const uint16 sq6SlowTransitionSignature2[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0xc8), // pushi $c8 + 0x51, 0x33, // class Styler + SIG_END +}; + +static const uint16 sq6SlowTransitionPatch2[] = { + 0x38, SIG_UINT16(30), // pushi 30 + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry sq6Signatures[] = { + { true, 0, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 }, + { true, 15, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 22, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 410, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 }, + { true, 460, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 500, "fix slow transitions", 1, sq6SlowTransitionSignature1, sq6SlowTransitionPatch1 }, + { true, 510, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#pragma mark - +#pragma mark Torins Passage + +// script, description, signature patch +static const SciScriptPatcherEntry torinSignatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // ================================================================================= ScriptPatcher::ScriptPatcher() { @@ -3756,7 +4895,7 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, cons return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize); } -// Attention: Magic DWord is returns using platform specific byte order. This is done on purpose for performance. +// Attention: Magic DWord is returned using platform specific byte order. This is done on purpose for performance. void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) { Selector curSelector = -1; int magicOffset; @@ -3837,7 +4976,7 @@ void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescriptio } if (!magicDWordLeft) { // Magic DWORD is now known, convert to platform specific byte order - calculatedMagicDWord = READ_LE_UINT32(magicDWord); + calculatedMagicDWord = READ_UINT32(magicDWord); } } break; @@ -3862,7 +5001,8 @@ void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescriptio magicDWord[4 - magicDWordLeft] = (byte)curValue; magicDWordLeft--; if (!magicDWordLeft) { - calculatedMagicDWord = READ_LE_UINT32(magicDWord); + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_UINT32(magicDWord); } } break; @@ -3961,15 +5101,25 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_FREDDYPHARKAS: signatureTable = freddypharkasSignatures; break; +#ifdef ENABLE_SCI32 case GID_GK1: signatureTable = gk1Signatures; break; + case GID_GK2: + signatureTable = gk2Signatures; + break; +#endif case GID_KQ5: signatureTable = kq5Signatures; break; case GID_KQ6: signatureTable = kq6Signatures; break; +#ifdef ENABLE_SCI32 + case GID_KQ7: + signatureTable = kq7Signatures; + break; +#endif case GID_LAURABOW: signatureTable = laurabow1Signatures; break; @@ -3988,12 +5138,27 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_LSL6: signatureTable = larry6Signatures; break; +#ifdef ENABLE_SCI32 + case GID_LSL6HIRES: + signatureTable = larry6HiresSignatures; + break; +#endif case GID_MOTHERGOOSE256: signatureTable = mothergoose256Signatures; break; +#ifdef ENABLE_SCI32 + case GID_PHANTASMAGORIA: + signatureTable = phantasmagoriaSignatures; + break; +#endif case GID_PQ1: signatureTable = pq1vgaSignatures; break; +#ifdef ENABLE_SCI32 + case GID_PQ4: + signatureTable = pq4Signatures; + break; +#endif case GID_QFG1: signatureTable = qfg1egaSignatures; break; @@ -4006,6 +5171,14 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_QFG3: signatureTable = qfg3Signatures; break; +#ifdef ENABLE_SCI32 + case GID_QFG4: + signatureTable = qfg4Signatures; + break; + case GID_SHIVERS: + signatureTable = shiversSignatures; + break; +#endif case GID_SQ1: signatureTable = sq1vgaSignatures; break; @@ -4015,6 +5188,14 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_SQ5: signatureTable = sq5Signatures; break; +#ifdef ENABLE_SCI32 + case GID_SQ6: + signatureTable = sq6Signatures; + break; + case GID_TORIN: + signatureTable = torinSignatures; + break; +#endif default: break; } diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h index 645e0946b3..f95806a3f3 100644 --- a/engines/sci/engine/script_patches.h +++ b/engines/sci/engine/script_patches.h @@ -35,13 +35,13 @@ namespace Sci { #define SIG_BYTEMASK 0x00FF #define SIG_MAGICDWORD 0xF000 #define SIG_CODE_ADDTOOFFSET 0xE000 -#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_ +#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_) #define SIG_CODE_SELECTOR16 0x9000 #define SIG_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_ #define SIG_CODE_SELECTOR8 0x8000 #define SIG_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_ #define SIG_CODE_UINT16 0x1000 -#define SIG_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8) +#define SIG_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8) #define SIG_CODE_BYTE 0x0000 #define PATCH_END SIG_END @@ -49,17 +49,17 @@ namespace Sci { #define PATCH_VALUEMASK SIG_VALUEMASK #define PATCH_BYTEMASK SIG_BYTEMASK #define PATCH_CODE_ADDTOOFFSET SIG_CODE_ADDTOOFFSET -#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_ +#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_) #define PATCH_CODE_GETORIGINALBYTE 0xD000 -#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | _offset_ +#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | (_offset_) #define PATCH_CODE_GETORIGINALBYTEADJUST 0xC000 -#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | _offset_, (uint16)(_adjustValue_) +#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | (_offset_), (uint16)(_adjustValue_) #define PATCH_CODE_SELECTOR16 SIG_CODE_SELECTOR16 #define PATCH_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_ #define PATCH_CODE_SELECTOR8 SIG_CODE_SELECTOR8 #define PATCH_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_ #define PATCH_CODE_UINT16 SIG_CODE_UINT16 -#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8) +#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8) #define PATCH_CODE_BYTE SIG_CODE_BYTE // defines maximum scratch area for getting original bytes from unpatched script data diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index f0157a6569..54b925a1b6 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -68,7 +68,7 @@ const char *opcodeNames[] = { #endif // REDUCE_MEMORY_USAGE // Disassembles one command from the heap, returns address of next command or 0 if a ret was encountered. -reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printBytecode) { +reg_t disassemble(EngineState *s, reg32_t pos, reg_t objAddr, bool printBWTag, bool printBytecode) { SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT); Script *script_entity = NULL; const byte *scr; @@ -99,17 +99,6 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco debugN("%04x:%04x: ", PRINT_REG(pos)); - if (opcode == op_pushSelf) { // 0x3e (62) - if ((opsize & 1) && g_sci->getGameId() != GID_FANMADE) { - // Debug opcode op_file - debugN("file \"%s\"\n", scr + pos.getOffset() + 1); // +1: op_pushSelf size - retval.incOffset(bytecount - 1); - return retval; - } - } - - opsize &= 1; // byte if true, word if false - if (printBytecode) { if (pos.getOffset() + bytecount > scr_size) { warning("Operation arguments extend beyond end of script"); @@ -123,13 +112,24 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco debugN(" "); } + opsize &= 1; // byte if true, word if false + if (printBWTag) debugN("[%c] ", opsize ? 'B' : 'W'); + if (opcode == op_pushSelf && opsize && g_sci->getGameId() != GID_FANMADE) { // 0x3e (62) + // Debug opcode op_file + debugN("file \"%s\"\n", scr + pos.getOffset() + 1); // +1: op_pushSelf size + retval.incOffset(bytecount - 1); + return retval; + } + #ifndef REDUCE_MEMORY_USAGE - debugN("%s", opcodeNames[opcode]); + debugN("%-5s", opcodeNames[opcode]); #endif + static const char *defaultSeparator = "\t\t; "; + i = 0; while (g_sci->_opcode_formats[opcode][i]) { switch (g_sci->_opcode_formats[opcode][i++]) { @@ -140,14 +140,21 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco case Script_SByte: case Script_Byte: param_value = scr[retval.getOffset()]; - debugN(" %02x", scr[retval.getOffset()]); + debugN("\t%02x", param_value); + if (param_value > 9) { + debugN("%s%u", defaultSeparator, param_value); + } retval.incOffset(1); break; case Script_Word: case Script_SWord: param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); - debugN(" %04x", READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()])); + debugN("\t%04x", param_value); + if (param_value > 9) { + debugN("%s%u", defaultSeparator, param_value); + } + retval.incOffset(2); break; @@ -166,12 +173,38 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco retval.incOffset(2); } - if (opcode == op_callk) - debugN(" %s[%x]", (param_value < kernel->_kernelFuncs.size()) ? + if (opcode == op_callk) { + debugN("\t%s[%x],", (param_value < kernel->_kernelFuncs.size()) ? ((param_value < kernel->getKernelNamesSize()) ? kernel->getKernelName(param_value).c_str() : "[Unknown(postulated)]") : "<invalid>", param_value); - else - debugN(opsize ? " %02x" : " %04x", param_value); + } else if (opcode == op_super) { + Object *obj; + if (objAddr != NULL_REG && (obj = s->_segMan->getObject(objAddr)) != nullptr) { + debugN("\t%s", s->_segMan->getObjectName(obj->getSuperClassSelector())); + debugN(opsize ? "[%02x]" : "[%04x]", param_value); + } else { + debugN(opsize ? "\t%02x" : "\t%04x", param_value); + } + + debugN(","); + } else { + const char *separator = defaultSeparator; + + debugN(opsize ? "\t%02x" : "\t%04x", param_value); + if (param_value > 9) { + debugN("%s%u", separator, param_value); + separator = ", "; + } + + if (param_value >= 0x20 && param_value <= 0x7e) { + debugN("%s'%c'", separator, param_value); + separator = ", "; + } + + if (opcode == op_pushi && param_value < kernel->getSelectorNamesSize()) { + debugN("%s%s", separator, kernel->getSelectorName(param_value).c_str()); + } + } break; @@ -183,19 +216,27 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); retval.incOffset(2); } - debugN(opsize ? " %02x" : " %04x", param_value); + + if (opcode == op_lofsa || opcode == op_lofss) { + reg_t addr = make_reg(pos.getSegment(), findOffset(param_value, script_entity, pos.getOffset())); + debugN("\t%s", s->_segMan->getObjectName(addr)); + debugN(opsize ? "[%02x]" : "[%04x]", param_value); + } else { + debugN(opsize ? "\t%02x" : "\t%04x", param_value); + } + break; case Script_SRelative: if (opsize) { int8 offset = (int8)scr[retval.getOffset()]; retval.incOffset(1); - debugN(" %02x [%04x]", 0xff & offset, 0xffff & (retval.getOffset() + offset)); + debugN("\t%02x [%04x]", 0xff & offset, 0xffff & (retval.getOffset() + offset)); } else { int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]); retval.incOffset(2); - debugN(" %04x [%04x]", 0xffff & offset, 0xffff & (retval.getOffset() + offset)); + debugN("\t%04x [%04x]", 0xffff & offset, 0xffff & (retval.getOffset() + offset)); } break; @@ -396,7 +437,7 @@ void SciEngine::scriptDebug() { } debugN("Step #%d\n", s->scriptStepCounter); - disassemble(s, s->xs->addr.pc, false, true); + disassemble(s, s->xs->addr.pc, s->xs->objp, false, true); if (_debugState.runningStep) { _debugState.runningStep--; @@ -499,7 +540,7 @@ void Kernel::dumpScriptClass(char *data, int seeker, int objsize) { void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - unsigned int _seeker = 0; + uint32 _seeker = 0; Resource *script = _resMan->findResource(ResourceId(kResourceTypeScript, scriptNumber), 0); if (!script) { @@ -510,7 +551,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { while (_seeker < script->size) { int objType = (int16)READ_SCI11ENDIAN_UINT16(script->data + _seeker); int objsize; - unsigned int seeker = _seeker + 4; + uint32 seeker = _seeker + 4; if (!objType) { debugN("End of script object (#0) encountered.\n"); @@ -631,6 +672,22 @@ bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) { return false; } +bool SciEngine::checkAddressBreakpoint(const reg32_t &address) { + if (_debugState._activeBreakpointTypes & BREAK_ADDRESS) { + Common::List<Breakpoint>::const_iterator bp; + for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) { + if (bp->type == BREAK_ADDRESS && bp->regAddress == address) { + _console->debugPrintf("Break at %04x:%04x\n", PRINT_REG(address)); + _debugState.debugging = true; + _debugState.breakpointWasHit = true; + return true; + } + } + } + + return false; +} + void debugSelectorCall(reg_t send_obj, Selector selector, int argc, StackPtr argp, ObjVarRef &varp, reg_t funcp, SegManager *segMan, SelectorType selectorType) { int activeBreakpointTypes = g_sci->_debugState._activeBreakpointTypes; const char *objectName = segMan->getObjectName(send_obj); @@ -741,13 +798,13 @@ void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *ke switch (mobj->getType()) { case SEG_TYPE_HUNK: { - HunkTable *ht = (HunkTable *)mobj; + HunkTable &ht = *(HunkTable *)mobj; int index = argv[parmNr].getOffset(); - if (ht->isValidEntry(index)) { + if (ht.isValidEntry(index)) { // NOTE: This ", deleted" isn't as useful as it could // be because it prints the status _after_ the kernel // call. - debugN(" ('%s' hunk%s)", ht->_table[index].type, ht->_table[index].mem ? "" : ", deleted"); + debugN(" ('%s' hunk%s)", ht[index].type, ht[index].mem ? "" : ", deleted"); } else debugN(" (INVALID hunk ref)"); break; diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 8090b1861d..159d3170f8 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -42,7 +42,7 @@ SegManager::SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher) #ifdef ENABLE_SCI32 _arraysSegId = 0; - _stringSegId = 0; + _bitmapSegId = 0; #endif createClassTable(); @@ -71,7 +71,7 @@ void SegManager::resetSegMan() { #ifdef ENABLE_SCI32 _arraysSegId = 0; - _stringSegId = 0; + _bitmapSegId = 0; #endif // Reinitialize class table @@ -86,9 +86,8 @@ void SegManager::initSysStrings() { _parserPtr = make_reg(_saveDirPtr.getSegment(), _saveDirPtr.getOffset() + 256); #ifdef ENABLE_SCI32 } else { - SciString *saveDirString = allocateString(&_saveDirPtr); - saveDirString->setSize(256); - saveDirString->setValue(0, 0); + SciArray *saveDirString = allocateArray(kArrayTypeString, 256, &_saveDirPtr); + saveDirString->byteAt(0) = '\0'; _parserPtr = NULL_REG; // no SCI2 game had a parser #endif @@ -247,9 +246,9 @@ Object *SegManager::getObject(reg_t pos) const { if (mobj != NULL) { if (mobj->getType() == SEG_TYPE_CLONES) { - CloneTable *ct = (CloneTable *)mobj; - if (ct->isValidEntry(pos.getOffset())) - obj = &(ct->_table[pos.getOffset()]); + CloneTable &ct = *(CloneTable *)mobj; + if (ct.isValidEntry(pos.getOffset())) + obj = &(ct[pos.getOffset()]); else warning("getObject(): Trying to get an invalid object"); } else if (mobj->getType() == SEG_TYPE_SCRIPT) { @@ -313,7 +312,7 @@ reg_t SegManager::findObjectByName(const Common::String &name, int index) { } else if (mobj->getType() == SEG_TYPE_CLONES) { // It's clone table, scan all objects in it const CloneTable *ct = (const CloneTable *)mobj; - for (uint idx = 0; idx < ct->_table.size(); ++idx) { + for (uint idx = 0; idx < ct->size(); ++idx) { if (!ct->isValidEntry(idx)) continue; @@ -404,7 +403,7 @@ reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) { offset = table->allocEntry(); reg_t addr = make_reg(_hunksSegId, offset); - Hunk *h = &(table->_table[offset]); + Hunk *h = &table->at(offset); if (!h) return NULL_REG; @@ -424,7 +423,7 @@ byte *SegManager::getHunkPointer(reg_t addr) { return NULL; } - return (byte *)ht->_table[addr.getOffset()].mem; + return (byte *)ht->at(addr.getOffset()).mem; } Clone *SegManager::allocateClone(reg_t *addr) { @@ -439,7 +438,7 @@ Clone *SegManager::allocateClone(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_clonesSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } List *SegManager::allocateList(reg_t *addr) { @@ -453,7 +452,7 @@ List *SegManager::allocateList(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_listsSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } Node *SegManager::allocateNode(reg_t *addr) { @@ -467,7 +466,7 @@ Node *SegManager::allocateNode(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_nodesSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } reg_t SegManager::newNode(reg_t value, reg_t key) { @@ -486,14 +485,14 @@ List *SegManager::lookupList(reg_t addr) { return NULL; } - ListTable *lt = (ListTable *)_heap[addr.getSegment()]; + ListTable < = *(ListTable *)_heap[addr.getSegment()]; - if (!lt->isValidEntry(addr.getOffset())) { + if (!lt.isValidEntry(addr.getOffset())) { error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } - return &(lt->_table[addr.getOffset()]); + return &(lt[addr.getOffset()]); } Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { @@ -507,9 +506,9 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { return NULL; } - NodeTable *nt = (NodeTable *)_heap[addr.getSegment()]; + NodeTable &nt = *(NodeTable *)_heap[addr.getSegment()]; - if (!nt->isValidEntry(addr.getOffset())) { + if (!nt.isValidEntry(addr.getOffset())) { if (!stopOnDiscarded) return NULL; @@ -517,7 +516,7 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { return NULL; } - return &(nt->_table[addr.getOffset()]); + return &(nt[addr.getOffset()]); } SegmentRef SegManager::dereference(reg_t pointer) { @@ -797,7 +796,7 @@ size_t SegManager::strlen(reg_t str) { } -Common::String SegManager::getString(reg_t pointer, int entries) { +Common::String SegManager::getString(reg_t pointer) { Common::String ret; if (pointer.isNull()) return ret; // empty text @@ -807,10 +806,7 @@ Common::String SegManager::getString(reg_t pointer, int entries) { warning("SegManager::getString(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); return ret; } - if (entries > src_r.maxSize) { - warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(pointer)); - return ret; - } + if (src_r.isRaw) ret = (char *)src_r.raw; else { @@ -861,7 +857,10 @@ bool SegManager::freeDynmem(reg_t addr) { } #ifdef ENABLE_SCI32 -SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { +#pragma mark - +#pragma mark Arrays + +SciArray *SegManager::allocateArray(SciArrayType type, uint16 size, reg_t *addr) { ArrayTable *table; int offset; @@ -873,78 +872,94 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_arraysSegId, offset); - return &(table->_table[offset]); + + SciArray *array = &table->at(offset); + array->setType(type); + array->resize(size); + return array; } -SciArray<reg_t> *SegManager::lookupArray(reg_t addr) { +SciArray *SegManager::lookupArray(reg_t addr) { if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()]; + ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()]; - if (!arrayTable->isValidEntry(addr.getOffset())) + if (!arrayTable.isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - return &(arrayTable->_table[addr.getOffset()]); + return &(arrayTable[addr.getOffset()]); } void SegManager::freeArray(reg_t addr) { if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()]; + ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()]; - if (!arrayTable->isValidEntry(addr.getOffset())) + if (!arrayTable.isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - arrayTable->_table[addr.getOffset()].destroy(); - arrayTable->freeEntry(addr.getOffset()); + arrayTable.freeEntry(addr.getOffset()); +} + +bool SegManager::isArray(reg_t addr) const { + return addr.getSegment() == _arraysSegId; } -SciString *SegManager::allocateString(reg_t *addr) { - StringTable *table; +#pragma mark - +#pragma mark Bitmaps + +SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor, const int16 originX, const int16 originY, const int16 xResolution, const int16 yResolution, const uint32 paletteSize, const bool remap, const bool gc) { + BitmapTable *table; int offset; - if (!_stringSegId) { - table = (StringTable *)allocSegment(new StringTable(), &(_stringSegId)); - } else - table = (StringTable *)_heap[_stringSegId]; + if (!_bitmapSegId) { + table = (BitmapTable *)allocSegment(new BitmapTable(), &(_bitmapSegId)); + } else { + table = (BitmapTable *)_heap[_bitmapSegId]; + } offset = table->allocEntry(); - *addr = make_reg(_stringSegId, offset); - return &(table->_table[offset]); + *addr = make_reg(_bitmapSegId, offset); + SciBitmap &bitmap = table->at(offset); + + bitmap.create(width, height, skipColor, originX, originY, xResolution, yResolution, paletteSize, remap, gc); + + return &bitmap; } -SciString *SegManager::lookupString(reg_t addr) { - if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING) - error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); +SciBitmap *SegManager::lookupBitmap(const reg_t addr) { + if (_heap[addr.getSegment()]->getType() != SEG_TYPE_BITMAP) + error("Attempt to use non-bitmap %04x:%04x as bitmap", PRINT_REG(addr)); - StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; + BitmapTable &bitmapTable = *(BitmapTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.getOffset())) - error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); + if (!bitmapTable.isValidEntry(addr.getOffset())) + error("Attempt to use invalid entry %04x:%04x as bitmap", PRINT_REG(addr)); - return &(stringTable->_table[addr.getOffset()]); + return &(bitmapTable.at(addr.getOffset())); } -void SegManager::freeString(reg_t addr) { - if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING) - error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); +void SegManager::freeBitmap(const reg_t addr) { + if (_heap[addr.getSegment()]->getType() != SEG_TYPE_BITMAP) + error("Attempt to free non-bitmap %04x:%04x as bitmap", PRINT_REG(addr)); - StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; + BitmapTable &bitmapTable = *(BitmapTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.getOffset())) - error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); + if (!bitmapTable.isValidEntry(addr.getOffset())) + error("Attempt to free invalid entry %04x:%04x as bitmap", PRINT_REG(addr)); - stringTable->_table[addr.getOffset()].destroy(); - stringTable->freeEntry(addr.getOffset()); + bitmapTable.freeEntry(addr.getOffset()); } +#pragma mark - + #endif void SegManager::createClassTable() { - Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1); + Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), false); if (!vocab996) error("SegManager: failed to open vocab 996"); @@ -958,8 +973,6 @@ void SegManager::createClassTable() { _classTable[classNr].reg = NULL_REG; _classTable[classNr].script = scriptNr; } - - _resMan->unlockResource(vocab996); } reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment) { diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index 9da7e58770..e3ccb9ae5f 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -29,6 +29,9 @@ #include "sci/engine/vm.h" #include "sci/engine/vm_types.h" #include "sci/engine/segment.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/celobj32.h" // kLowResX, kLowResY +#endif namespace Sci { @@ -301,11 +304,10 @@ public: * Return the string referenced by pointer. * pointer can point to either a raw or non-raw segment. * @param pointer The pointer to dereference - * @parm entries The number of values expected (for checking) * @return The string referenced, or an empty string if not enough * entries were available. */ - Common::String getString(reg_t pointer, int entries = 0); + Common::String getString(reg_t pointer); /** @@ -430,13 +432,14 @@ public: reg_t getParserPtr() const { return _parserPtr; } #ifdef ENABLE_SCI32 - SciArray<reg_t> *allocateArray(reg_t *addr); - SciArray<reg_t> *lookupArray(reg_t addr); + SciArray *allocateArray(SciArrayType type, uint16 size, reg_t *addr); + SciArray *lookupArray(reg_t addr); void freeArray(reg_t addr); - SciString *allocateString(reg_t *addr); - SciString *lookupString(reg_t addr); - void freeString(reg_t addr); - SegmentId getStringSegmentId() { return _stringSegId; } + bool isArray(reg_t addr) const; + + SciBitmap *allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor = kDefaultSkipColor, const int16 originX = 0, const int16 originY = 0, const int16 xResolution = kLowResX, const int16 yResolution = kLowResY, const uint32 paletteSize = 0, const bool remap = false, const bool gc = true); + SciBitmap *lookupBitmap(reg_t addr); + void freeBitmap(reg_t addr); #endif const Common::Array<SegmentObj *> &getSegments() const { return _heap; } @@ -461,7 +464,7 @@ private: #ifdef ENABLE_SCI32 SegmentId _arraysSegId; - SegmentId _stringSegId; + SegmentId _bitmapSegId; #endif public: diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index bb90698e6a..fffa7f4d7e 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -67,8 +67,8 @@ SegmentObj *SegmentObj::createSegmentObj(SegmentType type) { case SEG_TYPE_ARRAY: mem = new ArrayTable(); break; - case SEG_TYPE_STRING: - mem = new StringTable(); + case SEG_TYPE_BITMAP: + mem = new BitmapTable(); break; #endif default: @@ -97,7 +97,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { error("Unexpected request for outgoing references from clone at %04x:%04x", PRINT_REG(addr)); } - const Clone *clone = &(_table[addr.getOffset()]); + const Clone *clone = &at(addr.getOffset()); // Emit all member variables (including references to the 'super' delegate) for (uint i = 0; i < clone->getVarCount(); i++) @@ -112,7 +112,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const { void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) { #ifdef GC_DEBUG - Object *victim_obj = &(_table[addr.getOffset()]); + Object *victim_obj = &at(addr.getOffset()); if (!(victim_obj->_flags & OBJECT_FLAG_FREED)) warning("[GC] Clone %04x:%04x not reachable and not freed (freeing now)", PRINT_REG(addr)); @@ -208,7 +208,7 @@ Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const { error("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const List *list = &(_table[addr.getOffset()]); + const List *list = &at(addr.getOffset()); tmp.push_back(list->first); tmp.push_back(list->last); @@ -225,7 +225,7 @@ Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const { if (!isValidEntry(addr.getOffset())) { error("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const Node *node = &(_table[addr.getOffset()]); + const Node *node = &at(addr.getOffset()); // We need all four here. Can't just stick with 'pred' OR 'succ' because node operations allow us // to walk around from any given node @@ -251,63 +251,39 @@ SegmentRef DynMem::dereference(reg_t pointer) { SegmentRef ArrayTable::dereference(reg_t pointer) { SegmentRef ret; - ret.isRaw = false; - ret.maxSize = _table[pointer.getOffset()].getSize() * 2; - ret.reg = _table[pointer.getOffset()].getRawData(); - return ret; -} -void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.getOffset()].destroy(); - freeEntry(sub_addr.getOffset()); + SciArray &array = at(pointer.getOffset()); + const bool isRaw = array.getType() == kArrayTypeByte || array.getType() == kArrayTypeString; + + ret.isRaw = isRaw; + ret.maxSize = array.byteSize(); + if (isRaw) { + ret.raw = (byte *)array.getRawData(); + } else { + ret.reg = (reg_t *)array.getRawData(); + } + return ret; } Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const { - Common::Array<reg_t> tmp; + Common::Array<reg_t> refs; if (!isValidEntry(addr.getOffset())) { - error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); + // Scripts may still hold references to array memory that has been + // explicitly freed; ignore these references + return refs; } - const SciArray<reg_t> *array = &(_table[addr.getOffset()]); - - for (uint32 i = 0; i < array->getSize(); i++) { - reg_t value = array->getValue(i); - if (value.getSegment() != 0) - tmp.push_back(value); + SciArray &array = const_cast<SciArray &>(at(addr.getOffset())); + if (array.getType() == kArrayTypeID || array.getType() == kArrayTypeInt16) { + for (uint16 i = 0; i < array.size(); ++i) { + const reg_t value = array.getAsID(i); + if (value.isPointer()) { + refs.push_back(value); + } + } } - return tmp; -} - -Common::String SciString::toString() const { - if (_type != 3) - error("SciString::toString(): Array is not a string"); - - Common::String string; - for (uint32 i = 0; i < _size && _data[i] != 0; i++) - string += _data[i]; - - return string; -} - -void SciString::fromString(const Common::String &string) { - if (_type != 3) - error("SciString::fromString(): Array is not a string"); - - setSize(string.size() + 1); - - for (uint32 i = 0; i < string.size(); i++) - _data[i] = string[i]; - - _data[string.size()] = 0; -} - -SegmentRef StringTable::dereference(reg_t pointer) { - SegmentRef ret; - ret.isRaw = true; - ret.maxSize = _table[pointer.getOffset()].getSize(); - ret.raw = (byte *)_table[pointer.getOffset()].getRawData(); - return ret; + return refs; } #endif diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index de7f60ac16..c5295c5523 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -24,7 +24,7 @@ #define SCI_ENGINE_SEGMENT_H #include "common/serializer.h" - +#include "common/str.h" #include "sci/engine/object.h" #include "sci/engine/vm.h" #include "sci/engine/vm_types.h" // for reg_t @@ -70,7 +70,8 @@ enum SegmentType { #ifdef ENABLE_SCI32 SEG_TYPE_ARRAY = 11, - SEG_TYPE_STRING = 12, + // 12 used to be string, now obsolete + SEG_TYPE_BITMAP = 13, #endif SEG_TYPE_MAX // For sanity checking @@ -199,33 +200,57 @@ struct Node { struct List { reg_t first; reg_t last; + +#ifdef ENABLE_SCI32 + /** + * The next node for each level of recursion during iteration over this list + * by kListEachElementDo. + */ + reg_t nextNodes[10]; + + /** + * The current level of recursion of kListEachElementDo for this list. + */ + int numRecursions; + + List() : numRecursions(0) {} +#endif }; struct Hunk { void *mem; - unsigned int size; + uint32 size; const char *type; }; template<typename T> struct SegmentObjTable : public SegmentObj { typedef T value_type; - struct Entry : public T { + struct Entry { + T *data; int next_free; /* Only used for free entries */ }; enum { HEAPENTRY_INVALID = -1 }; - int first_free; /**< Beginning of a singly linked list for entries */ int entries_used; /**< Statistical information */ - Common::Array<Entry> _table; + typedef Common::Array<Entry> ArrayType; + ArrayType _table; public: SegmentObjTable(SegmentType type) : SegmentObj(type) { initTable(); } + ~SegmentObjTable() { + for (uint i = 0; i < _table.size(); i++) { + if (isValidEntry(i)) { + freeEntry(i); + } + } + } + void initTable() { entries_used = 0; first_free = HEAPENTRY_INVALID; @@ -239,10 +264,13 @@ public: first_free = _table[oldff].next_free; _table[oldff].next_free = oldff; + assert(_table[oldff].data == nullptr); + _table[oldff].data = new T; return oldff; } else { uint newIdx = _table.size(); _table.push_back(Entry()); + _table.back().data = new T; _table[newIdx].next_free = newIdx; // Tag as 'valid' return newIdx; } @@ -261,6 +289,8 @@ public: ::error("Table::freeEntry: Attempt to release invalid table index %d", idx); _table[idx].next_free = first_free; + delete _table[idx].data; + _table[idx].data = nullptr; first_free = idx; entries_used--; } @@ -272,6 +302,14 @@ public: tmp.push_back(make_reg(segId, i)); return tmp; } + + uint size() const { return _table.size(); } + + T &at(uint index) { return *_table[index].data; } + const T &at(uint index) const { return *_table[index].data; } + + T &operator[](uint index) { return at(index); } + const T &operator[](uint index) const { return at(index); } }; @@ -323,13 +361,13 @@ struct HunkTable : public SegmentObjTable<Hunk> { } void freeEntryContents(int idx) { - free(_table[idx].mem); - _table[idx].mem = 0; + free(at(idx).mem); + at(idx).mem = 0; } virtual void freeEntry(int idx) { - SegmentObjTable<Hunk>::freeEntry(idx); freeEntryContents(idx); + SegmentObjTable<Hunk>::freeEntry(idx); } virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { @@ -370,144 +408,783 @@ public: #ifdef ENABLE_SCI32 -template<typename T> -class SciArray { +#pragma mark - +#pragma mark Arrays + +enum SciArrayType { + kArrayTypeInt16 = 0, + kArrayTypeID = 1, + kArrayTypeByte = 2, + kArrayTypeString = 3, + // Type 4 was for 32-bit integers; never used + kArrayTypeInvalid = 5 +}; + +enum SciArrayTrim { + kArrayTrimRight = 1, ///< Trim whitespace after the last non-whitespace character + kArrayTrimCenter = 2, ///< Trim whitespace between non-whitespace characters + kArrayTrimLeft = 4 ///< Trim whitespace before the first non-whitespace character +}; + +class SciArray : public Common::Serializable { public: - SciArray() : _type(-1), _data(NULL), _size(0), _actualSize(0) { } + SciArray() : + _type(kArrayTypeInvalid), + _size(0), + _data(nullptr) {} - SciArray(const SciArray<T> &array) { + SciArray(const SciArray &array) { _type = array._type; _size = array._size; - _actualSize = array._actualSize; - _data = new T[_actualSize]; + _elementSize = array._elementSize; + _data = malloc(_elementSize * _size); assert(_data); - memcpy(_data, array._data, _size * sizeof(T)); + memcpy(_data, array._data, _elementSize * _size); } - SciArray<T>& operator=(const SciArray<T> &array) { + SciArray &operator=(const SciArray &array) { if (this == &array) return *this; - delete[] _data; + free(_data); _type = array._type; _size = array._size; - _actualSize = array._actualSize; - _data = new T[_actualSize]; + _elementSize = array._elementSize; + _data = malloc(_elementSize * _size); assert(_data); - memcpy(_data, array._data, _size * sizeof(T)); + memcpy(_data, array._data, _elementSize * _size); return *this; } virtual ~SciArray() { - destroy(); + free(_data); + _size = 0; + _type = kArrayTypeInvalid; } - virtual void destroy() { - delete[] _data; - _data = NULL; - _type = -1; - _size = _actualSize = 0; - } + void saveLoadWithSerializer(Common::Serializer &s); - void setType(byte type) { - if (_type >= 0) - error("SciArray::setType(): Type already set"); + /** + * Returns the type of this array. + */ + SciArrayType getType() const { + return _type; + } + /** + * Sets the type of this array. The type of the array may only be set once. + */ + void setType(const SciArrayType type) { + assert(_type == kArrayTypeInvalid); + switch(type) { + case kArrayTypeInt16: + case kArrayTypeID: + _elementSize = sizeof(reg_t); + break; + case kArrayTypeString: + _elementSize = sizeof(char); + break; + case kArrayTypeByte: + _elementSize = sizeof(byte); + break; + default: + error("Invalid array type %d", type); + } _type = type; } - void setSize(uint32 size) { - if (_type < 0) - error("SciArray::setSize(): No type set"); + /** + * Returns the size of the array, in elements. + */ + uint16 size() const { + return _size; + } - // Check if we don't have to do anything - if (_size == size) - return; + /** + * Returns the size of the array, in bytes. + */ + uint16 byteSize() const { + return _size * _elementSize; + } - // Check if we don't have to expand the array - if (size <= _actualSize) { - _size = size; - return; + /** + * Ensures the array is large enough to store at least the given number of + * values given in `newSize`. If `force` is true, the array will be resized + * to store exactly `newSize` values. New values are initialized to zero. + */ + void resize(uint16 newSize, const bool force = false) { + if (force || newSize > _size) { + _data = realloc(_data, _elementSize * newSize); + if (newSize > _size) { + memset((byte *)_data + _elementSize * _size, 0, (newSize - _size) * _elementSize); + } + _size = newSize; + } + } + + /** + * Shrinks a string array to its optimal size. + */ + void snug() { + assert(_type == kArrayTypeString || _type == kArrayTypeByte); + resize(strlen((char *)_data) + 1, true); + } + + /** + * Returns a pointer to the array's raw data storage. + */ + void *getRawData() { return _data; } + const void *getRawData() const { return _data; } + + /** + * Gets the value at the given index as a reg_t. + */ + reg_t getAsID(const uint16 index) { + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } + + switch(_type) { + case kArrayTypeInt16: + case kArrayTypeID: + return ((reg_t *)_data)[index]; + case kArrayTypeByte: + case kArrayTypeString: { + int16 value; + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + value = ((int8 *)_data)[index]; + } else { + value = ((uint8 *)_data)[index]; + } + + return make_reg(0, value); + } + default: + error("Invalid array type %d", _type); + } + } + + /** + * Sets the value at the given index from a reg_t. + */ + void setFromID(const uint16 index, const reg_t value) { + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } + + switch(_type) { + case kArrayTypeInt16: + case kArrayTypeID: + ((reg_t *)_data)[index] = value; + break; + case kArrayTypeByte: + case kArrayTypeString: + ((byte *)_data)[index] = value.toSint16(); + break; + default: + error("Invalid array type %d", _type); + } + } + + /** + * Gets the value at the given index as an int16. + */ + int16 getAsInt16(const uint16 index) { + assert(_type == kArrayTypeInt16); + + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } + + const reg_t value = ((reg_t *)_data)[index]; + assert(value.isNumber()); + return value.toSint16(); + } + + /** + * Sets the value at the given index from an int16. + */ + void setFromInt16(const uint16 index, const int16 value) { + assert(_type == kArrayTypeInt16); + + if (getSciVersion() >= SCI_VERSION_3) { + resize(index + 1); + } else { + assert(index < _size); + } + + ((reg_t *)_data)[index] = make_reg(0, value); + } + + /** + * Returns a reference to the byte at the given index. Only valid for + * string and byte arrays. + */ + byte &byteAt(const uint16 index) { + assert(_type == kArrayTypeString || _type == kArrayTypeByte); + + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } + + return ((byte *)_data)[index]; + } + + /** + * Returns a reference to the char at the given index. Only valid for + * string and byte arrays. + */ + char &charAt(const uint16 index) { + assert(_type == kArrayTypeString || _type == kArrayTypeByte); + + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } + + return ((char *)_data)[index]; + } + + /** + * Returns a reference to the reg_t at the given index. Only valid for ID + * and int16 arrays. + */ + reg_t &IDAt(const uint16 index) { + assert(_type == kArrayTypeID || _type == kArrayTypeInt16); + + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } + + return ((reg_t *)_data)[index]; + } + + /** + * Reads values from the given reg_t pointer and sets them in the array, + * growing the array if needed to store all values. + */ + void setElements(const uint16 index, uint16 count, const reg_t *values) { + resize(index + count); + + switch (_type) { + case kArrayTypeInt16: + case kArrayTypeID: { + const reg_t *source = values; + reg_t *target = (reg_t *)_data + index; + while (count--) { + *target++ = *source++; + } + break; } + case kArrayTypeByte: + case kArrayTypeString: { + const reg_t *source = values; + byte *target = (byte *)_data + index; + while (count--) { + if (!source->isNumber()) { + error("Non-number %04x:%04x sent to byte or string array", PRINT_REG(*source)); + } + *target++ = source->getOffset(); + ++source; + } + break; + } + default: + error("Attempted write to SciArray with invalid type %d", _type); + } + } - // So, we're going to have to create an array of some sort - T *newArray = new T[size]; - memset(newArray, 0, size * sizeof(T)); + /** + * Fills the array with the given value. Existing values will be + * overwritten. The array will be grown if needed to store all values. + */ + void fill(const uint16 index, uint16 count, const reg_t value) { + if (count == 65535 /* -1 */) { + count = size() - index; + } - // Check if we never created an array before - if (!_data) { - _size = _actualSize = size; - _data = newArray; + if (!count) { return; } - // Copy data from the old array to the new - memcpy(newArray, _data, _size * sizeof(T)); + resize(index + count); - // Now set the new array to the old and set the sizes - delete[] _data; - _data = newArray; - _size = _actualSize = size; + switch (_type) { + case kArrayTypeInt16: + case kArrayTypeID: { + reg_t *target = (reg_t *)_data + index; + while (count--) { + *target = value; + } + break; + } + case kArrayTypeByte: + case kArrayTypeString: { + byte *target = (byte *)_data + index; + const byte fillValue = value.getOffset(); + while (count--) { + *target = fillValue; + } + break; + } + case kArrayTypeInvalid: + error("Attempted write to uninitialized SciArray"); + } } - T getValue(uint16 index) const { - if (index >= _size) - error("SciArray::getValue(): %d is out of bounds (%d)", index, _size); + /** + * Copies values from the source array. Both arrays will be grown if needed + * to prevent out-of-bounds reads/writes. + */ + void copy(SciArray &source, const uint16 sourceIndex, const uint16 targetIndex, uint16 count) { + if (count == 65535 /* -1 */) { + count = source.size() - sourceIndex; + } + + if (!count) { + return; + } + + resize(targetIndex + count); + source.resize(sourceIndex + count); - return _data[index]; + assert(source._elementSize == _elementSize); + + const byte *sourceData = (byte *)source._data + sourceIndex * source._elementSize; + byte *targetData = (byte *)_data + targetIndex * _elementSize; + memmove(targetData, sourceData, count * _elementSize); + } + + void byteCopy(const SciArray &source, const uint16 sourceOffset, const uint16 targetOffset, const uint16 count) { + error("SciArray::byteCopy not implemented"); } - void setValue(uint16 index, T value) { - if (index >= _size) - error("SciArray::setValue(): %d is out of bounds (%d)", index, _size); + /** + * Removes whitespace from string data held in this array. + */ + void trim(const int8 flags, const char showChar) { + enum { + kWhitespaceBoundary = 32, + kAsciiBoundary = 128 + }; + + byte *data = (byte *)_data; + byte *source; + byte *target; + + if (flags & kArrayTrimLeft) { + target = data; + source = data; + while (*source != '\0' && *source != showChar && *source <= kWhitespaceBoundary) { + ++source; + } + strcpy((char *)target, (char *)source); + } + + if (flags & kArrayTrimRight) { + source = data + strlen((char *)data) - 1; + while (source > data && *source != showChar && *source <= kWhitespaceBoundary) { + --source; + } + *source = '\0'; + } - _data[index] = value; + if (flags & kArrayTrimCenter) { + target = data; + while (*target && *target <= kWhitespaceBoundary && *target != showChar) { + ++target; + } + + if (*target) { + while (*target && (*target > kWhitespaceBoundary || *target == showChar)) { + ++target; + } + + if (*target) { + source = target; + while (*source) { + while (*source && *source <= kWhitespaceBoundary && *source != showChar) { + ++source; + } + + while (*source && (*source > kWhitespaceBoundary || *source == showChar)) { + *target++ = *source++; + } + } + + --source; + while (source > target && (*source <= kWhitespaceBoundary || *source >= kAsciiBoundary) && *source != showChar) { + --source; + } + ++source; + + memmove(target, source, strlen((char *)source) + 1); + } + } + } } - byte getType() const { return _type; } - uint32 getSize() const { return _size; } - T *getRawData() { return _data; } - const T *getRawData() const { return _data; } + /** + * Copies the string data held by this array into a new Common::String. + */ + Common::String toString() const { + assert(_type == kArrayTypeString); + return Common::String((char *)_data); + } -protected: - int8 _type; - T *_data; - uint32 _size; // _size holds the number of entries that the scripts have requested - uint32 _actualSize; // _actualSize is the actual numbers of entries allocated -}; + /** + * Copies the string from the given Common::String into this array. + */ + void fromString(const Common::String &string) { + // At least LSL6hires uses a byte-type array to hold string data + assert(_type == kArrayTypeString || _type == kArrayTypeByte); + resize(string.size() + 1, true); + Common::strlcpy((char *)_data, string.c_str(), string.size() + 1); + } -class SciString : public SciArray<char> { -public: - SciString() : SciArray<char>() { setType(3); } + Common::String toDebugString() const { + const char *type; + switch(_type) { + case kArrayTypeID: + type = "reg_t"; + break; + case kArrayTypeByte: + type = "byte"; + break; + case kArrayTypeInt16: + type = "int16"; + break; + case kArrayTypeString: + type = "string"; + break; + case kArrayTypeInvalid: + type = "invalid"; + break; + } - // We overload destroy to ensure the string type is 3 after destroying - void destroy() { SciArray<char>::destroy(); _type = 3; } + return Common::String::format("type %s; %u entries; %u bytes", type, size(), byteSize()); + } - Common::String toString() const; - void fromString(const Common::String &string); +protected: + void *_data; + SciArrayType _type; + uint16 _size; + uint8 _elementSize; }; -struct ArrayTable : public SegmentObjTable<SciArray<reg_t> > { - ArrayTable() : SegmentObjTable<SciArray<reg_t> >(SEG_TYPE_ARRAY) {} +struct ArrayTable : public SegmentObjTable<SciArray> { + ArrayTable() : SegmentObjTable<SciArray>(SEG_TYPE_ARRAY) {} - virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; void saveLoadWithSerializer(Common::Serializer &ser); SegmentRef dereference(reg_t pointer); }; -struct StringTable : public SegmentObjTable<SciString> { - StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {} +#pragma mark - +#pragma mark Bitmaps - virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.getOffset()].destroy(); - freeEntry(sub_addr.getOffset()); +enum { + kDefaultSkipColor = 250 +}; + +#define BITMAP_PROPERTY(size, property, offset)\ +inline uint##size get##property() const {\ + return READ_SCI11ENDIAN_UINT##size(_data + (offset));\ +}\ +inline void set##property(uint##size value) {\ + WRITE_SCI11ENDIAN_UINT##size(_data + (offset), (value));\ +} + +struct BitmapTable; + +/** + * A convenience class for creating and modifying in-memory + * bitmaps. + */ +class SciBitmap : public Common::Serializable { + byte *_data; + int _dataSize; + Buffer _buffer; + bool _gc; + +public: + enum BitmapFlags { + kBitmapRemap = 2 + }; + + /** + * Gets the size of the bitmap header for the current + * engine version. + */ + static inline uint16 getBitmapHeaderSize() { + // TODO: These values are accurate for each engine, but there may be no reason + // to not simply just always use size 40, since SCI2.1mid does not seem to + // actually store any data above byte 40, and SCI2 did not allow bitmaps with + // scaling resolutions other than the default (320x200). Perhaps SCI3 used + // the extra bytes, or there is some reason why they tried to align the header + // size with other headers like pic headers? +// uint32 bitmapHeaderSize; +// if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +// bitmapHeaderSize = 46; +// } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) { +// bitmapHeaderSize = 40; +// } else { +// bitmapHeaderSize = 36; +// } +// return bitmapHeaderSize; + return 46; + } + + /** + * Gets the byte size of a bitmap with the given width + * and height. + */ + static inline uint32 getBitmapSize(const uint16 width, const uint16 height) { + return width * height + getBitmapHeaderSize(); + } + + inline SciBitmap() : _data(nullptr), _dataSize(0), _gc(true) {} + + inline SciBitmap(const SciBitmap &other) { + _dataSize = other._dataSize; + _data = (byte *)malloc(other._dataSize); + memcpy(_data, other._data, other._dataSize); + if (_dataSize) { + _buffer = Buffer(getWidth(), getHeight(), getPixels()); + } + _gc = other._gc; + } + + inline ~SciBitmap() { + free(_data); + _data = nullptr; + _dataSize = 0; + } + + inline SciBitmap &operator=(const SciBitmap &other) { + if (this == &other) { + return *this; + } + + free(_data); + _dataSize = other._dataSize; + _data = (byte *)malloc(other._dataSize); + memcpy(_data, other._data, _dataSize); + if (_dataSize) { + _buffer = Buffer(getWidth(), getHeight(), getPixels()); + } + _gc = other._gc; + + return *this; + } + + /** + * Allocates and initialises a new bitmap. + */ + inline void create(const int16 width, const int16 height, const uint8 skipColor, const int16 originX, const int16 originY, const int16 xResolution, const int16 yResolution, const uint32 paletteSize, const bool remap, const bool gc) { + + _dataSize = getBitmapSize(width, height) + paletteSize; + _data = (byte *)realloc(_data, _dataSize); + _gc = gc; + + const uint16 bitmapHeaderSize = getBitmapHeaderSize(); + + setWidth(width); + setHeight(height); + setOrigin(Common::Point(originX, originY)); + setSkipColor(skipColor); + _data[9] = 0; + WRITE_SCI11ENDIAN_UINT16(_data + 10, 0); + setRemap(remap); + setDataSize(width * height); + WRITE_SCI11ENDIAN_UINT32(_data + 16, 0); + setHunkPaletteOffset(paletteSize > 0 ? (width * height) : 0); + setDataOffset(bitmapHeaderSize); + setUncompressedDataOffset(bitmapHeaderSize); + setControlOffset(0); + setXResolution(xResolution); + setYResolution(yResolution); + + _buffer = Buffer(getWidth(), getHeight(), getPixels()); + } + + inline int getRawSize() const { + return _dataSize; + } + + inline byte *getRawData() const { + return _data; + } + + inline Buffer &getBuffer() { + return _buffer; + } + + inline bool getShouldGC() const { + return _gc; + } + + inline void enableGC() { + _gc = true; + } + + inline void disableGC() { + _gc = false; + } + + BITMAP_PROPERTY(16, Width, 0); + BITMAP_PROPERTY(16, Height, 2); + + inline Common::Point getOrigin() const { + return Common::Point( + (int16)READ_SCI11ENDIAN_UINT16(_data + 4), + (int16)READ_SCI11ENDIAN_UINT16(_data + 6) + ); + } + + inline void setOrigin(const Common::Point &origin) { + WRITE_SCI11ENDIAN_UINT16(_data + 4, (uint16)origin.x); + WRITE_SCI11ENDIAN_UINT16(_data + 6, (uint16)origin.y); + } + + inline uint8 getSkipColor() const { + return _data[8]; + } + + inline void setSkipColor(const uint8 skipColor) { + _data[8] = skipColor; + } + + inline bool getRemap() const { + return READ_SCI11ENDIAN_UINT16(_data + 10) & kBitmapRemap; + } + + inline void setRemap(const bool remap) { + uint16 flags = READ_SCI11ENDIAN_UINT16(_data + 10); + if (remap) { + flags |= kBitmapRemap; + } else { + flags &= ~kBitmapRemap; + } + WRITE_SCI11ENDIAN_UINT16(_data + 10, flags); + } + + BITMAP_PROPERTY(32, DataSize, 12); + + inline uint32 getHunkPaletteOffset() const { + return READ_SCI11ENDIAN_UINT32(_data + 20); + } + + inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) { + if (hunkPaletteOffset) { + hunkPaletteOffset += getBitmapHeaderSize(); + } + + WRITE_SCI11ENDIAN_UINT32(_data + 20, hunkPaletteOffset); + } + + BITMAP_PROPERTY(32, DataOffset, 24); + + // NOTE: This property is used as a "magic number" for + // validating that a block of memory is a valid bitmap, + // and so is always set to the size of the header. + BITMAP_PROPERTY(32, UncompressedDataOffset, 28); + + // NOTE: This property always seems to be zero + BITMAP_PROPERTY(32, ControlOffset, 32); + + inline uint16 getXResolution() const { + if (getDataOffset() >= 40) { + return READ_SCI11ENDIAN_UINT16(_data + 36); + } + + // SCI2 bitmaps did not have scaling ability + return 320; + } + + inline void setXResolution(uint16 xResolution) { + if (getDataOffset() >= 40) { + WRITE_SCI11ENDIAN_UINT16(_data + 36, xResolution); + } + } + + inline uint16 getYResolution() const { + if (getDataOffset() >= 40) { + return READ_SCI11ENDIAN_UINT16(_data + 38); + } + + // SCI2 bitmaps did not have scaling ability + return 200; + } + + inline void setYResolution(uint16 yResolution) { + if (getDataOffset() >= 40) { + WRITE_SCI11ENDIAN_UINT16(_data + 38, yResolution); + } + } + + inline byte *getPixels() { + return _data + getUncompressedDataOffset(); + } + + inline byte *getHunkPalette() { + if (getHunkPaletteOffset() == 0) { + return nullptr; + } + return _data + getHunkPaletteOffset(); + } + + virtual void saveLoadWithSerializer(Common::Serializer &ser); + + void applyRemap(SciArray &clut) { + const int length = getWidth() * getHeight(); + uint8 *pixel = getPixels(); + for (int i = 0; i < length; ++i) { + const int16 color = clut.getAsInt16(*pixel); + assert(color >= 0 && color <= 255); + *pixel++ = (uint8)color; + } + } + + Common::String toString() const { + return Common::String::format("%dx%d; res %dx%d; origin %dx%d; skip color %u; %s; %s)", + getWidth(), getHeight(), + getXResolution(), getYResolution(), + getOrigin().x, getOrigin().y, + getSkipColor(), + getRemap() ? "remap" : "no remap", + getShouldGC() ? "GC" : "no GC"); + } +}; + +#undef BITMAP_PROPERTY + +struct BitmapTable : public SegmentObjTable<SciBitmap> { + BitmapTable() : SegmentObjTable<SciBitmap>(SEG_TYPE_BITMAP) {} + + SegmentRef dereference(reg_t pointer) { + SegmentRef ret; + ret.isRaw = true; + ret.maxSize = at(pointer.getOffset()).getRawSize(); + ret.raw = at(pointer.getOffset()).getRawData(); + return ret; } void saveLoadWithSerializer(Common::Serializer &ser); - SegmentRef dereference(reg_t pointer); }; #endif diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 320f2c0664..2bc4051a79 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -21,6 +21,7 @@ */ #include "sci/sci.h" +#include "sci/engine/features.h" #include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" @@ -57,11 +58,11 @@ void Kernel::mapSelectors() { FIND_SELECTOR(nsTop); FIND_SELECTOR(nsLeft); FIND_SELECTOR(nsBottom); + FIND_SELECTOR(nsRight); FIND_SELECTOR(lsTop); FIND_SELECTOR(lsLeft); FIND_SELECTOR(lsBottom); FIND_SELECTOR(lsRight); - FIND_SELECTOR(nsRight); FIND_SELECTOR(signal); FIND_SELECTOR(illegalBits); FIND_SELECTOR(brTop); @@ -173,6 +174,7 @@ void Kernel::mapSelectors() { FIND_SELECTOR(left); FIND_SELECTOR(bottom); FIND_SELECTOR(right); + FIND_SELECTOR(seenRect); FIND_SELECTOR(resY); FIND_SELECTOR(resX); FIND_SELECTOR(dimmed); @@ -180,6 +182,7 @@ void Kernel::mapSelectors() { FIND_SELECTOR(back); FIND_SELECTOR(skip); FIND_SELECTOR(borderColor); + FIND_SELECTOR(width); FIND_SELECTOR(fixPriority); FIND_SELECTOR(mirrored); FIND_SELECTOR(visible); @@ -192,7 +195,12 @@ void Kernel::mapSelectors() { FIND_SELECTOR(textLeft); FIND_SELECTOR(textBottom); FIND_SELECTOR(textRight); + FIND_SELECTOR(title); + FIND_SELECTOR(titleFont); + FIND_SELECTOR(titleFore); + FIND_SELECTOR(titleBack); FIND_SELECTOR(magnifier); + FIND_SELECTOR(frameOut); FIND_SELECTOR(casts); #endif } @@ -207,10 +215,17 @@ reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId) { } #ifdef ENABLE_SCI32 -void updateInfoFlagViewVisible(Object *obj, int offset) { - // TODO: Make this correct for all SCI versions - // Selectors 26 through 44 are selectors for View script objects in SQ6 - if (offset >= 26 && offset <= 44 && getSciVersion() >= SCI_VERSION_2) { +void updateInfoFlagViewVisible(Object *obj, int index) { + int minIndex, maxIndex; + if (g_sci->_features->usesAlternateSelectors()) { + minIndex = 24; + maxIndex = 43; + } else { + minIndex = 26; + maxIndex = 44; + } + + if (index >= minIndex && index <= maxIndex && getSciVersion() >= SCI_VERSION_2) { obj->setInfoSelectorFlag(kInfoFlagViewVisible); } } @@ -220,20 +235,19 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t ObjVarRef address; if ((selectorId < 0) || (selectorId > (int)g_sci->getKernel()->getSelectorNamesSize())) { - error("Attempt to write to invalid selector %d of" - " object at %04x:%04x.", selectorId, PRINT_REG(object)); - return; + const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin(); + error("Attempt to write to invalid selector %d. Address %04x:%04x, %s", selectorId, PRINT_REG(object), origin.toString().c_str()); } - if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) - error("Selector '%s' of object at %04x:%04x could not be" - " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); - else { - *address.getPointer(segMan) = value; + if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) { + const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin(); + error("Selector '%s' of object could not be written to. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str()); + } + + *address.getPointer(segMan) = value; #ifdef ENABLE_SCI32 - updateInfoFlagViewVisible(segMan->getObject(object), selectorId); + updateInfoFlagViewVisible(segMan->getObject(object), address.varindex); #endif - } } void invokeSelector(EngineState *s, reg_t object, int selectorId, @@ -249,12 +263,12 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId, slc_type = lookupSelector(s->_segMan, object, selectorId, NULL, NULL); if (slc_type == kSelectorNone) { - error("Selector '%s' of object at %04x:%04x could not be invoked", - g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); + const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin(); + error("invokeSelector: Selector '%s' could not be invoked. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str()); } if (slc_type == kSelectorVariable) { - error("Attempting to invoke variable selector %s of object %04x:%04x", - g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); + const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin(); + error("invokeSelector: Attempting to invoke variable selector %s. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str()); } for (i = 0; i < argc; i++) @@ -282,8 +296,8 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector sel selectorId &= ~1; if (!obj) { - error("lookupSelector(): Attempt to send to non-object or invalid script. Address was %04x:%04x", - PRINT_REG(obj_location)); + const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin(); + error("lookupSelector: Attempt to send to non-object or invalid script. Address %04x:%04x, %s", PRINT_REG(obj_location), origin.toString().c_str()); } index = obj->locateVarSelector(segMan, selectorId); diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index 1952ca0599..8d1edeb489 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -135,29 +135,28 @@ struct SelectorCache { Selector bitmap; // Used to hold the text bitmap for SCI32 texts Selector plane; - Selector top; - Selector left; - Selector bottom; - Selector right; - Selector resX; - Selector resY; + Selector top, left, bottom, right; + Selector resX, resY; Selector fore; Selector back; Selector skip; Selector dimmed; Selector borderColor; + Selector width; Selector fixPriority; Selector mirrored; Selector visible; + Selector seenRect; Selector useInsetRect; Selector inTop, inLeft, inBottom, inRight; Selector textTop, textLeft, textBottom, textRight; + Selector title, titleFont, titleFore, titleBack; Selector magnifier; - + Selector frameOut; Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts) #endif }; @@ -201,10 +200,10 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId, /** * SCI32 set kInfoFlagViewVisible in the -info- selector if a certain * range of properties was written to. - * This function checks if offset is in the right range, and sets the flag + * This function checks if index is in the right range, and sets the flag * on obj.-info- if it is. */ -void updateInfoFlagViewVisible(Object *obj, int offset); +void updateInfoFlagViewVisible(Object *obj, int index); #endif } // End of namespace Sci diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index d53e6b48c8..c23add1211 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -70,9 +70,6 @@ static const uint16 s_halfWidthSJISMap[256] = { EngineState::EngineState(SegManager *segMan) : _segMan(segMan), -#ifdef ENABLE_SCI32 - _virtualIndexFile(0), -#endif _dirseeker() { reset(false); @@ -80,9 +77,6 @@ EngineState::EngineState(SegManager *segMan) EngineState::~EngineState() { delete _msgState; -#ifdef ENABLE_SCI32 - delete _virtualIndexFile; -#endif } void EngineState::reset(bool isRestoring) { @@ -95,6 +89,7 @@ void EngineState::reset(bool isRestoring) { // reset delayed restore game functionality _delayedRestoreGame = false; _delayedRestoreGameId = 0; + _delayedRestoreFromLauncher = false; executionStackBase = 0; _executionStackPosChanged = false; @@ -126,9 +121,6 @@ void EngineState::reset(bool isRestoring) { _videoState.reset(); _syncedAudioOptions = false; - - _vmdPalStart = 0; - _vmdPalEnd = 256; } void EngineState::speedThrottler(uint32 neededSleep) { @@ -167,11 +159,11 @@ void EngineState::initGlobals() { } uint16 EngineState::currentRoomNumber() const { - return variables[VAR_GLOBAL][13].toUint16(); + return variables[VAR_GLOBAL][kGlobalVarNewRoomNo].toUint16(); } void EngineState::setRoomNumber(uint16 roomNumber) { - variables[VAR_GLOBAL][13] = make_reg(0, roomNumber); + variables[VAR_GLOBAL][kGlobalVarNewRoomNo] = make_reg(0, roomNumber); } void EngineState::shrinkStackToBase() { @@ -210,12 +202,12 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu const byte *textPtr = (const byte *)str.c_str(); byte curChar = 0; byte curChar2 = 0; - + while (1) { curChar = *textPtr; if (!curChar) break; - + if ((curChar == '%') || (curChar == '#')) { curChar2 = *(textPtr + 1); foundLanguage = charToLanguage(curChar2); @@ -244,7 +236,7 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu while (1) { curChar = *textPtr; - + switch (curChar) { case 0: // Terminator NUL return fullWidth; @@ -265,7 +257,7 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu continue; } } - + textPtr++; mappedChar = s_halfWidthSJISMap[curChar]; @@ -388,4 +380,46 @@ void SciEngine::checkVocabularySwitch() { } } +SciCallOrigin EngineState::getCurrentCallOrigin() const { + // IMPORTANT: This method must always return values that match *exactly* the + // values in the workaround tables in workarounds.cpp, or workarounds will + // be broken + + Common::String curObjectName = _segMan->getObjectName(xs->sendp); + Common::String curMethodName; + const Script *localScript = _segMan->getScriptIfLoaded(xs->local_segment); + int curScriptNr = localScript->getScriptNumber(); + + if (xs->debugLocalCallOffset != -1) { + // if lastcall was actually a local call search back for a real call + Common::List<ExecStack>::const_iterator callIterator = _executionStack.end(); + while (callIterator != _executionStack.begin()) { + callIterator--; + const ExecStack &loopCall = *callIterator; + if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) { + xs->debugSelector = loopCall.debugSelector; + xs->debugExportId = loopCall.debugExportId; + break; + } + } + } + + if (xs->type == EXEC_STACK_TYPE_CALL) { + if (xs->debugSelector != -1) { + curMethodName = g_sci->getKernel()->getSelectorName(xs->debugSelector); + } else if (xs->debugExportId != -1) { + curObjectName = ""; + curMethodName = Common::String::format("export %d", xs->debugExportId); + } + } + + SciCallOrigin reply; + reply.objectName = curObjectName; + reply.methodName = curMethodName; + reply.scriptNr = curScriptNr; + reply.localCallOffset = xs->debugLocalCallOffset; + reply.roomNr = currentRoomNumber(); + return reply; +} + } // End of namespace Sci diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index 0f04e32fe5..5297a176d3 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -57,10 +57,6 @@ enum AbortGameState { kAbortQuitGame = 3 }; -// slot 0 is the ScummVM auto-save slot, which is not used by us, but is still reserved -#define SAVEGAMESLOT_FIRST 1 -#define SAVEGAMESLOT_LAST 99 - // We assume that scripts give us savegameId 0->99 for creating a new save slot // and savegameId 100->199 for existing save slots. Refer to kfile.cpp enum { @@ -99,6 +95,21 @@ struct VideoState { } }; +/** + * Trace information about a VM function call. + */ +struct SciCallOrigin { + int scriptNr; //< The source script of the function + Common::String objectName; //< The name of the object being called + Common::String methodName; //< The name of the method being called + int localCallOffset; //< The byte offset of a local script subroutine called by the origin method. -1 if not in a local subroutine. + int roomNr; //< The room that was loaded at the time of the call + + Common::String toString() const { + return Common::String::format("method %s::%s (room %d, script %d, localCall %x)", objectName.c_str(), methodName.c_str(), roomNr, scriptNr, localCallOffset); + } +}; + struct EngineState : public Common::Serializable { public: EngineState(SegManager *segMan); @@ -131,17 +142,15 @@ public: int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween int16 _lastSaveNewId; // last newly created filename-id by kSaveGame -#ifdef ENABLE_SCI32 - VirtualIndexFile *_virtualIndexFile; -#endif - // see detection.cpp / SciEngine::loadGameState() bool _delayedRestoreGame; // boolean, that triggers delayed restore (triggered by ScummVM menu) int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu) + bool _delayedRestoreFromLauncher; // is set, when the the delayed restore game was triggered from launcher uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition() + int16 _cursorWorkaroundPosCount; // When the cursor is reported to be at the previously set coordinate, we won't disable the workaround unless it happened for this many times Common::Point _cursorWorkaroundPoint; Common::Rect _cursorWorkaroundRect; @@ -203,14 +212,19 @@ public: uint16 _memorySegmentSize; byte _memorySegment[kMemorySegmentMax]; + // TODO: Excise video code from the state manager VideoState _videoState; - uint16 _vmdPalStart, _vmdPalEnd; bool _syncedAudioOptions; /** * Resets the engine state. */ void reset(bool isRestoring); + + /** + * Finds and returns the origin of the current call. + */ + SciCallOrigin getCurrentCallOrigin() const; }; } // End of namespace Sci diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 66d9fee5fd..8e407a6ab9 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -20,6 +20,7 @@ * */ +#include "common/config-manager.h" #include "common/debug.h" #include "common/debug-channels.h" @@ -124,32 +125,30 @@ static reg_t read_var(EngineState *s, int type, int index) { case VAR_TEMP: { // Uninitialized read on a temp // We need to find correct replacements for each situation manually - SciTrackOriginReply originReply; + SciCallOrigin originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply); if (solution.type == WORKAROUND_NONE) { #ifdef RELEASE_BUILD // If we are running an official ScummVM release -> fake 0 in unknown cases - warning("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)", - index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(), - originReply.scriptNr, originReply.localCallOffset); + warning("Uninitialized read for temp %d from %s", index, originReply.toString().c_str()); s->variables[type][index] = NULL_REG; break; #else - error("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)", - index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(), - originReply.scriptNr, originReply.localCallOffset); + error("Uninitialized read for temp %d from %s", index, originReply.toString().c_str()); #endif } assert(solution.type == WORKAROUND_FAKE); s->variables[type][index] = make_reg(0, solution.value); break; } - case VAR_PARAM: + case VAR_PARAM: { // Out-of-bounds read for a parameter that goes onto stack and hits an uninitialized temp // We return 0 currently in that case - debugC(kDebugLevelVM, "[VM] Read for a parameter goes out-of-bounds, onto the stack and gets uninitialized temp"); + const SciCallOrigin origin = s->getCurrentCallOrigin(); + warning("Uninitialized read for parameter %d from %s", index, origin.toString().c_str()); return NULL_REG; + } default: break; } @@ -179,7 +178,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) { // stopGroop object, which points to ego, to the new ego object. If this is not // done, ego's movement will not be updated properly, so the result is // unpredictable (for example in LSL5, Patti spins around instead of walking). - if (index == 0 && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) { // global 0 is ego + if (index == kGlobalVarEgo && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) { reg_t stopGroopPos = s->_segMan->findObjectByName("stopGroop"); if (!stopGroopPos.isNull()) { // does the game have a stopGroop object? // Find the "client" member variable of the stopGroop object, and update it @@ -200,9 +199,26 @@ static void write_var(EngineState *s, int type, int index, reg_t value) { s->variables[type][index] = value; - if (type == VAR_GLOBAL && index == 90) { +#ifdef ENABLE_SCI32 + if (type == VAR_GLOBAL && getSciVersion() >= SCI_VERSION_2 && g_sci->getEngineState()->_syncedAudioOptions) { + + switch (g_sci->getGameId()) { + case GID_LSL6HIRES: + if (index == kGlobalVarLSL6HiresTextSpeed) { + ConfMan.setInt("talkspeed", (14 - value.toSint16()) * 255 / 13); + } + break; + default: + if (index == kGlobalVarTextSpeed) { + ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8); + } + } + } +#endif + + if (type == VAR_GLOBAL && index == kGlobalVarMessageType) { // The game is trying to change its speech/subtitle settings - if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][4] == TRUE_REG) { + if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { // ScummVM audio options haven't been applied yet, so apply them. // We also force the ScummVM audio options when loading a game from // the launcher. @@ -232,15 +248,16 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP scr = s->_segMan->getScript(seg); } + // Check if a breakpoint is set on this method + g_sci->checkExportBreakpoint(script, pubfunct); + uint32 exportAddr = scr->validateExportFunc(pubfunct, false); if (!exportAddr) return NULL; - // Check if a breakpoint is set on this method - g_sci->checkExportBreakpoint(script, pubfunct); - + assert(argp[0].toUint16() == argc); // The first argument is argc ExecStack xstack(calling_obj, calling_obj, sp, argc, argp, - seg, make_reg32(seg, exportAddr), -1, pubfunct, -1, + seg, make_reg32(seg, exportAddr), -1, -1, -1, pubfunct, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_CALL); s->_executionStack.push_back(xstack); return &(s->_executionStack.back()); @@ -312,8 +329,9 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt if (activeBreakpointTypes || DebugMan.isDebugChannelEnabled(kDebugLevelScripts)) debugSelectorCall(send_obj, selector, argc, argp, varp, funcp, s->_segMan, selectorType); + assert(argp[0].toUint16() == argc); // The first argument is argc ExecStack xstack(work_obj, send_obj, curSP, argc, argp, - 0xFFFF, curFP, selector, -1, -1, + 0xFFFF, curFP, selector, -1, -1, -1, -1, origin, stackType); if (selectorType == kSelectorVariable) @@ -330,17 +348,20 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt argp += argc + 1; } // while (framesize > 0) + // Perform all varselector actions at the top of the stack immediately. + // Note that there may be some behind method selector calls as well; + // those will get executed by op_ret later. _exec_varselectors(s); return s->_executionStack.empty() ? NULL : &(s->_executionStack.back()); } -static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, reg_t *argv) { +static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int kernelSubCallNr, int argc, reg_t *argv) { // Add stack frame to indicate we're executing a callk. // This is useful in debugger backtraces if this // kernel function calls a script itself. ExecStack xstack(NULL_REG, NULL_REG, NULL, argc, argv - 1, 0xFFFF, make_reg32(0, 0), - kernelCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL); + -1, kernelCallNr, kernelSubCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL); s->_executionStack.push_back(xstack); } @@ -359,16 +380,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { if (kernelCall.signature && !kernel->signatureMatch(kernelCall.signature, argc, argv)) { // signature mismatch, check if a workaround is available - SciTrackOriginReply originReply; + SciCallOrigin originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply); switch (solution.type) { case WORKAROUND_NONE: { Common::String signatureDetailsStr; kernel->signatureDebug(signatureDetailsStr, kernelCall.signature, argc, argv); - error("\n%s[VM] k%s[%x]: signature mismatch in method %s::%s (room %d, script %d, localCall 0x%x)", - signatureDetailsStr.c_str(), - kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(), - s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset); + error("\n%s[VM] k%s[%x]: signature mismatch in %s", signatureDetailsStr.c_str(), kernelCall.name, kernelCallNr, originReply.toString().c_str()); break; } case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone @@ -386,7 +404,8 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { // Call kernel function if (!kernelCall.subFunctionCount) { - addKernelCallToExecStack(s, kernelCallNr, argc, argv); + argv[-1] = make_reg(0, argc); // The first argument is argc + addKernelCallToExecStack(s, kernelCallNr, -1, argc, argv); s->r_acc = kernelCall.function(s, argc, argv); if (kernelCall.debugLogging) @@ -402,6 +421,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr); if (argv[0].isPointer()) error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr); + +#ifdef ENABLE_SCI32 + // The Windows version of kShowMovie has subops, but the subop number + // is put in the second parameter in SCI2.1+, even though every other + // kcall with subops puts the subop in the first parameter. To allow use + // of the normal subops system, we swap the arguments so the subop + // number is in the usual place. + if (getSciVersion() > SCI_VERSION_2 && + g_sci->getPlatform() == Common::kPlatformWindows && + strcmp(kernelCall.name, "ShowMovie") == 0) { + assert(argc > 1); + SWAP(argv[0], argv[1]); + } +#endif + const uint16 subId = argv[0].toUint16(); // Skip over subfunction-id argc--; @@ -411,7 +445,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId]; if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) { // Signature mismatch - SciTrackOriginReply originReply; + SciCallOrigin originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply); switch (solution.type) { case WORKAROUND_NONE: { @@ -420,15 +454,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { int callNameLen = strlen(kernelCall.name); if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) { const char *subCallName = kernelSubCall.name + callNameLen; - error("\n%s[VM] k%s(%s): signature mismatch in method %s::%s (room %d, script %d, localCall %x)", - signatureDetailsStr.c_str(), - kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(), - s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset); + error("\n%s[VM] k%s(%s): signature mismatch in %s", + signatureDetailsStr.c_str(), kernelCall.name, subCallName, + originReply.toString().c_str()); } - error("\n%s[VM] k%s: signature mismatch in method %s::%s (room %d, script %d, localCall %x)", - signatureDetailsStr.c_str(), - kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(), - s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset); + error("\n%s[VM] k%s: signature mismatch in %s", + signatureDetailsStr.c_str(), kernelSubCall.name, + originReply.toString().c_str()); break; } case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone @@ -444,7 +476,8 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { } if (!kernelSubCall.function) error("[VM] k%s: subfunction ID %d requested, but not available", kernelCall.name, subId); - addKernelCallToExecStack(s, kernelCallNr, argc, argv); + argv[-1] = make_reg(0, argc); // The first argument is argc + addKernelCallToExecStack(s, kernelCallNr, subId, argc, argv); s->r_acc = kernelSubCall.function(s, argc, argv); if (kernelSubCall.debugLogging) @@ -545,6 +578,31 @@ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]) return offset; } +uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset) { + uint32 offset; + + switch (g_sci->_features->detectLofsType()) { + case SCI_VERSION_0_EARLY: + offset = (uint16)pcOffset + relOffset; + break; + case SCI_VERSION_1_MIDDLE: + offset = relOffset; + break; + case SCI_VERSION_1_1: + offset = relOffset + scr->getScriptSize(); + break; + case SCI_VERSION_3: + // In theory this can break if the variant with a one-byte argument is + // used. For now, assume it doesn't happen. + offset = scr->relocateOffsetSci3(pcOffset - 2); + break; + default: + error("Unknown lofs type"); + } + + return offset; +} + void run_vm(EngineState *s) { assert(s); @@ -612,6 +670,8 @@ void run_vm(EngineState *s) { if (s->abortScriptProcessing != kAbortNone) return; // Stop processing + g_sci->checkAddressBreakpoint(s->xs->addr.pc); + // Debug if this has been requested: // TODO: re-implement sci_debug_flags if (g_sci->_debugState.debugging /* sci_debug_flags*/) { @@ -834,14 +894,15 @@ void run_vm(EngineState *s) { int argc = (opparams[1] >> 1) // Given as offset, but we need count + 1 + s->r_rest; StackPtr call_base = s->xs->sp - argc; - s->xs->sp[1].incOffset(s->r_rest); uint32 localCallOffset = s->xs->addr.pc.getOffset() + opparams[0]; + int final_argc = (call_base->requireUint16()) + s->r_rest; + call_base[0] = make_reg(0, final_argc); // The first argument is argc ExecStack xstack(s->xs->objp, s->xs->objp, s->xs->sp, - (call_base->requireUint16()) + s->r_rest, call_base, + final_argc, call_base, s->xs->local_segment, make_reg32(s->xs->addr.pc.getSegment(), localCallOffset), - NULL_SELECTOR, -1, localCallOffset, s->_executionStack.size() - 1, + NULL_SELECTOR, -1, -1, -1, localCallOffset, s->_executionStack.size() - 1, EXEC_STACK_TYPE_CALL); s->_executionStack.push_back(xstack); @@ -938,9 +999,13 @@ void run_vm(EngineState *s) { if (old_xs->type == EXEC_STACK_TYPE_VARSELECTOR) { // varselector access? reg_t *var = old_xs->getVarPointer(s->_segMan); - if (old_xs->argc) // write? + if (old_xs->argc) { // write? *var = old_xs->variables_argp[1]; - else // No, read + +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(s->_segMan->getObject(old_xs->addr.varp.obj), old_xs->addr.varp.varindex); +#endif + } else // No, read s->r_acc = *var; } @@ -1100,7 +1165,7 @@ void run_vm(EngineState *s) { // Accumulator To Property validate_property(s, obj, opparams[0]) = s->r_acc; #ifdef ENABLE_SCI32 - updateInfoFlagViewVisible(obj, opparams[0]); + updateInfoFlagViewVisible(obj, opparams[0]>>1); #endif break; @@ -1113,7 +1178,7 @@ void run_vm(EngineState *s) { // Stack To Property validate_property(s, obj, opparams[0]) = POP32(); #ifdef ENABLE_SCI32 - updateInfoFlagViewVisible(obj, opparams[0]); + updateInfoFlagViewVisible(obj, opparams[0]>>1); #endif break; @@ -1130,7 +1195,7 @@ void run_vm(EngineState *s) { else opProperty -= 1; #ifdef ENABLE_SCI32 - updateInfoFlagViewVisible(obj, opparams[0]); + updateInfoFlagViewVisible(obj, opparams[0]>>1); #endif if (opcode == op_ipToa || opcode == op_dpToa) s->r_acc = opProperty; @@ -1140,38 +1205,21 @@ void run_vm(EngineState *s) { } case op_lofsa: // 0x39 (57) - case op_lofss: // 0x3a (58) + case op_lofss: { // 0x3a (58) // Load offset to accumulator or push to stack - r_temp.setSegment(s->xs->addr.pc.getSegment()); - - switch (g_sci->_features->detectLofsType()) { - case SCI_VERSION_0_EARLY: - r_temp.setOffset((uint16)s->xs->addr.pc.getOffset() + opparams[0]); - break; - case SCI_VERSION_1_MIDDLE: - r_temp.setOffset(opparams[0]); - break; - case SCI_VERSION_1_1: - r_temp.setOffset(opparams[0] + local_script->getScriptSize()); - break; - case SCI_VERSION_3: - // In theory this can break if the variant with a one-byte argument is - // used. For now, assume it doesn't happen. - r_temp.setOffset(local_script->relocateOffsetSci3(s->xs->addr.pc.getOffset() - 2)); - break; - default: - error("Unknown lofs type"); - } + r_temp.setSegment(s->xs->addr.pc.getSegment()); + r_temp.setOffset(findOffset(opparams[0], local_script, s->xs->addr.pc.getOffset())); if (r_temp.getOffset() >= scr->getBufSize()) error("VM: lofsa/lofss operation overflowed: %04x:%04x beyond end" - " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize()); + " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize()); if (opcode == op_lofsa) s->r_acc = r_temp; else PUSH32(r_temp); break; + } case op_push0: // 0x3b (59) PUSH(0); diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 514bf58b64..13f60fd49c 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -36,6 +36,7 @@ class SegManager; struct EngineState; class Object; class ResourceManager; +class Script; /** Number of bytes to be allocated for the stack */ #define VM_STACK_SIZE 0x1000 @@ -93,16 +94,19 @@ struct ExecStack { SegmentId local_segment; // local variables etc - Selector debugSelector; // The selector which was used to call or -1 if not applicable - int debugExportId; // The exportId which was called or -1 if not applicable - int debugLocalCallOffset; // Local call offset or -1 if not applicable - int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call + Selector debugSelector; // The selector which was used to call or -1 if not applicable + int debugExportId; // The exportId which was called or -1 if not applicable + int debugLocalCallOffset; // Local call offset or -1 if not applicable + int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call + int debugKernelFunction; // The kernel function called, or -1 if not applicable + int debugKernelSubFunction; // The kernel subfunction called, or -1 if not applicable ExecStackType type; reg_t* getVarPointer(SegManager *segMan) const; ExecStack(reg_t objp_, reg_t sendp_, StackPtr sp_, int argc_, StackPtr argp_, SegmentId localsSegment_, reg32_t pc_, Selector debugSelector_, + int debugKernelFunction_, int debugKernelSubFunction_, int debugExportId_, int debugLocalCallOffset_, int debugOrigin_, ExecStackType type_) { objp = objp_; @@ -112,12 +116,13 @@ struct ExecStack { fp = sp = sp_; argc = argc_; variables_argp = argp_; - *variables_argp = make_reg(0, argc); // The first argument is argc if (localsSegment_ != 0xFFFF) local_segment = localsSegment_; else local_segment = pc_.getSegment(); debugSelector = debugSelector_; + debugKernelFunction = debugKernelFunction_; + debugKernelSubFunction = debugKernelSubFunction_; debugExportId = debugExportId_; debugLocalCallOffset = debugLocalCallOffset_; debugOrigin = debugOrigin_; @@ -132,6 +137,23 @@ enum { VAR_PARAM = 3 }; +enum GlobalVar { + kGlobalVarEgo = 0, + kGlobalVarCurrentRoom = 2, + kGlobalVarSpeed = 3, // SCI16 + kGlobalVarQuit = 4, + kGlobalVarPlanes = 10, // SCI32 + kGlobalVarCurrentRoomNo = 11, + kGlobalVarPreviousRoomNo = 12, + kGlobalVarNewRoomNo = 13, + kGlobalVarScore = 15, + kGlobalVarFastCast = 84, // SCI16 + kGlobalVarMessageType = 90, + kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest + kGlobalVarLSL6HiresTextSpeed = 167, // 1 is fastest, 14 is slowest + kGlobalVarShivers1Score = 349 +}; + /** Number of kernel calls in between gcs; should be < 50000 */ enum { GC_INTERVAL = 0x8000 @@ -366,6 +388,16 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj, Selector selectorid, */ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]); +/** + * Finds the script-absolute offset of a relative object offset. + * + * @param[in] relOffset the relative object offset + * @param[in] scr the owner script object, used by SCI1.1+ + * @param[in] pcOffset the offset of the program counter, used by SCI0early and + * SCI3 + */ +uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset); + } // End of namespace Sci #endif // SCI_ENGINE_VM_H diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index 53a5a5c507..b2e250ab8b 100644 --- a/engines/sci/engine/vm_types.cpp +++ b/engines/sci/engine/vm_types.cpp @@ -66,13 +66,10 @@ void reg_t::setOffset(uint32 offset) { } reg_t reg_t::lookForWorkaround(const reg_t right, const char *operation) const { - SciTrackOriginReply originReply; + SciCallOrigin originReply; SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply); if (solution.type == WORKAROUND_NONE) - error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from method %s::%s (room %d, script %d, localCall %x)", - operation, PRINT_REG(*this), PRINT_REG(right), originReply.objectName.c_str(), - originReply.methodName.c_str(), g_sci->getEngineState()->currentRoomNumber(), originReply.scriptNr, - originReply.localCallOffset); + error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from %s", operation, PRINT_REG(*this), PRINT_REG(right), originReply.toString().c_str()); assert(solution.type == WORKAROUND_FAKE); return make_reg(0, solution.value); } @@ -230,6 +227,10 @@ int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { return toUint16() - right.toUint16(); else return toSint16() - right.toSint16(); +#ifdef ENABLE_SCI32 + } else if (getSciVersion() >= SCI_VERSION_2) { + return sci32Comparison(right); +#endif } else if (pointerComparisonWithInteger(right)) { return 1; } else if (right.pointerComparisonWithInteger(*this)) { @@ -238,6 +239,26 @@ int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { return lookForWorkaround(right, "comparison").toSint16(); } +#ifdef ENABLE_SCI32 +int reg_t::sci32Comparison(const reg_t right) const { + // In SCI32, MemIDs are normally indexes into the memory manager's handle + // list, but the engine reserves indexes at and above 20000 for objects + // that were created inside the engine (as opposed to inside the VM). The + // engine compares these as a tiebreaker for graphics objects that are at + // the same priority, and it is necessary to at least minimally handle + // this situation. + // This is obviously a bogus comparision, but then, this entire thing is + // bogus. For the moment, it just needs to be deterministic. + if (isNumber() && !right.isNumber()) { + return 1; + } else if (right.isNumber() && !isNumber()) { + return -1; + } + + return getOffset() - right.getOffset(); +} +#endif + bool reg_t::pointerComparisonWithInteger(const reg_t right) const { // This function handles the case where a script tries to compare a pointer // to a number. Normally, we would not want to allow that. However, SCI0 - diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index a646478a8e..e60f52e85c 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -160,6 +160,10 @@ private: int cmp(const reg_t right, bool treatAsUnsigned) const; reg_t lookForWorkaround(const reg_t right, const char *operation) const; bool pointerComparisonWithInteger(const reg_t right) const; + +#ifdef ENABLE_SCI32 + int sci32Comparison(const reg_t right) const; +#endif }; static inline reg_t make_reg(SegmentId segment, uint16 offset) { diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index aab32032f7..ed3c604f38 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -88,6 +88,7 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = { { GID_QFG2, 200, 200, 0, "astro", "messages", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152 { GID_QFG3, 780, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692 { GID_QFG4, 710,64941, 0, "RandCycle", "doit", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves + { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_div: when Lycentia knocks Torin out after he removes her collar SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -124,6 +125,23 @@ static const uint16 sig_uninitread_hoyle4_1[] = { SIG_END }; +// Game: Hoyle 5 +// Calling method: export 2 +// Subroutine offset: 0x2fb2 (script 300) +// Applies to at least: English PC demo +static const uint16 sig_uninitread_hoyle5_1[] = { + + 0x7e, SIG_ADDTOOFFSET(2), // line N + 0x7d, 0x68, 0x65, 0x61, 0x72, + 0x74, 0x73, 0x2e, 0x73, + 0x63, 0x00, // file "hearts.sc" + 0x3f, 0x01, // link 01 + 0x7e, SIG_ADDTOOFFSET(2), // line N + 0x39, 0x4b, // pushi 4bh + 0x78, // push1 + SIG_END +}; + // Game: Jones in the fast lane // Calling method: weekendText::draw // Subroutine offset: 0x03d3 (script 232) @@ -279,6 +297,10 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_HOYLE4, 500, 17, 1, "Character", "say", NULL, 504, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Cribbage (e.g. when the opponent says "Last Card") - bug #5662 { GID_HOYLE4, 800, 870, 0, "EuchreStrategy", "thinkLead", NULL, 0, { WORKAROUND_FAKE, 0 } }, // while playing Euchre, happens at least on 2nd or 3rd turn - bug #6602 { GID_HOYLE4, -1, 937, 0, "IconBar", "dispatchEvent", NULL, 408, { WORKAROUND_FAKE, 0 } }, // pressing ENTER on scoreboard while mouse is not on OK button, may not happen all the time - bug #6603 + { GID_HOYLE5, -1, 14, -1, NULL, "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // dragging the sliders in game settings + { GID_HOYLE5, -1, 64937, -1, NULL, "select", NULL, 7, { WORKAROUND_FAKE, 0 } }, // clicking the "control" and "options" buttons in the icon bar + { GID_HOYLE5, -1, 64937, -1, "IconBar", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // clicking on any button in the icon bar + { GID_HOYLE5, 300, 300, 0, "", "export 2", sig_uninitread_hoyle5_1, 0, { WORKAROUND_FAKE, 0 } }, // after passing around cards in hearts { GID_ISLANDBRAIN, 100, 937, 0, "IconBar", "dispatchEvent", NULL, 58, { WORKAROUND_FAKE, 0 } }, // when using ENTER at the startup menu - bug #5241 { GID_ISLANDBRAIN, 140, 140, 0, "piece", "init", NULL, 3, { WORKAROUND_FAKE, 1 } }, // first puzzle right at the start, some initialization variable. bnt is done on it, and it should be non-0 { GID_ISLANDBRAIN, 200, 268, 0, "anElement", "select", NULL, 0, { WORKAROUND_FAKE, 0 } }, // elements puzzle, gets used before super TextIcon @@ -298,6 +320,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_KQ6, -1, 907, 0, "tomato", "doVerb", NULL, 2, { WORKAROUND_FAKE, 0 } }, // when looking at the rotten tomato in the inventory - bug #5331 { GID_KQ6, -1, 928, 0, NULL, "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // gets caused by Text+Audio support (see script patcher) { GID_KQ7, -1, 64996, 0, "User", "handleEvent", NULL, 1, { WORKAROUND_FAKE, 0 } }, // called when pushing a keyboard key + { GID_KQ7, 2450, 2450, 0, "exBridge", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when walking up to the throne in the cave in chapter 2 { GID_LAURABOW, 37, 0, 0, "CB1", "doit", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when going up the stairs - bug #5084 { GID_LAURABOW, -1, 967, 0, "myIcon", "cycle", NULL, 1, { WORKAROUND_FAKE, 0 } }, // having any portrait conversation coming up - initial bug #4971 { GID_LAURABOW2, -1, 24, 0, "gcWin", "open", NULL, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu @@ -316,19 +339,19 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_LSL6, 820, 82, 0, "", "export 0", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when touching the electric fence - bug #5103 { GID_LSL6, -1, 85, 0, "washcloth", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // washcloth in inventory { GID_LSL6, -1, 928, -1, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // used by various objects that are even translated in foreign versions, that's why we use the base-class - { GID_LSL6HIRES, 0, 85, 0, "LL6Inv", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // on startup + { GID_LSL6HIRES, -1, 85, 0, "LL6Inv", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when creating a new game { GID_LSL6HIRES, -1, 64950, 1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // at least when entering swimming pool area { GID_LSL6HIRES, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game { GID_MOTHERGOOSE256, -1, 0, 0, "MG", "doit", NULL, 5, { WORKAROUND_FAKE, 0 } }, // SCI1.1: When moving the cursor all the way to the left during the game - bug #5224 { GID_MOTHERGOOSE256, -1, 992, 0, "AIPath", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Happens in the demo and full version. In the demo, it happens when walking two screens from mother goose's house to the north. In the full version, it happens in rooms 7 and 23 - bug #5269 { GID_MOTHERGOOSE256, 90, 90, 0, "introScript", "changeState", NULL, 65, { WORKAROUND_FAKE, 0 } }, // SCI1(CD): At the very end, after the game is completed and restarted - bug #5626 { GID_MOTHERGOOSE256, 94, 94, 0, "sunrise", "changeState", NULL, 367, { WORKAROUND_FAKE, 0 } }, // At the very end, after the game is completed - bug #5294 - { GID_MOTHERGOOSEHIRES,-1,64950, 1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // right when clicking on a child at the start and probably also later - { GID_MOTHERGOOSEHIRES,-1,64950, 1, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // see above + { GID_MOTHERGOOSEHIRES,-1,64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // right when clicking on a child at the start and probably also later + { GID_MOTHERGOOSEHIRES,-1,64950, -1, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // see above { GID_PEPPER, -1, 894, 0, "Package", "doVerb", NULL, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #5154 { GID_PEPPER, 150, 928, 0, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper { GID_PQ4, -1, 25, 0, "iconToggle", "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when toggling the icon bar to auto-hide or not - { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning + { GID_PQSWAT, -1, 64950, 0, NULL, "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using any menus in-game { GID_QFG1, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1/hq1: going to the brigands hideout { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #5309 { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1vga_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515 @@ -345,6 +368,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_QFG3, 330, 330, -1, "Teller", "doChild", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Rajah" (bug #5033, temp 1) or "Tarna" (temp 0), or when clicking on yourself and saying "Greet" (bug #5148, temp 1) { GID_QFG3, 700, 700, -1, "monsterIsDead", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // in the jungle, after winning any fight, bug #5169 { GID_QFG3, 470, 470, -1, "rm470", "notify", NULL, 0, { WORKAROUND_FAKE, 0 } }, // closing the character screen in the Simbani village in the room with the bridge, bug #5165 + { GID_QFG3, 470, 470, -1, "<invalid name>", "notify", NULL, 0, { WORKAROUND_FAKE, 0 } }, // same as previous, with rm470::name used for temp storage by fan patches added by GOG { GID_QFG3, 490, 490, -1, "computersMove", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when finishing awari game, bug #5167 { GID_QFG3, 490, 490, -1, "computersMove", "changeState", sig_uninitread_qfg3_2, 4, { WORKAROUND_FAKE, 0 } }, // also when finishing awari game { GID_QFG3, 851, 32, -1, "ProjObj", "doit", NULL, 1, { WORKAROUND_FAKE, 0 } }, // near the end, when throwing the spear of death, bug #5282 @@ -377,7 +401,10 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_SQ6, -1, 0, 0, "SQ6", "init", NULL, 2, { WORKAROUND_FAKE, 0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100) { GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places { GID_SQ6, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game + { GID_SQ6, 210, 210, 0, "buttonSecret", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // after winning the first round of stooge fighter 3 { GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version + { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one + { GID_TORIN, 20100, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // going down the cliff at the first screen of chapter 2 (washing area) SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -392,6 +419,13 @@ const SciWorkaroundEntry kAbs_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kArraySetElements_workarounds[] = { + { GID_GK1, 302, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when erasing a letter on the wall in St Louis Cemetery + { GID_PHANTASMAGORIA, -1, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when starting a new game and selecting a chapter above 1, or when quitting the chase (in every chase room), or when completing chase successfully + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kCelHigh_workarounds[] = { { GID_KQ5, -1, 255, 0, "deathIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when getting beaten up in the inn and probably more, called with 2nd parameter as object - bug #5049 { GID_PQ2, -1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects @@ -406,6 +440,7 @@ const SciWorkaroundEntry kCelWide_workarounds[] = { { GID_PQ2, -1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects { GID_SQ1, 1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // DEMO: Called with 2nd/3rd parameters as objects when clicking on the menu - bug #5012 { GID_FANMADE, -1, 979, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // In The Gem Scenario and perhaps other fanmade games, this is called with 2nd/3rd parameters as objects - bug #5144 + { GID_LSL6HIRES, -1, 94, 0, "ll6ControlPanel", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when opening the "controls" panel from the main menu, the third argument is missing SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -489,7 +524,7 @@ const SciWorkaroundEntry kDisplay_workarounds[] = { { GID_PQ2, 23, 23, 0, "rm23Script", "elements", sig_kDisplay_pq2_1, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the 2nd page of pate's file - 0x75 as id - bug #5223 { GID_QFG1, 11, 11, 0, "battle", "init", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: When entering battle, 0x75 as id { GID_SQ4, 397, 0, 0, "", "export 12", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // FLOPPY: when going into the computer store - bug #5227 - { GID_SQ4, 391, 391, 0, "doCatalog", "mode", sig_kDisplay_sq4_1, 0, { WORKAROUND_IGNORE, 0 } }, // CD: clicking on catalog in roboter sale - a parameter is an object + { GID_SQ4, 391, 391, 0, "doCatalog", "changeState", sig_kDisplay_sq4_1, 0, { WORKAROUND_IGNORE, 0 } }, // CD: clicking on catalog in roboter sale - a parameter is an object { GID_SQ4, 391, 391, 0, "choosePlug", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // CD: ordering connector in roboter sale - a parameter is an object SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -512,6 +547,16 @@ const SciWorkaroundEntry kDisposeScript_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kDoSoundPlay_workarounds[] = { + { GID_LSL6HIRES, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument + { GID_QFG4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument + { GID_PQ4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument + { GID_KQ7, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument + { GID_GK1, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Mac version always passes an extra null argument + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kDoSoundFade_workarounds[] = { { GID_KQ5, 213, 989, 0, "globalSound3", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when bandits leave the secret temple, parameter 4 is an object - bug #5078 { GID_KQ6, 105, 989, 0, "globalSound", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object @@ -530,6 +575,12 @@ const SciWorkaroundEntry kGetAngle_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kFileIOOpen_workarounds[] = { + { GID_TORIN, 61000, 61000, 0, "roSierraLogo", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Missing second argument when the game checks for autosave.cat after the Sierra logo + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kFindKey_workarounds[] = { { GID_ECOQUEST2, 100, 999, 0, "myList", "contains", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When Noah Greene gives Adam the Ecorder, and just before the game gives a demonstration, a null reference to a list is passed - bug #4987 { GID_HOYLE4, 300, 999, 0, "Piles", "contains", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When passing the three cards in Hearts, a null reference to a list is passed - bug #5664 @@ -630,12 +681,18 @@ const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kIsObject_workarounds[] = { - { GID_GK1, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950 + { GID_GK1DEMO, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950 { GID_ISLANDBRAIN, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when going to the game options, choosing "Info" and selecting anything from the list, gets called with an invalid parameter (type "error") - bug #4989 { GID_QFG3, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when asking for something, gets called with type error parameter SCI_WORKAROUNDENTRY_TERMINATOR }; +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kListAt_workarounds[] = { + { GID_HOYLE5, 100, 64999, 0, "theHands", "at", NULL, 0, { WORKAROUND_FAKE, 0 } }, // After the first hand is dealt in Crazy Eights game in demo, an object is passed instead of a number + SCI_WORKAROUNDENTRY_TERMINATOR +}; + // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kMemory_workarounds[] = { { GID_LAURABOW2, -1, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // during the intro, when exiting the train (room 160), talking to Mr. Augustini, etc. - bug #4944 @@ -656,21 +713,60 @@ const SciWorkaroundEntry kNewWindow_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = { + { GID_GK1, 370, 370, 0, "graceComeOut", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // there's an extra parameter in GK1, when changing chapters. This extra parameter seems to be a bug or just unimplemented functionality, as there's no visible change from the original in the chapter change room + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kPlatform32_workarounds[] = { + { GID_HOYLE5, -1, 0, 0, "hoyle4", "newRoom", NULL, 0, { WORKAROUND_FAKE, 1 } }, // at the start of the game, incorrectly uses SCI16 calling convention for kPlatform + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kRandom_workarounds[] = { + { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar + { GID_TORIN, 51400,64928, 0, "Blink", "cycleDone", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar + SCI_WORKAROUNDENTRY_TERMINATOR +}; + + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kReadNumber_workarounds[] = { { GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425 { GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425 SCI_WORKAROUNDENTRY_TERMINATOR }; +// Game: Leisure Suit Larry 6 hires +// Calling method: myCreditText::changeState +// Subroutine offset: 0x8c (script 740) +// Applies to at least: English PC CD +static const uint16 sig_kResCheck_lsl6hires_1[] = { + 0x3f, 0x01, // link 01 + 0x81, 0x13, // lag global[$13] + 0xa5, 0x00, // sat 00 + 0x7a, // push2 + SIG_END +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kResCheck_workarounds[] = { + { GID_LSL6HIRES, 740, 740, -1, "myCreditText", "handleEvent", sig_kResCheck_lsl6hires_1, -1, { WORKAROUND_IGNORE, 0 } }, // when clicking quit during the final credits + SCI_WORKAROUNDENTRY_TERMINATOR +}; + // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = { - { GID_QFG4, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947 + { GID_QFG4DEMO, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947 SCI_WORKAROUNDENTRY_TERMINATOR }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kSetCursor_workarounds[] = { { GID_KQ5, -1, 768, 0, "KQCursor", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters + { GID_MOTHERGOOSEHIRES,-1, 0, -1, "MG", "setCursor", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -731,13 +827,20 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = { { GID_LAURABOW2, -1, -1, 0, "sCartoon", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #4966 { GID_LSL6, 130, 130, 0, "recruitLarryScr", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident { GID_LSL6, 740, 740, 0, "showCartoon", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident + { GID_LSL6HIRES, 740, 740, 0, "showCartoon", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, multiple additional parameters are passed by accident { GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident { GID_SQ1, 43, 303, 0, "slotGuy", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error { GID_QFG4, -1, 110, 0, "dreamer", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during the dream sequence, a 3rd parameter is passed by accident SCI_WORKAROUNDENTRY_TERMINATOR }; -SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) { +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = { + { GID_PHANTASMAGORIA, 45, 64907, 0, "ScrollableWindow", "addString", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin) { // HACK for SCI3: Temporarily ignore this if (getSciVersion() == SCI_VERSION_3) { warning("SCI3 HACK: trackOriginAndFindWorkaround() called, ignoring"); @@ -749,37 +852,14 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun const EngineState *state = g_sci->getEngineState(); ExecStack *lastCall = state->xs; - const Script *localScript = state->_segMan->getScriptIfLoaded(lastCall->local_segment); - int curScriptNr = localScript->getScriptNumber(); - int curLocalCallOffset = lastCall->debugLocalCallOffset; - - if (curLocalCallOffset != -1) { - // if lastcall was actually a local call search back for a real call - Common::List<ExecStack>::const_iterator callIterator = state->_executionStack.end(); - while (callIterator != state->_executionStack.begin()) { - callIterator--; - ExecStack loopCall = *callIterator; - if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) { - lastCall->debugSelector = loopCall.debugSelector; - lastCall->debugExportId = loopCall.debugExportId; - break; - } - } - } - - Common::String curObjectName = state->_segMan->getObjectName(lastCall->sendp); - Common::String curMethodName; const SciGameId gameId = g_sci->getGameId(); - const int curRoomNumber = state->currentRoomNumber(); - - if (lastCall->type == EXEC_STACK_TYPE_CALL) { - if (lastCall->debugSelector != -1) { - curMethodName = g_sci->getKernel()->getSelectorName(lastCall->debugSelector); - } else if (lastCall->debugExportId != -1) { - curObjectName = ""; - curMethodName = Common::String::format("export %d", lastCall->debugExportId); - } - } + + *trackOrigin = state->getCurrentCallOrigin(); + const Common::String &curObjectName = trackOrigin->objectName; + const Common::String &curMethodName = trackOrigin->methodName; + const int &curRoomNumber = trackOrigin->roomNr; + const int &curScriptNr = trackOrigin->scriptNr; + const int &curLocalCallOffset = trackOrigin->localCallOffset; if (workaroundList) { // Search if there is a workaround for this one @@ -855,12 +935,6 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun } while (!searchObject.isNull()); // no parent left? } - // give caller origin data - trackOrigin->objectName = curObjectName; - trackOrigin->methodName = curMethodName; - trackOrigin->scriptNr = curScriptNr; - trackOrigin->localCallOffset = lastCall->debugLocalCallOffset; - SciWorkaroundSolution noneFound; noneFound.type = WORKAROUND_NONE; noneFound.value = 0; diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 46059a175c..86b4ee2902 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -24,6 +24,7 @@ #define SCI_ENGINE_WORKAROUNDS_H #include "sci/engine/vm_types.h" +#include "sci/engine/state.h" #include "sci/sci.h" namespace Sci { @@ -35,13 +36,6 @@ enum SciWorkaroundType { WORKAROUND_FAKE // fake kernel call / replace temp value / fake opcode }; -struct SciTrackOriginReply { - int scriptNr; - Common::String objectName; - Common::String methodName; - int localCallOffset; -}; - struct SciWorkaroundSolution { SciWorkaroundType type; uint16 value; @@ -74,7 +68,9 @@ extern const SciWorkaroundEntry kDeviceInfo_workarounds[]; extern const SciWorkaroundEntry kDisplay_workarounds[]; extern const SciWorkaroundEntry kDirLoop_workarounds[]; extern const SciWorkaroundEntry kDisposeScript_workarounds[]; +extern const SciWorkaroundEntry kDoSoundPlay_workarounds[]; extern const SciWorkaroundEntry kDoSoundFade_workarounds[]; +extern const SciWorkaroundEntry kFileIOOpen_workarounds[]; extern const SciWorkaroundEntry kFindKey_workarounds[]; extern const SciWorkaroundEntry kDeleteKey_workarounds[]; extern const SciWorkaroundEntry kGetAngle_workarounds[]; @@ -86,19 +82,27 @@ extern const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[]; extern const SciWorkaroundEntry kGraphFillBoxAny_workarounds[]; extern const SciWorkaroundEntry kGraphRedrawBox_workarounds[]; extern const SciWorkaroundEntry kIsObject_workarounds[]; +extern const SciWorkaroundEntry kListAt_workarounds[]; extern const SciWorkaroundEntry kMemory_workarounds[]; extern const SciWorkaroundEntry kMoveCursor_workarounds[]; extern const SciWorkaroundEntry kNewWindow_workarounds[]; +extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[]; +extern const SciWorkaroundEntry kPlatform32_workarounds[]; +extern const SciWorkaroundEntry kRandom_workarounds[]; extern const SciWorkaroundEntry kReadNumber_workarounds[]; +extern const SciWorkaroundEntry kResCheck_workarounds[]; extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[]; extern const SciWorkaroundEntry kSetCursor_workarounds[]; +extern const SciWorkaroundEntry kArraySetElements_workarounds[]; extern const SciWorkaroundEntry kSetPort_workarounds[]; extern const SciWorkaroundEntry kStrAt_workarounds[]; extern const SciWorkaroundEntry kStrCpy_workarounds[]; extern const SciWorkaroundEntry kStrLen_workarounds[]; extern const SciWorkaroundEntry kUnLoad_workarounds[]; +extern const SciWorkaroundEntry kStringNew_workarounds[]; +extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[]; -extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin); +extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin); } // End of namespace Sci |