diff options
Diffstat (limited to 'engines/sci/engine')
-rw-r--r-- | engines/sci/engine/script.cpp | 332 | ||||
-rw-r--r-- | engines/sci/engine/script.h | 33 | ||||
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 109 | ||||
-rw-r--r-- | engines/sci/engine/workarounds.cpp | 4 |
4 files changed, 389 insertions, 89 deletions
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 69f52495e0..6034378ef6 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -65,7 +65,11 @@ void Script::freeScript() { _lockers = 1; _markedAsDeleted = false; _objects.clear(); - _stringLookupList.clear(); + + _offsetLookupArray.clear(); + _offsetLookupObjectCount = 0; + _offsetLookupStringCount = 0; + _offsetLookupSaidCount = 0; } void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptPatcher) { @@ -208,39 +212,45 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP } // find all strings of this script - identifyStrings(); + identifyOffsets(); } -void Script::identifyStrings() { - stringLookupListEntry listEntry; - byte *scriptDataPtr = NULL; - byte *stringStartPtr = NULL; - byte *stringDataPtr = NULL; - int scriptDataLeft = 0; - int stringDataLeft = 0; +void Script::identifyOffsets() { + offsetLookupArrayEntry arrayEntry; + const byte *scriptDataPtr = NULL; + const byte *stringStartPtr = NULL; + const byte *stringDataPtr = NULL; + uint32 scriptDataLeft = 0; + uint32 stringDataLeft = 0; byte stringDataByte = 0; + uint16 typeObject_id = 0; + uint16 typeString_id = 0; + uint16 typeSaid_id = 0; + + uint16 blockType = 0; + uint16 blockSize = 0; - _stringLookupList.clear(); - //stringLookupListType _stringLookupList; // Table of string data, that is inside the currently loaded script + _offsetLookupArray.clear(); + _offsetLookupObjectCount = 0; + _offsetLookupStringCount = 0; + _offsetLookupSaidCount = 0; if (getSciVersion() < SCI_VERSION_1_1) { + // SCI0 + SCI1 scriptDataPtr = _buf; scriptDataLeft = _bufSize; - // Go through all SCI_OBJ_STRINGS blocks + // Go through all blocks if (getSciVersion() == SCI_VERSION_0_EARLY) { if (scriptDataLeft < 2) - error("Script::identifyStrings(): unexpected end of script"); + error("Script::identifyOffsets(): unexpected end of script %d", _nr); scriptDataPtr += 2; scriptDataLeft -= 2; } - - uint16 blockType; - uint16 blockSize; do { if (scriptDataLeft < 2) - error("Script::identifyStrings(): unexpected end of script"); + error("Script::identifyOffsets(): unexpected end of script %d", _nr); blockType = READ_LE_UINT16(scriptDataPtr); scriptDataPtr += 2; @@ -249,29 +259,50 @@ void Script::identifyStrings() { break; if (scriptDataLeft < 2) - error("Script::identifyStrings(): unexpected end of script"); + error("Script::identifyOffsets(): unexpected end of script %d", _nr); blockSize = READ_LE_UINT16(scriptDataPtr); if (blockSize < 4) - error("Script::identifyStrings(): invalid block size"); + error("Script::identifyOffsets(): invalid block size in script %d", _nr); blockSize -= 4; // block size includes block-type UINT16 and block-size UINT16 scriptDataPtr += 2; scriptDataLeft -= 2; if (scriptDataLeft < blockSize) - error("Script::identifyStrings(): invalid block size"); - - if (blockType == SCI_OBJ_STRINGS) { + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + + switch (blockType) { + case SCI_OBJ_OBJECT: + case SCI_OBJ_CLASS: + typeObject_id++; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT; + arrayEntry.id = typeObject_id; + arrayEntry.offset = scriptDataPtr - _buf + 8; // Calculate offset inside script data (VM uses +8) + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupObjectCount++; + break; + + case SCI_OBJ_STRINGS: // string block detected, we now grab all NUL terminated strings out of this block stringDataPtr = scriptDataPtr; stringDataLeft = blockSize; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING; + do { if (stringDataLeft < 1) // no more bytes left break; stringStartPtr = stringDataPtr; - listEntry.ptrOffset = stringStartPtr - _buf; // Calculate offset inside script data + + if (stringDataLeft == 1) { + // only 1 byte left and that byte is a [00], in that case we also exit + stringDataByte = *stringStartPtr; + if (stringDataByte == 0x00) + break; + } + // now look for terminating [NUL] do { stringDataByte = *stringDataPtr; @@ -279,13 +310,77 @@ void Script::identifyStrings() { stringDataLeft--; if (!stringDataByte) // NUL found, exit this loop break; + if (stringDataLeft < 1) { + // no more bytes left + warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr); + break; + } + } while (1); + + if (stringDataByte) + break; + + typeString_id++; + arrayEntry.id = typeString_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = stringDataPtr - stringStartPtr; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupStringCount++; + } while (1); + break; + + case SCI_OBJ_SAID: + // said block detected, we now try to find every single said "string" inside this block + // said strings are terminated with a 0xFF, the string itself may contain words (2 bytes), where + // the second byte of a word may also be a 0xFF. + stringDataPtr = scriptDataPtr; + stringDataLeft = blockSize; + + arrayEntry.type = SCI_SCR_OFFSET_TYPE_SAID; + + do { + if (stringDataLeft < 1) // no more bytes left + break; + + stringStartPtr = stringDataPtr; + if (stringDataLeft == 1) { + // only 1 byte left and that byte is a [00], in that case we also exit + // happens in some scripts, for example Conquests of Camelot, script 997 + // may have been a bug in the compiler or just an intentional filler byte + stringDataByte = *stringStartPtr; + if (stringDataByte == 0x00) + break; + } + + // now look for terminating 0xFF + do { + stringDataByte = *stringDataPtr; + stringDataPtr++; + stringDataLeft--; + if (stringDataByte == 0xFF) // Terminator found, exit this loop + break; if (stringDataLeft < 1) // no more bytes left - error("Script::identifyStrings(): string without terminating NUL"); + error("Script::identifyOffsets(): said-string without terminator in script %d", _nr); + if (stringDataByte < 0xF0) { + // Part of a word, skip second byte + stringDataPtr++; + stringDataLeft--; + if (stringDataLeft < 1) // no more bytes left + error("Script::identifyOffsets(): said-string without terminator in script %d", _nr); + } } while (1); - listEntry.stringSize = stringDataPtr - stringStartPtr; - _stringLookupList.push_back(listEntry); + typeSaid_id++; + arrayEntry.id = typeSaid_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupSaidCount++; } while (1); + break; + + default: + break; } scriptDataPtr += blockSize; @@ -293,33 +388,30 @@ void Script::identifyStrings() { } while (1); } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { - // Strings in SCI1.1 come after the object instances + // Strings in SCI1.1 up to SCI2 come after the object instances scriptDataPtr = _heapStart; scriptDataLeft = _heapSize; if (scriptDataLeft < 4) - error("Script::identifyStrings(): unexpected end of script"); + error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); uint16 endOfStringOffset = READ_SCI11ENDIAN_UINT16(scriptDataPtr); uint16 objectStartOffset = READ_SCI11ENDIAN_UINT16(scriptDataPtr + 2) * 2 + 4; if (scriptDataLeft < objectStartOffset) - error("Script::identifyStrings(): object start is beyond heap size"); + error("Script::identifyOffsets(): object start is beyond heap size in script %d", _nr); if (scriptDataLeft < endOfStringOffset) - error("Script::identifyStrings(): end of string is beyond heap size"); + error("Script::identifyOffsets(): end of string is beyond heap size in script %d", _nr); - byte *endOfStringPtr = scriptDataPtr + endOfStringOffset; + const byte *endOfStringPtr = scriptDataPtr + endOfStringOffset; scriptDataPtr += objectStartOffset; scriptDataLeft -= objectStartOffset; - uint16 blockType; - uint16 blockSize; - - // skip all objects + // go through all objects do { if (scriptDataLeft < 2) - error("Script::identifyStrings(): unexpected end of script"); + error("Script::identifyOffsets(): unexpected end of script %d", _nr); blockType = READ_SCI11ENDIAN_UINT16(scriptDataPtr); scriptDataPtr += 2; @@ -327,15 +419,26 @@ void Script::identifyStrings() { if (blockType != SCRIPT_OBJECT_MAGIC_NUMBER) break; + // Object found, add offset of object + typeObject_id++; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT; + arrayEntry.id = typeObject_id; + arrayEntry.offset = scriptDataPtr - _buf - 2; // the VM uses a pointer to the Magic-Number + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupObjectCount++; + if (scriptDataLeft < 2) - error("Script::identifyStrings(): unexpected end of script"); + error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr) * 2; + if (blockSize < 4) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); scriptDataPtr += 2; scriptDataLeft -= 2; blockSize -= 4; // blocksize contains UINT16 type and UINT16 size if (scriptDataLeft < blockSize) - error("Script::identifyStrings(): invalid block size"); + error("Script::identifyOffsets(): invalid block size in script %d", _nr); scriptDataPtr += blockSize; scriptDataLeft -= blockSize; @@ -343,17 +446,17 @@ void Script::identifyStrings() { // now scriptDataPtr points to right at the start of the strings if (scriptDataPtr > endOfStringPtr) - error("Script::identifyStrings(): string block / end-of-string block mismatch"); + error("Script::identifyOffsets(): string block / end-of-string block mismatch in script %d", _nr); stringDataPtr = scriptDataPtr; stringDataLeft = endOfStringPtr - scriptDataPtr; // Calculate byte count within string-block + arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING; do { if (stringDataLeft < 1) // no more bytes left break; stringStartPtr = stringDataPtr; - listEntry.ptrOffset = stringStartPtr - _buf; // Calculate offset inside script data // now look for terminating [NUL] do { stringDataByte = *stringDataPtr; @@ -361,35 +464,142 @@ void Script::identifyStrings() { stringDataLeft--; if (!stringDataByte) // NUL found, exit this loop break; - if (stringDataLeft < 1) // no more bytes left - error("Script::identifyStrings(): string without terminating NUL"); + if (stringDataLeft < 1) { + // no more bytes left + warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr); + break; + } } while (1); - listEntry.stringSize = stringDataPtr - stringStartPtr; - _stringLookupList.push_back(listEntry); + if (stringDataByte) + break; + + typeString_id++; + arrayEntry.id = typeString_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = stringDataPtr - stringStartPtr; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupStringCount++; } while (1); } else if (getSciVersion() == SCI_VERSION_3) { - warning("TODO: identifyStrings(): Implement SCI3 variant"); - return; - } -} + // SCI3 + uint32 sci3StringOffset = 0; + uint32 sci3RelocationOffset = 0; + uint32 sci3BoundaryOffset = 0; -void Script::debugPrintStrings(Console *con) { - stringLookupListType::iterator it; - const stringLookupListType::iterator end = _stringLookupList.end(); - byte *stringPtr; + if (_bufSize < 22) + error("Script::identifyOffsets(): script %d smaller than expected SCI3-header", _nr); - if (!_stringLookupList.size()) { - con->debugPrintf(" no strings\n"); - debugN(" no strings\n"); - return; - } + sci3StringOffset = READ_LE_UINT32(_buf + 4); + sci3RelocationOffset = READ_LE_UINT32(_buf + 8); + + if (sci3RelocationOffset > _bufSize) + error("Script::identifyOffsets(): relocation offset is beyond end of script %d", _nr); + + // First we get all the objects + scriptDataPtr = getSci3ObjectsPointer(); + scriptDataLeft = _bufSize - (scriptDataPtr - _buf); + do { + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script %d", _nr); + + blockType = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + if (blockType != SCRIPT_OBJECT_MAGIC_NUMBER) + break; + + // Object found, add offset of object + typeObject_id++; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT; + arrayEntry.id = typeObject_id; + arrayEntry.offset = scriptDataPtr - _buf - 2; // the VM uses a pointer to the Magic-Number + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupObjectCount++; + + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); - for (it = _stringLookupList.begin(); it != end; ++it) { - stringPtr = _buf + it->ptrOffset; - con->debugPrintf(" %04x: '%s' (size %d)\n", it->ptrOffset, stringPtr, it->stringSize); - debugN(" %04x: '%s' (size %d)\n", it->ptrOffset, stringPtr, it->stringSize); + blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + if (blockSize < 4) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + blockSize -= 4; // blocksize contains UINT16 type and UINT16 size + if (scriptDataLeft < blockSize) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + + scriptDataPtr += blockSize; + scriptDataLeft -= blockSize; + } while (1); + + // And now we get all the strings + if (sci3StringOffset > 0) { + // string offset set, we expect strings + if (sci3StringOffset > _bufSize) + error("Script::identifyOffsets(): string offset is beyond end of script %d", _nr); + + if (sci3RelocationOffset < sci3StringOffset) + error("Script::identifyOffsets(): string offset points beyond relocation offset in script %d", _nr); + + stringDataPtr = _buf + sci3StringOffset; + stringDataLeft = sci3RelocationOffset - sci3StringOffset; + + arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING; + + do { + if (stringDataLeft < 1) // no more bytes left + break; + + stringStartPtr = stringDataPtr; + + if (stringDataLeft == 1) { + // only 1 byte left and that byte is a [00], in that case we also exit + stringDataByte = *stringStartPtr; + if (stringDataByte == 0x00) + break; + } + + // now look for terminating [NUL] + do { + stringDataByte = *stringDataPtr; + stringDataPtr++; + stringDataLeft--; + if (!stringDataByte) // NUL found, exit this loop + break; + if (stringDataLeft < 1) { + // no more bytes left + warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr); + break; + } + } while (1); + + if (stringDataByte) + break; + + typeString_id++; + arrayEntry.id = typeString_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = stringDataPtr - stringStartPtr; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupStringCount++; + + // SCI3 seems to have aligned all string on DWORD boundaries + sci3BoundaryOffset = stringDataPtr - _buf; // Calculate current offset inside script data + sci3BoundaryOffset = sci3BoundaryOffset & 3; // Check boundary offset + if (sci3BoundaryOffset) { + // lower 2 bits are set? Then we have to adjust the offset + sci3BoundaryOffset = 4 - sci3BoundaryOffset; + if (stringDataLeft < sci3BoundaryOffset) + error("Script::identifyOffsets(): SCI3 string boundary adjustment goes beyond end of string block in script %d", _nr); + stringDataLeft -= sci3BoundaryOffset; + stringDataPtr += sci3BoundaryOffset; + } + } while (1); + } + return; } } diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h index fe774aeab5..755e2f3698 100644 --- a/engines/sci/engine/script.h +++ b/engines/sci/engine/script.h @@ -49,12 +49,20 @@ enum ScriptObjectTypes { typedef Common::HashMap<uint16, Object> ObjMap; -struct stringLookupListEntry { - uint16 ptrOffset; // offset of the string +enum ScriptOffsetEntryTypes { + SCI_SCR_OFFSET_TYPE_OBJECT = 0, // classes are handled by this type as well + SCI_SCR_OFFSET_TYPE_STRING, + SCI_SCR_OFFSET_TYPE_SAID +}; + +struct offsetLookupArrayEntry { + uint16 type; // type of entry + uint16 id; // id of this type, first item inside script data is 1, second item is 2, etc. + uint32 offset; // offset of entry within script resource data uint16 stringSize; // size of string, including terminating [NUL] }; -typedef Common::List<stringLookupListEntry> stringLookupListType; +typedef Common::Array<offsetLookupArrayEntry> offsetLookupArrayType; class Script : public SegmentObj { private: @@ -82,7 +90,13 @@ private: ObjMap _objects; /**< Table for objects, contains property variables */ - stringLookupListType _stringLookupList; // Table of string data, that is inside the currently loaded script +protected: + offsetLookupArrayType _offsetLookupArray; // Table of all elements of currently loaded script, that may get pointed to + +private: + uint16 _offsetLookupObjectCount; + uint16 _offsetLookupStringCount; + uint16 _offsetLookupSaidCount; public: int getLocalsOffset() const { return _localsOffset; } @@ -258,9 +272,12 @@ public: int getCodeBlockOffsetSci3() { return READ_SCI11ENDIAN_UINT32(_buf); } /** - * Print string lookup table (list) to debug console + * Get the offset array */ - void debugPrintStrings(Console *con); + const offsetLookupArrayType *getOffsetArray() { return &_offsetLookupArray; }; + uint16 getOffsetObjectCount() { return _offsetLookupObjectCount; }; + uint16 getOffsetStringCount() { return _offsetLookupStringCount; }; + uint16 getOffsetSaidCount() { return _offsetLookupSaidCount; }; private: /** @@ -310,9 +327,9 @@ private: LocalVariables *allocLocalsSegment(SegManager *segMan); /** - * Identifies strings within script data and set up lookup-table + * Identifies certain offsets within script data and set up lookup-table */ - void identifyStrings(); + void identifyOffsets(); }; } // End of namespace Sci diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 4011273ca3..6915e12a0e 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -2123,21 +2123,30 @@ static const uint16 qfg1vgaPatchMoveToCrusher[] = { // Same pathfinding bug as above, where Ego is set to move to an impossible // spot when sneaking. In GuardsTrumpet::changeState, we change the final -// location where Ego is moved from 111, 111 to 114, 114. +// location where Ego is moved from 111, 111 to 116, 116. +// target coordinate is really problematic here. +// +// 114, 114 works when the speed slider is all the way up, but doesn't work +// when the speed slider is not. +// +// It seems that this bug was fixed by Sierra for the Macintosh version. +// +// Applies to at least: English PC floppy +// Responsible method: GuardsTrumpet::changeState(8) // Fixes bug: #6248 static const uint16 qfg1vgaSignatureMoveToCastleGate[] = { + 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo SIG_MAGICDWORD, - 0x51, 0x1f, // class MoveTo 0x36, // push - 0x39, 0x6f, // pushi 6f (111 - x) - 0x3c, // dup (111 - y) + 0x39, 0x6f, // pushi 6f (111d) + 0x3c, // dup (111d) - coordinates 111, 111 0x7c, // pushSelf SIG_END }; static const uint16 qfg1vgaPatchMoveToCastleGate[] = { PATCH_ADDTOOFFSET(+3), - 0x39, 0x72, // pushi 72 (114 - x) + 0x39, 0x74, // pushi 74 (116d), changes coordinates to 116, 116 PATCH_END }; @@ -2335,29 +2344,37 @@ static const SciScriptPatcherEntry qfg1vgaSignatures[] = { // which finally re-enables controls // // A fix is difficult to implement. The code in script 20 is generic and used by multiple objects -// That's why I have decided to change the responsible globals (66h and A1h) during mountSaurus::changeState(5) // -// This fix could cause issues in case there is a cutscene, that contains ego getting on a saurus and -// requires controls not getting re-enabled after getting back up on the saurus. +// Originally I decided to change the responsible globals (66h and A1h) during mountSaurus::changeState(5). +// This worked as far as for controls, but mountSaurus::init changes a few selectors of ego as well, which +// won't get restored in that situation, which then messes up room changes and other things. +// +// I have now decided to change sheepScript::changeState(2) in script 665 instead. +// +// This fix could cause issues in case there is a cutscene, where ego is supposed to get onto the saurus using +// sheepScript. // // Applies to at least: English PC Floppy, English Amiga Floppy // Responsible method: mountSaurus::changeState(), mountSaurus::init(), mountSaurus::dispose() // Fixes bug: #5156 static const uint16 qfg2SignatureSaurusFreeze[] = { 0x3c, // dup - 0x35, 0x05, // ldi 5 + 0x35, 0x02, // ldi 5 SIG_MAGICDWORD, 0x1a, // eq? - 0x30, SIG_UINT16(0x004e), // bnt [ret] - 0x39, SIG_SELECTOR8(contains), // pushi [selector contains] - 0x78, // push1 + 0x30, SIG_UINT16(0x0043), // bnt [ret] + 0x76, // push0 + SIG_ADDTOOFFSET(+61), // skip to dispose code + 0x39, SIG_SELECTOR8(dispose), // pushi "dispose" + 0x76, // push0 + 0x54, 0x04, // self 04 SIG_END }; static const uint16 qfg2PatchSaurusFreeze[] = { - 0x35, 0x01, // ldi 1 - 0xa1, 0x66, // sag 66h - 0xa0, SIG_UINT16(0x00a1), // sag 00A1h + 0x81, 0x66, // lag 66h + 0x2e, SIG_UINT16(0x0040), // bt [to dispose code] + 0x35, 0x00, // ldi 0 (waste 2 bytes) PATCH_END }; @@ -2441,7 +2458,7 @@ static const uint16 qfg2PatchImportCharType[] = { // script, description, signature patch static const SciScriptPatcherEntry qfg2Signatures[] = { - { true, 660, "getting back on saurus freeze fix", 1, qfg2SignatureSaurusFreeze, qfg2PatchSaurusFreeze }, + { true, 665, "getting back on saurus freeze fix", 1, qfg2SignatureSaurusFreeze, qfg2PatchSaurusFreeze }, { true, 805, "import character type fix", 1, qfg2SignatureImportCharType, qfg2PatchImportCharType }, { true, 944, "import dialog continuous calls", 1, qfg2SignatureImportDialog, qfg2PatchImportDialog }, SCI_SIGNATUREENTRY_TERMINATOR @@ -2758,9 +2775,64 @@ static const uint16 sq4CdSignatureWalkInFromBelowRoom45[] = { static const uint16 sq4CdPatchWalkInFromBelowRoom45[] = { PATCH_ADDTOOFFSET(+2), - 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh + 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh PATCH_ADDTOOFFSET(+15), - 0x38, PATCH_UINT16(0x00bb), // pushi 00BBh + 0x38, PATCH_UINT16(0x00bb), // pushi 00BBh + PATCH_END +}; + +// It seems that Sierra forgot to set a script flag, when cleaning out the bank account +// in Space Quest 4 CD. This was probably caused by the whole bank account interaction +// getting a rewrite and polish in the CD version. +// +// Because of this bug, points for changing back clothes will not get awarded, which +// makes it impossible to get a perfect point score in the CD version of the game. +// The points are awarded by rm371::doit in script 371. +// +// We fix this. Bug also happened, when using the original interpreter. +// Bug does not happen for PC floppy. +// +// Attention: Some Let's Plays on youtube show that points are in fact awarded. Which is true. +// But those Let's Plays were actually created by playing a hacked Space Quest 4 version +// (which is part Floppy, part CD version - we consider it to be effectively pirated) +// and not the actual CD version of Space Quest 4. +// It's easy to identify - talkie + store called "Radio Shack" -> is hacked version. +// +// Applies to at least: English PC CD +// Responsible method: but2Script::changeState(2) +// Fixes bug: #6866 +static const uint16 sq4CdSignatureGetPointsForChangingBackClothes[] = { + 0x35, 0x02, // ldi 02 + SIG_MAGICDWORD, + 0x1a, // eq? + 0x30, SIG_UINT16(0x006a), // bnt [state 3] + 0x76, + SIG_ADDTOOFFSET(+46), // jump over "withdraw funds" code + 0x33, 0x33, // jmp [end of state 2, set cycles code] + SIG_ADDTOOFFSET(+51), // jump over "clean bank account" code + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x33, 0x0b, // jmp [toss/ret] + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x05, // bnt [toss/ret] + SIG_END +}; + +static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = { + PATCH_ADDTOOFFSET(+3), + 0x30, PATCH_UINT16(0x0070), // bnt [state 3] + PATCH_ADDTOOFFSET(+47), // "withdraw funds" code + 0x33, 0x39, // jmp [end of state 2, set cycles code] + PATCH_ADDTOOFFSET(+51), + 0x78, // push1 + 0x39, 0x1d, // ldi 1Dh + 0x45, 0x07, 0x02, // call export 7 of script 0 (set flag) -> effectively sets global 73h, bit 2 + 0x35, 0x02, // ldi 02 + 0x65, 0x1c, // aTop cycles + 0x33, 0x05, // jmp [toss/ret] + // check for state 3 code removed to save 6 bytes PATCH_END }; @@ -2859,6 +2931,7 @@ static const SciScriptPatcherEntry sq4Signatures[] = { { true, 298, "Floppy: endless flight", 1, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight }, { 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, 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 }, diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index ea3443e551..aab32032f7 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -603,8 +603,8 @@ const SciWorkaroundEntry kGraphRedrawBox_workarounds[] = { { GID_SQ4, 405, 405, 0, "swimAfterEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified { GID_SQ4, 405, 405, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 { GID_SQ4, 406, 406, 0, "egoFollowed", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // FLOPPY: when getting shot by the police - accidental additional parameter specified - { GID_SQ4, 406, 406, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified - { GID_SQ4, 406, 406, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 (is for both egoFollowed and swimAndShoot) + { GID_SQ4, -1, 406, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified + { GID_SQ4, -1, 406, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 (is for both egoFollowed and swimAndShoot) { GID_SQ4, 410, 410, 0, "swimAfterEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified { GID_SQ4, 410, 410, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 { GID_SQ4, 411, 411, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified |