aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/engine')
-rw-r--r--engines/sci/engine/script.cpp332
-rw-r--r--engines/sci/engine/script.h33
-rw-r--r--engines/sci/engine/script_patches.cpp109
-rw-r--r--engines/sci/engine/workarounds.cpp4
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