diff options
Diffstat (limited to 'engines/sci/engine/script_patches.cpp')
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 1255 |
1 files changed, 1218 insertions, 37 deletions
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index fc0dca5123..0c4f0da959 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -103,6 +103,9 @@ static const char *const selectorNameTable[] = { "modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "cycler", // Space Quest 4 / system selector "setLoop", // Laura Bow 1 Colonel's Bequest +#ifdef ENABLE_SCI32 + "newWith", // SCI2 array script +#endif NULL }; @@ -132,8 +135,86 @@ enum ScriptPatcherSelectors { SELECTOR_modNum, SELECTOR_cycler, SELECTOR_setLoop +#ifdef ENABLE_SCI32 + , + SELECTOR_newWith +#endif +}; + +#ifdef ENABLE_SCI32 +// It is not possible to change the directory for ScummVM save games, so disable +// the "change directory" button in the standard save dialogue +static const uint16 sci2ChangeDirSignature[] = { + 0x72, SIG_ADDTOOFFSET(+2), // lofsa changeDirI + 0x4a, SIG_UINT16(0x04), // send 4 + SIG_MAGICDWORD, + 0x36, // push + 0x35, 0xF7, // ldi $f7 + 0x12, // and + 0x36, // push + SIG_END +}; + +static const uint16 sci2ChangeDirPatch[] = { + PATCH_ADDTOOFFSET(+3), // lofsa changeDirI + PATCH_ADDTOOFFSET(+3), // send 4 + PATCH_ADDTOOFFSET(+1), // push + 0x35, 0x00, // ldi 0 + PATCH_END +}; + +// Save game script hardcodes the maximum number of save games to 20, but +// this is an artificial constraint that does not apply to ScummVM +static const uint16 sci2NumSavesSignature1[] = { + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x14, // ldi 20 + 0x22, // lt? + SIG_END +}; + +static const uint16 sci2NumSavesPatch1[] = { + PATCH_ADDTOOFFSET(+2), // lsl local[2] + 0x35, 0x63, // ldi 99 + PATCH_END +}; + +static const uint16 sci2NumSavesSignature2[] = { + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x14, // ldi 20 + 0x1a, // eq? + SIG_END +}; + +static const uint16 sci2NumSavesPatch2[] = { + PATCH_ADDTOOFFSET(+2), // lsl local[2] + 0x35, 0x63, // ldi 99 + PATCH_END }; +// Phantasmagoria & SQ6 try to initialize the first entry of an int16 array +// using an empty string, which is not valid (it should be a number) +static const uint16 sci21IntArraySignature[] = { + 0x38, SIG_SELECTOR16(newWith), // pushi newWith + 0x7a, // push2 + 0x39, 0x04, // pushi $4 + 0x72, SIG_ADDTOOFFSET(+2), // lofsa string "" + SIG_MAGICDWORD, + 0x36, // push + 0x51, 0x0b, // class IntArray + 0x4a, 0x8, // send $8 + SIG_END +}; + +static const uint16 sci21IntArrayPatch[] = { + PATCH_ADDTOOFFSET(+6), // push $b9; push2; pushi $4 + 0x76, // push0 + 0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes) + PATCH_END +}; +#endif + // =========================================================================== // Conquests of Camelot // At the bazaar in Jerusalem, it's possible to see a girl taking a shower. @@ -379,12 +460,40 @@ static const SciScriptPatcherEntry ecoquest2Signatures[] = { }; // =========================================================================== +// Fan-made games +// Attention: Try to make script patches as specific as possible + +// CascadeQuest::autosave in script 994 is called various times to auto-save the game. +// The script use a fixed slot "999" for this purpose. This doesn't work in ScummVM, because we do not let +// scripts save directly into specific slots, but instead use virtual slots / detect scripts wanting to +// create a new slot. +// +// For this game we patch the code to use slot 99 instead. kSaveGame also checks for Cascade Quest, +// will then check, if slot 99 is asked for and will then use the actual slot 0, which is the official +// ScummVM auto-save slot. +// +// Responsible method: CascadeQuest::autosave +// Fixes bug: #7007 +static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x03e7), // pushi 3E7 (999d) -> save game slot 999 + 0x74, SIG_UINT16(0x06f8), // lofss "AutoSave" + 0x89, 0x1e, // lsg global[1E] + 0x43, 0x2d, 0x08, // callk SaveGame + SIG_END +}; + +static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = { + 0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot + PATCH_END +}; + // EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the // wrong address when an incorrect word is typed, therefore leading to an // infinite loop. This script bug was not apparent in SSCI, probably because // event handling was slightly different there, so it was never discovered. // Fixes bug: #5120 -static const uint16 fanmadeSignatureInfiniteLoop[] = { +static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = { 0x38, SIG_UINT16(0x004c), // pushi 004c 0x39, 0x00, // pushi 00 0x87, 0x01, // lap 01 @@ -395,15 +504,16 @@ static const uint16 fanmadeSignatureInfiniteLoop[] = { SIG_END }; -static const uint16 fanmadePatchInfiniteLoop[] = { +static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = { PATCH_ADDTOOFFSET(+10), - 0x30, SIG_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c + 0x30, PATCH_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c PATCH_END }; -// script, description, signature patch +// script, description, signature patch static const SciScriptPatcherEntry fanmadeSignatures[] = { - { true, 999, "infinite loop on typo", 1, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop }, + { true, 994, "Cascade Quest: fix auto-saving", 1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving }, + { true, 999, "Demo Quest: infinite loop on typo", 1, fanmadeSignatureDemoQuestInfiniteLoop, fanmadePatchDemoQuestInfiniteLoop }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -585,6 +695,10 @@ static const SciScriptPatcherEntry freddypharkasSignatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Gabriel Knight 1 + // =========================================================================== // daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220. // this is not enough time to get to the door, so we patch that to 23 seconds @@ -634,6 +748,68 @@ static const uint16 gk1PatchDay6PoliceSleep[] = { PATCH_END }; +// At the start of day 5, there is like always some dialogue with Grace. +// +// The dialogue script code about the drum book + veve newspaper clip is a bit broken. +// +// In case the player already has the veve, but is supposed to get the drum book, then the drum book +// dialogue is repeated twice and the veve newspaper dialogue is also repeated (although it was played on day 4 +// in such case already). +// +// Drum book dialogue is called twice. +// Once via GetTheVeve::changeState(0) and a second time via GetTheVeve::changeState(11). +// +// GetTheVeve::changeState(0) would also play the first line of the veve pattern newspaper and that's skipped, +// when the player is supposed to get the drum book. +// GetTheVeve::changeState(1) up to state 10 will do the dialogue about the veve newspaper. +// At the start of state 1 though, the player will get the drum book in case he ask for research. +// Right after that the scripts check, if the player has the drum book and then go the veve newspaper route. +// +// We fix this by skipping the drum book check in case the player just got the drum book. +// The scripts will then skip to state 12, skipping over the second drum book dialogue call. +// +// More notes: The veve newspaper item is inventory 9. The drum book is inventory 14. +// The flag for veve research is 36, the flag for drum research is 73. +// +// This bug of course also occurs, when using the original interpreter. +// +// Special thanks, credits and kudos to sluicebox on IRC, who did a ton of research on this and even found this game bug originally. +// +// Applies to at least: English PC-CD, German PC-CD +// Responsible method: getTheVeve::changeState(1) - script 212 +static const uint16 gk1SignatureDay5DrumBookDialogue[] = { + 0x31, 0x0b, // bnt [skip giving player drum book code] + 0x38, SIG_UINT16(0x0200), // pushi 0200h + 0x78, // push1 + SIG_MAGICDWORD, + 0x39, 0x0e, // pushi 0Eh + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, 0x00, // send 06 - GKEgo::get(0Eh) + // end of giving player drum book code + 0x38, SIG_UINT16(0x0202), // pushi 0202h + 0x78, // push1 + 0x39, 0x0e, // pushi 0Eh + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, 0x00, // send 06 - GKEgo::has(0Eh) + 0x18, // not + 0x30, SIG_UINT16(0x0025), // bnt [veve newspaper code] + SIG_END +}; + +static const uint16 gk1PatchDay5DrumBookDialogue[] = { + 0x31, 0x0d, // bnt [skip giving player drum book code] adjusted + PATCH_ADDTOOFFSET(+11), // skip give player drum book original code + 0x33, 0x0D, // jmp [over the check inventory for drum book code] + // check inventory for drum book + 0x38, SIG_UINT16(0x0202), // pushi 0202h + 0x78, // push1 + 0x39, 0x0e, // pushi 0Eh + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, 0x00, // send 06 - GKEgo::has(0Eh) + 0x2f, 0x23, // bt [veve newspaper code] (adjusted, saves 2 bytes) + PATCH_END +}; + // startOfDay5::changeState (20h) - when gabriel goes to the phone the script will hang // Applies to at least: English PC-CD, German PC-CD, English Mac // Responsible method: startOfDay5::changeState @@ -700,7 +876,7 @@ static const uint16 gk1PatchInterrogationBug[] = { 0x76, // push0 0x4a, 0x04, 0x00, // send 0004 0xa5, 0x00, // sat 00 - 0x38, SIG_SELECTOR16(dispose), // pushi dispose + 0x38, PATCH_SELECTOR16(dispose), // pushi dispose 0x76, // push0 0x63, 0x50, // pToa 50 0x4a, 0x04, 0x00, // send 0004 @@ -717,15 +893,135 @@ static const uint16 gk1PatchInterrogationBug[] = { PATCH_END }; -// script, description, signature patch +// On day 10 nearly at the end of the game, Gabriel Knight dresses up and right after that +// someone will be at the door. Gabriel turns around to see what's going on. +// +// In ScummVM Gabriel turning around plays endlessly. This is caused by the loop of Gabriel +// being kept at 1, but view + cel were changed accordingly. The view used - which is view 859 - +// does not have a loop 1. kNumCels is called on that, BUT kNumCels in SSCI is broken in that +// regard. It checks for loop > count and not loop >= count and will return basically random data +// in case loop == count. +// +// In SSCI this simply worked by accident. kNumCels returned 0x53 in this case, but later script code +// fixed that up somehow, so it worked out in the end. +// +// The setup for this is done in SDJEnters::changeState(0). The cycler will never reach the goal +// because the goal will be cel -1, so it loops endlessly. +// +// We fix this by adding a setLoop(0). +// +// Applies to at least: English PC-CD, German PC-CD +// Responsible method: sDJEnters::changeState +static const uint16 gk1SignatureDay10GabrielDressUp[] = { + 0x87, 0x01, // lap param[1] + 0x65, 0x14, // aTop state + 0x36, // push + 0x3c, // dup + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x30, SIG_UINT16(0x006f), // bnt [next state 1] + SIG_ADDTOOFFSET(+84), + 0x39, 0x0e, // pushi 0Eh (view) + 0x78, // push1 + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x035B), // pushi 035Bh (859d) + 0x38, SIG_UINT16(0x0141), // pushi 0141h (setCel) + 0x78, // push1 + 0x76, // push0 + 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle) + 0x7a, // push2 + 0x51, 0x18, // class End + 0x36, // push + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + 0x4a, 0x14, 0x00, // send 14h + // GKEgo::view(859) + // GKEgo::setCel(0) + // GKEgo::setCycle(End, sDJEnters) + 0x32, SIG_UINT16(0x0233), // jmp [ret] + // next state + 0x3c, // dup + 0x35, 0x01, // ldi 01 + 0x1a, // eq? + 0x31, 0x07, // bnt [next state 2] + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x0226), // jmp [ret] + // next state + 0x3c, // dup + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x2a, // bnt [next state 3] + 0x78, // push1 + SIG_ADDTOOFFSET(+34), + // part of state 2 code, delays for 1 cycle + 0x35, 0x01, // ldi 1 + 0x65, 0x1a, // aTop cycles + SIG_END +}; + +static const uint16 gk1PatchDay10GabrielDressUp[] = { + PATCH_ADDTOOFFSET(+9), + 0x30, SIG_UINT16(0x0073), // bnt [next state 1] - offset adjusted + SIG_ADDTOOFFSET(+84 + 11), + // added by us: setting loop to 0 (5 bytes needed) + 0x38, SIG_UINT16(0x00FB), // pushi 00FBh (setLoop) + 0x78, // push1 + 0x76, // push0 + // original code, but offset changed + 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle) + 0x7a, // push2 + 0x51, 0x18, // class End + 0x36, // push + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + 0x4a, 0x1a, 0x00, // send 1Ah - adjusted + // GKEgo::view(859) + // GKEgo::setCel(0) + // GKEgo::setLoop(0) <-- new, by us + // GKEgo::setCycle(End, sDJEnters) + // end of original code + 0x3a, // toss + 0x48, // ret (saves 1 byte) + // state 1 code + 0x3c, // dup + 0x34, SIG_UINT16(0x0001), // ldi 0001 (waste 1 byte) + 0x1a, // eq? + 0x31, 2, // bnt [next state 2] + 0x33, 41, // jmp to state 2 delay code + SIG_ADDTOOFFSET(+41), + // wait 2 cycles instead of only 1 + 0x35, 0x02, // ldi 2 + PATCH_END +}; + +// script, description, signature patch static const SciScriptPatcherEntry gk1Signatures[] = { - { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug }, - { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, - { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, - { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug }, + { true, 212, "day 5 drum book dialogue error", 1, gk1SignatureDay5DrumBookDialogue, gk1PatchDay5DrumBookDialogue }, + { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, + { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, + { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { true, 808, "day 10 gabriel dress up infinite turning", 1, gk1SignatureDay10GabrielDressUp, gk1PatchDay10GabrielDressUp }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, SCI_SIGNATUREENTRY_TERMINATOR }; +#pragma mark - +#pragma mark Gabriel Knight 2 + +// script, description, signature patch +static const SciScriptPatcherEntry gk2Signatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // at least during harpy scene export 29 of script 0 is called in kq5cd and // has an issue for those calls, where temp 3 won't get inititialized, but @@ -813,6 +1109,32 @@ static const uint16 kq5PatchWitchCageInit[] = { PATCH_END }; +// The multilingual releases of KQ5 hang right at the end during the magic battle with Mordack. +// It seems additional code was added to wait for signals, but the signals are never set and thus +// the game hangs. We disable that code, so that the battle works again. +// This also happened in the original interpreter. +// We must not change similar code, that happens before. + +// Applies to at least: French PC floppy, German PC floppy, Spanish PC floppy +// Responsible method: stingScript::changeState, dragonScript::changeState, snakeScript::changeState +static const uint16 kq5SignatureMultilingualEndingGlitch[] = { + SIG_MAGICDWORD, + 0x89, 0x57, // lsg global[57h] + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x18, // not + 0x30, SIG_UINT16(0x0011), // bnt [skip signal check] + SIG_ADDTOOFFSET(+8), // skip globalSound::prevSignal get code + 0x36, // push + 0x35, 0x0a, // ldi 0Ah + SIG_END +}; + +static const uint16 kq5PatchMultilingualEndingGlitch[] = { + PATCH_ADDTOOFFSET(+6), + 0x32, // change BNT into JMP + PATCH_END +}; // In the final battle, the DOS version uses signals in the music to handle // timing, while in the Windows version another method is used and the GM @@ -843,9 +1165,10 @@ static const uint16 kq5PatchWinGMSignals[] = { // script, description, signature patch static const SciScriptPatcherEntry kq5Signatures[] = { - { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, - { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit }, - { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals }, + { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, + { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit }, + { true, 124, "Multilingual: Ending glitching out", 3, kq5SignatureMultilingualEndingGlitch, kq5PatchMultilingualEndingGlitch }, + { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1165,6 +1488,7 @@ static const uint16 kq6CDPatchAudioTextSupport2[] = { // Additional patch specifically for King's Quest 6 // Fixes special windows, used for example in the Pawn shop (room 280), // when the man in a robe complains about no more mints. +// Or also in room 300 at the cliffs (aka copy protection), when Alexander falls down the cliffs. // We have to change even more code, because the game uses PODialog class for // text windows and myDialog class for audio. Both are saved to KQ6Print::dialog // Sadly PODialog is created during KQ6Print::addText, myDialog is set during @@ -1191,13 +1515,34 @@ static const uint16 kq6CDSignatureAudioTextSupport3[] = { }; static const uint16 kq6CDPatchAudioTextSupport3[] = { - 0x31, 0x5c, // adjust jump to reuse audio mode addText-calling code - PATCH_ADDTOOFFSET(102), - 0x48, // ret - 0x48, // ret (waste byte) + 0x31, 0x68, // adjust jump to reuse audio mode addText-calling code + PATCH_ADDTOOFFSET(+85), // right at the MAGIC_DWORD + // check, if text is supposed to be shown. If yes, skip the follow-up check (param[1]) + 0x89, 0x5a, // lsg global[5Ah] + 0x35, 0x01, // ldi 01 + 0x12, // and + 0x2f, 0x07, // bt [skip over param check] + // original code, checks param[1] + 0x8f, 0x01, // lsp param[1] + 0x35, 0x01, // ldi 01 + 0x1a, // eq? + 0x31, 0x10, // bnt [code to set property repressText to 1], adjusted + // use myDialog class, so that text box automatically disappears (this is not done for text only mode, like in the original) 0x72, 0x0e, 0x00, // lofsa myDialog 0x65, 0x12, // aTop dialog - 0x33, 0xed, // jump back to audio mode addText-calling code + // followed by original addText-calling code + 0x38, + PATCH_GETORIGINALBYTE(+95), + PATCH_GETORIGINALBYTE(+96), // pushi addText + 0x78, // push1 + 0x8f, 0x02, // lsp param[2] + 0x59, 0x03, // &rest 03 + 0x54, 0x06, // self 06 + 0x48, // ret + + 0x35, 0x01, // ldi 01 + 0x65, 0x2e, // aTop repressText + 0x48, // ret PATCH_END }; @@ -1396,6 +1741,173 @@ static const SciScriptPatcherEntry kq6Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Kings Quest 7 + +// =========================================================================== + +// King's Quest 7 has really weird subtitles. It seems as if the subtitles were +// not fully finished. +// +// Method kqMessager::findTalker in script 0 tries to figure out, which class to use for +// displaying subtitles. It uses the "talker" data of the given message to do that. +// Strangely this "talker" data seems to be quite broken. +// For example chapter 2 starts with a cutscene. +// Troll king: "Welcome, most beautiful of princesses!" - talker 6 +// Which is followed by the princess going +// "Hmm?" - which is set to talker 99, normally the princess is talker 7. +// +// Talker 99 is seen as unknown and thus treated as "narrator", which makes +// the scripts put the text at the top of the game screen and even use a +// different font. +// +// In other cases, when the player character thinks to himself talker 99 +// is also used. In such situations it may make somewhat sense to do so, +// but putting the text at the top of the screen is also irritating to the player. +// It's really weird. +// +// The scripts also put the regular text in the middle of the screen, blocking +// animations. +// +// And for certain rooms, the subtitle box may use another color +// like for example pink/purple at the start of chapter 5. +// +// We fix all of that (hopefully - lots of testing is required). +// We put the text at the bottom of the play screen. +// We also make the scripts use the regular KQTalker instead of KQNarrator. +// And we also make the subtitle box use color 255, which is fixed white. +// +// Applies to at least: PC CD 1.4 English, 1.51 English, 1.51 German, 2.00 English +// Patched method: KQNarrator::init (script 31) +static const uint16 kq7SignatureSubtitleFix1[] = { + SIG_MAGICDWORD, + 0x39, 0x25, // pushi 25h (fore) + 0x78, // push1 + 0x39, 0x06, // pushi 06 - sets back to 6 + 0x39, 0x26, // pushi 26 (back) + 0x78, // push1 + 0x78, // push1 - sets back to 1 + 0x39, 0x2a, // pushi 2Ah (font) + 0x78, // push1 + 0x89, 0x16, // lsg global[16h] - sets font to global[16h] + 0x7a, // push2 (y) + 0x78, // push1 + 0x76, // push0 - sets y to 0 + 0x54, SIG_UINT16(0x0018), // self 18h + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix1[] = { + 0x33, 0x12, // jmp [skip special init code] + PATCH_END +}; + +// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English +// Patched method: Narrator::init (script 64928) +static const uint16 kq7SignatureSubtitleFix2[] = { + SIG_MAGICDWORD, + 0x89, 0x5a, // lsg global[5a] + 0x35, 0x02, // ldi 02 + 0x12, // and + 0x31, 0x1e, // bnt [skip audio volume code] + 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51) + 0x76, // push0 + 0x81, 0x01, // lag global[1] + 0x4a, 0x04, 0x00, // send 04 + 0x65, 0x32, // aTop curVolume + 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51) + 0x78, // push1 + 0x67, 0x32, // pTos curVolume + 0x35, 0x02, // ldi 02 + 0x06, // mul + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x08, // div + 0x36, // push + 0x81, 0x01, // lag global[1] + 0x4a, 0x06, 0x00, // send 06 + // end of volume code + 0x35, 0x01, // ldi 01 + 0x65, 0x28, // aTop initialized + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix2[] = { + PATCH_ADDTOOFFSET(+5), // skip to bnt + 0x31, 0x1b, // bnt [skip audio volume code] + PATCH_ADDTOOFFSET(+15), // right after "aTop curVolume / pushi masterVolume / push1" + 0x7a, // push2 + 0x06, // mul (saves 3 bytes in total) + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x08, // div + 0x36, // push + 0x81, 0x01, // lag global[1] + 0x4a, 0x06, 0x00, // send 06 + // end of volume code + 0x35, 118, // ldi 118d + 0x65, 0x16, // aTop y + 0x78, // push1 (saves 1 byte) + 0x69, 0x28, // sTop initialized + PATCH_END +}; + +// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English +// Patched method: Narrator::say (script 64928) +static const uint16 kq7SignatureSubtitleFix3[] = { + SIG_MAGICDWORD, + 0x63, 0x28, // pToa initialized + 0x18, // not + 0x31, 0x07, // bnt [skip init code] + 0x38, SIG_ADDTOOFFSET(+2), // pushi init (008Eh for 2.00, 0093h for 1.51) + 0x76, // push0 + 0x54, SIG_UINT16(0x0004), // self 04 + // end of init code + 0x8f, 0x00, // lsp param[0] + 0x35, 0x01, // ldi 01 + 0x1e, // gt? + 0x31, 0x08, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x31, 0x04, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x33, 0x02, // jmp [over set acc to 0 code] + 0x35, 0x00, // ldi 00 + 0x65, 0x18, // aTop caller + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix3[] = { + PATCH_ADDTOOFFSET(+2), // skip over "pToa initialized code" + 0x2f, 0x0c, // bt [skip init code] - saved 1 byte + 0x38, + PATCH_GETORIGINALBYTE(+6), + PATCH_GETORIGINALBYTE(+7), // pushi (init) + 0x76, // push0 + 0x54, PATCH_UINT16(0x0004), // self 04 + // additionally set background color here (5 bytes) + 0x34, PATCH_UINT16(255), // pushi 255d + 0x65, 0x2e, // aTop back + // end of init code + 0x8f, 0x00, // lsp param[0] + 0x35, 0x01, // ldi 01 - this may get optimized to get another byte + 0x1e, // gt? + 0x31, 0x04, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x2f, 0x02, // bt [over set acc to 0 code] + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry kq7Signatures[] = { + { true, 31, "subtitle fix 1/3", 1, kq7SignatureSubtitleFix1, kq7PatchSubtitleFix1 }, + { true, 64928, "subtitle fix 2/3", 1, kq7SignatureSubtitleFix2, kq7PatchSubtitleFix2 }, + { true, 64928, "subtitle fix 3/3", 1, kq7SignatureSubtitleFix3, kq7PatchSubtitleFix3 }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // Script 210 in the German version of Longbow handles the case where Robin // hands out the scroll to Marion and then types his name using the hand code. @@ -1436,9 +1948,109 @@ static const uint16 longbowPatchShowHandCode[] = { PATCH_END }; +// When walking through the forest, arithmetic errors may occur at "random". +// The scripts try to add a value and a pointer to the object "berryBush". +// +// This is caused by a local variable overflow. +// +// The scripts create berry bush objects dynamically. The array storage for +// those bushes may hold a total of 8 bushes. But sometimes 10 bushes +// are created. This overwrites 2 additional locals in script 225 and +// those locals are used normally for value lookups. +// +// Changing the total of bushes could cause all sorts of other issues, +// that's why I rather patched the code, that uses the locals for a lookup. +// Which means it doesn't matter anymore when those locals are overwritten. +// +// Applies to at least: English PC floppy, German PC floppy, English Amiga floppy +// Responsible method: export 2 of script 225 +// Fixes bug: #6751 +static const uint16 longbowSignatureBerryBushFix[] = { + 0x89, 0x70, // lsg global[70h] + 0x35, 0x03, // ldi 03h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x002d), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x04, // ldi 04h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0025), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x05, // ldi 05h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x001d), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x06, // ldi 06h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0015), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x18, // ldi 18h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x000d), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x19, // ldi 19h + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0005), // bt [process code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x1a, // ldi 1Ah + 0x1a, // eq? + // jump location for the "bt" instructions + 0x30, SIG_UINT16(0x0011), // bnt [skip over follow up code, to offset 0c35] + // 55 bytes until here + 0x85, 00, // lat temp[0] + SIG_MAGICDWORD, + 0x9a, SIG_UINT16(0x0110), // lsli local[110h] -> 110h points normally to 110h / 2Bh + // 5 bytes + 0x7a, // push2 + SIG_END +}; + +static const uint16 longbowPatchBerryBushFix[] = { + PATCH_ADDTOOFFSET(+4), // keep: lsg global[70h], ldi 03h + 0x22, // lt? (global < 03h) + 0x2f, 0x42, // bt [skip over all the code directly] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x06, // ldi 06h + 0x24, // le? (global <= 06h) + 0x2f, 0x0e, // bt [to kRandom code] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x18, // ldi 18h + 0x22, // lt? (global < 18h) + 0x2f, 0x34, // bt [skip over all the code directly] + 0x89, 0x70, // lsg global[70h] + 0x35, 0x1a, // ldi 1Ah + 0x24, // le? (global <= 1Ah) + 0x31, 0x2d, // bnt [skip over all the code directly] + // 28 bytes, 27 bytes saved + // kRandom code + 0x85, 0x00, // lat temp[0] + 0x2f, 0x05, // bt [skip over case 0] + // temp[0] == 0 + 0x38, SIG_UINT16(0x0110), // pushi 0110h - that's what's normally at local[110h] + 0x33, 0x18, // jmp [kRandom call] + // check temp[0] further + 0x78, // push1 + 0x1a, // eq? + 0x31, 0x05, // bt [skip over case 1] + // temp[0] == 1 + 0x38, SIG_UINT16(0x002b), // pushi 002Bh - that's what's normally at local[111h] + 0x33, 0x0F, // jmp [kRandom call] + // temp[0] >= 2 + 0x8d, 00, // lst temp[0] + 0x35, 0x02, // ldi 02 + 0x04, // sub + 0x9a, SIG_UINT16(0x0112), // lsli local[112h] -> look up value in 2nd table + // this may not be needed at all and was just added for safety reasons + // waste 9 spare bytes + 0x35, 0x00, // ldi 00 + 0x35, 0x00, // ldi 00 + 0x34, PATCH_UINT16(0x0000), // ldi 0000 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry longbowSignatures[] = { { true, 210, "hand code crash", 5, longbowSignatureShowHandCode, longbowPatchShowHandCode }, + { true, 225, "arithmetic berry bush fix", 1, longbowSignatureBerryBushFix, longbowPatchBerryBushFix }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1596,6 +2208,39 @@ static const SciScriptPatcherEntry larry6Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Leisure Suit Larry 6 Hires + +// When entering room 270 (diving board) from room 230, a typo in the game +// script means that `setScale` is called accidentally instead of `setScaler`. +// In SSCI this did not do much because the first argument happened to be +// smaller than the y-position of `ego`, but in ScummVM the first argument is +// larger and so a debug message "y value less than vanishingY" is displayed. +static const uint16 larry6HiresSignatureSetScale[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x14b), // pushi 014b (setScale) + 0x38, SIG_UINT16(0x05), // pushi 0005 + 0x51, 0x2c, // class 2c (Scaler) + SIG_END +}; + +static const uint16 larry6HiresPatchSetScale[] = { + 0x38, SIG_UINT16(0x14f), // pushi 014f (setScaler) + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry larry6HiresSignatures[] = { + { true, 270, "fix incorrect setScale call", 1, larry6HiresSignatureSetScale, larry6HiresPatchSetScale }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // Laura Bow 1 - Colonel's Bequest // @@ -1627,9 +2272,160 @@ static const uint16 laurabow1PatchEasterEggViewFix[] = { PATCH_END }; +// When oiling the armor or opening the visor of the armor, the scripts +// first check if Laura/ego is near the armor and if she is not, they will move her +// to the armor. After that further code is executed. +// +// The current location is checked by a ego::inRect() call. +// +// The given rect for the inRect call inside openVisor::changeState was made larger for Atari ST/Amiga versions. +// We change the PC version to use the same rect. +// +// Additionally the coordinate, that Laura is moved to, is 152, 107 and may not be reachable depending on where +// Laura/ego was, when "use oil on helmet of armor" / "open visor of armor" got entered. +// Bad coordinates are for example 82, 110, which then cause collisions and effectively an endless loop. +// Game will effectively "freeze" and the user is only able to restore a previous game. +// This also happened, when using the original interpreter. +// We change the destination coordinate to 152, 110, which seems to be reachable all the time. +// +// The following patch fixes the rect for the PC version of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: openVisor::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorOpenVisorFix[] = { + 0x39, 0x04, // pushi 04 + SIG_MAGICDWORD, + 0x39, 0x6a, // pushi 6a (106d) + 0x38, SIG_UINT16(0x96), // pushi 0096 (150d) + 0x39, 0x6c, // pushi 6c (108d) + 0x38, SIG_UINT16(0x98), // pushi 0098 (152d) + SIG_END +}; + +static const uint16 laurabow1PatchArmorOpenVisorFix[] = { + PATCH_ADDTOOFFSET(+2), + 0x39, 0x68, // pushi 68 (104d) (-2) + 0x38, SIG_UINT16(0x94), // pushi 0094 (148d) (-2) + 0x39, 0x6f, // pushi 6f (111d) (+3) + 0x38, SIG_UINT16(0x9a), // pushi 009a (154d) (+2) + PATCH_END +}; + +// This here fixes the destination coordinate (exact details are above). +// +// Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy +// Responsible method: openVisor::changeState, oiling::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorMoveToFix[] = { + SIG_MAGICDWORD, + 0x36, // push + 0x39, 0x6b, // pushi 6B (107d) + 0x38, SIG_UINT16(0x0098), // pushi 98 (152d) + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + SIG_END +}; + +static const uint16 laurabow1PatchArmorMoveToFix[] = { + PATCH_ADDTOOFFSET(+1), + 0x39, 0x6e, // pushi 6E (110d) - adjust x, so that no collision can occur anymore + PATCH_END +}; + +// In some cases like for example when the player oils the arm of the armor, command input stays +// disabled, even when the player exits fast enough, so that Laura doesn't die. +// +// This is caused by the scripts only enabling control (directional movement), but do not enable command input as well. +// +// This bug also happens, when using the original interpreter. +// And it was fixed for the Atari ST + Amiga versions of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7) +// Fixes bug: #7154 +static const uint16 laurabow1SignatureArmorOilingArmFix[] = { + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + SIG_MAGICDWORD, + 0x72, SIG_UINT16(0x1a5c), // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x19a1), // lofsa "Visor" + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x194a), // lofsa "note" + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x18f3), // lofsa "valve" + 0x4a, 0x04, // send 04 + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x02, // ldi 02 + 0x1c, // ne? + 0x30, SIG_UINT16(0x0014), // bnt [to ret] + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x05, // ldi 05 + 0x1c, // ne? + 0x30, SIG_UINT16(0x000c), // bnt [to ret] + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x06, // ldi 06 + 0x1c, // ne? + 0x30, SIG_UINT16(0x0004), // bnt [to ret] + // followed by code to call script 0 export to re-enable controls and call setMotion + SIG_END +}; + +static const uint16 laurabow1PatchArmorOilingArmFix[] = { + PATCH_ADDTOOFFSET(+3), // skip over pushi 89h + 0x3c, // dup + 0x3c, // dup + 0x3c, // dup + // saves a total of 6 bytes + 0x76, // push0 + 0x72, SIG_UINT16(0x1a59), // lofsa "Can" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x19a1), // lofsa "Visor" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x194d), // lofsa "note" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x18f9), // lofsa "valve" 18f3 + 0x4a, 0x04, // send 04 + // new code to enable input as well, needs 9 spare bytes + 0x38, SIG_UINT16(0x00e2), // canInput + 0x78, // push1 + 0x78, // push1 + 0x51, 0x2b, // class User + 0x4a, 0x06, // send 06 -> call User::canInput(1) + // original code, but changed a bit to save some more bytes + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x02, // ldi 02 + 0x04, // sub + 0x31, 0x12, // bnt [to ret] + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x04, // sub + 0x31, 0x0c, // bnt [to ret] + 0x78, // push1 + 0x1a, // eq? + 0x2f, 0x08, // bt [to ret] + // saves 7 bytes, we only need 3, so waste 4 bytes + 0x35, 0x00, // ldi 0 + 0x35, 0x00, // ldi 0 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry laurabow1Signatures[] = { - { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, + { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, + { true, 37, "armor open visor fix", 1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix }, + { true, 37, "armor move to fix", 2, laurabow1SignatureArmorMoveToFix, laurabow1PatchArmorMoveToFix }, + { true, 37, "allowing input, after oiling arm", 1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2066,6 +2862,18 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Phantasmagoria + +// script, description, signature patch +static const SciScriptPatcherEntry phantasmagoriaSignatures[] = { + { true, 901, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // Police Quest 1 VGA @@ -2207,6 +3015,20 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Police Quest 4 + +// script, description, signature patch +static const SciScriptPatcherEntry pq4Signatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // At the healer's house there is a bird's nest up on the tree. // The player can throw rocks at it until it falls to the ground. @@ -2547,10 +3369,41 @@ static const uint16 qfg1vgaPatchWhiteStagDagger[] = { PATCH_END }; +// The dagger range has a script bug that can freeze the game or cause Brutus to kill you even after you've killed him. +// This is a bug in the original game. +// +// When Bruno leaves, a 300 tick countdown starts. If you kill Brutus or leave room 73 within those 300 ticks then +// the game is left in a broken state. For the rest of the game, if you ever return to the dagger range from the +// east or west during the first half of the day then the game will freeze or Brutus will come back to life +// and kill you, even if you already killed him. +// +// Special thanks, credits and kudos to sluicebox, who did a ton of research on this and even found this game bug originally. +// +// Applies to at least: English floppy, Mac floppy +// Responsible method: brutusWaits::changeState +// Fixes bug #9558 +static const uint16 qfg1vgaSignatureBrutusScriptFreeze[] = { + 0x78, // push1 + 0x38, SIG_UINT16(0x144), // pushi 144h (324d) + 0x45, 0x05, 0x02, // call export 5 of script 0 + SIG_MAGICDWORD, + 0x34, SIG_UINT16(0x12c), // ldi 12Ch (300d) + 0x65, 0x20, // aTop ticks + SIG_END +}; + +static const uint16 qfg1vgaPatchBrutusScriptFreeze[] = { + 0x34, PATCH_UINT16(0), // ldi 0 (waste 7 bytes) + 0x35, 0x00, // ldi 0 + 0x35, 0x00, // ldi 0 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg1vgaSignatures[] = { { true, 41, "moving to castle gate", 1, qfg1vgaSignatureMoveToCastleGate, qfg1vgaPatchMoveToCastleGate }, { true, 55, "healer's hut, no delay for buy/steal", 1, qfg1vgaSignatureHealerHutNoDelay, qfg1vgaPatchHealerHutNoDelay }, + { true, 73, "brutus script freeze glitch", 1, qfg1vgaSignatureBrutusScriptFreeze, qfg1vgaPatchBrutusScriptFreeze }, { true, 77, "white stag dagger throw animation glitch", 1, qfg1vgaSignatureWhiteStagDagger, qfg1vgaPatchWhiteStagDagger }, { true, 96, "funny room script bug fixed", 1, qfg1vgaSignatureFunnyRoomFix, qfg1vgaPatchFunnyRoomFix }, { true, 210, "cheetaur description fixed", 1, qfg1vgaSignatureCheetaurDescription, qfg1vgaPatchCheetaurDescription }, @@ -2734,7 +3587,7 @@ static const uint16 qfg3PatchImportDialog[] = { // Teller::doChild. We jump to this call of hero::solvePuzzle to get that same // behaviour. // Applies to at least: English, German, Italian, French, Spanish Floppy -// Responsible method: unknown +// Responsible method: uhuraTell::doChild // Fixes bug: #5172 static const uint16 qfg3SignatureWooDialog[] = { SIG_MAGICDWORD, @@ -2907,6 +3760,84 @@ static const uint16 qfg3PatchChiefPriority[] = { PATCH_END }; +// There are 3 points that can't be achieved in the game. They should've been +// awarded for telling Rakeesh and Kreesha (room 285) about the Simabni +// initiation. +// However the array of posibble messages the hero can tell in that room +// (local 156) is missing the "Tell about Initiation" message (#31) which +// awards these points. +// This patch adds the message to that array, thus allowing the hero to tell +// that message (after completing the initiation) and gain the 3 points. +// A side effect of increasing the local156 array is that the next local +// array is shifted and shrinks in size from 4 words to 3. The patch changes +// the 2 locations in the script that reference that array, to point to the new +// location ($aa --> $ab). It is safe to shrink the 2nd array to 3 words +// because only the first element in it is ever used. +// +// Note: You have to re-enter the room in case a saved game was loaded from a +// previous version of ScummVM and that saved game was made inside that room. +// +// Applies to: English, French, German, Italian, Spanish and the GOG release. +// Responsible method: heap in script 285 +// Fixes bug #7086 +static const uint16 qfg3SignatureMissingPoints1[] = { + // local[$9c] = [0 -41 -76 1 -30 -77 -33 -34 -35 -36 -37 -42 -80 999] + // local[$aa] = [0 0 0 0] + SIG_UINT16(0x0000), // 0 START MARKER + SIG_MAGICDWORD, + SIG_UINT16(0xFFD7), // -41 "Greet" + SIG_UINT16(0xFFB4), // -76 "Say Good-bye" + SIG_UINT16(0x0001), // 1 "Tell about Tarna" + SIG_UINT16(0xFFE2), // -30 "Tell about Simani" + SIG_UINT16(0xFFB3), // -77 "Tell about Prisoner" + SIG_UINT16(0xFFDF), // -33 "Dispelled Leopard Lady" + SIG_UINT16(0xFFDE), // -34 "Tell about Leopard Lady" + SIG_UINT16(0xFFDD), // -35 "Tell about Leopard Lady" + SIG_UINT16(0xFFDC), // -36 "Tell about Leopard Lady" + SIG_UINT16(0xFFDB), // -37 "Tell about Village" + SIG_UINT16(0xFFD6), // -42 "Greet" + SIG_UINT16(0xFFB0), // -80 "Say Good-bye" + SIG_UINT16(0x03E7), // 999 END MARKER + SIG_ADDTOOFFSET(+2), // local[$aa][0] + SIG_END +}; + +static const uint16 qfg3PatchMissingPoints1[] = { + PATCH_ADDTOOFFSET(+14), + PATCH_UINT16(0xFFE1), // -31 "Tell about Initiation" + PATCH_UINT16(0xFFDE), // -34 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDD), // -35 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDC), // -36 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDB), // -37 "Tell about Village" + PATCH_UINT16(0xFFD6), // -42 "Greet" + PATCH_UINT16(0xFFB0), // -80 "Say Good-bye" + PATCH_UINT16(0x03E7), // 999 END MARKER + PATCH_GETORIGINALBYTE(+28), // local[$aa][0].low + PATCH_GETORIGINALBYTE(+29), // local[$aa][0].high + PATCH_END +}; + +static const uint16 qfg3SignatureMissingPoints2a[] = { + SIG_MAGICDWORD, + 0x35, 0x00, // ldi 0 + 0xb3, 0xaa, // sali local[$aa] + SIG_END +}; + +static const uint16 qfg3SignatureMissingPoints2b[] = { + SIG_MAGICDWORD, + 0x36, // push + 0x5b, 0x02, 0xaa, // lea local[$aa] + SIG_END +}; + +static const uint16 qfg3PatchMissingPoints2[] = { + PATCH_ADDTOOFFSET(+3), + 0xab, // local[$aa] ==> local[$ab] + PATCH_END +}; + + // Partly WORKAROUND: // During combat, the game is not properly throttled. That's because the game uses // an inner loop for combat and does not iterate through the main loop. @@ -2966,19 +3897,119 @@ static const uint16 qfg3PatchCombatSpeedThrottling2[] = { PATCH_END }; +// In room #750, when the hero enters from the top east path (room #755), it +// could go out of the contained-access polygon bounds, and be able to travel +// freely in the room. +// The reason is that the cutoff y value (42) that determines whether the hero +// enters from the top or bottom path is inaccurate: it's possible to enter the +// top path from as low as y=45. +// This patch changes the cutoff to be 50 which should be low enough. +// It also changes the position in which the hero enters from the top east path +// as the current location is hidden behind the tree. +// +// Applies to: English, French, German, Italian, Spanish and the GOG release. +// Responsible method: enterEast::changeState (script 750) +// Fixes bug #6693 +static const uint16 qfg3SignatureRoom750Bounds1[] = { + // (if (< (ego y?) 42) + 0x76, // push0 ("y") + 0x76, // push0 + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x04, // send 4 + SIG_MAGICDWORD, + 0x36, // push + 0x35, 42, // ldi 42 <-- comparing ego.y with 42 + 0x22, // lt? + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds1[] = { + // (if (< (ego y?) 50) + PATCH_ADDTOOFFSET(+8), + 50, // 42 --> 50 + PATCH_END +}; + +static const uint16 qfg3SignatureRoom750Bounds2[] = { + // (ego x: 294 y: 39) + 0x78, // push1 ("x") + 0x78, // push1 + 0x38, SIG_UINT16(294), // pushi 294 + 0x76, // push0 ("y") + 0x78, // push1 + SIG_MAGICDWORD, + 0x39, 29, // pushi 29 + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x0c, // send 12 + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds2[] = { + // (ego x: 320 y: 39) + PATCH_ADDTOOFFSET(+3), + PATCH_UINT16(320), // 294 --> 320 + PATCH_ADDTOOFFSET(+3), + 39, // 29 --> 39 + PATCH_END +}; + +static const uint16 qfg3SignatureRoom750Bounds3[] = { + // (ego setMotion: MoveTo 282 29 self) + 0x38, SIG_SELECTOR16(setMotion), // pushi "setMotion" 0x133 in QfG3 + 0x39, 0x04, // pushi 4 + 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo + 0x36, // push + 0x38, SIG_UINT16(282), // pushi 282 + SIG_MAGICDWORD, + 0x39, 29, // pushi 29 + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x0c, // send 12 + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds3[] = { + // (ego setMotion: MoveTo 309 35 self) + PATCH_ADDTOOFFSET(+9), + PATCH_UINT16(309), // 282 --> 309 + PATCH_ADDTOOFFSET(+1), + 35, // 29 --> 35 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg3Signatures[] = { - { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt }, - { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar }, - { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char }, - { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority }, - { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 }, - { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 }, + { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, + { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, + { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt }, + { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar }, + { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char }, + { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority }, + { true, 285, "missing points for telling about initiation heap", 1, qfg3SignatureMissingPoints1, qfg3PatchMissingPoints1 }, + { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2a, qfg3PatchMissingPoints2 }, + { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2b, qfg3PatchMissingPoints2 }, + { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 }, + { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 }, + { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds1, qfg3PatchRoom750Bounds1 }, + { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds2, qfg3PatchRoom750Bounds2 }, + { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds3, qfg3PatchRoom750Bounds3 }, SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Quest for Glory 4 + +// script, description, signature patch +static const SciScriptPatcherEntry qfg4Signatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // =========================================================================== // script 298 of sq4/floppy has an issue. object "nest" uses another property // which isn't included in property count. We return 0 in that case, the game @@ -3406,24 +4437,35 @@ static const uint16 sq1vgaSignatureSpiderDroidTiming[] = { 0x30, SIG_UINT16(0x0005), // bnt [further method code] 0x35, 0x00, // ldi 00 0x32, SIG_UINT16(0x0052), // jmp [super-call] - 0x89, 0xa6, // lsg global[a6] + 0x89, 0xa6, // lsg global[a6] <-- flag gets set to 1 when ego went up the skeleton tail, when going down it's set to 2 0x35, 0x01, // ldi 01 0x1a, // eq? - 0x30, SIG_UINT16(0x0012), // bnt [2nd code], in case global A6 <> 1 + 0x30, SIG_UINT16(0x0012), // bnt [PChase set code], in case global A6 <> 1 0x81, 0xb5, // lag global[b5] - 0x30, SIG_UINT16(0x000d), // bnt [2nd code], in case global B5 == 0 + 0x30, SIG_UINT16(0x000d), // bnt [PChase set code], in case global B5 == 0 0x38, SIG_UINT16(0x008c), // pushi 008c 0x78, // push1 0x72, SIG_UINT16(0x1cb6), // lofsa 1CB6 (moveToPath) 0x36, // push 0x54, 0x06, // self 06 0x32, SIG_UINT16(0x0038), // jmp [super-call] + // PChase set call 0x81, 0xb5, // lag global[B5] 0x18, // not 0x30, SIG_UINT16(0x0032), // bnt [super-call], in case global B5 <> 0 + // followed by: + // is spider in current room + // is global A6h == 2? -> set PChase SIG_END }; // 58 bytes) +// Global A6h <> 1 (did NOT went up the skeleton) +// Global B5h = 0 -> set PChase +// Global B5h <> 0 -> do not do anything +// Global A6h = 1 (did went up the skeleton) +// Global B5h = 0 -> set PChase +// Global B5h <> 0 -> set moveToPath + static const uint16 sq1vgaPatchSpiderDroidTiming[] = { 0x63, 0x4e, // pToa script 0x2f, 0x68, // bt [super-call] @@ -3448,8 +4490,8 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = { 0x65, 0x4c, // aTop cycleSpeed 0x65, 0x5e, // aTop moveSpeed // new code end - 0x89, 0xb5, // lsg global[B5] - 0x31, 0x13, // bnt [2nd code chunk] + 0x81, 0xb5, // lag global[B5] + 0x31, 0x13, // bnt [PChase code chunk] 0x89, 0xa6, // lsg global[A6] 0x35, 0x01, // ldi 01 0x1a, // eq? @@ -3531,6 +4573,103 @@ static const SciScriptPatcherEntry sq5Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Shivers + +// In room 35170, there is a CCTV control station with a joystick that must be +// clicked and dragged to pan the camera. In order to enable dragging, on +// mousedown, the vJoystick::handleEvent method calls vJoystick::doVerb(1), +// which enables the drag functionality of the joystick. However, +// vJoystick::handleEvent then makes a super call to ShiversProp::handleEvent, +// which calls vJoystick::doVerb(). This second call, which fails to pass an +// argument, causes an uninitialized read off the stack for the first parameter. +// In SSCI, this happens to work because the uninitialized value on the stack +// happens to be 1. Disabling the super call avoids the bad doVerb call without +// any apparent ill effect. +static const uint16 shiversSignatureJoystickFix[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0xa5), // pushi handleEvent + 0x78, // push1 + 0x8f, 0x01, // lsp 1 + 0x59, 0x02, // &rest 2 + 0x57, 0x7f, SIG_UINT16(6), // super ShiversProp[7f], 6 + SIG_END +}; + +static const uint16 shiversPatchJoystickFix[] = { + 0x48, // ret + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry shiversSignatures[] = { + { true, 35170, "fix CCTV joystick interaction", 1, shiversSignatureJoystickFix, shiversPatchJoystickFix }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#pragma mark - +#pragma mark Space Quest 6 + +// After the explosion in the Quarters of Deepship 86, the game tries to perform +// a dramatic long fade, but does this with an unreasonably large number of +// divisions which takes tens of seconds to finish (because transitions are not +// CPU-dependent in ScummVM). +static const uint16 sq6SlowTransitionSignature1[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x578), // pushi $0578 + 0x51, 0x33, // class Styler + SIG_END +}; + +static const uint16 sq6SlowTransitionPatch1[] = { + 0x38, SIG_UINT16(500), // pushi 500 + PATCH_END +}; + +// For whatever reason, SQ6 sets the default number of transition divisions to +// be a much larger value at startup (200 vs 30) if it thinks it is running in +// Windows. Room 410 (eulogy room) also unconditionally resets divisions to the +// larger value. +static const uint16 sq6SlowTransitionSignature2[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0xc8), // pushi $c8 + 0x51, 0x33, // class Styler + SIG_END +}; + +static const uint16 sq6SlowTransitionPatch2[] = { + 0x38, SIG_UINT16(30), // pushi 30 + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry sq6Signatures[] = { + { true, 0, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 }, + { true, 15, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 22, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 410, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 }, + { true, 460, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 500, "fix slow transitions", 1, sq6SlowTransitionSignature1, sq6SlowTransitionPatch1 }, + { true, 510, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#pragma mark - +#pragma mark Torins Passage + +// script, description, signature patch +static const SciScriptPatcherEntry torinSignatures[] = { + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, + { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +#endif + // ================================================================================= ScriptPatcher::ScriptPatcher() { @@ -3756,7 +4895,7 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, cons return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize); } -// Attention: Magic DWord is returns using platform specific byte order. This is done on purpose for performance. +// Attention: Magic DWord is returned using platform specific byte order. This is done on purpose for performance. void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) { Selector curSelector = -1; int magicOffset; @@ -3837,7 +4976,7 @@ void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescriptio } if (!magicDWordLeft) { // Magic DWORD is now known, convert to platform specific byte order - calculatedMagicDWord = READ_LE_UINT32(magicDWord); + calculatedMagicDWord = READ_UINT32(magicDWord); } } break; @@ -3862,7 +5001,8 @@ void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescriptio magicDWord[4 - magicDWordLeft] = (byte)curValue; magicDWordLeft--; if (!magicDWordLeft) { - calculatedMagicDWord = READ_LE_UINT32(magicDWord); + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_UINT32(magicDWord); } } break; @@ -3961,15 +5101,25 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_FREDDYPHARKAS: signatureTable = freddypharkasSignatures; break; +#ifdef ENABLE_SCI32 case GID_GK1: signatureTable = gk1Signatures; break; + case GID_GK2: + signatureTable = gk2Signatures; + break; +#endif case GID_KQ5: signatureTable = kq5Signatures; break; case GID_KQ6: signatureTable = kq6Signatures; break; +#ifdef ENABLE_SCI32 + case GID_KQ7: + signatureTable = kq7Signatures; + break; +#endif case GID_LAURABOW: signatureTable = laurabow1Signatures; break; @@ -3988,12 +5138,27 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_LSL6: signatureTable = larry6Signatures; break; +#ifdef ENABLE_SCI32 + case GID_LSL6HIRES: + signatureTable = larry6HiresSignatures; + break; +#endif case GID_MOTHERGOOSE256: signatureTable = mothergoose256Signatures; break; +#ifdef ENABLE_SCI32 + case GID_PHANTASMAGORIA: + signatureTable = phantasmagoriaSignatures; + break; +#endif case GID_PQ1: signatureTable = pq1vgaSignatures; break; +#ifdef ENABLE_SCI32 + case GID_PQ4: + signatureTable = pq4Signatures; + break; +#endif case GID_QFG1: signatureTable = qfg1egaSignatures; break; @@ -4006,6 +5171,14 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_QFG3: signatureTable = qfg3Signatures; break; +#ifdef ENABLE_SCI32 + case GID_QFG4: + signatureTable = qfg4Signatures; + break; + case GID_SHIVERS: + signatureTable = shiversSignatures; + break; +#endif case GID_SQ1: signatureTable = sq1vgaSignatures; break; @@ -4015,6 +5188,14 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_SQ5: signatureTable = sq5Signatures; break; +#ifdef ENABLE_SCI32 + case GID_SQ6: + signatureTable = sq6Signatures; + break; + case GID_TORIN: + signatureTable = torinSignatures; + break; +#endif default: break; } |