diff options
Diffstat (limited to 'engines/sci/engine')
43 files changed, 4128 insertions, 1997 deletions
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index be062dba64..a993506f7a 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -40,7 +40,6 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan) _moveCountType = kMoveCountUninitialized; #ifdef ENABLE_SCI32 _sci21KernelType = SCI_VERSION_NONE; - _sci2StringFunctionType = kSci2StringFunctionUninitialized; #endif _usesCdTrack = Common::File::exists("cdaudio.map"); if (!ConfMan.getBool("use_cdaudio")) @@ -143,8 +142,8 @@ SciVersion GameFeatures::detectDoSoundType() { // SCI0LATE. Although the last SCI0EARLY game (lsl2) uses SCI0LATE resources _doSoundType = g_sci->getResMan()->detectEarlySound() ? SCI_VERSION_0_EARLY : SCI_VERSION_0_LATE; #ifdef ENABLE_SCI32 - } else if (getSciVersion() >= SCI_VERSION_2_1) { - _doSoundType = SCI_VERSION_2_1; + } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { + _doSoundType = SCI_VERSION_2_1_EARLY; #endif } else if (SELECTOR(nodePtr) == -1) { // No nodePtr selector, so this game is definitely using newer @@ -271,7 +270,7 @@ SciVersion GameFeatures::detectLofsType() { return _lofsType; } - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { // SCI1.1 type, i.e. we compensate for the fact that the heap is attached // to the end of the script _lofsType = SCI_VERSION_1_1; @@ -475,7 +474,7 @@ bool GameFeatures::autoDetectSci21KernelType() { } warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table"); - _sci21KernelType = SCI_VERSION_2_1; + _sci21KernelType = SCI_VERSION_2_1_EARLY; return true; } @@ -514,7 +513,7 @@ bool GameFeatures::autoDetectSci21KernelType() { _sci21KernelType = SCI_VERSION_2; return true; } else if (kFuncNum == 0x75) { - _sci21KernelType = SCI_VERSION_2_1; + _sci21KernelType = SCI_VERSION_2_1_EARLY; return true; } } @@ -532,65 +531,6 @@ SciVersion GameFeatures::detectSci21KernelType() { } return _sci21KernelType; } - -Sci2StringFunctionType GameFeatures::detectSci2StringFunctionType() { - if (_sci2StringFunctionType == kSci2StringFunctionUninitialized) { - if (getSciVersion() <= SCI_VERSION_1_1) { - error("detectSci21StringFunctionType() called from SCI1.1 or earlier"); - } else if (getSciVersion() == SCI_VERSION_2) { - // SCI2 games are always using the old type - _sci2StringFunctionType = kSci2StringFunctionOld; - } else if (getSciVersion() == SCI_VERSION_3) { - // SCI3 games are always using the new type - _sci2StringFunctionType = kSci2StringFunctionNew; - } else { // SCI2.1 - if (!autoDetectSci21StringFunctionType()) - _sci2StringFunctionType = kSci2StringFunctionOld; - else - _sci2StringFunctionType = kSci2StringFunctionNew; - } - } - - debugC(1, kDebugLevelVM, "Detected SCI2 kString type: %s", (_sci2StringFunctionType == kSci2StringFunctionOld) ? "old" : "new"); - - return _sci2StringFunctionType; -} - -bool GameFeatures::autoDetectSci21StringFunctionType() { - // Look up the script address - reg_t addr = getDetectionAddr("Str", SELECTOR(size)); - - if (!addr.getSegment()) - return false; - - uint16 offset = addr.getOffset(); - Script *script = _segMan->getScript(addr.getSegment()); - - while (true) { - int16 opparams[4]; - byte extOpcode; - byte opcode; - offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams); - opcode = extOpcode >> 1; - - // Check for end of script - if (opcode == op_ret || offset >= script->getBufSize()) - break; - - if (opcode == op_callk) { - uint16 kFuncNum = opparams[0]; - - // SCI2.1 games which use the new kString functions call kString(8). - // Earlier ones call the callKernel script function, but not kString - // directly - if (_kernel->getKernelName(kFuncNum) == "String") - return true; - } - } - - return false; // not found a call to kString -} - #endif bool GameFeatures::autoDetectMoveCountType() { diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index a4d715fee0..1c410267e6 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -34,12 +34,6 @@ enum MoveCountType { kIncrementMoveCount }; -enum Sci2StringFunctionType { - kSci2StringFunctionUninitialized, - kSci2StringFunctionOld, - kSci2StringFunctionNew -}; - class GameFeatures { public: GameFeatures(SegManager *segMan, Kernel *kernel); @@ -82,13 +76,6 @@ public: * @return Graphics functions type, SCI_VERSION_2 / SCI_VERSION_2_1 */ SciVersion detectSci21KernelType(); - - /** - * Autodetects the string subfunctions used in SCI2 - SCI3 - * @return string subfunctions type, kSci2StringFunctionOld / kSci2StringFunctionNew - */ - Sci2StringFunctionType detectSci2StringFunctionType(); - #endif /** @@ -132,13 +119,11 @@ private: bool autoDetectMoveCountType(); #ifdef ENABLE_SCI32 bool autoDetectSci21KernelType(); - bool autoDetectSci21StringFunctionType(); #endif SciVersion _doSoundType, _setCursorType, _lofsType, _gfxFunctionsType, _messageFunctionType; #ifdef ENABLE_SCI32 SciVersion _sci21KernelType; - Sci2StringFunctionType _sci2StringFunctionType; #endif MoveCountType _moveCountType; diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp index 623caec856..156f6f51f7 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,112 @@ 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, + bool truncate, 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, + bool truncate, + bool compress) +: MemoryDynamicRWStream(DisposeAfterUse::YES), + _fileName(fileName), _compress(compress) +{ + if (!truncate && inFile) { + unsigned int s = inFile->size(); + ensureCapacity(s); + inFile->read(_data, s); + _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 @@ -66,15 +173,52 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u bool isCompressed = true; const SciGameId gameId = g_sci->getGameId(); - if ((gameId == GID_QFG1 || gameId == GID_QFG1VGA || gameId == GID_QFG2 || gameId == GID_QFG3) - && englishName.hasSuffix(".sav")) { - // QFG Characters are saved via the CharSave object. - // We leave them uncompressed so that they can be imported in later QFG - // games. - // Rooms/Scripts: QFG1: 601, QFG2: 840, QFG3/4: 52 - isCompressed = false; + + // QFG Characters are saved via the CharSave object. + // We leave them uncompressed so that they can be imported in later QFG + // games, even when using the original interpreter. + // We check for room numbers in here, because the file suffix can be changed by the user. + // Rooms/Scripts: QFG1(EGA/VGA): 601, QFG2: 840, QFG3/4: 52 + switch (gameId) { + case GID_QFG1: + case GID_QFG1VGA: + if (s->currentRoomNumber() == 601) + isCompressed = false; + break; + case GID_QFG2: + if (s->currentRoomNumber() == 840) + isCompressed = false; + break; + case GID_QFG3: + case GID_QFG4: + if (s->currentRoomNumber() == 52) + isCompressed = false; + break; + default: + break; } +#ifdef ENABLE_SCI32 + if (mode != _K_FILE_MODE_OPEN_OR_FAIL && ( + (g_sci->getGameId() == GID_PHANTASMAGORIA && filename == "phantsg.dir") || + (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); + + SaveFileRewriteStream *stream; + stream = new SaveFileRewriteStream(wrappedName, inFile, mode == _K_FILE_MODE_CREATE, 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); @@ -110,15 +254,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; @@ -129,7 +265,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u } FileHandle *getFileFromHandle(EngineState *s, uint handle) { - if (handle == 0 || handle == VIRTUALFILE_HANDLE) { + if ((handle == 0) || ((handle >= VIRTUALFILE_HANDLE_START) && (handle <= VIRTUALFILE_HANDLE_END))) { error("Attempt to use invalid file handle (%d)", handle); return 0; } @@ -236,8 +372,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(); @@ -349,119 +489,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 052eb735e9..982d7b7823 100644 --- a/engines/sci/engine/file.h +++ b/engines/sci/engine/file.h @@ -41,8 +41,10 @@ enum { MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ }; -#define VIRTUALFILE_HANDLE 200 -#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir" +#define VIRTUALFILE_HANDLE_START 32000 +#define VIRTUALFILE_HANDLE_SCI32SAVE 32100 +#define VIRTUALFILE_HANDLE_SCIAUDIO 32300 +#define VIRTUALFILE_HANDLE_END 32300 struct SavegameDesc { int16 id; @@ -90,50 +92,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..b229490570 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 @@ -150,6 +154,12 @@ AddrSet *findAllActiveReferences(EngineState *s) { } } +#ifdef ENABLE_SCI32 + // Init: ScrollWindows + if (g_sci->_gfxControls32) + wm.pushArray(g_sci->_gfxControls32->listObjectReferences()); +#endif + debugC(kDebugLevelGC, "[GC] -- Finished explicitly loaded scripts, done with root set"); processWorkList(s->_segMan, wm, heap); diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index bfb7bfcd08..2afb8b73d1 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -35,6 +35,9 @@ namespace Sci { Kernel::Kernel(ResourceManager *resMan, SegManager *segMan) : _resMan(resMan), _segMan(segMan), _invalid("<invalid>") { +#ifdef ENABLE_SCI32 + _kernelFunc_StringId = 0; +#endif } Kernel::~Kernel() { @@ -81,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) @@ -118,7 +127,7 @@ void Kernel::loadSelectorNames() { // Starting with KQ7, Mac versions have a BE name table. GK1 Mac and earlier (and all // other platforms) always use LE. - bool isBE = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1 + bool isBE = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY && g_sci->getGameId() != GID_GK1); if (!r) { // No such resource? @@ -603,6 +612,10 @@ void Kernel::mapFunctions() { } #ifdef ENABLE_SCI32 + if (kernelName == "String") { + _kernelFunc_StringId = id; + } + // HACK: Phantasmagoria Mac uses a modified kDoSound (which *nothing* // else seems to use)! if (g_sci->getPlatform() == Common::kPlatformMacintosh && g_sci->getGameId() == GID_PHANTASMAGORIA && kernelName == "DoSound") { @@ -842,11 +855,11 @@ void Kernel::loadKernelNames(GameFeatures *features) { // In the Windows version of KQ6 CD, the empty kSetSynonyms // function has been replaced with kPortrait. In KQ6 Mac, // kPlayBack has been replaced by kShowMovie. - if (g_sci->getPlatform() == Common::kPlatformWindows) + if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) _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 } @@ -863,15 +876,17 @@ void Kernel::loadKernelNames(GameFeatures *features) { _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2); break; - case SCI_VERSION_2_1: + case SCI_VERSION_2_1_EARLY: + case SCI_VERSION_2_1_MIDDLE: + case SCI_VERSION_2_1_LATE: if (features->detectSci21KernelType() == SCI_VERSION_2) { // Some early SCI2.1 games use a modified SCI2 kernel table instead of // the SCI2.1 kernel table. We detect which version to use based on // 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 @@ -882,7 +897,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 57b4d9455b..5ff4f932be 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -75,6 +75,10 @@ struct SciWorkaroundEntry; // from workarounds.h * vocab.997. This results in much more readable code. Thus, this vocabulary isn't * used at all. * + * 993.voc (unneeded) - Contains the SCI3 equivalent of vocab.994; like its predecessor, + * the raw selector numbers can be deduced and used instead. In fact, one version of this + * file has turned out to cover all versiona of SCI3. + * * SCI0 parser vocabularies: * - vocab.901 / 901.voc - suffix vocabulary * - vocab.900 / 900.voc - parse tree branches @@ -154,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. @@ -173,6 +178,12 @@ public: typedef Common::Array<KernelFunction> KernelFunctionArray; KernelFunctionArray _kernelFuncs; /**< Table of kernel functions. */ +#ifdef ENABLE_SCI32 + // id of kString function, for quick usage in kArray + // kArray calls kString in case parameters are strings + uint16 _kernelFunc_StringId; +#endif + /** * Determines whether a list of registers matches a given signature. * If no signature is given (i.e., if sig is NULL), this is always @@ -410,27 +421,117 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv); #ifdef ENABLE_SCI32 // SCI2 Kernel Functions +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 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 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 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 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 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 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 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); -// "Screen items" in SCI32 are views +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); -// Text + reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv); -reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv); -// "Planes" in SCI32 are pictures +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 kBitmapSetDisplace(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); @@ -445,12 +546,33 @@ 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 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 kPalCycle(EngineState *s, int argc, reg_t *argv); -reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv); -reg_t kPalVaryUnknown2(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); +reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv); +reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv); +reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv); +reg_t kPalVarySetStart(EngineState *s, int argc, reg_t *argv); +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 kTextSize32(EngineState *s, int argc, reg_t *argv); +reg_t kTextWidth(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 kList(EngineState *s, int argc, reg_t *argv); @@ -463,14 +585,15 @@ 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 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); @@ -484,7 +607,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); @@ -499,7 +621,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); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 2cbd79366d..8a1176eed8 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -34,6 +34,15 @@ namespace Sci { // . -> any type // i* -> optional multiple integers // .* -> any parameters afterwards (or none) +// +// data types: +// i - regular integer +// o - object +// r - reference +// n - node +// 0 - NULL +// . - any +// ! - invalid reference/offset struct SciKernelMapSubEntry { SciVersion fromVersion; @@ -51,12 +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_SCI21 SCI_VERSION_2_1, 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_UNTIL_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY +#define SIG_UNTIL_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 @@ -65,7 +81,7 @@ struct SciKernelMapSubEntry { #define SIG_SOUNDSCI0 SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE #define SIG_SOUNDSCI1EARLY SCI_VERSION_1_EARLY, SCI_VERSION_1_EARLY #define SIG_SOUNDSCI1LATE SCI_VERSION_1_LATE, SCI_VERSION_1_LATE -#define SIG_SOUNDSCI21 SCI_VERSION_2_1, SCI_VERSION_3 +#define SIG_SOUNDSCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 #define SIGFOR_ALL 0x3f #define SIGFOR_DOS 1 << 0 @@ -86,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 }, @@ -99,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 }, @@ -112,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 }, @@ -127,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 }, @@ -136,36 +152,97 @@ 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 +// +// 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), "", 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 @@ -190,31 +267,43 @@ static const SciKernelMapSubEntry kGraph_subops[] = { // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kPalVary_subops[] = { - { SIG_SCI21, 0, MAP_CALL(PalVaryInit), "ii(i)(i)(i)", NULL }, - { SIG_SCIALL, 0, MAP_CALL(PalVaryInit), "ii(i)(i)", NULL }, - { SIG_SCIALL, 1, MAP_CALL(PalVaryReverse), "(i)(i)(i)", NULL }, - { SIG_SCIALL, 2, MAP_CALL(PalVaryGetCurrentStep), "", NULL }, - { SIG_SCIALL, 3, MAP_CALL(PalVaryDeinit), "", NULL }, - { SIG_SCIALL, 4, MAP_CALL(PalVaryChangeTarget), "i", NULL }, - { SIG_SCIALL, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, - { SIG_SCIALL, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, + { SIG_SCI16, 0, MAP_CALL(PalVaryInit), "ii(i)(i)", NULL }, + { SIG_SCI16, 1, MAP_CALL(PalVaryReverse), "(i)(i)(i)", NULL }, + { SIG_SCI16, 2, MAP_CALL(PalVaryGetCurrentStep), "", NULL }, + { SIG_SCI16, 3, MAP_CALL(PalVaryDeinit), "", NULL }, + { SIG_SCI16, 4, MAP_CALL(PalVaryChangeTarget), "i", NULL }, + { SIG_SCI16, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, + { SIG_SCI16, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, #ifdef ENABLE_SCI32 - { SIG_SCI32, 8, MAP_CALL(PalVaryUnknown), "i", NULL }, - { SIG_SCI32, 9, MAP_CALL(PalVaryUnknown2), "i", NULL }, + { SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(ii)", 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 }, + { SIG_SCI32, 5, MAP_CALL(PalVarySetTime), "i", NULL }, + { SIG_SCI32, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, + { SIG_SCI32, 7, MAP_CALL(PalVarySetTarget), "i", NULL }, + { SIG_SCI32, 8, MAP_CALL(PalVarySetStart), "i", NULL }, + { SIG_SCI32, 9, MAP_CALL(PalVaryMergeStart), "i", NULL }, #endif SCI_SUBOPENTRY_TERMINATOR }; // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kPalette_subops[] = { - { SIG_SCIALL, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, - { SIG_SCIALL, 2, MAP_CALL(PaletteSetFlag), "iii", NULL }, - { SIG_SCIALL, 3, MAP_CALL(PaletteUnsetFlag), "iii", kPaletteUnsetFlag_workarounds }, - { SIG_SCIALL, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL }, - { SIG_SCIALL, 5, MAP_CALL(PaletteFindColor), "iii", NULL }, - { SIG_SCIALL, 6, MAP_CALL(PaletteAnimate), "i*", NULL }, - { SIG_SCIALL, 7, MAP_CALL(PaletteSave), "", NULL }, - { SIG_SCIALL, 8, MAP_CALL(PaletteRestore), "[r0]", 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, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL }, + { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "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 }, SCI_SUBOPENTRY_TERMINATOR }; @@ -246,6 +335,17 @@ 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 }, @@ -260,32 +360,168 @@ static const SciKernelMapSubEntry kSave_subops[] = { }; // 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(BitmapSetDisplace), "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_SCI21LATE, 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 kList_subops[] = { - { SIG_SCI21, 0, MAP_CALL(NewList), "", NULL }, - { SIG_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, - { SIG_SCI21, 2, MAP_CALL(NewNode), ".(.)", NULL }, - { SIG_SCI21, 3, MAP_CALL(FirstNode), "[l0]", NULL }, - { SIG_SCI21, 4, MAP_CALL(LastNode), "l", NULL }, - { SIG_SCI21, 5, MAP_CALL(EmptyList), "l", NULL }, - { SIG_SCI21, 6, MAP_CALL(NextNode), "n", NULL }, - { SIG_SCI21, 7, MAP_CALL(PrevNode), "n", NULL }, - { SIG_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL }, - { SIG_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL }, - { SIG_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL }, - { SIG_SCI21, 11, MAP_CALL(AddToEnd), "ln(.)", NULL }, - { SIG_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL }, - { SIG_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL }, - { SIG_SCI21, 14, MAP_CALL(MoveToEnd), "ln", NULL }, - { SIG_SCI21, 15, MAP_CALL(FindKey), "l.", NULL }, - { SIG_SCI21, 16, MAP_CALL(DeleteKey), "l.", NULL }, - { SIG_SCI21, 17, MAP_CALL(ListAt), "li", NULL }, - { SIG_SCI21, 18, MAP_CALL(ListIndexOf) , "l[io]", NULL }, - { SIG_SCI21, 19, MAP_CALL(ListEachElementDo), "li(.*)", NULL }, - { SIG_SCI21, 20, MAP_CALL(ListFirstTrue), "li(.*)", NULL }, - { SIG_SCI21, 21, MAP_CALL(ListAllTrue), "li(.*)", NULL }, - { SIG_SCI21, 22, MAP_CALL(Sort), "ooo", NULL }, + { SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(NewNode), ".(.)", NULL }, + { SIG_SINCE_SCI21, 3, MAP_CALL(FirstNode), "[l0]", NULL }, + { SIG_SINCE_SCI21, 4, MAP_CALL(LastNode), "l", NULL }, + { SIG_SINCE_SCI21, 5, MAP_CALL(EmptyList), "l", NULL }, + { 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, 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, 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 }, + 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, 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 }, + 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 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 }, + 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), "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 +}; + #endif struct SciKernelMapEntry { @@ -314,12 +550,16 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, { 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(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(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(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL }, { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL }, @@ -337,7 +577,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 }, @@ -420,7 +663,7 @@ static SciKernelMapEntry s_kernelMap[] = { { 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(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, @@ -429,14 +672,17 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL }, { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_SCI21, SIGFOR_ALL, "i(i)([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(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 + { MAP_CALL(SetNowSeen), 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 }, @@ -454,10 +700,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 }, @@ -489,14 +735,18 @@ static SciKernelMapEntry s_kernelMap[] = { #ifdef ENABLE_SCI32 // SCI2 Kernel Functions // TODO: whoever knows his way through those calls, fix the signatures. + { "TextSize", kTextSize32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL }, + { MAP_DUMMY(TextColors), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_DUMMY(TextFonts), SIG_UNTIL_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(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 }, - { MAP_CALL(FrameOut), SIG_EVERYWHERE, "", 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 }, { MAP_CALL(IsHiRes), SIG_EVERYWHERE, "", NULL, NULL }, @@ -505,6 +755,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 @@ -513,7 +765,7 @@ static SciKernelMapEntry s_kernelMap[] = { // our garbage collector (i.e. the SCI0-SCI1.1 semantics). { "Purge", kFlushResources, SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(SetShowStyle), SIG_EVERYWHERE, "ioiiiii([ri])(i)", NULL, NULL }, - { MAP_CALL(String), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(String), SIG_EVERYWHERE, "(.*)", kString_subops, NULL }, { MAP_CALL(UpdatePlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(ObjectIntersect), SIG_EVERYWHERE, "oo", NULL, NULL }, @@ -521,7 +773,7 @@ static SciKernelMapEntry s_kernelMap[] = { { 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(PalCycle), SIG_EVERYWHERE, "(.*)", kPalCycle_subops, NULL }, // SCI2 Empty functions @@ -557,39 +809,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_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, { MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(TextWidth), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL }, { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - // SetScroll is called by script 64909, Styler::doit(), but it doesn't seem to - // be used at all (plus, it was then changed to a dummy function in SCI3). - // Since this is most likely unused, and we got no test case, error out when - // it is called in order to find an actual call to it. - { MAP_DUMMY(SetScroll), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL }, - { MAP_CALL(List), SIG_SCI21, SIGFOR_ALL, "(.*)", kList_subops, 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(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL }, { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, - { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", 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(ScrollWindow), SIG_EVERYWHERE, "io(.*)", NULL, NULL }, - { MAP_CALL(SetFontRes), SIG_EVERYWHERE, "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(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, 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 @@ -638,16 +888,18 @@ static SciKernelMapEntry s_kernelMap[] = { // <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 - // 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 - // MorphOn - used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270) + // 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 }, + + { MAP_CALL(SetPalStyleRange), SIG_EVERYWHERE, "ii", NULL, NULL }, + + { MAP_CALL(MorphOn), SIG_EVERYWHERE, "", NULL, NULL }, // SCI3 Kernel Functions - { MAP_CALL(PlayDuck), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(PlayDuck), SIG_EVERYWHERE, "(.*)", NULL, NULL }, #endif - { NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL } + { NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL } }; /** Default kernel name table. */ @@ -941,7 +1193,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 8e16e0a07a..534d9ce713 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -42,6 +42,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[1]; SciEvent curEvent; int modifier_mask = getSciVersion() <= SCI_VERSION_01 ? SCI_KEYMOD_ALL : SCI_KEYMOD_NO_FOOLOCK; + uint16 modifiers = 0; SegManager *segMan = s->_segMan; Common::Point mousePos; @@ -58,7 +59,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { // 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) + if (getSciVersion() >= SCI_VERSION_2_1_EARLY) g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); #endif // Limit the mouse cursor position, if necessary @@ -82,11 +83,12 @@ 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) - 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(); @@ -100,7 +102,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; @@ -110,7 +130,27 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x); writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y); - //s->_gui->moveCursor(s->gfx_state->pointer_pos.x, s->gfx_state->pointer_pos.y); + // Get current keyboard modifiers, only keep relevant bits + modifiers = curEvent.modifiers & modifier_mask; + if (g_sci->getPlatform() == Common::kPlatformDOS) { + // We are supposed to emulate SCI running in DOS + + // We set the higher byte of the modifiers to 02h + // Original SCI also did that indirectly, because it asked BIOS for shift status + // via AH=0x02 INT16, which then sets the shift flags in AL + // AH is supposed to be destroyed in that case and it's not defined that 0x02 + // is still in it on return. The value of AX was then set into the modifiers selector. + // At least one fan-made game (Betrayed Alliance) requires 0x02 to be in the upper byte, + // otherwise the darts game (script 111) will not work properly. + + // It seems Sierra fixed this behaviour (effectively bug) in the SCI1 keyboard driver. + // SCI32 also resets the upper byte. + + // This was verified in SSCI itself by creating a SCI game and checking behavior. + if (getSciVersion() <= SCI_VERSION_01) { + modifiers |= 0x0200; + } + } switch (curEvent.type) { case SCI_EVENT_QUIT: @@ -125,34 +165,21 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.character); // We only care about the translated character - writeSelectorValue(segMan, obj, SELECTOR(modifiers), curEvent.modifiers & modifier_mask); + writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers); break; case SCI_EVENT_MOUSE_RELEASE: case SCI_EVENT_MOUSE_PRESS: - // track left buttton clicks, if requested - if (curEvent.type == SCI_EVENT_MOUSE_PRESS && curEvent.data == 1 && g_debug_track_mouse_clicks) { + if (curEvent.type == SCI_EVENT_MOUSE_PRESS && curEvent.modifiers == 0 && g_debug_track_mouse_clicks) { g_sci->getSciDebugger()->debugPrintf("Mouse clicked at %d, %d\n", mousePos.x, mousePos.y); } if (mask & curEvent.type) { - int extra_bits = 0; - - switch (curEvent.data) { - case 2: - extra_bits = SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT; - break; - case 3: - extra_bits = SCI_KEYMOD_CTRL; - default: - break; - } - writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type); writeSelectorValue(segMan, obj, SELECTOR(message), 0); - writeSelectorValue(segMan, obj, SELECTOR(modifiers), (curEvent.modifiers | extra_bits) & modifier_mask); + writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers); s->r_acc = make_reg(0, 1); } break; @@ -161,7 +188,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { // Return a null event writeSelectorValue(segMan, obj, SELECTOR(type), SCI_EVENT_NONE); writeSelectorValue(segMan, obj, SELECTOR(message), 0); - writeSelectorValue(segMan, obj, SELECTOR(modifiers), curEvent.modifiers & modifier_mask); + writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers); s->r_acc = NULL_REG; } diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 61ac76d0a7..4508a481a0 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" @@ -37,7 +38,6 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/savegame.h" -#include "sci/graphics/menu.h" #include "sci/sound/audio.h" #include "sci/console.h" @@ -258,20 +258,12 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { } debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); -#ifdef ENABLE_SCI32 - if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { - if (s->_virtualIndexFile) { - return make_reg(0, VIRTUALFILE_HANDLE); - } 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); - } - } + 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 // 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 @@ -309,18 +301,18 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { listSavegames(saves); int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START); - 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); - } + int size = strlen(saves[savegameNr].name) + 2; + char *buf = (char *)malloc(size); + strcpy(buf, saves[savegameNr].name); + buf[size - 1] = 0; // Spot description (empty) + + uint handle = findFreeFileHandle(s); - 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); - return make_reg(0, VIRTUALFILE_HANDLE); + s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buf, size, DisposeAfterUse::YES); + s->_fileHandles[handle]._out = nullptr; + s->_fileHandles[handle]._name = ""; + + return make_reg(0, handle); } } #endif @@ -345,12 +337,10 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { uint16 handle = argv[0].toUint16(); -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) { - s->_virtualIndexFile->close(); + if (handle >= VIRTUALFILE_HANDLE_START) { + // it's a virtual handle? ignore it return SIGNAL_REG; } -#endif FileHandle *f = getFileFromHandle(s, handle); if (f) { @@ -372,17 +362,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) { - 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 @@ -402,20 +384,11 @@ reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { s->_segMan->memcpy((byte *)buf, argv[1], size); debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size); -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) { - s->_virtualIndexFile->write(buf, size); + FileHandle *f = getFileFromHandle(s, handle); + if (f) { + f->_out->write(buf, size); success = true; - } else { -#endif - FileHandle *f = getFileFromHandle(s, handle); - if (f) { - f->_out->write(buf, size); - success = true; - } -#ifdef ENABLE_SCI32 } -#endif delete[] buf; if (success) @@ -454,13 +427,6 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { 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); result = saveFileMan->removeSavefile(wrappedName); @@ -479,12 +445,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) - 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; @@ -503,7 +464,7 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { // We skip creating these files, and instead handle the calls // directly. Since the sciAudio calls are only creating text files, // this is probably the most straightforward place to handle them. - if (handle == 0xFFFF && str.hasPrefix("(sciAudio")) { + if (handle == VIRTUALFILE_HANDLE_SCIAUDIO) { Common::List<ExecStack>::const_iterator iter = s->_executionStack.reverse_begin(); iter--; // sciAudio iter--; // sciAudio child @@ -511,13 +472,6 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) { - s->_virtualIndexFile->write(str.c_str(), str.size()); - return NULL_REG; - } -#endif - FileHandle *f = getFileFromHandle(s, handle); if (f) { @@ -538,11 +492,6 @@ reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) { uint16 whence = argv[2].toUint16(); debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) - return make_reg(0, s->_virtualIndexFile->seek(offset, whence)); -#endif - FileHandle *f = getFileFromHandle(s, handle); if (f && f->_in) { @@ -582,16 +531,21 @@ 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) { + // HACK: Special case for Pepper's Adventure in Time + // The game checks like crazy for the file CDAUDIO when entering the game menu. + // On at least Windows that makes the engine slow down to a crawl and takes at least 1 second. + // Should get solved properly by changing the code below. This here is basically for 1.8.0 release. + // TODO: Fix this properly. + if (name == "CDAUDIO") + return NULL_REG; + } + + // 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); @@ -787,25 +741,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 = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; 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()) - break; + if (savegameId > SAVEGAMESLOT_LAST) + error("kSavegame: no more savegame slots available"); } - if (savegameId == SAVEGAMEID_OFFICIALRANGE_START) - error("kSavegame: no more savegame slots available"); } } else { error("kSaveGame: invalid savegameId used"); @@ -897,50 +873,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { gamestate_restore(s, in); delete in; - switch (g_sci->getGameId()) { - case GID_MOTHERGOOSE: - // WORKAROUND: Mother Goose SCI0 - // Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the - // current number of children plus 1. - // We can't trust that global, that's why we set the actual savedgame id right here directly after - // restoring a saved game. - // If we didn't, the game would always save to a new slot - s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); - break; - case GID_MOTHERGOOSE256: - // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for - // saving a previously restored game. - // We set the current savedgame-id directly and remove the script - // code concerning this via script patch. - s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); - break; - case GID_JONES: - // HACK: The code that enables certain menu items isn't called when a game is restored from the - // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723. - // These menu entries are disabled when the game is launched, and are enabled when a new game is - // started. The code for enabling these entries is is all in script 1, room1::init, but that code - // path is never followed in these two cases (restoring game from the menu, or restoring a game - // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here. - // These two are needed when restoring from the launcher - // FIXME: The original interpreter saves and restores the menu state, so these attributes - // are automatically reset there. We may want to do the same. - g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones - g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help - // The rest are normally enabled from room1::init - g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player - g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game - g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game - 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_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. - g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game - break; - default: - break; - } + gamestate_afterRestoreFixUp(s, savegameId); + } else { s->r_acc = TRUE_REG; warning("Savegame #%d not found", savegameId); diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 8b790e6a58..cae5a09789 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" @@ -254,7 +255,7 @@ reg_t kGraph(EngineState *s, int argc, reg_t *argv) { } reg_t kGraphGetColorCount(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, g_sci->_gfxPalette->getTotalColorCount()); + return make_reg(0, g_sci->_gfxPalette16->getTotalColorCount()); } reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv) { @@ -359,12 +360,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { 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) { @@ -492,7 +495,7 @@ reg_t kNumLoops(EngineState *s, int argc, reg_t *argv) { loopCount = g_sci->_gfxCache->kernelViewGetLoopCount(viewId); - debugC(kDebugLevelGraphics, "NumLoops(view.%d) = %d", viewId, loopCount); + debugC(9, kDebugLevelGraphics, "NumLoops(view.%d) = %d", viewId, loopCount); return make_reg(0, loopCount); } @@ -505,7 +508,7 @@ reg_t kNumCels(EngineState *s, int argc, reg_t *argv) { celCount = g_sci->_gfxCache->kernelViewGetCelCount(viewId, loopNo); - debugC(kDebugLevelGraphics, "NumCels(view.%d, %d) = %d", viewId, loopNo, celCount); + debugC(9, kDebugLevelGraphics, "NumCels(view.%d, %d) = %d", viewId, loopNo, celCount); return make_reg(0, celCount); } @@ -576,9 +579,17 @@ 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; +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); + return NULL_REG; + } else { +#endif + g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); + return s->r_acc; +#ifdef ENABLE_SCI32 + } +#endif } reg_t kPalette(EngineState *s, int argc, reg_t *argv) { @@ -596,10 +607,10 @@ reg_t kPaletteSetFromResource(EngineState *s, int argc, reg_t *argv) { // Non-VGA games don't use palette resources. // This has been changed to 64 colors because Longbow Amiga does have // one palette (palette 999). - if (g_sci->_gfxPalette->getTotalColorCount() < 64) + if (g_sci->_gfxPalette16->getTotalColorCount() < 64) return s->r_acc; - g_sci->_gfxPalette->kernelSetFromResource(resourceId, force); + g_sci->_gfxPalette16->kernelSetFromResource(resourceId, force); return s->r_acc; } @@ -607,7 +618,7 @@ reg_t kPaletteSetFlag(EngineState *s, int argc, reg_t *argv) { uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); uint16 flags = argv[2].toUint16(); - g_sci->_gfxPalette->kernelSetFlag(fromColor, toColor, flags); + g_sci->_gfxPalette16->kernelSetFlag(fromColor, toColor, flags); return s->r_acc; } @@ -615,7 +626,7 @@ reg_t kPaletteUnsetFlag(EngineState *s, int argc, reg_t *argv) { uint16 fromColor = CLIP<uint16>(argv[0].toUint16(), 1, 255); uint16 toColor = CLIP<uint16>(argv[1].toUint16(), 1, 255); uint16 flags = argv[2].toUint16(); - g_sci->_gfxPalette->kernelUnsetFlag(fromColor, toColor, flags); + g_sci->_gfxPalette16->kernelUnsetFlag(fromColor, toColor, flags); return s->r_acc; } @@ -626,10 +637,10 @@ reg_t kPaletteSetIntensity(EngineState *s, int argc, reg_t *argv) { bool setPalette = (argc < 4) ? true : (argv[3].isNull()) ? true : false; // Palette intensity in non-VGA SCI1 games has been removed - if (g_sci->_gfxPalette->getTotalColorCount() < 256) + if (g_sci->_gfxPalette16->getTotalColorCount() < 256) return s->r_acc; - g_sci->_gfxPalette->kernelSetIntensity(fromColor, toColor, intensity, setPalette); + g_sci->_gfxPalette16->kernelSetIntensity(fromColor, toColor, intensity, setPalette); return s->r_acc; } @@ -637,7 +648,7 @@ reg_t kPaletteFindColor(EngineState *s, int argc, reg_t *argv) { uint16 r = argv[0].toUint16(); uint16 g = argv[1].toUint16(); uint16 b = argv[2].toUint16(); - return make_reg(0, g_sci->_gfxPalette->kernelFindColor(r, g, b)); + return make_reg(0, g_sci->_gfxPalette16->kernelFindColor(r, g, b)); } reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { @@ -645,18 +656,18 @@ reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { bool paletteChanged = false; // Palette animation in non-VGA SCI1 games has been removed - if (g_sci->_gfxPalette->getTotalColorCount() < 256) + if (g_sci->_gfxPalette16->getTotalColorCount() < 256) return s->r_acc; for (argNr = 0; argNr < argc; argNr += 3) { uint16 fromColor = argv[argNr].toUint16(); uint16 toColor = argv[argNr + 1].toUint16(); int16 speed = argv[argNr + 2].toSint16(); - if (g_sci->_gfxPalette->kernelAnimate(fromColor, toColor, speed)) + if (g_sci->_gfxPalette16->kernelAnimate(fromColor, toColor, speed)) paletteChanged = true; } if (paletteChanged) - g_sci->_gfxPalette->kernelAnimateSet(); + g_sci->_gfxPalette16->kernelAnimateSet(); // WORKAROUND: The game scripts in SQ4 floppy count the number of elapsed // cycles in the intro from the number of successive kAnimate calls during @@ -676,11 +687,11 @@ reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { } reg_t kPaletteSave(EngineState *s, int argc, reg_t *argv) { - return g_sci->_gfxPalette->kernelSave(); + return g_sci->_gfxPalette16->kernelSave(); } reg_t kPaletteRestore(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxPalette->kernelRestore(argv[0]); + g_sci->_gfxPalette16->kernelRestore(argv[0]); return argv[0]; } @@ -695,7 +706,7 @@ reg_t kPalVaryInit(EngineState *s, int argc, reg_t *argv) { uint16 ticks = argv[1].toUint16(); uint16 stepStop = argc >= 3 ? argv[2].toUint16() : 64; uint16 direction = argc >= 4 ? argv[3].toUint16() : 1; - if (g_sci->_gfxPalette->kernelPalVaryInit(paletteId, ticks, stepStop, direction)) + if (g_sci->_gfxPalette16->kernelPalVaryInit(paletteId, ticks, stepStop, direction)) return SIGNAL_REG; return NULL_REG; } @@ -705,40 +716,40 @@ reg_t kPalVaryReverse(EngineState *s, int argc, reg_t *argv) { int16 stepStop = argc >= 2 ? argv[1].toUint16() : 0; int16 direction = argc >= 3 ? argv[2].toSint16() : -1; - return make_reg(0, g_sci->_gfxPalette->kernelPalVaryReverse(ticks, stepStop, direction)); + return make_reg(0, g_sci->_gfxPalette16->kernelPalVaryReverse(ticks, stepStop, direction)); } reg_t kPalVaryGetCurrentStep(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, g_sci->_gfxPalette->kernelPalVaryGetCurrentStep()); + return make_reg(0, g_sci->_gfxPalette16->kernelPalVaryGetCurrentStep()); } reg_t kPalVaryDeinit(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxPalette->kernelPalVaryDeinit(); + g_sci->_gfxPalette16->kernelPalVaryDeinit(); return NULL_REG; } reg_t kPalVaryChangeTarget(EngineState *s, int argc, reg_t *argv) { GuiResourceId paletteId = argv[0].toUint16(); - int16 currentStep = g_sci->_gfxPalette->kernelPalVaryChangeTarget(paletteId); + int16 currentStep = g_sci->_gfxPalette16->kernelPalVaryChangeTarget(paletteId); return make_reg(0, currentStep); } reg_t kPalVaryChangeTicks(EngineState *s, int argc, reg_t *argv) { uint16 ticks = argv[0].toUint16(); - g_sci->_gfxPalette->kernelPalVaryChangeTicks(ticks); + g_sci->_gfxPalette16->kernelPalVaryChangeTicks(ticks); return NULL_REG; } reg_t kPalVaryPauseResume(EngineState *s, int argc, reg_t *argv) { bool pauseState = !argv[0].isNull(); - g_sci->_gfxPalette->kernelPalVaryPause(pauseState); + g_sci->_gfxPalette16->kernelPalVaryPause(pauseState); return NULL_REG; } reg_t kAssertPalette(EngineState *s, int argc, reg_t *argv) { GuiResourceId paletteId = argv[0].toUint16(); - g_sci->_gfxPalette->kernelAssertPalette(paletteId); + g_sci->_gfxPalette16->kernelAssertPalette(paletteId); return s->r_acc; } @@ -1253,16 +1264,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->_gfxPalette->resetRemapping(); - g_sci->_gfxPalette->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->_gfxPalette->resetRemapping(); - g_sci->_gfxPalette->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 8953f45266..9cfe53255b 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" @@ -45,14 +43,19 @@ #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/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" #endif namespace Sci { @@ -61,63 +64,67 @@ 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) + const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer(); + if (buffer.screenWidth < 640 || buffer.screenHeight < 400) return make_reg(0, 0); return make_reg(0, 1); } -// 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; - - return NULL_REG; -} - reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { - if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL) - g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); - else - g_sci->_gfxFrameout->kernelUpdateScreenItem(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 s->r_acc; } reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { + debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); return s->r_acc; } reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { + debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]); return s->r_acc; } reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { + 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)", 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)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelDeletePlane(argv[0]); return s->r_acc; } -reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]); +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 pictureX = argv[2].toSint16(); - int16 pictureY = argv[3].toSint16(); + int16 x = argv[2].toSint16(); + int16 y = argv[3].toSint16(); + bool mirrorX = argc > 4 ? argv[4].toSint16() : false; - g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY); + g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX); return s->r_acc; } @@ -126,8 +133,14 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { } reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelFrameout(); - return NULL_REG; + bool showBits = argc > 0 ? argv[0].toUint16() : true; + 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 s->r_acc; } reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { @@ -136,81 +149,95 @@ 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 illegalBits = argv[3].getOffset(); - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(targetObject); - - uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); - uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); - // If top and left are negative, we need to adjust coordinates by the item's x and y - if (nsRect.left < 0) - nsRect.translate(itemX, 0); - if (nsRect.top < 0) - nsRect.translate(0, itemY); - - // we assume that x, y are local coordinates - - bool contained = nsRect.contains(x, y); - if (contained && illegalBits) { - // If illegalbits are set, we check the color of the pixel that got clicked on - // for now, we return false if the pixel is transparent - // although illegalBits may get differently set, don't know yet how this really works out - uint16 viewId = readSelectorValue(s->_segMan, targetObject, SELECTOR(view)); - int16 loopNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(loop)); - int16 celNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(cel)); - if (g_sci->_gfxCompare->kernelIsItSkip(viewId, loopNo, celNo, Common::Point(x - nsRect.left, y - nsRect.top))) - contained = false; - } - return make_reg(0, contained); + int16 x = argv[0].toSint16(); + int16 y = argv[1].toSint16(); + reg_t object = argv[2]; + bool checkPixel = argv[3].toSint16(); + + return g_sci->_gfxFrameout->kernelIsOnMe(object, Common::Point(x, y), checkPixel); } reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: { - if (argc != 4) { - warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[3]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debugC(kDebugLevelStrings, "kCreateTextBitmap case 0 (%04x:%04x, %04x:%04x, %04x:%04x)", - PRINT_REG(argv[1]), PRINT_REG(argv[2]), PRINT_REG(argv[3])); - debugC(kDebugLevelStrings, "%s", text.c_str()); - int16 maxWidth = argv[1].toUint16(); - int16 maxHeight = argv[2].toUint16(); - g_sci->_gfxCoordAdjuster->fromScriptToDisplay(maxHeight, maxWidth); - // These values can be larger than the screen in the SQ6 demo, room 100 - // TODO: Find out why. For now, don't show any text in that room. - if (g_sci->getGameId() == GID_SQ6 && g_sci->isDemo() && s->currentRoomNumber() == 100) - return NULL_REG; - return g_sci->_gfxText32->createTextBitmap(object, maxWidth, maxHeight); - } - case 1: { - if (argc != 2) { - warning("kCreateTextBitmap(1): expected 2 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[1]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debugC(kDebugLevelStrings, "kCreateTextBitmap case 1 (%04x:%04x)", PRINT_REG(argv[1])); - debugC(kDebugLevelStrings, "%s", text.c_str()); - return g_sci->_gfxText32->createTextBitmap(object); - } - default: - warning("CreateTextBitmap(%d)", argv[0].toUint16()); + SegManager *segMan = s->_segMan; + + int16 subop = argv[0].toUint16(); + + int16 width = 0; + int16 height = 0; + reg_t object; + + if (subop == 0) { + width = argv[1].toUint16(); + height = argv[2].toUint16(); + object = argv[3]; + } else if (subop == 1) { + object = argv[1]; + } else { + warning("Invalid kCreateTextBitmap subop %d", subop); return NULL_REG; } + + Common::String text = segMan->getString(readSelector(segMan, object, SELECTOR(text))); + int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore)); + int16 backColor = readSelectorValue(segMan, object, SELECTOR(back)); + int16 skipColor = readSelectorValue(segMan, object, SELECTOR(skip)); + GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font)); + int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor)); + int16 dimmed = readSelectorValue(segMan, object, SELECTOR(dimmed)); + + Common::Rect rect( + readSelectorValue(segMan, object, SELECTOR(textLeft)), + readSelectorValue(segMan, object, SELECTOR(textTop)), + readSelectorValue(segMan, object, SELECTOR(textRight)) + 1, + readSelectorValue(segMan, object, SELECTOR(textBottom)) + 1 + ); + + if (subop == 0) { + TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode)); + return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, 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)); + return g_sci->_gfxText32->createFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed); + } } -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()); + + reg_t *rect = s->_segMan->derefRegPtr(argv[0], 4); + 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); + rect[0] = make_reg(0, textRect.left); + rect[1] = make_reg(0, textRect.top); + rect[2] = make_reg(0, textRect.right - 1); + rect[3] = make_reg(0, textRect.bottom - 1); 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: @@ -228,442 +255,508 @@ 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()); +} + /** - * Used for scene transitions, replacing (but reusing parts of) the old - * transition code. + * Causes an immediate plane transition with an optional transition + * effect */ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - // Can be called with 7 or 8 parameters - // The style defines which transition to perform. Related to the transition - // tables inside graphics/transitions.cpp - uint16 showStyle = argv[0].toUint16(); // 0 - 15 - reg_t planeObj = argv[1]; // the affected plane - Common::String planeObjName = s->_segMan->getObjectName(planeObj); - uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts - uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff - int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out - uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts - uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out + ShowStyleType type = (ShowStyleType)argv[0].toUint16(); + reg_t planeObj = argv[1]; + int16 seconds = argv[2].toSint16(); + // NOTE: This value seems to indicate whether the transition is an + // “exit” transition (0) or an “enter” transition (-1) for fade + // transitions. For other types of transitions, it indicates a palette + // index value to use when filling the screen. + int16 back = argv[3].toSint16(); + int16 priority = argv[4].toSint16(); + int16 animate = argv[5].toSint16(); + // TODO: Rename to frameOutNow? + int16 refFrame = argv[6].toSint16(); + int16 blackScreen; + reg_t pFadeArray; int16 divisions; - // If the game has the pFadeArray selector, another parameter is used here, - // before the optional last parameter - bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0; - if (hasFadeArray) { - // argv[7] - divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?) - } else { - divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?) + // SCI 2–2.1early + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + blackScreen = 0; + pFadeArray = NULL_REG; + divisions = argc > 7 ? argv[7].toSint16() : -1; } - - if (showStyle > 15) { - warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj)); - return s->r_acc; + // SCI 2.1mid–2.1late + else if (getSciVersion() < SCI_VERSION_3) { + blackScreen = 0; + pFadeArray = argc > 7 ? argv[7] : NULL_REG; + divisions = argc > 8 ? argv[8].toSint16() : -1; + } + // SCI 3 + else { + blackScreen = argv[7].toSint16(); + pFadeArray = argc > 8 ? argv[8] : NULL_REG; + divisions = argc > 9 ? argv[9].toSint16() : -1; } - // GK1 calls fadeout (13) / fadein (14) with the following parameters: - // seconds: 1 - // backColor: 0 / -1 - // fade: 200 - // animate: 0 - // refFrame: 0 - // divisions: 0 / 20 +// 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, " +// "pFadeArray: %04x:%04x (%s), divisions: %d", +// type, PRINT_REG(planeObj), s->_segMan->getObjectName(planeObj), seconds, +// back, priority, animate, refFrame, blackScreen, +// PRINT_REG(pFadeArray), s->_segMan->getObjectName(pFadeArray), divisions); - // TODO: Check if the plane is in the list of planes to draw + // 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); - Common::String effectName = "unknown"; + return s->r_acc; +} - switch (showStyle) { - case 0: // no transition / show - effectName = "show"; - break; - case 13: // fade out - effectName = "fade out"; - // TODO - break; - case 14: // fade in - effectName = "fade in"; - // TODO - break; - default: - // TODO - break; - } +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._scaledHeight))); +} - warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, " - "back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d", - showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(), - seconds, backColor, priority, animate, refFrame, divisions); - return s->r_acc; +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._scaledWidth))); } 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()); -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); + int16 result = 0; + + switch (argv[0].toUint16()) { + case 0: + result = view._displace.x; break; - case 17: // Destroy, called by ScrollableWindow::dispose - g_sci->_gfxFrameout->clearScrollTexts(); + case 1: + result = view._displace.y; break; - case 13: // Delete, unused - case 18: // Text, unused - case 19: // Reconstruct, unused - error("kScrollWindow: Unused subop %d invoked", op); + case 2: + case 3: + // null operation break; - default: - error("kScrollWindow: unknown subop %d", op); + 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) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) { + 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; + 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) { + 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) { + 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 kSetFontRes(EngineState *s, int argc, reg_t *argv) { - // TODO: This defines the resolution that the fonts are supposed to be displayed - // in. Currently, this is only used for showing high-res fonts in GK1 Mac, but - // should be extended to handle other font resolutions such as those +reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); - int xResolution = argv[0].toUint16(); - //int yResolution = argv[1].toUint16(); + 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); +} - g_sci->_gfxScreen->setFontIsUpscaled(xResolution == 640 && - g_sci->_gfxScreen->getUpscaledHires() != GFX_SCREEN_UPSCALED_DISABLED); +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 kFont(EngineState *s, int argc, reg_t *argv) { - // Handle font settings for SCI2.1 +reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); - switch (argv[0].toUint16()) { - case 1: - // Set font resolution - return kSetFontRes(s, argc - 1, argv + 1); - default: - warning("kFont: unknown subop %d", argv[0].toUint16()); - } + scrollWindow->show(); + + return s->r_acc; +} + +reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + + scrollWindow->pageUp(); + + return s->r_acc; +} + +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; } -// TODO: Eventually, all of the kBitmap operations should be put -// in a separate class +reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv) { + ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); -#define BITMAP_HEADER_SIZE 46 + 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; +} + +reg_t kFont(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +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->_scaledHeight = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_scaledHeight - 1) / g_sci->_gfxText32->_scaledHeight; + return make_reg(0, g_sci->_gfxText32->_scaledHeight); +} + +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 s->r_acc; +} 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. + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - 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_LE_UINT16(memoryPtr, width); - WRITE_LE_UINT16(memoryPtr + 2, height); - 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]; - } - } - - } - 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); - } - - curX = textX; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces - } - - } - 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; - } - } - - } - break; - default: - kStub(s, argc, argv); - break; - } +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 scaledWidth = argc > 4 ? argv[4].toSint16() : g_sci->_gfxText32->_scaledWidth; + int16 scaledHeight = argc > 5 ? argv[5].toSint16() : g_sci->_gfxText32->_scaledHeight; + bool useRemap = argc > 6 ? argv[6].toSint16() : false; + + BitmapResource bitmap(s->_segMan, width, height, skipColor, 0, 0, scaledWidth, scaledHeight, 0, useRemap); + memset(bitmap.getPixels(), backColor, width * height); + return bitmap.getObject(); +} +reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) { + s->_segMan->freeHunkEntry(argv[0]); return s->r_acc; } -// 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 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); +} - if (!controlObject.isNull()) { - g_sci->_gfxControls32->kernelTexteditChange(controlObject); - } +reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) { + BitmapResource bitmap(argv[0]); + CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16()); + + 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; + + Common::Point position( + x == -1 ? bitmap.getDisplace().x : x, + y == -1 ? bitmap.getDisplace().y : y + ); + + position.x -= alignX == -1 ? view._displace.x : alignX; + position.y -= alignY == -1 ? view._displace.y : alignY; + + 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; +} + +reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) { + // called e.g. from TextButton::createBitmap() in Torin's Passage, script 64894 + + BitmapResource bitmap(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); + CelObjMem textCel(textBitmapObject); + textCel.draw(bitmap.getBuffer(), textRect, Common::Point(textRect.left, textRect.top), false); + s->_segMan->freeHunkEntry(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 + + BitmapResource bitmap(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 kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv) { + BitmapResource bitmap(argv[0]); + bitmap.setDisplace(Common::Point(argv[1].toSint16(), argv[2].toSint16())); + return s->r_acc; +} + +reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) { + // viewId, loopNo, celNo, skipColor, backColor, useRemap, source overlay bitmap + + return kStub(s, argc + 1, argv - 1); +} + +reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) { + // target bitmap, source bitmap + + 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) { + // bitmap + + // argc 1 = get width + // argc 2 = pixel at row 0 col n + // argc 3 = pixel at row n col n + return kStub(s, argc + 1, argv - 1); +} + +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) { - 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); + 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) { - 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; } + reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) { - 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; } @@ -691,158 +784,214 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { return kStub(s, argc, argv); } -reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv) { - // TODO: Unknown (seems to be SCI32 exclusive) - return kStub(s, argc, argv); +// 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 s->r_acc; } -reg_t kPalVaryUnknown2(EngineState *s, int argc, reg_t *argv) { - // TODO: Unknown (seems to be SCI32 exclusive) - // It seems to be related to the day/night palette effects in QFG4, and - // accepts a palette resource ID. It is triggered right when the night - // effect is initially applied (when exiting the caves). - // In QFG4, there are two scene palettes: 790 for night, and 791 for day. - // Initially, the game starts at night time, but this is called with the - // ID of the day time palette (i.e. 791). - return kStub(s, argc, argv); +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)); +} + +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(); + g_sci->_gfxPalette32->setFade(percent, fromColor, toColor); + 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; + int16 fromColor; + int16 toColor; + + if (argc > 4) { + fromColor = argv[3].toSint16(); + toColor = argv[4].toSint16(); + } else { + fromColor = toColor = -1; + } + + g_sci->_gfxPalette32->kernelPalVarySet(paletteId, percent, time, fromColor, toColor); + 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 s->r_acc; +} + +reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_gfxPalette32->getVaryPercent()); +} + +reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxPalette32->varyOff(); + return s->r_acc; +} + +reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) { + 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; + g_sci->_gfxPalette32->setVaryTime(time); + return s->r_acc; +} + +reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv) { + 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(); + 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(); + g_sci->_gfxPalette32->kernelPalVaryMergeStart(paletteId); + return make_reg(0, g_sci->_gfxPalette32->getVaryPercent()); } 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 0: { // Palette animation initialization - // 3 or 4 extra params - // Case 1 sends fromColor and speed again, so we don't need them here. - // Only toColor is stored - //uint16 fromColor = argv[1].toUint16(); - s->_palCycleToColor = argv[2].toUint16(); - //uint16 speed = argv[3].toUint16(); - - // Invalidate the picture, so that the palette steps calls (case 1 - // below) can update its palette without it being overwritten by the - // view/picture palettes. - g_sci->_gfxScreen->_picNotValid = 1; - - // TODO: The fourth optional parameter is an unknown integer, and is 0 by default - if (argc == 5) { - // When this variant is used, picNotValid doesn't seem to be set - // (e.g. GK1 room 480). In this case, the animation step calls are - // not made, so perhaps this signifies the palette cycling steps - // to make. - // GK1 sets this to 6 (6 palette steps?) - g_sci->_gfxScreen->_picNotValid = 0; - } - kStub(s, argc, argv); - } - break; - case 1: { // Palette animation step - // This is the same as the old kPaletteAnimate call, with 1 set of colors. - // The end color is set up during initialization in case 0 above. - - // 1 or 2 extra params - uint16 fromColor = argv[1].toUint16(); - uint16 speed = (argc == 2) ? 1 : argv[2].toUint16(); - // TODO: For some reason, this doesn't set the color correctly - // (e.g. LSL6 intro, room 100, Sierra logo) - if (g_sci->_gfxPalette->kernelAnimate(fromColor, s->_palCycleToColor, speed)) - g_sci->_gfxPalette->kernelAnimateSet(); - } - // No kStub() call here, as this gets called loads of times, like kPaletteAnimate - break; - // case 2 hasn't been encountered - // case 3 hasn't been encountered - case 4: // reset any palette cycling and make the picture valid again - // Gets called when changing rooms and after palette cycling animations finish - // 0 or 1 extra params - if (argc == 1) { - g_sci->_gfxScreen->_picNotValid = 0; - // TODO: This also seems to perform more steps - } else { - // The variant with the 1 extra param resets remapping to base - // TODO - } - kStub(s, argc, argv); - break; - default: - // TODO - kStub(s, argc, argv); - break; +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; +} + +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->_gfxPalette->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->_gfxPalette->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->_gfxPalette->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->_gfxPalette->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->_gfxPalette->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->_gfxPalette->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 66590da23f..c0da2daaeb 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -669,26 +669,44 @@ reg_t kArray(EngineState *s, int argc, reg_t *argv) { // 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) - return kString(s, argc, argv); + callStringFunc = true; } else { if (s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_STRING || s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_SCRIPT) { - return kString(s, argc, argv); + 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) { - return kString(s, argc, argv); + callStringFunc = true; } } #endif } + if (callStringFunc) { + Kernel *kernel = g_sci->getKernel(); + uint16 kernelStringFuncId = kernel->_kernelFunc_StringId; + if (kernelStringFuncId) { + const KernelFunction *kernelStringFunc = &kernel->_kernelFuncs[kernelStringFuncId]; + + 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); + } + } + } + switch (op) { case 0: { // New reg_t arrayHandle; diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index 448e641b63..1924848717 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -218,7 +218,6 @@ enum { reg_t kGetTime(EngineState *s, int argc, reg_t *argv) { TimeDate loc_time; - uint32 elapsedTime = g_engine->getTotalPlayTime(); int retval = 0; // Avoid spurious warning g_system->getTimeAndDate(loc_time); @@ -232,7 +231,7 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) { switch (mode) { case KGETTIME_TICKS : - retval = elapsedTime * 60 / 1000; + retval = g_sci->getTickCount(); debugC(kDebugLevelTime, "GetTime(elapsed) returns %d", retval); break; case KGETTIME_TIME_12HOUR : @@ -244,10 +243,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; @@ -269,7 +276,10 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case K_MEMORY_ALLOCATE_CRITICAL: { int byteCount = argv[1].toUint16(); - // WORKAROUND: + // Sierra themselves allocated at least 2 bytes more than requested. + // Probably as a safety margin. And they also made size even. + // + // This behavior is required by at least these: // - pq3 (multilingual) room 202 // when plotting crimes, allocates the returned bytes from kStrLen // on "W" and "E" and wants to put a string in there, which doesn't @@ -277,18 +287,22 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { // - lsl5 (multilingual) room 280 // allocates memory according to a previous kStrLen for the name of // the airport ladies (bug #3093818), which isn't enough - - // We always allocate 1 byte more, because of this - byteCount++; + byteCount += 2 + (byteCount & 1); if (!s->_segMan->allocDynmem(byteCount, "kMemory() critical", &s->r_acc)) { error("Critical heap allocation failed"); } break; } - case K_MEMORY_ALLOCATE_NONCRITICAL: - s->_segMan->allocDynmem(argv[1].toUint16(), "kMemory() non-critical", &s->r_acc); + case K_MEMORY_ALLOCATE_NONCRITICAL: { + int byteCount = argv[1].toUint16(); + + // See above + byteCount += 2 + (byteCount & 1); + + s->_segMan->allocDynmem(byteCount, "kMemory() non-critical", &s->r_acc); break; + } case K_MEMORY_FREE : if (!s->_segMan->freeDynmem(argv[1])) { if (g_sci->getGameId() == GID_QFG1VGA) { @@ -395,6 +409,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()); } @@ -487,7 +510,7 @@ 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 - if (getSciVersion() >= SCI_VERSION_2_1) // Set Mac cursor remap + 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) warning("Unknown SCI1 kMacPlatform(0) call"); @@ -540,6 +563,11 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } + if (g_sci->forceHiresGraphics()) { + // force Windows platform, so that hires-graphics are enabled + isWindows = true; + } + uint16 operation = (argc == 0) ? 0 : argv[0].toUint16(); switch (operation) { @@ -586,18 +614,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 67d814b86f..06f16299aa 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -312,10 +312,10 @@ static void draw_line(EngineState *s, Common::Point p1, Common::Point p2, int ty // Red : Barred access // Yellow: Contained access int poly_colors[4] = { - g_sci->_gfxPalette->kernelFindColor(0, 255, 0), // green - g_sci->_gfxPalette->kernelFindColor(0, 0, 255), // blue - g_sci->_gfxPalette->kernelFindColor(255, 0, 0), // red - g_sci->_gfxPalette->kernelFindColor(255, 255, 0) // yellow + g_sci->_gfxPalette16->kernelFindColor(0, 255, 0), // green + g_sci->_gfxPalette16->kernelFindColor(0, 0, 255), // blue + g_sci->_gfxPalette16->kernelFindColor(255, 0, 0), // red + g_sci->_gfxPalette16->kernelFindColor(255, 255, 0) // yellow }; // Clip @@ -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) { @@ -334,8 +334,8 @@ static void draw_point(EngineState *s, Common::Point p, int start, int width, in // Green: End point // Blue: Starting point int point_colors[2] = { - g_sci->_gfxPalette->kernelFindColor(0, 255, 0), // green - g_sci->_gfxPalette->kernelFindColor(0, 0, 255) // blue + g_sci->_gfxPalette16->kernelFindColor(0, 255, 0), // green + g_sci->_gfxPalette16->kernelFindColor(0, 0, 255) // blue }; Common::Rect rect = Common::Rect(p.x - 1, p.y - 1, p.x - 1 + 3, p.y - 1 + 3); @@ -1943,14 +1943,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 +1960,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 +2402,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 5c271780dd..6fd130bceb 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -229,7 +229,7 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { uint16 address = scr->validateExportFunc(index, true); // Point to the heap for SCI1.1 - SCI2.1 games - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) address += scr->getScriptSize(); // Bugfix for the intro speed in PQ2 version 1.002.011. @@ -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 6a1708d21a..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) { - 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; } @@ -206,8 +230,15 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { // athrxx: It seems from disasm that the original KQ5 FM-Towns loads a default language (Japanese) audio map at the beginning // right after loading the video and audio drivers. The -1 language argument in here simply means that the original will stick // with Japanese. Instead of doing that we switch to the language selected in the launcher. - if (g_sci->getPlatform() == Common::kPlatformFMTowns && language == -1) - language = (g_sci->getLanguage() == Common::JA_JPN) ? K_LANG_JAPANESE : K_LANG_ENGLISH; + if (g_sci->getPlatform() == Common::kPlatformFMTowns && language == -1) { + // FM-Towns calls us to get the current language / also set the default language + // This doesn't just happen right at the start, but also when the user clicks on the Sierra logo in the game menu + // It uses the result of this call to either show "English Voices" or "Japanese Voices". + + // Language should have been set by setLauncherLanguage() already (or could have been modified by the scripts). + // Get this language setting, so that the chosen language will get set for resource manager. + language = g_sci->getSciLanguage(); + } debugC(kDebugLevelSound, "kDoAudio: set language to %d", language); @@ -225,27 +256,40 @@ 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. kDoAudio in Pharkas sits at seg026:038C + // 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C case 11: // Not sure where this is used yet warning("kDoAudio: Unhandled case 11, %d extra arguments passed", argc - 1); break; case 12: - // Seems to be some sort of audio sync, used in Pharkas. Silenced the - // warning due to the high level of spam it produces. (takes no params) - //warning("kDoAudio: Unhandled case 12, %d extra arguments passed", argc - 1); + // SSCI calls this function with no parameters from + // the TalkRandCycle class and branches on the return + // value like a boolean. The conjectured purpose of + // this function is to ensure that the talker's mouth + // does not move if there is read jitter (slow CD + // drive, scratched CD). The old behavior here of not + // doing anything caused a nonzero value to be left in + // the accumulator by chance. This is equivalent, but + // more explicit. + + return make_reg(0, 1); break; case 13: - // Used in Pharkas whenever a speech sample starts (takes no params) - //warning("kDoAudio: Unhandled case 13, %d extra arguments passed", argc - 1); + // SSCI returns a serial number for the played audio + // here, used in the PointsSound class. The reason is severalfold: + + // 1. SSCI does not support multiple wave effects at once + // 2. FPFP may disable its icon bar during the points sound. + // 3. Each new sound preempts any sound already playing. + // 4. If the points sound is interrupted before completion, + // the icon bar could remain disabled. + + // Since points (1) and (3) do not apply to us, we can simply + // return a constant here. This is equivalent to the + // old behavior, as above. + return make_reg(0, 1); break; case 17: // Seems to be some sort of audio sync, used in SQ6. Silenced the @@ -260,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) { @@ -278,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()); @@ -295,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 @@ -309,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 eef758a0d9..1c08bf597c 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -661,202 +661,238 @@ 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; +// 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); + + return stringHandle; +} + +reg_t kStringSize(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, s->_segMan->getString(argv[0]).size()); +} + +// 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; + + if (index + count > 65535) + return NULL_REG; + + if (string->getSize() < index + count) + string->setSize(index + count); + + for (uint16 i = 0; i < count; i++) + string->setValue(i + index, argv[i + 2].toUint16()); + + return argv[0]; // We also have to return the handle +} + +reg_t kStringFree(EngineState *s, int argc, reg_t *argv) { + // Freeing of strings is handled by the garbage collector return s->r_acc; } -reg_t kString(EngineState *s, int argc, reg_t *argv) { - uint16 op = argv[0].toUint16(); +reg_t kStringFill(EngineState *s, int argc, reg_t *argv) { + SciString *string = s->_segMan->lookupString(argv[0]); + uint16 index = argv[1].toUint16(); - if (g_sci->_features->detectSci2StringFunctionType() == kSci2StringFunctionNew) { - if (op >= 8) // Dup, GetData have been removed - op += 2; - } + // 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(); - switch (op) { - case 0: { // New - reg_t stringHandle; - SciString *string = s->_segMan->allocateString(&stringHandle); - string->setSize(argv[1].toUint16()); + if (stringSize < index + count) + string->setSize(index + count); - // Make sure the first character is a null character - if (string->getSize() > 0) - string->setValue(0, 0); + for (uint16 i = 0; i < count; i++) + string->setValue(i + index, argv[3].toUint16()); - return stringHandle; - } - case 1: // Size - return make_reg(0, s->_segMan->getString(argv[1]).size()); - case 2: { // At (return value at an index) - // Note that values are put in bytes to avoid sign extension - if (argv[1].getSegment() == s->_segMan->getStringSegmentId()) { - SciString *string = s->_segMan->lookupString(argv[1]); - byte val = string->getRawData()[argv[2].toUint16()]; - return make_reg(0, val); - } else { - Common::String string = s->_segMan->getString(argv[1]); - byte val = string[argv[2].toUint16()]; - return make_reg(0, val); + return argv[0]; +} + +reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) { + const char *string2 = 0; + uint32 string2Size = 0; + Common::String string; + + 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; + } + + 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"); } - case 3: { // Atput (put value at an index) - SciString *string = s->_segMan->lookupString(argv[1]); - uint32 index = argv[2].toUint16(); - uint32 count = argc - 3; + // The original engine ignores bad copies too + if (index2 >= string2Size) + return NULL_REG; - if (index + count > 65535) - break; + // A count of -1 means fill the rest of the array + uint32 count = string2Size - index2; + if (argv[4].toSint16() != -1) { + count = MIN(count, (uint32)argv[4].toUint16()); + } +// reg_t strAddress = argv[0]; - if (string->getSize() < index + count) - string->setSize(index + count); + SciString *string1 = s->_segMan->lookupString(argv[0]); + //SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress); - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[i + 3].toUint16()); + if (string1->getSize() < index1 + count) + string1->setSize(index1 + count); - return argv[1]; // We also have to return the handle - } - case 4: // Free - // Freeing of strings is handled by the garbage collector - return s->r_acc; - case 5: { // Fill - SciString *string = s->_segMan->lookupString(argv[1]); - uint16 index = argv[2].toUint16(); + // 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]); - // A count of -1 means fill the rest of the array - uint16 count = argv[3].toSint16() == -1 ? string->getSize() - index : argv[3].toUint16(); - uint16 stringSize = string->getSize(); + return argv[0]; +} + +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]); + + 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())); +} - if (stringSize < index + count) - string->setSize(index + count); +// was removed for SCI2.1 Late+ +reg_t kStringDup(EngineState *s, int argc, reg_t *argv) { + reg_t stringHandle; - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[4].toUint16()); + SciString *dupString = s->_segMan->allocateString(&stringHandle); - return argv[1]; + if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) { + *dupString = *s->_segMan->lookupString(argv[0]); + } else { + dupString->fromString(s->_segMan->getString(argv[0])); } - case 6: { // Cpy - const char *string2 = 0; - uint32 string2Size = 0; - Common::String string; - - if (argv[3].getSegment() == s->_segMan->getStringSegmentId()) { - SciString *sstr; - sstr = s->_segMan->lookupString(argv[3]); - string2 = sstr->getRawData(); - string2Size = sstr->getSize(); - } else { - string = s->_segMan->getString(argv[3]); - string2 = string.c_str(); - string2Size = string.size() + 1; - } - uint32 index1 = argv[2].toUint16(); - uint32 index2 = argv[4].toUint16(); + return stringHandle; +} - // The original engine ignores bad copies too - if (index2 > string2Size) - break; +// 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]; - // A count of -1 means fill the rest of the array - uint32 count = argv[5].toSint16() == -1 ? string2Size - index2 + 1 : argv[5].toUint16(); - reg_t strAddress = argv[1]; + return readSelector(s->_segMan, argv[0], SELECTOR(data)); +} - SciString *string1 = s->_segMan->lookupString(argv[1]); - //SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress); +reg_t kStringLen(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, s->_segMan->strlen(argv[0])); +} - if (string1->getSize() < index1 + count) - string1->setSize(index1 + count); +reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv) { + reg_t stringHandle; + s->_segMan->allocateString(&stringHandle); - // 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]); + reg_t *adjustedArgs = new reg_t[argc + 1]; + adjustedArgs[0] = stringHandle; + memcpy(&adjustedArgs[1], argv, argc * sizeof(reg_t)); - return strAddress; - } - case 7: { // Cmp - Common::String string1 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]); - Common::String string2 = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); - - if (argc == 4) // Strncmp - return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[3].toUint16())); - else // Strcmp - return make_reg(0, strcmp(string1.c_str(), string2.c_str())); - } - case 8: { // Dup - reg_t stringHandle; + kFormat(s, argc + 1, adjustedArgs); + delete[] adjustedArgs; + return stringHandle; +} - SciString *dupString = s->_segMan->allocateString(&stringHandle); +reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv) { + return kFormat(s, argc, argv); +} - if (argv[1].getSegment() == s->_segMan->getStringSegmentId()) { - *dupString = *s->_segMan->lookupString(argv[1]); - } else { - dupString->fromString(s->_segMan->getString(argv[1])); - } +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())); +} - return stringHandle; - } - case 9: // Getdata - if (!s->_segMan->isHeapObject(argv[1])) - return argv[1]; - - return readSelector(s->_segMan, argv[1], SELECTOR(data)); - case 10: // Stringlen - return make_reg(0, s->_segMan->strlen(argv[1])); - case 11: { // Printf - reg_t stringHandle; - s->_segMan->allocateString(&stringHandle); - - reg_t *adjustedArgs = new reg_t[argc]; - adjustedArgs[0] = stringHandle; - memcpy(&adjustedArgs[1], argv + 1, (argc - 1) * sizeof(reg_t)); - - kFormat(s, argc, adjustedArgs); - delete[] adjustedArgs; - return stringHandle; - } - case 12: // Printf Buf - return kFormat(s, argc - 1, argv + 1); - case 13: { // atoi - Common::String string = s->_segMan->getString(argv[1]); - return make_reg(0, (uint16)atoi(string.c_str())); - } - // New subops in SCI2.1 late / SCI3 - case 14: // unknown - warning("kString, subop %d", op); - return NULL_REG; - case 15: { // upper - Common::String string = s->_segMan->getString(argv[1]); +reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) { + Common::String string = s->_segMan->getString(argv[0]); - string.toUppercase(); - s->_segMan->strcpy(argv[1], string.c_str()); - return NULL_REG; - } - case 16: { // lower - Common::String string = s->_segMan->getString(argv[1]); + 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; +} - string.toLowercase(); - s->_segMan->strcpy(argv[1], string.c_str()); - return NULL_REG; - } - default: - error("Unknown kString subop %d", argv[0].toUint16()); - } +reg_t kStringUpper(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; +} +reg_t kStringLower(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; +} + +// Possibly kStringTranslateExclude? +reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv) { + warning("kStringTrnExclude (argc = %d)", argc); return NULL_REG; } +reg_t kString(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + #endif } // End of namespace Sci diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index f925111fc9..1096e78cca 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 @@ -181,7 +182,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { // for the video, so we'll just play it from there for now. #ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1) { + 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) @@ -231,7 +232,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { initGraphics(screenWidth, screenHeight, screenWidth > 320); else { g_sci->_gfxScreen->kernelSyncWithFramebuffer(); - g_sci->_gfxPalette->kernelSyncScreenPalette(); + g_sci->_gfxPalette16->kernelSyncScreenPalette(); } } @@ -289,113 +290,73 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { } 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; - - switch (operation) { - case 0: // init - s->_videoState.reset(); - s->_videoState.fileName = s->_segMan->derefString(argv[1]); + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - 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; - } +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; - warning("x, y: %d, %d", argv[1].getOffset(), argv[2].getOffset()); - s->_videoState.x = argv[1].getOffset(); - s->_videoState.y = argv[2].getOffset(); + return make_reg(0, g_sci->_video32->getVMDPlayer().open(fileName, flags)); +} - 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 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; } - case 6: // Play - videoDecoder = new Video::AdvancedVMDDecoder(); - if (s->_videoState.fileName.empty()) { - // Happens in Lighthouse - warning("kPlayVMD: Empty filename passed"); - return s->r_acc; - } + g_sci->_video32->getVMDPlayer().init(x, y, flags, boostPercent, boostStartColor, boostEndColor); - if (!videoDecoder->loadFile(s->_videoState.fileName)) { - warning("Could not open VMD %s", s->_videoState.fileName.c_str()); - break; - } + return make_reg(0, 0); +} - if (reshowCursor) - g_sci->_gfxCursor->kernelHide(); +reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getVMDPlayer().close()); +} - playVideo(videoDecoder, s->_videoState); +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)); +} - 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 kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getVMDPlayer().setShowCursor((bool)argv[0].toUint16()); + return s->r_acc; +} - for (int i = 0; i < argc; i++) { - warningMsg += Common::String::format("%04x:%04x", PRINT_REG(argv[i])); - warningMsg += (i == argc - 1 ? ")" : ", "); - } +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; - warning("%s", warningMsg.c_str()); - break; - } + Common::Rect blackoutArea; + blackoutArea.left = MAX((int16)0, argv[0].toSint16()); + blackoutArea.top = MAX((int16)0, argv[1].toSint16()); + blackoutArea.right = MIN(scriptWidth, (int16)(argv[2].toSint16() + 1)); + blackoutArea.bottom = MIN(scriptHeight, (int16)(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 640175b20a..5300b72b71 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -182,7 +182,7 @@ bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &re #ifdef ENABLE_SCI32 case 5: // v5 seems to be compatible with v4 // SCI32 Mac is different than SCI32 DOS/Win here - if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1) + if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY) reader = new MessageReaderV4_MacSCI32(res->data, res->size); else #endif diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp index eeff45163d..0566d6955f 100644 --- a/engines/sci/engine/object.cpp +++ b/engines/sci/engine/object.cpp @@ -45,7 +45,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme return false; } block[idx].setSegment(segment); // Perform relocation - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) block[idx].incOffset(scriptSize); return true; @@ -63,7 +63,7 @@ void Object::init(byte *buf, reg_t obj_pos, bool initVariables) { for (int i = 0; i < _methodCount * 2 + 2; ++i) { _baseMethod.push_back(READ_SCI11ENDIAN_UINT16(data + READ_LE_UINT16(data + kOffsetFunctionArea) + i * 2)); } - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { _variables.resize(READ_SCI11ENDIAN_UINT16(data + 2)); _baseVars = (const uint16 *)(buf + READ_SCI11ENDIAN_UINT16(data + 4)); _methodCount = READ_SCI11ENDIAN_UINT16(buf + READ_SCI11ENDIAN_UINT16(data + 6)); @@ -75,7 +75,7 @@ void Object::init(byte *buf, reg_t obj_pos, bool initVariables) { } if (initVariables) { - if (getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() <= SCI_VERSION_2_1_LATE) { for (uint i = 0; i < _variables.size(); i++) _variables[i] = make_reg(0, READ_SCI11ENDIAN_UINT16(data + (i * 2))); } else { @@ -92,7 +92,7 @@ int Object::locateVarSelector(SegManager *segMan, Selector slc) const { const byte *buf = 0; uint varnum = 0; - if (getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() <= SCI_VERSION_2_1_LATE) { const Object *obj = getClass(segMan); varnum = getSciVersion() <= SCI_VERSION_1_LATE ? getVarCount() : obj->getVariable(1).toUint16(); buf = (const byte *)obj->_baseVars; @@ -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 00fe7c6875..a7be170f4f 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -41,8 +41,21 @@ enum { }; enum infoSelectorFlags { - kInfoFlagClone = 0x0001, - kInfoFlagClass = 0x8000 + kInfoFlagClone = 0x0001, +#ifdef ENABLE_SCI32 + /** + * When set, indicates to game scripts that a screen + * item can be updated. + */ + kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ? + + /** + * When set, the object has an associated screen item in + * the rendering tree. + */ + kInfoFlagViewInserted = 0x0010, +#endif + kInfoFlagClass = 0x8000 }; enum ObjectOffsets { @@ -79,51 +92,68 @@ public: } reg_t getSpeciesSelector() const { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) return _variables[_offset]; else // SCI3 return _speciesSelectorSci3; } void setSpeciesSelector(reg_t value) { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) _variables[_offset] = value; else // SCI3 _speciesSelectorSci3 = value; } reg_t getSuperClassSelector() const { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) return _variables[_offset + 1]; else // SCI3 return _superClassPosSci3; } void setSuperClassSelector(reg_t value) { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) _variables[_offset + 1] = value; else // SCI3 _superClassPosSci3 = value; } reg_t getInfoSelector() const { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) return _variables[_offset + 2]; else // SCI3 return _infoSelectorSci3; } void setInfoSelector(reg_t info) { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) _variables[_offset + 2] = info; else // SCI3 _infoSelectorSci3 = info; } - // No setter for the -info- selector +#ifdef ENABLE_SCI32 + void setInfoSelectorFlag(infoSelectorFlags flag) { + if (getSciVersion() < SCI_VERSION_3) { + _variables[_offset + 2] |= flag; + } else { + _infoSelectorSci3 |= flag; + } + } + + // NOTE: In real engine, -info- is treated as byte size + void clearInfoSelectorFlag(infoSelectorFlags flag) { + if (getSciVersion() < SCI_VERSION_3) { + _variables[_offset + 2] &= ~flag; + } else { + _infoSelectorSci3 &= ~flag; + } + } +#endif reg_t getNameSelector() const { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) return _offset + 3 < (uint16)_variables.size() ? _variables[_offset + 3] : NULL_REG; else // SCI3 return _variables.size() ? _variables[0] : NULL_REG; @@ -132,7 +162,7 @@ public: // No setter for the name selector reg_t getPropDictSelector() const { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) return _variables[2]; else // This should never occur, this is called from a SCI1.1 - SCI2.1 only function @@ -140,7 +170,7 @@ public: } void setPropDictSelector(reg_t value) { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) _variables[2] = value; else // This should never occur, this is called from a SCI1.1 - SCI2.1 only function @@ -148,14 +178,14 @@ public: } reg_t getClassScriptSelector() const { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) return _variables[4]; else // SCI3 return make_reg(0, READ_SCI11ENDIAN_UINT16(_baseObj + 6)); } void setClassScriptSelector(reg_t value) { - if (getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() < SCI_VERSION_3) _variables[4] = value; else // SCI3 // This should never occur, this is called from a SCI1.1 - SCI2.1 only function @@ -232,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); @@ -248,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 93b3a997cc..31fb848a2c 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -39,6 +39,7 @@ #include "sci/engine/vm_types.h" #include "sci/engine/script.h" // for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS #include "sci/graphics/helpers.h" +#include "sci/graphics/menu.h" #include "sci/graphics/palette.h" #include "sci/graphics/ports.h" #include "sci/graphics/screen.h" @@ -48,6 +49,8 @@ #ifdef ENABLE_SCI32 #include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" #endif namespace Sci { @@ -58,24 +61,125 @@ 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); +} + +#ifdef ENABLE_SCI32 +void syncWithSerializer(Common::Serializer &s, SciArray<reg_t> &obj) { + 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); + } +} + +void syncWithSerializer(Common::Serializer &s, SciString &obj) { + 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 + +#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); 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) const { + s.syncAsSint32LE(entry.next_free); + + syncWithSerializer(s, entry.data); + } +}; + /** * Sync a Common::Array using a Common::Serializer. * When saving, this writes the length of the array, then syncs (writes) all entries. @@ -114,18 +218,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) { @@ -245,12 +341,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); @@ -272,7 +362,11 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) if (s.getVersion() >= 26) s.syncAsUint32LE(obj.playTime); } else { - obj.playTime = g_engine->getTotalPlayTime() / 1000; + if (s.getVersion() >= 34) { + obj.playTime = g_sci->getTickCount(); + } else { + obj.playTime = g_engine->getTotalPlayTime() / 1000; + } s.syncAsUint32LE(obj.playTime); } } @@ -304,7 +398,13 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { _segMan->saveLoadWithSerializer(s); g_sci->_soundCmd->syncPlayList(s); - g_sci->_gfxPalette->saveLoadWithSerializer(s); + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + g_sci->_gfxPalette32->saveLoadWithSerializer(s); + } else +#endif + g_sci->_gfxPalette16->saveLoadWithSerializer(s); } void Vocabulary::saveLoadWithSerializer(Common::Serializer &s) { @@ -324,102 +424,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) { @@ -465,7 +476,7 @@ void Script::syncStringHeap(Common::Serializer &s) { break; } while (1); - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1){ + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE){ // Strings in SCI1.1 come after the object instances byte *buf = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; @@ -721,6 +732,116 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) { } } +#ifdef ENABLE_SCI32 +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); + s.syncAsByte(palette->colors[i].r); + s.syncAsByte(palette->colors[i].g); + s.syncAsByte(palette->colors[i].b); + } +} + +void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) { + bool hasPalette; + if (s.isSaving()) { + hasPalette = (*palette != nullptr); + } + s.syncAsByte(hasPalette); + if (hasPalette) { + if (s.isLoading()) { + *palette = new Palette; + } + saveLoadPalette32(s, *palette); + } +} + +void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 34) { + return; + } + + if (s.isLoading()) { + ++_version; + } + + s.syncAsSint16LE(_varyDirection); + s.syncAsSint16LE(_varyPercent); + s.syncAsSint16LE(_varyTargetPercent); + s.syncAsSint16LE(_varyFromColor); + s.syncAsSint16LE(_varyToColor); + s.syncAsUint16LE(_varyNumTimesPaused); + s.syncAsByte(_needsUpdate); + s.syncAsSint32LE(_varyTime); + s.syncAsUint32LE(_varyLastTick); + + for (int i = 0; i < ARRAYSIZE(_fadeTable); ++i) { + s.syncAsByte(_fadeTable[i]); + } + for (int i = 0; i < ARRAYSIZE(_cycleMap); ++i) { + s.syncAsByte(_cycleMap[i]); + } + + saveLoadOptionalPalette32(s, &_varyTargetPalette); + saveLoadOptionalPalette32(s, &_varyStartPalette); + // NOTE: _sourcePalette and _nextPalette are not saved + // by SCI engine + + for (int i = 0; i < ARRAYSIZE(_cyclers); ++i) { + PalCycler *cycler = nullptr; + + bool hasCycler; + if (s.isSaving()) { + cycler = _cyclers[i]; + hasCycler = (cycler != nullptr); + } + s.syncAsByte(hasCycler); + + if (hasCycler) { + if (s.isLoading()) { + _cyclers[i] = cycler = new PalCycler; + } + + s.syncAsByte(cycler->fromColor); + s.syncAsUint16LE(cycler->numColorsToCycle); + s.syncAsByte(cycler->currentCycle); + s.syncAsByte(cycler->direction); + s.syncAsUint32LE(cycler->lastUpdateTick); + s.syncAsSint16LE(cycler->delay); + 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); + } + + if (s.isLoading()) { + _needsUpdate = true; + } +} +#endif + void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { // reset() is called directly way earlier in gamestate_restore() if (s.getVersion() >= 27) { @@ -811,7 +932,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) { @@ -866,7 +987,8 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin extern void showScummVMDialog(const Common::String &message); void gamestate_delayedrestore(EngineState *s) { - Common::String fileName = g_sci->getSavegameName(s->_delayedRestoreGameId); + int savegameId = s->_delayedRestoreGameId; // delayedRestoreGameId gets destroyed within gamestate_restore()! + Common::String fileName = g_sci->getSavegameName(savegameId); Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName); if (in) { @@ -874,6 +996,7 @@ void gamestate_delayedrestore(EngineState *s) { gamestate_restore(s, in); delete in; if (s->r_acc != make_reg(0, 1)) { + gamestate_afterRestoreFixUp(s, savegameId); return; } } @@ -881,6 +1004,73 @@ void gamestate_delayedrestore(EngineState *s) { error("Restoring gamestate '%s' failed", fileName.c_str()); } +void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) { + switch (g_sci->getGameId()) { + case GID_MOTHERGOOSE: + // WORKAROUND: Mother Goose SCI0 + // Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the + // current number of children plus 1. + // We can't trust that global, that's why we set the actual savedgame id right here directly after + // restoring a saved game. + // If we didn't, the game would always save to a new slot + s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); + break; + case GID_MOTHERGOOSE256: + // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for + // saving a previously restored game. + // We set the current savedgame-id directly and remove the script + // code concerning this via script patch. + s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); + break; + case GID_JONES: + // HACK: The code that enables certain menu items isn't called when a game is restored from the + // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723. + // These menu entries are disabled when the game is launched, and are enabled when a new game is + // started. The code for enabling these entries is is all in script 1, room1::init, but that code + // path is never followed in these two cases (restoring game from the menu, or restoring a game + // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here. + // These two are needed when restoring from the launcher + // FIXME: The original interpreter saves and restores the menu state, so these attributes + // are automatically reset there. We may want to do the same. + g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones + g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help + // The rest are normally enabled from room1::init + g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player + g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game + g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game + 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. + g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game + break; + default: + break; + } +} + void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { SavegameMetadata meta; @@ -922,13 +1112,31 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (g_sci->_gfxPorts) g_sci->_gfxPorts->reset(); // clear screen - if (g_sci->_gfxScreen) - g_sci->_gfxScreen->clearForRestoreGame(); + if (getSciVersion() <= SCI_VERSION_1_1) { + // Only do clearing the screen for SCI16 + // Both SCI16 + SCI32 did not clear the screen. + // We basically do it for SCI16, because of KQ6. + // When hires portraits are shown and the user restores during that time, the portraits + // wouldn't get fully removed. In original SCI, the user wasn't able to restore during that time, + // so this is basically a workaround, so that ScummVM features work properly. + // For SCI32, behavior was verified in DOSBox, that SCI32 does not clear and also not redraw the screen. + // It only redraws elements that have changed in comparison to the state before the restore. + // If we cleared the screen for SCI32, we would have issues because of this behavior. + if (g_sci->_gfxScreen) + g_sci->_gfxScreen->clearForRestoreGame(); + } #ifdef ENABLE_SCI32 - // Also clear any SCI32 planes/screen items currently showing so they - // don't show up after the load. - if (getSciVersion() >= SCI_VERSION_2) - g_sci->_gfxFrameout->clear(); + // Delete current planes/elements of actively loaded VM, only when our ScummVM dialogs are patched in + // 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) { + 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); @@ -944,11 +1152,22 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { // Time state: s->lastWaitTime = g_system->getMillis(); s->_screenUpdateTime = g_system->getMillis(); - g_engine->setTotalPlayTime(meta.playTime * 1000); + if (meta.version >= 34) { + g_sci->setTickCount(meta.playTime); + } else { + g_engine->setTotalPlayTime(meta.playTime * 1000); + } if (g_sci->_gfxPorts) g_sci->_gfxPorts->saveLoadWithSerializer(ser); + // SCI32: + // Current planes/screen elements of freshly loaded VM are re-added by scripts in [gameID]::replay + // We don't have to do that in here. + // But we may have to do it ourselves in case we ever implement some soft-error handling in case + // a saved game can't be restored. That way we can restore the game screen. + // see _gfxFrameout->syncWithScripts() + Vocabulary *voc = g_sci->getVocabulary(); if (ser.getVersion() >= 30 && voc) voc->saveLoadWithSerializer(ser); @@ -966,6 +1185,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 5512b90fa8..43909accf2 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,6 +37,8 @@ struct EngineState; * * Version - new/changed feature * ============================= + * 35 - SCI32 remap + * 34 - SCI32 palettes, and store play time in ticks * 33 - new overridePriority flag in MusicEntry * 32 - new playBed flag in MusicEntry * 31 - priority for sound effects/music is now a signed int16, instead of a byte @@ -58,7 +60,7 @@ struct EngineState; */ enum { - CURRENT_SAVEGAME_VERSION = 33, + CURRENT_SAVEGAME_VERSION = 35, MINIMUM_SAVEGAME_VERSION = 14 }; @@ -86,6 +88,9 @@ bool gamestate_save(EngineState *s, Common::WriteStream *save, const Common::Str // does a delayed saved game restore, used by ScummVM game menu - see detection.cpp / SciEngine::loadGameState() void gamestate_delayedrestore(EngineState *s); +// does a few fixups right after restoring a saved game +void gamestate_afterRestoreFixUp(EngineState *s, int savegameId); + /** * Restores a game state from a directory. * @param s An older state from the same game diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 36e33ccfa6..26a7ff5718 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -84,7 +84,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP if (getSciVersion() == SCI_VERSION_0_EARLY) { _bufSize += READ_LE_UINT16(script->data) * 2; - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { // In SCI1.1 - SCI2.1, the heap was in a separate space from the script. We append // it to the end of the script, and adjust addressing accordingly. // However, since we address the heap with a 16-bit pointer, the @@ -142,7 +142,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP assert(_bufSize >= script->size); memcpy(_buf, script->data, script->size); - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0); assert(heap != 0); @@ -171,7 +171,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP _localsOffset = localsBlock - _buf + 4; _localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1; // half block size } - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } 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); @@ -387,7 +387,7 @@ void Script::identifyOffsets() { scriptDataLeft -= blockSize; } while (1); - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { // Strings in SCI1.1 up to SCI2 come after the object instances scriptDataPtr = _heapStart; scriptDataLeft = _heapSize; @@ -668,7 +668,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme return false; } block[idx].setSegment(segment); // Perform relocation - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) block[idx].incOffset(scriptSize); return true; @@ -702,7 +702,7 @@ void Script::relocateSci0Sci21(reg_t block) { uint16 heapSize = (uint16)_bufSize; uint16 heapOffset = 0; - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { heap = _heapStart; heapSize = (uint16)_heapSize; heapOffset = _scriptSize; @@ -930,7 +930,7 @@ void Script::initializeClasses(SegManager *segMan) { if (getSciVersion() <= SCI_VERSION_1_LATE) { seeker = findBlockSCI0(SCI_OBJ_CLASS); mult = 1; - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { seeker = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; mult = 2; } else if (getSciVersion() == SCI_VERSION_3) { @@ -962,7 +962,7 @@ void Script::initializeClasses(SegManager *segMan) { if (isClass) species = READ_SCI11ENDIAN_UINT16(seeker + 12); classpos += 12; - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { isClass = (READ_SCI11ENDIAN_UINT16(seeker + 14) & kInfoFlagClass); // -info- selector species = READ_SCI11ENDIAN_UINT16(seeker + 10); } else if (getSciVersion() == SCI_VERSION_3) { @@ -1104,7 +1104,7 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) { void Script::initializeObjects(SegManager *segMan, SegmentId segmentId) { if (getSciVersion() <= SCI_VERSION_1_LATE) initializeObjectsSci0(segMan, segmentId); - else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) + else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) initializeObjectsSci11(segMan, segmentId); else if (getSciVersion() == SCI_VERSION_3) initializeObjectsSci3(segMan, segmentId); diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 6915e12a0e..e6eed0b4b7 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -101,6 +101,8 @@ static const char *const selectorNameTable[] = { "startText", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "startAudio", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "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 NULL }; @@ -127,7 +129,9 @@ enum ScriptPatcherSelectors { SELECTOR_timesShownID, SELECTOR_startText, SELECTOR_startAudio, - SELECTOR_modNum + SELECTOR_modNum, + SELECTOR_cycler, + SELECTOR_setLoop }; // =========================================================================== @@ -375,12 +379,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 @@ -391,19 +423,73 @@ 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 }; // =========================================================================== + +// WORKAROUND +// Freddy Pharkas intro screen +// Sierra used inner loops for the scaling of the 2 title views. +// Those inner loops don't call kGameIsRestarting, which is why +// we do not update the screen and we also do not throttle. +// +// This patch fixes this and makes it work. +// Applies to at least: English PC-CD +// Responsible method: sTownScript::changeState(1), sTownScript::changeState(3) (script 110) +static const uint16 freddypharkasSignatureIntroScaling[] = { + 0x38, SIG_ADDTOOFFSET(+2), // pushi (setLoop) (009b for PC CD) + 0x78, // push1 + PATCH_ADDTOOFFSET(1), // push0 for first code, push1 for second code + 0x38, SIG_ADDTOOFFSET(+2), // pushi (setStep) (0143 for PC CD) + 0x7a, // push2 + 0x39, 0x05, // pushi 05 + 0x3c, // dup + 0x72, SIG_ADDTOOFFSET(+2), // lofsa (view) + SIG_MAGICDWORD, + 0x4a, 0x1e, // send 1e + 0x35, 0x0a, // ldi 0a + 0xa3, 0x02, // sal local[2] + // start of inner loop + 0x8b, 0x02, // lsl local[2] + SIG_ADDTOOFFSET(+43), // skip almost all of inner loop + 0xa3, 0x02, // sal local[2] + 0x33, 0xcf, // jmp [inner loop start] + SIG_END +}; + +static const uint16 freddypharkasPatchIntroScaling[] = { + // remove setLoop(), objects in heap are already prepared, saves 5 bytes + 0x38, + PATCH_GETORIGINALBYTE(+6), + PATCH_GETORIGINALBYTE(+7), // pushi (setStep) + 0x7a, // push2 + 0x39, 0x05, // pushi 05 + 0x3c, // dup + 0x72, + PATCH_GETORIGINALBYTE(+13), + PATCH_GETORIGINALBYTE(+14), // lofsa (view) + 0x4a, 0x18, // send 18 - adjusted + 0x35, 0x0a, // ldi 0a + 0xa3, 0x02, // sal local[2] + // start of new inner loop + 0x39, 0x00, // pushi 00 + 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered + SIG_ADDTOOFFSET(+47), // skip almost all of inner loop + 0x33, 0xca, // jmp [inner loop start] + PATCH_END +}; + // script 0 of freddy pharkas/CD PointsSound::check waits for a signal and if // no signal received will call kDoSound(0xD) which is a dummy in sierra sci // and ScummVM and will use acc (which is not set by the dummy) to trigger @@ -522,6 +608,7 @@ static const uint16 freddypharkasPatchMacInventory[] = { static const SciScriptPatcherEntry freddypharkasSignatures[] = { { true, 0, "CD: score early disposal", 1, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal }, { true, 15, "Mac: broken inventory", 1, freddypharkasSignatureMacInventory, freddypharkasPatchMacInventory }, + { true, 110, "intro scaling workaround", 2, freddypharkasSignatureIntroScaling, freddypharkasPatchIntroScaling }, { true, 235, "CD: canister pickup hang", 3, freddypharkasSignatureCanisterHang, freddypharkasPatchCanisterHang }, { true, 320, "ladder event issue", 2, freddypharkasSignatureLadderEvent, freddypharkasPatchLadderEvent }, SCI_SIGNATUREENTRY_TERMINATOR @@ -755,6 +842,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 @@ -785,9 +898,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 }; @@ -1201,8 +1315,10 @@ static const uint16 kq6CDPatchAudioTextSupportJumpAlways[] = { }; // Fixes "Girl In The Tower" to get played in dual mode as well +// Also changes credits to use CD audio for dual mode. +// // Applies to at least: PC-CD -// Patched method: rm740::cue +// Patched method: rm740::cue (script 740), sCredits::init (script 52) static const uint16 kq6CDSignatureAudioTextSupportGirlInTheTower[] = { SIG_MAGICDWORD, 0x89, 0x5a, // lsg global[5a] @@ -1329,6 +1445,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = { { false, 928, "CD: audio + text support KQ6 4", 1, kq6CDSignatureAudioTextSupport4, kq6CDPatchAudioTextSupport4 }, { false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards }, { false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways }, + { false, 52, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, { false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, { false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel }, { false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport }, @@ -1336,6 +1453,167 @@ static const SciScriptPatcherEntry kq6Signatures[] = { }; // =========================================================================== + +// 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 +}; + +// =========================================================================== // 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. // The German version script contains a typo (probably a copy/paste error), @@ -1375,9 +1653,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 }; @@ -1536,6 +1914,194 @@ static const SciScriptPatcherEntry larry6Signatures[] = { }; // =========================================================================== +// Laura Bow 1 - Colonel's Bequest +// +// This is basically just a broken easter egg in Colonel's Bequest. +// A plane can show up in room 4, but that only happens really rarely. +// Anyway the Sierra developer seems to have just entered the wrong loop, +// which is why the statue view is used instead (loop 0). +// We fix it to use the correct loop. +// +// This is only broken in the PC version. It was fixed for Amiga + Atari ST. +// +// Credits to OmerMor, for finding it. + +// Applies to at least: English PC Floppy +// Responsible method: room4::init +static const uint16 laurabow1SignatureEasterEggViewFix[] = { + 0x78, // push1 + 0x76, // push0 + SIG_MAGICDWORD, + 0x38, SIG_SELECTOR16(setLoop), // pushi "setLoop" + 0x78, // push1 + 0x39, 0x03, // pushi 3 (loop 3, view only has 3 loops) + SIG_END +}; + +static const uint16 laurabow1PatchEasterEggViewFix[] = { + PATCH_ADDTOOFFSET(+7), + 0x02, // change loop to 2 + 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, 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 +}; + +// =========================================================================== // Laura Bow 2 // // Moving away the painting in the room with the hidden safe is problematic @@ -1840,15 +2406,103 @@ static const SciScriptPatcherEntry laurabow2Signatures[] = { // MG::replay somewhat calculates the savedgame-id used when saving again // this doesn't work right and we remove the code completely. // We set the savedgame-id directly right after restoring in kRestoreGame. +// We also draw the background picture in here instead. +// This Mixed Up Mother Goose draws the background picture before restoring, +// instead of doing it properly in MG::replay. This fixes graphic issues, +// when restoring from GMM. +// +// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns +// Responsible method: MG::replay (script 0) static const uint16 mothergoose256SignatureReplay[] = { + 0x7a, // push2 + 0x78, // push1 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x43, 0x70, 0x04, // callk MemorySegment + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x76, // push0 + 0x43, 0x62, 0x04, // callk StrAt + 0xa1, 0xaa, // sag global[AAh] + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x78, // push1 + 0x43, 0x62, 0x04, // callk StrAt + 0x36, // push + 0x35, 0x20, // ldi 20 + 0x04, // sub + 0xa1, SIG_ADDTOOFFSET(+1), // sag global[57h] -> FM-Towns [9Dh] + // 35 bytes + 0x39, 0x03, // pushi 03 + 0x89, SIG_ADDTOOFFSET(+1), // lsg global[1Dh] -> FM-Towns [1Eh] + 0x76, // push0 + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x7a, // push2 + 0x43, 0x62, 0x04, // callk StrAt + 0x36, // push + 0x35, 0x01, // ldi 01 + 0x04, // sub + 0x36, // push + 0x43, 0x62, 0x06, // callk StrAt + // 22 bytes + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BE] + 0x36, // push + 0x39, 0x03, // pushi 03 + 0x43, 0x62, 0x04, // callk StrAt + // 10 bytes 0x36, // push 0x35, SIG_MAGICDWORD, 0x20, // ldi 20 0x04, // sub 0xa1, 0xb3, // sag global[b3] + // 6 bytes SIG_END }; static const uint16 mothergoose256PatchReplay[] = { + 0x39, 0x06, // pushi 06 + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(200), // pushi 200d + 0x38, PATCH_UINT16(320), // pushi 320d + 0x76, // push0 + 0x76, // push0 + 0x43, 0x15, 0x0c, // callk SetPort -> set picture port to full screen + // 15 bytes + 0x39, 0x04, // pushi 04 + 0x3c, // dup + 0x76, // push0 + 0x38, PATCH_UINT16(255), // pushi 255d + 0x76, // push0 + 0x43, 0x6f, 0x08, // callk Palette -> set intensity to 0 for all colors + // 11 bytes + 0x7a, // push2 + 0x38, PATCH_UINT16(800), // pushi 800 + 0x76, // push0 + 0x43, 0x08, 0x04, // callk DrawPic -> draw picture 800 + // 8 bytes + 0x39, 0x06, // pushi 06 + 0x39, 0x0c, // pushi 0Ch + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(200), // push 200 + 0x38, PATCH_UINT16(320), // push 320 + 0x78, // push1 + 0x43, 0x6c, 0x0c, // callk Graph -> send everything to screen + // 16 bytes + 0x39, 0x06, // pushi 06 + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(156), // pushi 156d + 0x38, PATCH_UINT16(258), // pushi 258d + 0x39, 0x03, // pushi 03 + 0x39, 0x04, // pushi 04 + 0x43, 0x15, 0x0c, // callk SetPort -> set picture port back + // 17 bytes 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy) 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy) PATCH_END @@ -1856,6 +2510,9 @@ static const uint16 mothergoose256PatchReplay[] = { // when saving, it also checks if the savegame ID is below 13. // we change this to check if below 113 instead +// +// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns +// Responsible method: Game::save (script 994 for SCI1), MG::save (script 0 for SCI1.1) static const uint16 mothergoose256SignatureSaveLimit[] = { 0x89, SIG_MAGICDWORD, 0xb3, // lsg global[b3] 0x35, 0x0d, // ldi 0d @@ -1879,6 +2536,50 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = { // =========================================================================== // Police Quest 1 VGA + +// When briefing is about to start in room 15, other officers will get into the room too. +// When one of those officers gets into the way of ego, they will tell the player to sit down. +// But control will be disabled right at that point. Ego may then go to his seat by himself, +// or more often than not will just stand there. The player is unable to do anything. +// +// Sergeant Dooley will then enter the room. Tell the player to sit down 3 times and after +// that it's game over. +// +// Because the Sergeant is telling the player to sit down, one has to assume that the player +// is meant to still be in control. Which is why this script patch removes disabling of player control. +// +// The script also tries to make ego walk to the chair, but it fails because it gets stuck with other +// actors. So I guess the safest way is to remove all of that and let the player do it manually. +// +// The responsible method seems to use a few hardcoded texts, which is why I have to assume that it's +// not used anywhere else. I also checked all scripts and couldn't find any other calls to it. +// +// This of course also happens when using the original interpreter. +// +// Scripts work like this: manX::doit (script 134) triggers gab::changeState, which then triggers rm015::notify +// +// Applies to at least: English floppy +// Responsible method: gab::changeState (script 152) +// Fixes bug: #5865 +static const uint16 pq1vgaSignatureBriefingGettingStuck[] = { + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 (disable control) + 0x38, SIG_ADDTOOFFSET(+2), // pushi notify + 0x76, // push0 + 0x81, 0x02, // lag global[2] (get current room) + 0x4a, 0x04, // send 04 + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x01, // ldi 01 + 0x02, // add + SIG_END +}; + +static const uint16 pq1vgaPatchBriefingGettingStuck[] = { + 0x33, 0x0a, // jmp to lsl local[2], skip over export 2 and ::notify + PATCH_END // rm015::notify would try to make ego walk to the chair +}; + // When at the police station, you can put or get your gun from your locker. // The script, that handles this, is buggy. It disposes the gun as soon as // you click, but then waits 2 seconds before it also closes the locker. @@ -1966,10 +2667,11 @@ static const uint16 pq1vgaPatchMapSaveRestoreBug[] = { PATCH_END }; -// script, description, signature patch +// script, description, signature patch static const SciScriptPatcherEntry pq1vgaSignatures[] = { - { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug }, - { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug }, + { true, 152, "getting stuck while briefing is about to start", 1, pq1vgaSignatureBriefingGettingStuck, pq1vgaPatchBriefingGettingStuck }, + { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug }, + { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2500,7 +3202,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, @@ -2673,14 +3375,239 @@ 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. +// It also doesn't call kGameIsRestarting. This may get fixed properly at some point +// by rewriting the speed throttler. +// +// Additionally Sierra set the cycle speed of the hero to 0. Which explains +// why the actions of the hero are so incredibly fast. This issue also happened +// in the original interpreter, when the computer was too powerful. +// +// Applies to at least: English, French, German, Italian, Spanish PC floppy +// Responsible method: combatControls::dispatchEvent (script 550) + WarriorObj in heap +// Fixes bug #6247 +static const uint16 qfg3SignatureCombatSpeedThrottling1[] = { + 0x31, 0x0d, // bnt [skip code] + SIG_MAGICDWORD, + 0x89, 0xd2, // lsg global[D2h] + 0x35, 0x00, // ldi 0 + 0x1e, // gt? + 0x31, 0x06, // bnt [skip code] + 0xe1, 0xd2, // -ag global[D2h] (jump skips over this) + 0x81, 0x58, // lag global[58h] + 0xa3, 0x01, // sal local[01] + SIG_END +}; + +static const uint16 qfg3PatchCombatSpeedThrottling1[] = { + 0x80, 0xd2, // lsg global[D2h] + 0x14, // or + 0x31, 0x06, // bnt [skip code] - saves 4 bytes + 0xe1, 0xd2, // -ag global[D2h] + 0x81, 0x58, // lag global[58h] + 0xa3, 0x01, // sal local[01] (jump skips over this) + // our code + 0x76, // push0 + 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered + PATCH_END +}; + +static const uint16 qfg3SignatureCombatSpeedThrottling2[] = { + SIG_MAGICDWORD, + SIG_UINT16(12), // priority 12 + SIG_UINT16(0), // underbits 0 + SIG_UINT16(0x4010), // signal 4010h + SIG_ADDTOOFFSET(+18), + SIG_UINT16(0), // scaleSignal 0 + SIG_UINT16(128), // scaleX + SIG_UINT16(128), // scaleY + SIG_UINT16(128), // maxScale + SIG_UINT16(0), // cycleSpeed + SIG_END +}; + +static const uint16 qfg3PatchCombatSpeedThrottling2[] = { + PATCH_ADDTOOFFSET(+32), + PATCH_UINT16(5), // set cycleSpeed to 5 + 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, 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 }; @@ -2836,6 +3763,66 @@ static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = { PATCH_END }; + +// For Space Quest 4 CD, Sierra added a pick up animation for Roger, when he picks up the rope. +// +// When the player is detected by the zombie right at the start of the game, while picking up the rope, +// scripts bomb out. This also happens, when using the original interpreter. +// +// This is caused by code, that's supposed to make Roger face the arriving drone. +// We fix it, by checking if ego::cycler is actually set before calling that code. +// +// Applies to at least: English PC CD +// Responsible method: droidShoots::changeState(3) +// Fixes bug: #6076 +static const uint16 sq4CdSignatureGettingShotWhileGettingRope[] = { + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x02fa), // jmp [end] + SIG_MAGICDWORD, + 0x3c, // dup + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x0b, // bnt [state 3 check] + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x02e9), // jmp [end] + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x1e, // bnt [state 4 check] + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls again?? + 0x7a, // push2 + 0x89, 0x00, // lsg global[0] + 0x72, SIG_UINT16(0x0242), // lofsa deathDroid + 0x36, // push + 0x45, 0x0d, 0x04, // call export 13 of script 0 -> set heading of ego to face droid + SIG_END +}; + +static const uint16 sq4CdPatchGettingShotWhileGettingRope[] = { + PATCH_ADDTOOFFSET(+11), + // this makes state 2 only do the 2 cycles wait, controls should always be disabled already at this point + 0x2f, 0xf3, // bt [previous state aTop cycles code] + // Now we check for state 3, this change saves us 11 bytes + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x29, // bnt [state 4 check] + // new state 3 code + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 (disable controls, actually not needed) + 0x38, PATCH_SELECTOR16(cycler), // pushi cycler + 0x76, // push0 + 0x81, 0x00, // lag global[0] + 0x4a, 0x04, // send 04 (get ego::cycler) + 0x30, PATCH_UINT16(10), // bnt [jump over heading call] + PATCH_END +}; + // The scripts in SQ4CD support simultaneous playing of speech and subtitles, // but this was not available as an option. The following two patches enable // this functionality in the game's GUI options dialog. @@ -2932,6 +3919,7 @@ static const SciScriptPatcherEntry sq4Signatures[] = { { true, 700, "Floppy: throw stuff at sequel police bug", 1, sq4FloppySignatureThrowStuffAtSequelPoliceBug, sq4FloppyPatchThrowStuffAtSequelPoliceBug }, { true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 }, { true, 396, "CD: get points for changing back clothes fix",1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes }, + { true, 701, "CD: getting shot, while getting rope", 1, sq4CdSignatureGettingShotWhileGettingRope, sq4CdPatchGettingShotWhileGettingRope }, { true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon }, { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions }, { true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton }, @@ -3050,24 +4038,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] @@ -3092,8 +4091,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? @@ -3374,20 +4373,20 @@ bool ScriptPatcher::verifySignature(uint32 byteOffset, const uint16 *signatureDa } // will return -1 if no match was found, otherwise an offset to the start of the signature match -int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) { +int32 ScriptPatcher::findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize) { if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay return -1; - const uint32 magicDWord = runtimeEntry->magicDWord; // is platform-specific BE/LE form, so that the later match will work + // magicDWord is in platform-specific BE/LE form, so that the later match will work, this was done for performance const uint32 searchLimit = scriptSize - 3; uint32 DWordOffset = 0; // first search for the magic DWORD while (DWordOffset < searchLimit) { if (magicDWord == READ_UINT32(scriptData + DWordOffset)) { // magic DWORD found, check if actual signature matches - uint32 offset = DWordOffset + runtimeEntry->magicOffset; + uint32 offset = DWordOffset + magicOffset; - if (verifySignature(offset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize)) + if (verifySignature(offset, signatureData, patchDescription, scriptData, scriptSize)) return offset; } DWordOffset++; @@ -3396,22 +4395,147 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciS return -1; } -// This method calculates the magic DWORD for each entry in the signature table -// and it also initializes the selector table for selectors used in the signatures/patches of the current game -void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { - const SciScriptPatcherEntry *curEntry = patchTable; - SciScriptPatcherRuntimeEntry *curRuntimeEntry; +int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) { + return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize); +} + +// 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 step; int magicOffset; byte magicDWord[4]; int magicDWordLeft = 0; - const uint16 *curData; uint16 curWord; uint16 curCommand; uint32 curValue; byte byte1 = 0; byte byte2 = 0; + + memset(magicDWord, 0, sizeof(magicDWord)); + + curWord = *signatureData; + magicOffset = 0; + while (curWord != SIG_END) { + curCommand = curWord & SIG_COMMANDMASK; + curValue = curWord & SIG_VALUEMASK; + switch (curCommand) { + case SIG_MAGICDWORD: { + if (magicDWordIncluded) { + if ((calculatedMagicDWord) || (magicDWordLeft)) + error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", signatureDescription); + magicDWordLeft = 4; + calculatedMagicDWordOffset = magicOffset; + } else { + error("Script-Patcher: Magic-DWORD sequence found in patch data\nFaulty patch: '%s'", signatureDescription); + } + break; + } + case SIG_CODE_ADDTOOFFSET: { + magicOffset -= curValue; + if (magicDWordLeft) + error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", signatureDescription); + break; + } + case SIG_CODE_UINT16: + case SIG_CODE_SELECTOR16: { + // UINT16 or 1 + switch (curCommand) { + case SIG_CODE_UINT16: { + signatureData++; curWord = *signatureData; + if (curWord & SIG_COMMANDMASK) + error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", signatureDescription); + if (!_isMacSci11) { + byte1 = curValue; + byte2 = curWord & SIG_BYTEMASK; + } else { + byte1 = curWord & SIG_BYTEMASK; + byte2 = curValue; + } + break; + } + case SIG_CODE_SELECTOR16: { + curSelector = _selectorIdTable[curValue]; + if (curSelector == -1) { + curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); + _selectorIdTable[curValue] = curSelector; + } + if (!_isMacSci11) { + byte1 = curSelector & 0x00FF; + byte2 = curSelector >> 8; + } else { + byte1 = curSelector >> 8; + byte2 = curSelector & 0x00FF; + } + break; + } + } + magicOffset -= 2; + if (magicDWordLeft) { + // Remember current word for Magic DWORD + magicDWord[4 - magicDWordLeft] = byte1; + magicDWordLeft--; + if (magicDWordLeft) { + magicDWord[4 - magicDWordLeft] = byte2; + magicDWordLeft--; + } + if (!magicDWordLeft) { + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_UINT32(magicDWord); + } + } + break; + } + case SIG_CODE_BYTE: + case SIG_CODE_SELECTOR8: { + if (curCommand == SIG_CODE_SELECTOR8) { + curSelector = _selectorIdTable[curValue]; + if (curSelector == -1) { + curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); + _selectorIdTable[curValue] = curSelector; + if (curSelector != -1) { + if (curSelector & 0xFF00) + error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", signatureDescription); + } + } + curValue = curSelector; + } + magicOffset--; + if (magicDWordLeft) { + // Remember current byte for Magic DWORD + magicDWord[4 - magicDWordLeft] = (byte)curValue; + magicDWordLeft--; + if (!magicDWordLeft) { + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_UINT32(magicDWord); + } + } + break; + } + case PATCH_CODE_GETORIGINALBYTEADJUST: { + signatureData++; // skip over extra uint16 + break; + } + default: + break; + } + signatureData++; + curWord = *signatureData; + } + + if (magicDWordLeft) + error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", signatureDescription); + if (magicDWordIncluded) { + if (!calculatedMagicDWord) { + error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", signatureDescription); + } + } +} + +// This method calculates the magic DWORD for each entry in the signature table +// and it also initializes the selector table for selectors used in the signatures/patches of the current game +void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { + const SciScriptPatcherEntry *curEntry = patchTable; + SciScriptPatcherRuntimeEntry *curRuntimeEntry; int patchEntryCount = 0; // Count entries and allocate runtime data @@ -3425,120 +4549,14 @@ void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { curRuntimeEntry = _runtimeTable; while (curEntry->signatureData) { // process signature - memset(magicDWord, 0, sizeof(magicDWord)); - curRuntimeEntry->active = curEntry->defaultActive; curRuntimeEntry->magicDWord = 0; curRuntimeEntry->magicOffset = 0; - for (step = 0; step < 2; step++) { - switch (step) { - case 0: curData = curEntry->signatureData; break; - case 1: curData = curEntry->patchData; break; - } - - curWord = *curData; - magicOffset = 0; - while (curWord != SIG_END) { - curCommand = curWord & SIG_COMMANDMASK; - curValue = curWord & SIG_VALUEMASK; - switch (curCommand) { - case SIG_MAGICDWORD: { - if (step == 0) { - if ((curRuntimeEntry->magicDWord) || (magicDWordLeft)) - error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", curEntry->description); - magicDWordLeft = 4; - curRuntimeEntry->magicOffset = magicOffset; - } - break; - } - case SIG_CODE_ADDTOOFFSET: { - magicOffset -= curValue; - if (magicDWordLeft) - error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", curEntry->description); - break; - } - case SIG_CODE_UINT16: - case SIG_CODE_SELECTOR16: { - // UINT16 or 1 - switch (curCommand) { - case SIG_CODE_UINT16: { - curData++; curWord = *curData; - if (curWord & SIG_COMMANDMASK) - error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", curEntry->description); - if (!_isMacSci11) { - byte1 = curValue; - byte2 = curWord & SIG_BYTEMASK; - } else { - byte1 = curWord & SIG_BYTEMASK; - byte2 = curValue; - } - break; - } - case SIG_CODE_SELECTOR16: { - curSelector = _selectorIdTable[curValue]; - if (curSelector == -1) { - curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); - _selectorIdTable[curValue] = curSelector; - } - if (!_isMacSci11) { - byte1 = curSelector & 0x00FF; - byte2 = curSelector >> 8; - } else { - byte1 = curSelector >> 8; - byte2 = curSelector & 0x00FF; - } - break; - } - } - magicOffset -= 2; - if (magicDWordLeft) { - // Remember current word for Magic DWORD - magicDWord[4 - magicDWordLeft] = byte1; - magicDWordLeft--; - if (magicDWordLeft) { - magicDWord[4 - magicDWordLeft] = byte2; - magicDWordLeft--; - } - if (!magicDWordLeft) { - curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord); - } - } - break; - } - case SIG_CODE_BYTE: - case SIG_CODE_SELECTOR8: { - if (curCommand == SIG_CODE_SELECTOR8) { - curSelector = _selectorIdTable[curValue]; - if (curSelector == -1) { - curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); - _selectorIdTable[curValue] = curSelector; - if (curSelector != -1) { - if (curSelector & 0xFF00) - error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", curEntry->description); - } - } - curValue = curSelector; - } - magicOffset--; - if (magicDWordLeft) { - // Remember current byte for Magic DWORD - magicDWord[4 - magicDWordLeft] = (byte)curValue; - magicDWordLeft--; - if (!magicDWordLeft) { - curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord); - } - } - } - } - curData++; - curWord = *curData; - } - } - if (magicDWordLeft) - error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", curEntry->description); - if (!curRuntimeEntry->magicDWord) - error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", curEntry->description); + // We verify the signature data and remember the calculated magic DWord from the signature data + calculateMagicDWordAndVerify(curEntry->description, curEntry->signatureData, true, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset); + // We verify the patch data + calculateMagicDWordAndVerify(curEntry->description, curEntry->patchData, false, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset); curEntry++; curRuntimeEntry++; } @@ -3596,6 +4614,12 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_KQ6: signatureTable = kq6Signatures; break; + case GID_KQ7: + signatureTable = kq7Signatures; + break; + case GID_LAURABOW: + signatureTable = laurabow1Signatures; + break; case GID_LAURABOW2: signatureTable = laurabow2Signatures; break; diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h index d15fce321b..645e0946b3 100644 --- a/engines/sci/engine/script_patches.h +++ b/engines/sci/engine/script_patches.h @@ -90,13 +90,32 @@ public: ScriptPatcher(); ~ScriptPatcher(); + // Calculates the magic DWord for fast search and verifies signature/patch data + // Returns the magic DWord in platform-specific byte-order. This is done on purpose for performance. + void calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset); + + // Called when a script is loaded to check for signature matches and apply patches in such cases void processScript(uint16 scriptNr, byte *scriptData, const uint32 scriptSize); + + // Verifies, if a given signature matches the given script data (pointed to by additional byte offset) bool verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const byte *scriptData, const uint32 scriptSize); + // searches for a given signature inside script data + // returns -1 in case it was not found or an offset to the matching data + int32 findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize); + private: + // Initializes a patch table and creates run time information for it (for enabling/disabling), also calculates magic DWORD) void initSignature(const SciScriptPatcherEntry *patchTable); + + // Enables a patch inside the patch table (used for optional patches like CD+Text support for KQ6 & LB2) void enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription); - int32 findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize); + + // Searches for a given signature entry inside script data + // returns -1 in case it was not found or an offset to the matching data + int32 findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize); + + // Applies a patch to a given script + offset (overwrites parts) void applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset); Selector *_selectorIdTable; diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index f0157a6569..b017e62df7 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -499,7 +499,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 +510,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"); @@ -741,13 +741,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 58c2b8d3e3..95e3cd15f9 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -144,11 +144,21 @@ Script *SegManager::allocateScript(int script_nr, SegmentId *segid) { return (Script *)mem; } +SegmentId SegManager::getActualSegment(SegmentId seg) const { + if (getSciVersion() <= SCI_VERSION_2_1_LATE) { + return seg; + } else { + // Return the lower 14 bits of the segment + return (seg & 0x3FFF); + } +} + void SegManager::deallocate(SegmentId seg) { - if (seg < 1 || (uint)seg >= _heap.size()) + SegmentId actualSegment = getActualSegment(seg); + if (actualSegment < 1 || (uint)actualSegment >= _heap.size()) error("Attempt to deallocate an invalid segment ID"); - SegmentObj *mobj = _heap[seg]; + SegmentObj *mobj = _heap[actualSegment]; if (!mobj) error("Attempt to deallocate an already freed segment"); @@ -169,7 +179,7 @@ void SegManager::deallocate(SegmentId seg) { } delete mobj; - _heap[seg] = NULL; + _heap[actualSegment] = NULL; } bool SegManager::isHeapObject(reg_t pos) const { @@ -185,22 +195,24 @@ void SegManager::deallocateScript(int script_nr) { } Script *SegManager::getScript(const SegmentId seg) { - if (seg < 1 || (uint)seg >= _heap.size()) { - error("SegManager::getScript(): seg id %x out of bounds", seg); + SegmentId actualSegment = getActualSegment(seg); + if (actualSegment < 1 || (uint)actualSegment >= _heap.size()) { + error("SegManager::getScript(): seg id %x out of bounds", actualSegment); } - if (!_heap[seg]) { - error("SegManager::getScript(): seg id %x is not in memory", seg); + if (!_heap[actualSegment]) { + error("SegManager::getScript(): seg id %x is not in memory", actualSegment); } - if (_heap[seg]->getType() != SEG_TYPE_SCRIPT) { - error("SegManager::getScript(): seg id %x refers to type %d != SEG_TYPE_SCRIPT", seg, _heap[seg]->getType()); + if (_heap[actualSegment]->getType() != SEG_TYPE_SCRIPT) { + error("SegManager::getScript(): seg id %x refers to type %d != SEG_TYPE_SCRIPT", actualSegment, _heap[actualSegment]->getType()); } - return (Script *)_heap[seg]; + return (Script *)_heap[actualSegment]; } Script *SegManager::getScriptIfLoaded(const SegmentId seg) const { - if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg] || _heap[seg]->getType() != SEG_TYPE_SCRIPT) + SegmentId actualSegment = getActualSegment(seg); + if (actualSegment < 1 || (uint)actualSegment >= _heap.size() || !_heap[actualSegment] || _heap[actualSegment]->getType() != SEG_TYPE_SCRIPT) return 0; - return (Script *)_heap[seg]; + return (Script *)_heap[actualSegment]; } SegmentId SegManager::findSegmentByType(int type) const { @@ -211,19 +223,22 @@ SegmentId SegManager::findSegmentByType(int type) const { } SegmentObj *SegManager::getSegmentObj(SegmentId seg) const { - if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg]) + SegmentId actualSegment = getActualSegment(seg); + if (actualSegment < 1 || (uint)actualSegment >= _heap.size() || !_heap[actualSegment]) return 0; - return _heap[seg]; + return _heap[actualSegment]; } SegmentType SegManager::getSegmentType(SegmentId seg) const { - if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg]) + SegmentId actualSegment = getActualSegment(seg); + if (actualSegment < 1 || (uint)actualSegment >= _heap.size() || !_heap[actualSegment]) return SEG_TYPE_INVALID; - return _heap[seg]->getType(); + return _heap[actualSegment]->getType(); } SegmentObj *SegManager::getSegment(SegmentId seg, SegmentType type) const { - return getSegmentType(seg) == type ? _heap[seg] : NULL; + SegmentId actualSegment = getActualSegment(seg); + return getSegmentType(actualSegment) == type ? _heap[actualSegment] : NULL; } Object *SegManager::getObject(reg_t pos) const { @@ -232,9 +247,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) { @@ -298,7 +313,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; @@ -389,7 +404,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; @@ -409,7 +424,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) { @@ -424,7 +439,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) { @@ -438,7 +453,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) { @@ -452,7 +467,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) { @@ -471,14 +486,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) { @@ -492,9 +507,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; @@ -502,7 +517,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) { @@ -822,10 +837,13 @@ byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) { d._size = size; - if (size == 0) + // Original SCI only zeroed out heap memory on initialize + // They didn't do it again for every allocation + if (size) { + d._buf = (byte *)calloc(size, 1); + } else { d._buf = NULL; - else - d._buf = (byte *)malloc(size); + } d._description = descr; @@ -855,32 +873,32 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_arraysSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } SciArray<reg_t> *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[addr.getOffset()].destroy(); + arrayTable.freeEntry(addr.getOffset()); } SciString *SegManager::allocateString(reg_t *addr) { @@ -895,32 +913,32 @@ SciString *SegManager::allocateString(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_stringSegId, offset); - return &(table->_table[offset]); + return &table->at(offset); } 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)); - StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; + StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.getOffset())) + if (!stringTable.isValidEntry(addr.getOffset())) error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - return &(stringTable->_table[addr.getOffset()]); + return &(stringTable[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)); - StringTable *stringTable = (StringTable *)_heap[addr.getSegment()]; + StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()]; - if (!stringTable->isValidEntry(addr.getOffset())) + if (!stringTable.isValidEntry(addr.getOffset())) error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - stringTable->_table[addr.getOffset()].destroy(); - stringTable->freeEntry(addr.getOffset()); + stringTable[addr.getOffset()].destroy(); + stringTable.freeEntry(addr.getOffset()); } #endif diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index 2d6e624f6f..9da7e58770 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -472,6 +472,18 @@ private: void createClassTable(); SegmentId findFreeSegment() const; + + /** + * This implements our handling of scripts greater than 64K in size. + * They occur sporadically in SCI3 games (and in The Realm (SCI2.1), + * if we ever decide to support it). It works by "stealing" the upper + * two bits of the segment value to use as extra offset bits, making + * the maximum offset 0x3FFFF (262143). This is enough. + * + * The "actual" segment, then, is the segment value with the upper two + * bits masked out. + */ + SegmentId getActualSegment(SegmentId seg) const; }; } // End of namespace Sci diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index bb90698e6a..2cff799f4b 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -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 @@ -252,13 +252,13 @@ 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(); + ret.maxSize = at(pointer.getOffset()).getSize() * 2; + ret.reg = at(pointer.getOffset()).getRawData(); return ret; } void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.getOffset()].destroy(); + at(sub_addr.getOffset()).destroy(); freeEntry(sub_addr.getOffset()); } @@ -268,7 +268,7 @@ Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const { error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); } - const SciArray<reg_t> *array = &(_table[addr.getOffset()]); + const SciArray<reg_t> *array = &at(addr.getOffset()); for (uint32 i = 0; i < array->getSize(); i++) { reg_t value = array->getValue(i); @@ -305,8 +305,8 @@ void SciString::fromString(const Common::String &string) { SegmentRef StringTable::dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = true; - ret.maxSize = _table[pointer.getOffset()].getSize(); - ret.raw = (byte *)_table[pointer.getOffset()].getRawData(); + ret.maxSize = at(pointer.getOffset()).getSize(); + ret.raw = (byte *)at(pointer.getOffset()).getRawData(); return ret; } diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index de7f60ac16..50c77d0538 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -203,23 +203,24 @@ struct List { 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) { @@ -272,6 +273,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,8 +332,8 @@ 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) { @@ -502,7 +511,7 @@ struct StringTable : public SegmentObjTable<SciString> { StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {} virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - _table[sub_addr.getOffset()].destroy(); + at(sub_addr.getOffset()).destroy(); freeEntry(sub_addr.getOffset()); } diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 910f1f885f..ac621f58ae 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -179,6 +179,8 @@ void Kernel::mapSelectors() { FIND_SELECTOR(fore); FIND_SELECTOR(back); FIND_SELECTOR(skip); + FIND_SELECTOR(borderColor); + FIND_SELECTOR(width); FIND_SELECTOR(fixPriority); FIND_SELECTOR(mirrored); FIND_SELECTOR(visible); @@ -187,6 +189,17 @@ void Kernel::mapSelectors() { FIND_SELECTOR(inLeft); FIND_SELECTOR(inBottom); FIND_SELECTOR(inRight); + FIND_SELECTOR(textTop); + 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 } @@ -199,6 +212,16 @@ reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId) { return *address.getPointer(segMan); } +#ifdef ENABLE_SCI32 +void updateInfoFlagViewVisible(Object *obj, int index) { + // TODO: Make this correct for all SCI versions + // Selectors 26 through 44 are selectors for View script objects in SQ6 + if (index >= 26 && index <= 44 && getSciVersion() >= SCI_VERSION_2) { + obj->setInfoSelectorFlag(kInfoFlagViewVisible); + } +} +#endif + void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t value) { ObjVarRef address; @@ -211,8 +234,12 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t 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 + else { *address.getPointer(segMan) = value; +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(segMan->getObject(object), selectorId); +#endif + } } void invokeSelector(EngineState *s, reg_t object, int selectorId, diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index b3dd393708..f2d06d1cf4 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -146,6 +146,8 @@ struct SelectorCache { Selector back; Selector skip; Selector dimmed; + Selector borderColor; + Selector width; Selector fixPriority; Selector mirrored; @@ -153,6 +155,12 @@ struct SelectorCache { 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 }; @@ -191,6 +199,16 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t void invokeSelector(EngineState *s, reg_t object, int selectorId, int k_argc, StackPtr k_argp, int argc = 0, const reg_t *argv = 0); +#ifdef ENABLE_SCI32 +/** + * SCI32 set kInfoFlagViewVisible in the -info- selector if a certain + * range of properties was written to. + * 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 index); +#endif + } // End of namespace Sci #endif // SCI_ENGINE_KERNEL_H diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 417d98e2e2..2c85907628 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; @@ -129,8 +124,6 @@ void EngineState::reset(bool isRestoring) { _vmdPalStart = 0; _vmdPalEnd = 256; - - _palCycleToColor = 255; } void EngineState::speedThrottler(uint32 neededSleep) { diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index e7499e66c9..dd8d76f002 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -57,6 +57,10 @@ 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 { @@ -127,17 +131,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; @@ -199,12 +201,11 @@ public: uint16 _memorySegmentSize; byte _memorySegment[kMemorySegmentMax]; + // TODO: Excise video code from the state manager VideoState _videoState; uint16 _vmdPalStart, _vmdPalEnd; bool _syncedAudioOptions; - uint16 _palCycleToColor; - /** * Resets the engine state. */ diff --git a/engines/sci/engine/static_selectors.cpp b/engines/sci/engine/static_selectors.cpp index 188da3d5a2..8aa1697f07 100644 --- a/engines/sci/engine/static_selectors.cpp +++ b/engines/sci/engine/static_selectors.cpp @@ -114,16 +114,16 @@ static const SelectorRemap sciSelectorRemap[] = { { SCI_VERSION_1_1, SCI_VERSION_1_1, "cantBeHere", 54 }, // The following are not really needed. They've only been defined to // ease game debugging. - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-objID-", 4096 }, - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-size-", 4097 }, - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-propDict-", 4098 }, - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-methDict-", 4099 }, - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-classScript-", 4100 }, - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-script-", 4101 }, - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-super-", 4102 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-objID-", 4096 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-size-", 4097 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-propDict-", 4098 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-methDict-", 4099 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-classScript-", 4100 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-script-", 4101 }, + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-super-", 4102 }, // - { SCI_VERSION_1_1, SCI_VERSION_2_1, "-info-", 4103 }, - { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, 0 } + { SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-info-", 4103 }, + { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, 0 } }; struct ClassReference { diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 6f02c96de8..3e12084ed6 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -239,8 +239,9 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP // 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()); @@ -258,6 +259,10 @@ static void _exec_varselectors(EngineState *s) { if (xs.argc) { // write? *var = xs.variables_argp[1]; +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(s->_segMan->getObject(xs.addr.varp.obj), xs.addr.varp.varindex); +#endif + } else // No, read s->r_acc = *var; } @@ -308,8 +313,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) @@ -331,12 +337,12 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt 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); } @@ -382,7 +388,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) @@ -440,7 +447,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) @@ -830,14 +838,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); @@ -974,21 +983,24 @@ void run_vm(EngineState *s) { break; - case 0x26: // (38) - case 0x27: // (39) - if (getSciVersion() == SCI_VERSION_3) { - if (extOpcode == 0x4c) - s->r_acc = obj->getInfoSelector(); - else if (extOpcode == 0x4d) - PUSH32(obj->getInfoSelector()); - else if (extOpcode == 0x4e) - s->r_acc = obj->getSuperClassSelector(); // TODO: is this correct? - // TODO: There are also opcodes in - // here to get the superclass, and possibly the species too. - else - error("Dummy opcode 0x%x called", opcode); // should never happen - } else + case op_infoToa: // (38) + if (getSciVersion() < SCI_VERSION_3) + error("Dummy opcode 0x%x called", opcode); // should never happen + + if (!(extOpcode & 1)) + s->r_acc = obj->getInfoSelector(); + else + PUSH32(obj->getInfoSelector()); + break; + + case op_superToa: // (39) + if (getSciVersion() < SCI_VERSION_3) error("Dummy opcode 0x%x called", opcode); // should never happen + + if (!(extOpcode & 1)) + s->r_acc = obj->getSuperClassSelector(); + else + PUSH32(obj->getSuperClassSelector()); break; case op_class: // 0x28 (40) @@ -1092,6 +1104,9 @@ void run_vm(EngineState *s) { case op_aTop: // 0x32 (50) // Accumulator To Property validate_property(s, obj, opparams[0]) = s->r_acc; +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(obj, opparams[0]>>1); +#endif break; case op_pTos: // 0x33 (51) @@ -1102,6 +1117,9 @@ void run_vm(EngineState *s) { case op_sTop: // 0x34 (52) // Stack To Property validate_property(s, obj, opparams[0]) = POP32(); +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(obj, opparams[0]>>1); +#endif break; case op_ipToa: // 0x35 (53) @@ -1116,7 +1134,9 @@ void run_vm(EngineState *s) { opProperty += 1; else opProperty -= 1; - +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(obj, opparams[0]>>1); +#endif if (opcode == op_ipToa || opcode == op_dpToa) s->r_acc = opProperty; else diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index cf65803929..c41060dc32 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -93,16 +93,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 +115,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_; @@ -176,8 +180,8 @@ enum SciOpcodes { op_calle = 0x23, // 035 op_ret = 0x24, // 036 op_send = 0x25, // 037 - // dummy 0x26, // 038 - // dummy 0x27, // 039 + op_infoToa = 0x26, // 038 + op_superToa = 0x27, // 039 op_class = 0x28, // 040 // dummy 0x29, // 041 op_self = 0x2a, // 042 diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index 65a82832cc..d74e2b194c 100644 --- a/engines/sci/engine/vm_types.cpp +++ b/engines/sci/engine/vm_types.cpp @@ -29,7 +29,7 @@ namespace Sci { SegmentId reg_t::getSegment() const { - if (getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() < SCI_VERSION_3) { return _segment; } else { // Return the lower 14 bits of the segment @@ -38,7 +38,7 @@ SegmentId reg_t::getSegment() const { } void reg_t::setSegment(SegmentId segment) { - if (getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() < SCI_VERSION_3) { _segment = segment; } else { // Set the lower 14 bits of the segment, and preserve the upper 2 ones for the offset @@ -47,7 +47,7 @@ void reg_t::setSegment(SegmentId segment) { } uint32 reg_t::getOffset() const { - if (getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() < SCI_VERSION_3) { return _offset; } else { // Return the lower 16 bits from the offset, and the 17th and 18th bits from the segment @@ -56,7 +56,7 @@ uint32 reg_t::getOffset() const { } void reg_t::setOffset(uint32 offset) { - if (getSciVersion() <= SCI_VERSION_2_1) { + if (getSciVersion() < SCI_VERSION_3) { _offset = offset; } else { // Store the lower 16 bits in the offset, and the 17th and 18th bits in the segment @@ -210,12 +210,30 @@ reg_t reg_t::operator^(const reg_t right) const { return lookForWorkaround(right, "bitwise XOR"); } +#ifdef ENABLE_SCI32 +reg_t reg_t::operator&(int16 right) const { + return *this & make_reg(0, right); +} + +reg_t reg_t::operator|(int16 right) const { + return *this | make_reg(0, right); +} + +reg_t reg_t::operator^(int16 right) const { + return *this ^ make_reg(0, right); +} +#endif + int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { if (getSegment() == right.getSegment()) { // can compare things in the same segment if (treatAsUnsigned || !isNumber()) 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)) { @@ -224,6 +242,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 af78bd0b84..e60f52e85c 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -136,6 +136,19 @@ struct reg_t { reg_t operator|(const reg_t right) const; reg_t operator^(const reg_t right) const; +#ifdef ENABLE_SCI32 + reg_t operator&(int16 right) const; + reg_t operator|(int16 right) const; + reg_t operator^(int16 right) const; + + void operator&=(const reg_t &right) { *this = *this & right; } + void operator|=(const reg_t &right) { *this = *this | right; } + void operator^=(const reg_t &right) { *this = *this ^ right; } + void operator&=(int16 right) { *this = *this & right; } + void operator|=(int16 right) { *this = *this | right; } + void operator^=(int16 right) { *this = *this ^ right; } +#endif + private: /** * Compares two reg_t's. @@ -147,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..f304f774af 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -298,6 +298,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 @@ -378,6 +379,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { 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_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 SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -406,6 +408,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 }; @@ -512,6 +515,14 @@ 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 + 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 @@ -630,7 +641,7 @@ 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 @@ -656,6 +667,12 @@ 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 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 @@ -664,7 +681,7 @@ const SciWorkaroundEntry kReadNumber_workarounds[] = { // 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 }; @@ -737,6 +754,12 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = { SCI_WORKAROUNDENTRY_TERMINATOR }; +// 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 +}; + + SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) { // HACK for SCI3: Temporarily ignore this if (getSciVersion() == SCI_VERSION_3) { @@ -758,7 +781,7 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun Common::List<ExecStack>::const_iterator callIterator = state->_executionStack.end(); while (callIterator != state->_executionStack.begin()) { callIterator--; - ExecStack loopCall = *callIterator; + const ExecStack &loopCall = *callIterator; if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) { lastCall->debugSelector = loopCall.debugSelector; lastCall->debugExportId = loopCall.debugExportId; diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 46059a175c..248d37fc6c 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -74,6 +74,7 @@ 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 kFindKey_workarounds[]; extern const SciWorkaroundEntry kDeleteKey_workarounds[]; @@ -89,6 +90,7 @@ extern const SciWorkaroundEntry kIsObject_workarounds[]; extern const SciWorkaroundEntry kMemory_workarounds[]; extern const SciWorkaroundEntry kMoveCursor_workarounds[]; extern const SciWorkaroundEntry kNewWindow_workarounds[]; +extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[]; extern const SciWorkaroundEntry kReadNumber_workarounds[]; extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[]; extern const SciWorkaroundEntry kSetCursor_workarounds[]; @@ -97,6 +99,7 @@ extern const SciWorkaroundEntry kStrAt_workarounds[]; extern const SciWorkaroundEntry kStrCpy_workarounds[]; extern const SciWorkaroundEntry kStrLen_workarounds[]; extern const SciWorkaroundEntry kUnLoad_workarounds[]; +extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[]; extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin); |