diff options
Diffstat (limited to 'engines/sci/engine/script_patches.cpp')
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 2148 |
1 files changed, 1899 insertions, 249 deletions
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 03cd1d06e9..d45c689985 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -94,11 +94,15 @@ static const char *const selectorNameTable[] = { "deskSarg", // Gabriel Knight "localize", // Freddy Pharkas "put", // Police Quest 1 VGA + "say", // Quest For Glory 1 VGA + "contains", // Quest For Glory 2 "solvePuzzle", // Quest For Glory 3 "timesShownID", // Space Quest 1 VGA "startText", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "startAudio", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support + "cycler", // Space Quest 4 / system selector + "setLoop", // Laura Bow 1 Colonel's Bequest NULL }; @@ -119,11 +123,15 @@ enum ScriptPatcherSelectors { SELECTOR_deskSarg, SELECTOR_localize, SELECTOR_put, + SELECTOR_say, + SELECTOR_contains, SELECTOR_solvePuzzle, SELECTOR_timesShownID, SELECTOR_startText, SELECTOR_startAudio, - SELECTOR_modNum + SELECTOR_modNum, + SELECTOR_cycler, + SELECTOR_setLoop }; // =========================================================================== @@ -371,12 +379,40 @@ static const SciScriptPatcherEntry ecoquest2Signatures[] = { }; // =========================================================================== +// Fan-made games +// Attention: Try to make script patches as specific as possible + +// CascadeQuest::autosave in script 994 is called various times to auto-save the game. +// The script use a fixed slot "999" for this purpose. This doesn't work in ScummVM, because we do not let +// scripts save directly into specific slots, but instead use virtual slots / detect scripts wanting to +// create a new slot. +// +// For this game we patch the code to use slot 99 instead. kSaveGame also checks for Cascade Quest, +// will then check, if slot 99 is asked for and will then use the actual slot 0, which is the official +// ScummVM auto-save slot. +// +// Responsible method: CascadeQuest::autosave +// Fixes bug: #7007 +static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x03e7), // pushi 3E7 (999d) -> save game slot 999 + 0x74, SIG_UINT16(0x06f8), // lofss "AutoSave" + 0x89, 0x1e, // lsg global[1E] + 0x43, 0x2d, 0x08, // callk SaveGame + SIG_END +}; + +static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = { + 0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot + PATCH_END +}; + // EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the // wrong address when an incorrect word is typed, therefore leading to an // infinite loop. This script bug was not apparent in SSCI, probably because // event handling was slightly different there, so it was never discovered. // Fixes bug: #5120 -static const uint16 fanmadeSignatureInfiniteLoop[] = { +static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = { 0x38, SIG_UINT16(0x004c), // pushi 004c 0x39, 0x00, // pushi 00 0x87, 0x01, // lap 01 @@ -387,19 +423,73 @@ static const uint16 fanmadeSignatureInfiniteLoop[] = { SIG_END }; -static const uint16 fanmadePatchInfiniteLoop[] = { +static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = { PATCH_ADDTOOFFSET(+10), - 0x30, SIG_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c + 0x30, PATCH_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c PATCH_END }; -// script, description, signature patch +// script, description, signature patch static const SciScriptPatcherEntry fanmadeSignatures[] = { - { true, 999, "infinite loop on typo", 1, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop }, + { true, 994, "Cascade Quest: fix auto-saving", 1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving }, + { true, 999, "Demo Quest: infinite loop on typo", 1, fanmadeSignatureDemoQuestInfiniteLoop, fanmadePatchDemoQuestInfiniteLoop }, SCI_SIGNATUREENTRY_TERMINATOR }; // =========================================================================== + +// WORKAROUND +// Freddy Pharkas intro screen +// Sierra used inner loops for the scaling of the 2 title views. +// Those inner loops don't call kGameIsRestarting, which is why +// we do not update the screen and we also do not throttle. +// +// This patch fixes this and makes it work. +// Applies to at least: English PC-CD +// Responsible method: sTownScript::changeState(1), sTownScript::changeState(3) (script 110) +static const uint16 freddypharkasSignatureIntroScaling[] = { + 0x38, SIG_ADDTOOFFSET(+2), // pushi (setLoop) (009b for PC CD) + 0x78, // push1 + PATCH_ADDTOOFFSET(1), // push0 for first code, push1 for second code + 0x38, SIG_ADDTOOFFSET(+2), // pushi (setStep) (0143 for PC CD) + 0x7a, // push2 + 0x39, 0x05, // pushi 05 + 0x3c, // dup + 0x72, SIG_ADDTOOFFSET(+2), // lofsa (view) + SIG_MAGICDWORD, + 0x4a, 0x1e, // send 1e + 0x35, 0x0a, // ldi 0a + 0xa3, 0x02, // sal local[2] + // start of inner loop + 0x8b, 0x02, // lsl local[2] + SIG_ADDTOOFFSET(+43), // skip almost all of inner loop + 0xa3, 0x02, // sal local[2] + 0x33, 0xcf, // jmp [inner loop start] + SIG_END +}; + +static const uint16 freddypharkasPatchIntroScaling[] = { + // remove setLoop(), objects in heap are already prepared, saves 5 bytes + 0x38, + PATCH_GETORIGINALBYTE(+6), + PATCH_GETORIGINALBYTE(+7), // pushi (setStep) + 0x7a, // push2 + 0x39, 0x05, // pushi 05 + 0x3c, // dup + 0x72, + PATCH_GETORIGINALBYTE(+13), + PATCH_GETORIGINALBYTE(+14), // lofsa (view) + 0x4a, 0x18, // send 18 - adjusted + 0x35, 0x0a, // ldi 0a + 0xa3, 0x02, // sal local[2] + // start of new inner loop + 0x39, 0x00, // pushi 00 + 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered + SIG_ADDTOOFFSET(+47), // skip almost all of inner loop + 0x33, 0xca, // jmp [inner loop start] + PATCH_END +}; + // script 0 of freddy pharkas/CD PointsSound::check waits for a signal and if // no signal received will call kDoSound(0xD) which is a dummy in sierra sci // and ScummVM and will use acc (which is not set by the dummy) to trigger @@ -518,6 +608,7 @@ static const uint16 freddypharkasPatchMacInventory[] = { static const SciScriptPatcherEntry freddypharkasSignatures[] = { { true, 0, "CD: score early disposal", 1, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal }, { true, 15, "Mac: broken inventory", 1, freddypharkasSignatureMacInventory, freddypharkasPatchMacInventory }, + { true, 110, "intro scaling workaround", 2, freddypharkasSignatureIntroScaling, freddypharkasPatchIntroScaling }, { true, 235, "CD: canister pickup hang", 3, freddypharkasSignatureCanisterHang, freddypharkasPatchCanisterHang }, { true, 320, "ladder event issue", 2, freddypharkasSignatureLadderEvent, freddypharkasPatchLadderEvent }, SCI_SIGNATUREENTRY_TERMINATOR @@ -562,12 +653,12 @@ static const uint16 gk1SignatureDay6PoliceSleep[] = { 0x34, SIG_UINT16(0x00dc), // ldi 220 0x65, SIG_ADDTOOFFSET(+1), // aTop cycles (1a for PC, 1c for Mac) 0x32, // jmp [end] - 0 + SIG_END }; static const uint16 gk1PatchDay6PoliceSleep[] = { PATCH_ADDTOOFFSET(+5), - 0x34, SIG_UINT16(0x002a), // ldi 42 + 0x34, PATCH_UINT16(0x002a), // ldi 42 0x65, PATCH_GETORIGINALBYTEADJUST(+9, +2), // aTop seconds (1c for PC, 1e for Mac) PATCH_END }; @@ -655,12 +746,115 @@ 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 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 }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -751,6 +945,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 @@ -781,9 +1001,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 }; @@ -1197,8 +1418,10 @@ static const uint16 kq6CDPatchAudioTextSupportJumpAlways[] = { }; // Fixes "Girl In The Tower" to get played in dual mode as well +// Also changes credits to use CD audio for dual mode. +// // Applies to at least: PC-CD -// Patched method: rm740::cue +// Patched method: rm740::cue (script 740), sCredits::init (script 52) static const uint16 kq6CDSignatureAudioTextSupportGirlInTheTower[] = { SIG_MAGICDWORD, 0x89, 0x5a, // lsg global[5a] @@ -1325,6 +1548,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = { { false, 928, "CD: audio + text support KQ6 4", 1, kq6CDSignatureAudioTextSupport4, kq6CDPatchAudioTextSupport4 }, { false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards }, { false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways }, + { false, 52, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, { false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, { false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel }, { false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport }, @@ -1332,6 +1556,167 @@ static const SciScriptPatcherEntry kq6Signatures[] = { }; // =========================================================================== + +// King's Quest 7 has really weird subtitles. It seems as if the subtitles were +// not fully finished. +// +// Method kqMessager::findTalker in script 0 tries to figure out, which class to use for +// displaying subtitles. It uses the "talker" data of the given message to do that. +// Strangely this "talker" data seems to be quite broken. +// For example chapter 2 starts with a cutscene. +// Troll king: "Welcome, most beautiful of princesses!" - talker 6 +// Which is followed by the princess going +// "Hmm?" - which is set to talker 99, normally the princess is talker 7. +// +// Talker 99 is seen as unknown and thus treated as "narrator", which makes +// the scripts put the text at the top of the game screen and even use a +// different font. +// +// In other cases, when the player character thinks to himself talker 99 +// is also used. In such situations it may make somewhat sense to do so, +// but putting the text at the top of the screen is also irritating to the player. +// It's really weird. +// +// The scripts also put the regular text in the middle of the screen, blocking +// animations. +// +// And for certain rooms, the subtitle box may use another color +// like for example pink/purple at the start of chapter 5. +// +// We fix all of that (hopefully - lots of testing is required). +// We put the text at the bottom of the play screen. +// We also make the scripts use the regular KQTalker instead of KQNarrator. +// And we also make the subtitle box use color 255, which is fixed white. +// +// Applies to at least: PC CD 1.4 English, 1.51 English, 1.51 German, 2.00 English +// Patched method: KQNarrator::init (script 31) +static const uint16 kq7SignatureSubtitleFix1[] = { + SIG_MAGICDWORD, + 0x39, 0x25, // pushi 25h (fore) + 0x78, // push1 + 0x39, 0x06, // pushi 06 - sets back to 6 + 0x39, 0x26, // pushi 26 (back) + 0x78, // push1 + 0x78, // push1 - sets back to 1 + 0x39, 0x2a, // pushi 2Ah (font) + 0x78, // push1 + 0x89, 0x16, // lsg global[16h] - sets font to global[16h] + 0x7a, // push2 (y) + 0x78, // push1 + 0x76, // push0 - sets y to 0 + 0x54, SIG_UINT16(0x0018), // self 18h + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix1[] = { + 0x33, 0x12, // jmp [skip special init code] + PATCH_END +}; + +// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English +// Patched method: Narrator::init (script 64928) +static const uint16 kq7SignatureSubtitleFix2[] = { + SIG_MAGICDWORD, + 0x89, 0x5a, // lsg global[5a] + 0x35, 0x02, // ldi 02 + 0x12, // and + 0x31, 0x1e, // bnt [skip audio volume code] + 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51) + 0x76, // push0 + 0x81, 0x01, // lag global[1] + 0x4a, 0x04, 0x00, // send 04 + 0x65, 0x32, // aTop curVolume + 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51) + 0x78, // push1 + 0x67, 0x32, // pTos curVolume + 0x35, 0x02, // ldi 02 + 0x06, // mul + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x08, // div + 0x36, // push + 0x81, 0x01, // lag global[1] + 0x4a, 0x06, 0x00, // send 06 + // end of volume code + 0x35, 0x01, // ldi 01 + 0x65, 0x28, // aTop initialized + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix2[] = { + PATCH_ADDTOOFFSET(+5), // skip to bnt + 0x31, 0x1b, // bnt [skip audio volume code] + PATCH_ADDTOOFFSET(+15), // right after "aTop curVolume / pushi masterVolume / push1" + 0x7a, // push2 + 0x06, // mul (saves 3 bytes in total) + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x08, // div + 0x36, // push + 0x81, 0x01, // lag global[1] + 0x4a, 0x06, 0x00, // send 06 + // end of volume code + 0x35, 118, // ldi 118d + 0x65, 0x16, // aTop y + 0x78, // push1 (saves 1 byte) + 0x69, 0x28, // sTop initialized + PATCH_END +}; + +// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English +// Patched method: Narrator::say (script 64928) +static const uint16 kq7SignatureSubtitleFix3[] = { + SIG_MAGICDWORD, + 0x63, 0x28, // pToa initialized + 0x18, // not + 0x31, 0x07, // bnt [skip init code] + 0x38, SIG_ADDTOOFFSET(+2), // pushi init (008Eh for 2.00, 0093h for 1.51) + 0x76, // push0 + 0x54, SIG_UINT16(0x0004), // self 04 + // end of init code + 0x8f, 0x00, // lsp param[0] + 0x35, 0x01, // ldi 01 + 0x1e, // gt? + 0x31, 0x08, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x31, 0x04, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x33, 0x02, // jmp [over set acc to 0 code] + 0x35, 0x00, // ldi 00 + 0x65, 0x18, // aTop caller + SIG_END +}; + +static const uint16 kq7PatchSubtitleFix3[] = { + PATCH_ADDTOOFFSET(+2), // skip over "pToa initialized code" + 0x2f, 0x0c, // bt [skip init code] - saved 1 byte + 0x38, + PATCH_GETORIGINALBYTE(+6), + PATCH_GETORIGINALBYTE(+7), // pushi (init) + 0x76, // push0 + 0x54, PATCH_UINT16(0x0004), // self 04 + // additionally set background color here (5 bytes) + 0x34, PATCH_UINT16(255), // pushi 255d + 0x65, 0x2e, // aTop back + // end of init code + 0x8f, 0x00, // lsp param[0] + 0x35, 0x01, // ldi 01 - this may get optimized to get another byte + 0x1e, // gt? + 0x31, 0x04, // bnt [set acc to 0] + 0x87, 0x02, // lap param[2] + 0x2f, 0x02, // bt [over set acc to 0 code] + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry kq7Signatures[] = { + { true, 31, "subtitle fix 1/3", 1, kq7SignatureSubtitleFix1, kq7PatchSubtitleFix1 }, + { true, 64928, "subtitle fix 2/3", 1, kq7SignatureSubtitleFix2, kq7PatchSubtitleFix2 }, + { true, 64928, "subtitle fix 3/3", 1, kq7SignatureSubtitleFix3, kq7PatchSubtitleFix3 }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +// =========================================================================== // Script 210 in the German version of Longbow handles the case where Robin // hands out the scroll to Marion and then types his name using the hand code. // The German version script contains a typo (probably a copy/paste error), @@ -1371,9 +1756,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 }; @@ -1421,6 +1906,37 @@ static const SciScriptPatcherEntry larry2Signatures[] = { // =========================================================================== // Leisure Suit Larry 5 +// In Miami the player can call the green card telephone number and get +// green card including limo at the same time in the English 1.000 PC release. +// This results later in a broken game in case the player doesn't read +// the second telephone number for the actual limousine service, because +// in that case it's impossible for the player to get back to the airport. +// +// We disable the code, that is responsible to make the limo arrive. +// +// This bug was fixed in the European (dual language) versions of the game. +// +// Applies to at least: English PC floppy (1.000) +// Responsible method: sPhone::changeState(40) +static const uint16 larry5SignatureGreenCardLimoBug[] = { + 0x7a, // push2 + SIG_MAGICDWORD, + 0x39, 0x07, // pushi 07 + 0x39, 0x0c, // pushi 0Ch + 0x45, 0x0a, 0x04, // call export 10 of script 0 + 0x78, // push1 + 0x39, 0x26, // pushi 26h (limo arrived flag) + 0x45, 0x07, 0x02, // call export 7 of script 0 (sets flag) + SIG_END +}; + +static const uint16 larry5PatchGreenCardLimoBug[] = { + PATCH_ADDTOOFFSET(+8), + 0x34, PATCH_UINT16(0), // ldi 0000 (dummy) + 0x34, PATCH_UINT16(0), // ldi 0000 (dummy) + PATCH_END +}; + // In one of the conversations near the end (to be exact - room 380 and the text // about using champagne on Reverse Biaz - only used when you actually did that // in the game), the German text is too large, causing the textbox to get too large. @@ -1444,6 +1960,7 @@ static const uint16 larry5PatchGermanEndingPattiTalker[] = { // script, description, signature patch static const SciScriptPatcherEntry larry5Signatures[] = { + { true, 280, "English-only: fix green card limo bug", 1, larry5SignatureGreenCardLimoBug, larry5PatchGreenCardLimoBug }, { true, 380, "German-only: Enlarge Patti Textbox", 1, larry5SignatureGermanEndingPattiTalker, larry5PatchGermanEndingPattiTalker }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1500,6 +2017,194 @@ static const SciScriptPatcherEntry larry6Signatures[] = { }; // =========================================================================== +// Laura Bow 1 - Colonel's Bequest +// +// This is basically just a broken easter egg in Colonel's Bequest. +// A plane can show up in room 4, but that only happens really rarely. +// Anyway the Sierra developer seems to have just entered the wrong loop, +// which is why the statue view is used instead (loop 0). +// We fix it to use the correct loop. +// +// This is only broken in the PC version. It was fixed for Amiga + Atari ST. +// +// Credits to OmerMor, for finding it. + +// Applies to at least: English PC Floppy +// Responsible method: room4::init +static const uint16 laurabow1SignatureEasterEggViewFix[] = { + 0x78, // push1 + 0x76, // push0 + SIG_MAGICDWORD, + 0x38, SIG_SELECTOR16(setLoop), // pushi "setLoop" + 0x78, // push1 + 0x39, 0x03, // pushi 3 (loop 3, view only has 3 loops) + SIG_END +}; + +static const uint16 laurabow1PatchEasterEggViewFix[] = { + PATCH_ADDTOOFFSET(+7), + 0x02, // change loop to 2 + PATCH_END +}; + +// When oiling the armor or opening the visor of the armor, the scripts +// first check if Laura/ego is near the armor and if she is not, they will move her +// to the armor. After that further code is executed. +// +// The current location is checked by a ego::inRect() call. +// +// The given rect for the inRect call inside openVisor::changeState was made larger for Atari ST/Amiga versions. +// We change the PC version to use the same rect. +// +// Additionally the coordinate, that Laura is moved to, is 152, 107 and may not be reachable depending on where +// Laura/ego was, when "use oil on helmet of armor" / "open visor of armor" got entered. +// Bad coordinates are for example 82, 110, which then cause collisions and effectively an endless loop. +// Game will effectively "freeze" and the user is only able to restore a previous game. +// This also happened, when using the original interpreter. +// We change the destination coordinate to 152, 110, which seems to be reachable all the time. +// +// The following patch fixes the rect for the PC version of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: openVisor::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorOpenVisorFix[] = { + 0x39, 0x04, // pushi 04 + SIG_MAGICDWORD, + 0x39, 0x6a, // pushi 6a (106d) + 0x38, SIG_UINT16(0x96), // pushi 0096 (150d) + 0x39, 0x6c, // pushi 6c (108d) + 0x38, SIG_UINT16(0x98), // pushi 0098 (152d) + SIG_END +}; + +static const uint16 laurabow1PatchArmorOpenVisorFix[] = { + PATCH_ADDTOOFFSET(+2), + 0x39, 0x68, // pushi 68 (104d) (-2) + 0x38, SIG_UINT16(0x94), // pushi 0094 (148d) (-2) + 0x39, 0x6f, // pushi 6f (111d) (+3) + 0x38, SIG_UINT16(0x9a), // pushi 009a (154d) (+2) + PATCH_END +}; + +// This here fixes the destination coordinate (exact details are above). +// +// Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy +// Responsible method: openVisor::changeState, oiling::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorMoveToFix[] = { + SIG_MAGICDWORD, + 0x36, // push + 0x39, 0x6b, // pushi 6B (107d) + 0x38, SIG_UINT16(0x0098), // pushi 98 (152d) + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + SIG_END +}; + +static const uint16 laurabow1PatchArmorMoveToFix[] = { + PATCH_ADDTOOFFSET(+1), + 0x39, 0x6e, // pushi 6E (110d) - adjust x, so that no collision can occur anymore + PATCH_END +}; + +// In some cases like for example when the player oils the arm of the armor, command input stays +// disabled, even when the player exits fast enough, so that Laura doesn't die. +// +// This is caused by the scripts only enabling control (directional movement), but do not enable command input as well. +// +// This bug also happens, when using the original interpreter. +// And it was fixed for the Atari ST + Amiga versions of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7) +// Fixes bug: #7154 +static const uint16 laurabow1SignatureArmorOilingArmFix[] = { + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + SIG_MAGICDWORD, + 0x72, SIG_UINT16(0x1a5c), // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x19a1), // lofsa "Visor" + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x194a), // lofsa "note" + 0x4a, 0x04, // send 04 + 0x38, SIG_UINT16(0x0089), // pushi 89h + 0x76, // push0 + 0x72, SIG_UINT16(0x18f3), // lofsa "valve" + 0x4a, 0x04, // send 04 + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x02, // ldi 02 + 0x1c, // ne? + 0x30, SIG_UINT16(0x0014), // bnt [to ret] + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x05, // ldi 05 + 0x1c, // ne? + 0x30, SIG_UINT16(0x000c), // bnt [to ret] + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x06, // ldi 06 + 0x1c, // ne? + 0x30, SIG_UINT16(0x0004), // bnt [to ret] + // followed by code to call script 0 export to re-enable controls and call setMotion + SIG_END +}; + +static const uint16 laurabow1PatchArmorOilingArmFix[] = { + PATCH_ADDTOOFFSET(+3), // skip over pushi 89h + 0x3c, // dup + 0x3c, // dup + 0x3c, // dup + // saves a total of 6 bytes + 0x76, // push0 + 0x72, SIG_UINT16(0x1a59), // lofsa "Can" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x19a1), // lofsa "Visor" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x194d), // lofsa "note" + 0x4a, 0x04, // send 04 + 0x76, // push0 + 0x72, SIG_UINT16(0x18f9), // lofsa "valve" 18f3 + 0x4a, 0x04, // send 04 + // new code to enable input as well, needs 9 spare bytes + 0x38, SIG_UINT16(0x00e2), // canInput + 0x78, // push1 + 0x78, // push1 + 0x51, 0x2b, // class User + 0x4a, 0x06, // send 06 -> call User::canInput(1) + // original code, but changed a bit to save some more bytes + 0x8b, 0x34, // lsl local[34h] + 0x35, 0x02, // ldi 02 + 0x04, // sub + 0x31, 0x12, // bnt [to ret] + 0x36, // push + 0x35, 0x03, // ldi 03 + 0x04, // sub + 0x31, 0x0c, // bnt [to ret] + 0x78, // push1 + 0x1a, // eq? + 0x2f, 0x08, // bt [to ret] + // saves 7 bytes, we only need 3, so waste 4 bytes + 0x35, 0x00, // ldi 0 + 0x35, 0x00, // ldi 0 + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry laurabow1Signatures[] = { + { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, + { true, 37, "armor open visor fix", 1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix }, + { true, 37, "armor move to fix", 2, laurabow1SignatureArmorMoveToFix, laurabow1PatchArmorMoveToFix }, + { true, 37, "allowing input, after oiling arm", 1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +// =========================================================================== // Laura Bow 2 // // Moving away the painting in the room with the hidden safe is problematic @@ -1804,15 +2509,103 @@ static const SciScriptPatcherEntry laurabow2Signatures[] = { // MG::replay somewhat calculates the savedgame-id used when saving again // this doesn't work right and we remove the code completely. // We set the savedgame-id directly right after restoring in kRestoreGame. +// We also draw the background picture in here instead. +// This Mixed Up Mother Goose draws the background picture before restoring, +// instead of doing it properly in MG::replay. This fixes graphic issues, +// when restoring from GMM. +// +// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns +// Responsible method: MG::replay (script 0) static const uint16 mothergoose256SignatureReplay[] = { + 0x7a, // push2 + 0x78, // push1 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x43, 0x70, 0x04, // callk MemorySegment + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x76, // push0 + 0x43, 0x62, 0x04, // callk StrAt + 0xa1, 0xaa, // sag global[AAh] + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x78, // push1 + 0x43, 0x62, 0x04, // callk StrAt + 0x36, // push + 0x35, 0x20, // ldi 20 + 0x04, // sub + 0xa1, SIG_ADDTOOFFSET(+1), // sag global[57h] -> FM-Towns [9Dh] + // 35 bytes + 0x39, 0x03, // pushi 03 + 0x89, SIG_ADDTOOFFSET(+1), // lsg global[1Dh] -> FM-Towns [1Eh] + 0x76, // push0 + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x7a, // push2 + 0x43, 0x62, 0x04, // callk StrAt + 0x36, // push + 0x35, 0x01, // ldi 01 + 0x04, // sub + 0x36, // push + 0x43, 0x62, 0x06, // callk StrAt + // 22 bytes + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BE] + 0x36, // push + 0x39, 0x03, // pushi 03 + 0x43, 0x62, 0x04, // callk StrAt + // 10 bytes 0x36, // push 0x35, SIG_MAGICDWORD, 0x20, // ldi 20 0x04, // sub 0xa1, 0xb3, // sag global[b3] + // 6 bytes SIG_END }; static const uint16 mothergoose256PatchReplay[] = { + 0x39, 0x06, // pushi 06 + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(200), // pushi 200d + 0x38, PATCH_UINT16(320), // pushi 320d + 0x76, // push0 + 0x76, // push0 + 0x43, 0x15, 0x0c, // callk SetPort -> set picture port to full screen + // 15 bytes + 0x39, 0x04, // pushi 04 + 0x3c, // dup + 0x76, // push0 + 0x38, PATCH_UINT16(255), // pushi 255d + 0x76, // push0 + 0x43, 0x6f, 0x08, // callk Palette -> set intensity to 0 for all colors + // 11 bytes + 0x7a, // push2 + 0x38, PATCH_UINT16(800), // pushi 800 + 0x76, // push0 + 0x43, 0x08, 0x04, // callk DrawPic -> draw picture 800 + // 8 bytes + 0x39, 0x06, // pushi 06 + 0x39, 0x0c, // pushi 0Ch + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(200), // push 200 + 0x38, PATCH_UINT16(320), // push 320 + 0x78, // push1 + 0x43, 0x6c, 0x0c, // callk Graph -> send everything to screen + // 16 bytes + 0x39, 0x06, // pushi 06 + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(156), // pushi 156d + 0x38, PATCH_UINT16(258), // pushi 258d + 0x39, 0x03, // pushi 03 + 0x39, 0x04, // pushi 04 + 0x43, 0x15, 0x0c, // callk SetPort -> set picture port back + // 17 bytes 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy) 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy) PATCH_END @@ -1820,6 +2613,9 @@ static const uint16 mothergoose256PatchReplay[] = { // when saving, it also checks if the savegame ID is below 13. // we change this to check if below 113 instead +// +// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns +// Responsible method: Game::save (script 994 for SCI1), MG::save (script 0 for SCI1.1) static const uint16 mothergoose256SignatureSaveLimit[] = { 0x89, SIG_MAGICDWORD, 0xb3, // lsg global[b3] 0x35, 0x0d, // ldi 0d @@ -1843,6 +2639,50 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = { // =========================================================================== // Police Quest 1 VGA + +// When briefing is about to start in room 15, other officers will get into the room too. +// When one of those officers gets into the way of ego, they will tell the player to sit down. +// But control will be disabled right at that point. Ego may then go to his seat by himself, +// or more often than not will just stand there. The player is unable to do anything. +// +// Sergeant Dooley will then enter the room. Tell the player to sit down 3 times and after +// that it's game over. +// +// Because the Sergeant is telling the player to sit down, one has to assume that the player +// is meant to still be in control. Which is why this script patch removes disabling of player control. +// +// The script also tries to make ego walk to the chair, but it fails because it gets stuck with other +// actors. So I guess the safest way is to remove all of that and let the player do it manually. +// +// The responsible method seems to use a few hardcoded texts, which is why I have to assume that it's +// not used anywhere else. I also checked all scripts and couldn't find any other calls to it. +// +// This of course also happens when using the original interpreter. +// +// Scripts work like this: manX::doit (script 134) triggers gab::changeState, which then triggers rm015::notify +// +// Applies to at least: English floppy +// Responsible method: gab::changeState (script 152) +// Fixes bug: #5865 +static const uint16 pq1vgaSignatureBriefingGettingStuck[] = { + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 (disable control) + 0x38, SIG_ADDTOOFFSET(+2), // pushi notify + 0x76, // push0 + 0x81, 0x02, // lag global[2] (get current room) + 0x4a, 0x04, // send 04 + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x01, // ldi 01 + 0x02, // add + SIG_END +}; + +static const uint16 pq1vgaPatchBriefingGettingStuck[] = { + 0x33, 0x0a, // jmp to lsl local[2], skip over export 2 and ::notify + PATCH_END // rm015::notify would try to make ego walk to the chair +}; + // When at the police station, you can put or get your gun from your locker. // The script, that handles this, is buggy. It disposes the gun as soon as // you click, but then waits 2 seconds before it also closes the locker. @@ -1930,10 +2770,11 @@ static const uint16 pq1vgaPatchMapSaveRestoreBug[] = { PATCH_END }; -// script, description, signature patch +// script, description, signature patch static const SciScriptPatcherEntry pq1vgaSignatures[] = { - { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug }, - { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug }, + { true, 152, "getting stuck while briefing is about to start", 1, pq1vgaSignatureBriefingGettingStuck, pq1vgaPatchBriefingGettingStuck }, + { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug }, + { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2087,21 +2928,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 }; @@ -2116,7 +2966,7 @@ static const uint16 qfg1vgaSignatureCheetaurDescription[] = { 0x34, SIG_UINT16(0x01b8), // ldi 01b8 0x1a, // eq? 0x31, 0x16, // bnt 16 - 0x38, SIG_UINT16(0x0127), // pushi 0127 + 0x38, SIG_SELECTOR16(say), // pushi 0127h (selector "say") 0x39, 0x06, // pushi 06 0x39, 0x03, // pushi 03 0x78, // push1 @@ -2140,6 +2990,7 @@ static const uint16 qfg1vgaPatchCheetaurDescription[] = { // Local 5 of that room is a timer, that closes the door (object door11). // Setting it to 1 during happyFace::changeState(0) stops door11::doit from // calling goTo6::init, so the whole issue is stopped from happening. +// // Applies to at least: English floppy // Responsible method: happyFace::changeState, door11::doit // Fixes bug #6181 @@ -2166,20 +3017,172 @@ static const uint16 qfg1vgaPatchFunnyRoomFix[] = { PATCH_END }; +// The player is able to buy (and also steal) potions in the healer's hut +// Strangely Sierra delays the actual buy/get potion code for 60 ticks +// Why they did that is unknown. The code is triggered anyway only after +// the relevant dialog boxes are closed. +// +// This delay causes problems in case the user quickly enters the inventory. +// That's why we change the amount of ticks to 1, so that the remaining states +// are executed right after the dialog boxes are closed. +// +// Applies to at least: English floppy +// Responsible method: cueItScript::changeState +// Fixes bug #6706 +static const uint16 qfg1vgaSignatureHealerHutNoDelay[] = { + 0x65, 0x14, // aTop 14 (state) + 0x36, // push + 0x3c, // dup + 0x35, 0x00, // ldi 00 + 0x1a, // eq? + 0x31, 0x07, // bnt 07 [-> next state] + SIG_MAGICDWORD, + 0x35, 0x3c, // ldi 3c (60 ticks) + 0x65, 0x20, // aTop ticks + 0x32, // jmp [-> end of method] + SIG_END +}; + +static const uint16 qfg1vgaPatchHealerHutNoDelay[] = { + PATCH_ADDTOOFFSET(+9), + 0x35, 0x01, // ldi 01 (1 tick only, so that execution will resume as soon as dialog box is closed) + PATCH_END +}; + +// When following the white stag, you can actually enter the 2nd room from the mushroom/fairy location, +// which results in ego entering from the top. When you then throw a dagger at the stag, one animation +// frame will stay on screen, because of a script bug. +// +// Applies to at least: English floppy, Mac floppy +// Responsible method: stagHurt::changeState +// Fixes bug #6135 +static const uint16 qfg1vgaSignatureWhiteStagDagger[] = { + 0x87, 0x01, // lap param[1] + 0x65, 0x14, // aTop state + 0x36, // push + 0x3c, // dup + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x31, 0x16, // bnt [next parameter check] + 0x76, // push0 + 0x45, 0x02, 0x00, // callb export 2 from script 0, 0 + SIG_MAGICDWORD, + 0x38, SIG_SELECTOR16(say), // pushi 0127h (selector "say") + 0x39, 0x05, // pushi 05 + 0x39, 0x03, // pushi 03 + 0x39, 0x51, // pushi 51h + 0x76, // push0 + 0x76, // push0 + 0x7c, // pushSelf + 0x81, 0x5b, // lag global[5Bh] -> qg1Messager + 0x4a, 0x0e, // send 0Eh -> qg1Messager::say(3, 51h, 0, 0, stagHurt) + 0x33, 0x12, // jmp -> [ret] + 0x3c, // dup + 0x35, 0x01, // ldi 1 + 0x1a, // eq? + 0x31, 0x0c, // bnt [ret] + 0x38, // pushi... + SIG_ADDTOOFFSET(+11), + 0x3a, // toss + 0x48, // ret + SIG_END +}; + +static const uint16 qfg1vgaPatchWhiteStagDagger[] = { + PATCH_ADDTOOFFSET(+4), + 0x2f, 0x05, // bt [next check] (state != 0) + // state = 0 code + 0x35, 0x01, // ldi 1 + 0x65, 0x1a, // aTop cycles + 0x48, // ret + 0x36, // push + 0x35, 0x01, // ldi 1 + 0x1a, // eq? + 0x31, 0x16, // bnt [state = 2 code] + // state = 1 code + 0x76, // push0 + 0x45, 0x02, 0x00, // callb export 2 from script 0, 0 + 0x38, PATCH_SELECTOR16(say), // pushi 0127h (selector "say") + 0x39, 0x05, // pushi 05 + 0x39, 0x03, // pushi 03 + 0x39, 0x51, // pushi 51h + 0x76, // push0 + 0x76, // push0 + 0x7c, // pushSelf + 0x81, 0x5b, // lag global[5Bh] -> qg1Messager + 0x4a, 0x0e, // send 0Eh -> qg1Messager::say(3, 51h, 0, 0, stagHurt) + 0x48, // ret + // state = 2 code + PATCH_ADDTOOFFSET(+13), + 0x48, // ret (remove toss) + 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, 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 }, { true, 215, "fight event issue", 1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents }, { true, 216, "weapon master event issue", 1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents }, + { true, 331, "moving to crusher", 1, qfg1vgaSignatureMoveToCrusher, qfg1vgaPatchMoveToCrusher }, { true, 814, "window text temp space", 1, qfg1vgaSignatureTempSpace, qfg1vgaPatchTempSpace }, { true, 814, "dialog header offset", 3, qfg1vgaSignatureDialogHeader, qfg1vgaPatchDialogHeader }, - { true, 331, "moving to crusher", 1, qfg1vgaSignatureMoveToCrusher, qfg1vgaPatchMoveToCrusher }, - { true, 41, "moving to castle gate", 1, qfg1vgaSignatureMoveToCastleGate, qfg1vgaPatchMoveToCastleGate }, - { true, 210, "cheetaur description fixed", 1, qfg1vgaSignatureCheetaurDescription, qfg1vgaPatchCheetaurDescription }, - { true, 96, "funny room script bug fixed", 1, qfg1vgaSignatureFunnyRoomFix, qfg1vgaPatchFunnyRoomFix }, SCI_SIGNATUREENTRY_TERMINATOR }; // =========================================================================== + +// This is a very complicated bug. +// When the player encounters an enemy in the desert while riding a saurus and later +// tries to get back on it by entering "ride", the game will not give control back +// to the player. +// +// This is caused by script mountSaurus getting triggered twice. +// Once by entering the command "ride" and then a second time by a proximity check. +// +// Both are calling mountSaurus::init() in script 20, this one disables controls +// then mountSaurus::changeState() from script 660 is triggered +// mountSaurus::changeState(5) finally calls mountSaurus::dispose(), which is also in script 20 +// which finally re-enables controls +// +// A fix is difficult to implement. The code in script 20 is generic and used by multiple objects +// +// 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, 0x02, // ldi 5 + SIG_MAGICDWORD, + 0x1a, // eq? + 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[] = { + 0x81, 0x66, // lag 66h + 0x2e, SIG_UINT16(0x0040), // bt [to dispose code] + 0x35, 0x00, // ldi 0 (waste 2 bytes) + PATCH_END +}; + // Script 944 in QFG2 contains the FileSelector system class, used in the // character import screen. This gets incorrectly called constantly, whenever // the user clicks on a button in order to refresh the file list. This was @@ -2211,9 +3214,58 @@ static const uint16 qfg2PatchImportDialog[] = { PATCH_END }; -// script, description, signature patch +// Quest For Glory 2 character import doesn't properly set the character type +// in versions 1.102 and below, which makes all importerted characters a fighter. +// +// Sierra released an official patch. However the fix is really easy to +// implement on our side, so we also patch the flaw in here in case we find it. +// +// The version released on GOG is 1.102 without this patch applied, so us +// patching it is quite useful. +// +// Applies to at least: English Floppy +// Responsible method: importHero::changeState +// Fixes bug: inside versions 1.102 and below +static const uint16 qfg2SignatureImportCharType[] = { + 0x35, 0x04, // ldi 04 + 0x90, SIG_UINT16(0x023b), // lagi global[23Bh] + 0x02, // add + 0x36, // push + 0x35, 0x04, // ldi 04 + 0x08, // div + 0x36, // push + 0x35, 0x0d, // ldi 0D + 0xb0, SIG_UINT16(0x023b), // sagi global[023Bh] + 0x8b, 0x1f, // lsl local[1Fh] + 0x35, 0x05, // ldi 05 + SIG_MAGICDWORD, + 0xb0, SIG_UINT16(0x0150), // sagi global[0150h] + 0x8b, 0x02, // lsl local[02h] + SIG_END +}; + +static const uint16 qfg2PatchImportCharType[] = { + 0x80, PATCH_UINT16(0x023f), // lag global[23Fh] <-- patched to save 2 bytes + 0x02, // add + 0x36, // push + 0x35, 0x04, // ldi 04 + 0x08, // div + 0x36, // push + 0xa8, SIG_UINT16(0x0248), // ssg global[0248h] <-- patched to save 2 bytes + 0x8b, 0x1f, // lsl local[1Fh] + 0xa8, SIG_UINT16(0x0155), // ssg global[0155h] <-- patched to save 2 bytes + // new code, directly from the official sierra patch file + 0x83, 0x01, // lal local[01h] + 0xa1, 0xbb, // sag global[BBh] + 0xa1, 0x73, // sag global[73h] + PATCH_END +}; + +// script, description, signature patch static const SciScriptPatcherEntry qfg2Signatures[] = { - { true, 944, "import dialog continuous calls", 1, qfg2SignatureImportDialog, qfg2PatchImportDialog }, + { 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 }; @@ -2253,7 +3305,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, @@ -2282,10 +3334,383 @@ static const uint16 qfg3PatchWooDialog[] = { PATCH_END }; -// script, description, signature patch +// Alternative version, with uint16 offsets, for GOG release of QfG3. +static const uint16 qfg3SignatureWooDialogAlt[] = { + SIG_MAGICDWORD, + 0x67, 0x12, // pTos 12 (query) + 0x35, 0xb6, // ldi b6 + 0x1a, // eq? + 0x2e, SIG_UINT16(0x0005), // bt 05 + 0x67, 0x12, // pTos 12 (query) + 0x35, 0x9b, // ldi 9b + 0x1a, // eq? + 0x30, SIG_UINT16(0x000c), // bnt 0c + 0x38, SIG_SELECTOR16(solvePuzzle), // pushi 0297 + 0x7a, // push2 + 0x38, SIG_UINT16(0x010c), // pushi 010c + 0x7a, // push2 + 0x81, 0x00, // lag 00 + 0x4a, 0x08, // send 08 + 0x67, 0x12, // pTos 12 (query) + 0x35, 0xb5, // ldi b5 + SIG_END +}; + +static const uint16 qfg3PatchWooDialogAlt[] = { + PATCH_ADDTOOFFSET(+0x2C), + 0x33, 0x12, // jmp to 0x708, the call to hero::solvePuzzle for 0xFFFC + PATCH_END +}; + +// When exporting characters at the end of Quest for Glory 3, the underlying +// code has issues with values, that are above 9999. +// For further study: https://github.com/Blazingstix/QFGImporter/blob/master/QFGImporter/QFGImporter/QFG3.txt +// +// If a value is above 9999, parts or even the whole character file will get corrupted. +// +// We are fixing the code because of that. We are patching code, that is calculating the checksum +// and add extra code to lower such values to 9999. +// +// Applies to at least: English, French, German, Italian, Spanish floppy +// Responsible method: saveHero::changeState +// Fixes bug #6807 +static const uint16 qfg3SignatureExportChar[] = { + 0x35, SIG_ADDTOOFFSET(+1), // ldi 00 / ldi 01 (2 loops, we patch both) + 0xa5, 0x00, // sat temp[0] [contains index to data] + 0x8d, 0x00, // lst temp[0] + SIG_MAGICDWORD, + 0x35, 0x2c, // ldi 2c + 0x22, // lt? [index above or equal 2Ch (44d)? + 0x31, 0x23, // bnt [exit loop] + // from this point it's actually useless code, maybe a sci compiler bug + 0x8d, 0x00, // lst temp[0] + 0x35, 0x01, // ldi 01 + 0x02, // add + 0x9b, 0x00, // lsli local[0] ---------- load local[0 + ACC] onto stack + 0x8d, 0x00, // lst temp[0] + 0x35, 0x01, // ldi 01 + 0x02, // add + 0xb3, 0x00, // sali local[0] ---------- save stack to local[0 + ACC] + // end of useless code + 0x8b, SIG_ADDTOOFFSET(+1), // lsl local[36h/37h] ---- load local[36h/37h] onto stack + 0x8d, 0x00, // lst temp[0] + 0x35, 0x01, // ldi 01 + 0x02, // add + 0x93, 0x00, // lali local[0] ---------- load local[0 + ACC] into ACC + 0x02, // add -------------------- add ACC + stack and put into ACC + 0xa3, SIG_ADDTOOFFSET(+1), // sal local[36h/37h] ---- save ACC to local[36h/37h] + 0x8d, 0x00, // lst temp[0] ------------ temp[0] to stack + 0x35, 0x02, // ldi 02 + 0x02, // add -------------------- add 2 to stack + 0xa5, 0x00, // sat temp[0] ------------ save ACC to temp[0] + 0x33, 0xd6, // jmp [loop] + SIG_END +}; + +static const uint16 qfg3PatchExportChar[] = { + PATCH_ADDTOOFFSET(+11), + 0x85, 0x00, // lat temp[0] + 0x9b, 0x01, // lsli local[0] + 1 ------ load local[ ACC + 1] onto stack + 0x3c, // dup + 0x34, PATCH_UINT16(0x2710), // ldi 2710h (10000d) + 0x2c, // ult? ------------------- is value smaller than 10000? + 0x2f, 0x0a, // bt [jump over] + 0x3a, // toss + 0x38, PATCH_UINT16(0x270f), // pushi 270fh (9999d) + 0x3c, // dup + 0x85, 0x00, // lat temp[0] + 0xba, PATCH_UINT16(0x0001), // ssli local[0] + 1 ------ save stack to local[ ACC + 1] (UINT16 to waste 1 byte) + // jump offset + 0x83, PATCH_GETORIGINALBYTE(+26), // lal local[37h/36h] ---- load local[37h/36h] into ACC + 0x02, // add -------------------- add local[37h/36h] + data value + PATCH_END +}; + +// Quest for Glory 3 doesn't properly import the character type of Quest for Glory 1 character files. +// This issue was never addressed. It's caused by Sierra reading data directly from the local +// area, which is only set by Quest For Glory 2 import data, instead of reading the properly set global variable. +// +// We fix it, by also directly setting the local variable. +// +// Applies to at least: English, French, German, Italian, Spanish floppy +// Responsible method: importHero::changeState(4) +static const uint16 qfg3SignatureImportQfG1Char[] = { + SIG_MAGICDWORD, + 0x82, SIG_UINT16(0x0238), // lal local[0x0238] + 0xa0, SIG_UINT16(0x016a), // sag global[0x016a] + 0xa1, 0x7d, // sag global[0x7d] + 0x35, 0x01, // ldi 01 + 0x99, 0xfb, // lsgi global[0xfb] + SIG_END +}; + +static const uint16 qfg3PatchImportQfG1Char[] = { + PATCH_ADDTOOFFSET(+8), + 0xa3, 0x01, // sal 01 -> also set local[01] + 0x89, 0xfc, // lsg global[0xFD] -> save 2 bytes + PATCH_END +}; + +// The chief in his hut (room 640) is not drawn using the correct priority, +// which results in a graphical glitch. This is a game bug and also happens +// in Sierra's SCI. We adjust priority accordingly to fix it. +// +// Applies to at least: English, French, German, Italian, Spanish floppy +// Responsible method: heap in script 640 +// Fixes bug #5173 +static const uint16 qfg3SignatureChiefPriority[] = { + SIG_MAGICDWORD, + SIG_UINT16(0x0002), // yStep 0x0002 + SIG_UINT16(0x0281), // view 0x0281 + SIG_UINT16(0x0000), // loop 0x0000 + SIG_UINT16(0x0000), // cel 0x0000 + SIG_UINT16(0x0000), // priority 0x0000 + SIG_UINT16(0x0000), // underbits 0x0000 + SIG_UINT16(0x1000), // signal 0x1000 + SIG_END +}; + +static const uint16 qfg3PatchChiefPriority[] = { + PATCH_ADDTOOFFSET(+8), + PATCH_UINT16(0x000A), // new priority 0x000A (10d) + PATCH_ADDTOOFFSET(+2), + PATCH_UINT16(0x1010), // signal 0x1010 (set fixed priority flag) + PATCH_END +}; + +// There are 3 points that can't be achieved in the game. They should've been +// awarded for telling Rakeesh and Kreesha (room 285) about the Simabni +// initiation. +// However the array of posibble messages the hero can tell in that room +// (local 156) is missing the "Tell about Initiation" message (#31) which +// awards these points. +// This patch adds the message to that array, thus allowing the hero to tell +// that message (after completing the initiation) and gain the 3 points. +// A side effect of increasing the local156 array is that the next local +// array is shifted and shrinks in size from 4 words to 3. The patch changes +// the 2 locations in the script that reference that array, to point to the new +// location ($aa --> $ab). It is safe to shrink the 2nd array to 3 words +// because only the first element in it is ever used. +// +// Note: You have to re-enter the room in case a saved game was loaded from a +// previous version of ScummVM and that saved game was made inside that room. +// +// Applies to: English, French, German, Italian, Spanish and the GOG release. +// Responsible method: heap in script 285 +// Fixes bug #7086 +static const uint16 qfg3SignatureMissingPoints1[] = { + // local[$9c] = [0 -41 -76 1 -30 -77 -33 -34 -35 -36 -37 -42 -80 999] + // local[$aa] = [0 0 0 0] + SIG_UINT16(0x0000), // 0 START MARKER + SIG_MAGICDWORD, + SIG_UINT16(0xFFD7), // -41 "Greet" + SIG_UINT16(0xFFB4), // -76 "Say Good-bye" + SIG_UINT16(0x0001), // 1 "Tell about Tarna" + SIG_UINT16(0xFFE2), // -30 "Tell about Simani" + SIG_UINT16(0xFFB3), // -77 "Tell about Prisoner" + SIG_UINT16(0xFFDF), // -33 "Dispelled Leopard Lady" + SIG_UINT16(0xFFDE), // -34 "Tell about Leopard Lady" + SIG_UINT16(0xFFDD), // -35 "Tell about Leopard Lady" + SIG_UINT16(0xFFDC), // -36 "Tell about Leopard Lady" + SIG_UINT16(0xFFDB), // -37 "Tell about Village" + SIG_UINT16(0xFFD6), // -42 "Greet" + SIG_UINT16(0xFFB0), // -80 "Say Good-bye" + SIG_UINT16(0x03E7), // 999 END MARKER + SIG_ADDTOOFFSET(+2), // local[$aa][0] + SIG_END +}; + +static const uint16 qfg3PatchMissingPoints1[] = { + PATCH_ADDTOOFFSET(+14), + PATCH_UINT16(0xFFE1), // -31 "Tell about Initiation" + PATCH_UINT16(0xFFDE), // -34 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDD), // -35 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDC), // -36 "Tell about Leopard Lady" + PATCH_UINT16(0xFFDB), // -37 "Tell about Village" + PATCH_UINT16(0xFFD6), // -42 "Greet" + PATCH_UINT16(0xFFB0), // -80 "Say Good-bye" + PATCH_UINT16(0x03E7), // 999 END MARKER + PATCH_GETORIGINALBYTE(+28), // local[$aa][0].low + PATCH_GETORIGINALBYTE(+29), // local[$aa][0].high + PATCH_END +}; + +static const uint16 qfg3SignatureMissingPoints2a[] = { + SIG_MAGICDWORD, + 0x35, 0x00, // ldi 0 + 0xb3, 0xaa, // sali local[$aa] + SIG_END +}; + +static const uint16 qfg3SignatureMissingPoints2b[] = { + SIG_MAGICDWORD, + 0x36, // push + 0x5b, 0x02, 0xaa, // lea local[$aa] + SIG_END +}; + +static const uint16 qfg3PatchMissingPoints2[] = { + PATCH_ADDTOOFFSET(+3), + 0xab, // local[$aa] ==> local[$ab] + PATCH_END +}; + + +// Partly WORKAROUND: +// During combat, the game is not properly throttled. That's because the game uses +// an inner loop for combat and does not iterate through the main loop. +// It also doesn't call kGameIsRestarting. This may get fixed properly at some point +// by rewriting the speed throttler. +// +// Additionally Sierra set the cycle speed of the hero to 0. Which explains +// why the actions of the hero are so incredibly fast. This issue also happened +// in the original interpreter, when the computer was too powerful. +// +// Applies to at least: English, French, German, Italian, Spanish PC floppy +// Responsible method: combatControls::dispatchEvent (script 550) + WarriorObj in heap +// Fixes bug #6247 +static const uint16 qfg3SignatureCombatSpeedThrottling1[] = { + 0x31, 0x0d, // bnt [skip code] + SIG_MAGICDWORD, + 0x89, 0xd2, // lsg global[D2h] + 0x35, 0x00, // ldi 0 + 0x1e, // gt? + 0x31, 0x06, // bnt [skip code] + 0xe1, 0xd2, // -ag global[D2h] (jump skips over this) + 0x81, 0x58, // lag global[58h] + 0xa3, 0x01, // sal local[01] + SIG_END +}; + +static const uint16 qfg3PatchCombatSpeedThrottling1[] = { + 0x80, 0xd2, // lsg global[D2h] + 0x14, // or + 0x31, 0x06, // bnt [skip code] - saves 4 bytes + 0xe1, 0xd2, // -ag global[D2h] + 0x81, 0x58, // lag global[58h] + 0xa3, 0x01, // sal local[01] (jump skips over this) + // our code + 0x76, // push0 + 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered + PATCH_END +}; + +static const uint16 qfg3SignatureCombatSpeedThrottling2[] = { + SIG_MAGICDWORD, + SIG_UINT16(12), // priority 12 + SIG_UINT16(0), // underbits 0 + SIG_UINT16(0x4010), // signal 4010h + SIG_ADDTOOFFSET(+18), + SIG_UINT16(0), // scaleSignal 0 + SIG_UINT16(128), // scaleX + SIG_UINT16(128), // scaleY + SIG_UINT16(128), // maxScale + SIG_UINT16(0), // cycleSpeed + SIG_END +}; + +static const uint16 qfg3PatchCombatSpeedThrottling2[] = { + PATCH_ADDTOOFFSET(+32), + PATCH_UINT16(5), // set cycleSpeed to 5 + PATCH_END +}; + +// In room #750, when the hero enters from the top east path (room #755), it +// could go out of the contained-access polygon bounds, and be able to travel +// freely in the room. +// The reason is that the cutoff y value (42) that determines whether the hero +// enters from the top or bottom path is inaccurate: it's possible to enter the +// top path from as low as y=45. +// This patch changes the cutoff to be 50 which should be low enough. +// It also changes the position in which the hero enters from the top east path +// as the current location is hidden behind the tree. +// +// Applies to: English, French, German, Italian, Spanish and the GOG release. +// Responsible method: enterEast::changeState (script 750) +// Fixes bug #6693 +static const uint16 qfg3SignatureRoom750Bounds1[] = { + // (if (< (ego y?) 42) + 0x76, // push0 ("y") + 0x76, // push0 + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x04, // send 4 + SIG_MAGICDWORD, + 0x36, // push + 0x35, 42, // ldi 42 <-- comparing ego.y with 42 + 0x22, // lt? + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds1[] = { + // (if (< (ego y?) 50) + PATCH_ADDTOOFFSET(+8), + 50, // 42 --> 50 + PATCH_END +}; + +static const uint16 qfg3SignatureRoom750Bounds2[] = { + // (ego x: 294 y: 39) + 0x78, // push1 ("x") + 0x78, // push1 + 0x38, SIG_UINT16(294), // pushi 294 + 0x76, // push0 ("y") + 0x78, // push1 + SIG_MAGICDWORD, + 0x39, 29, // pushi 29 + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x0c, // send 12 + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds2[] = { + // (ego x: 320 y: 39) + PATCH_ADDTOOFFSET(+3), + PATCH_UINT16(320), // 294 --> 320 + PATCH_ADDTOOFFSET(+3), + 39, // 29 --> 39 + PATCH_END +}; + +static const uint16 qfg3SignatureRoom750Bounds3[] = { + // (ego setMotion: MoveTo 282 29 self) + 0x38, SIG_SELECTOR16(setMotion), // pushi "setMotion" 0x133 in QfG3 + 0x39, 0x04, // pushi 4 + 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo + 0x36, // push + 0x38, SIG_UINT16(282), // pushi 282 + SIG_MAGICDWORD, + 0x39, 29, // pushi 29 + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] (ego) + 0x4a, 0x0c, // send 12 + SIG_END +}; + +static const uint16 qfg3PatchRoom750Bounds3[] = { + // (ego setMotion: MoveTo 309 35 self) + PATCH_ADDTOOFFSET(+9), + PATCH_UINT16(309), // 282 --> 309 + PATCH_ADDTOOFFSET(+1), + 35, // 29 --> 35 + PATCH_END +}; + +// script, description, signature patch static const SciScriptPatcherEntry qfg3Signatures[] = { - { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, + { true, 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 }; @@ -2345,6 +3770,162 @@ static const uint16 sq4FloppyPatchThrowStuffAtSequelPoliceBug[] = { PATCH_END }; +// Right at the start of Space Quest 4 CD, when walking up in the first room, ego will +// immediately walk down just after entering the upper room. +// +// This is caused by the scripts setting ego's vertical coordinate to 189 (BDh), which is the +// trigger in rooms to walk to the room below it. Sometimes this isn't triggered, because +// the scripts also initiate a motion to vertical coordinate 188 (BCh). When you lower the game's speed, +// this bug normally always triggers. And it triggers of course also in the original interpreter. +// +// It doesn't happen in PC floppy, because nsRect is not the same as in CD. +// +// We fix it by setting ego's vertical coordinate to 188 and we also initiate a motion to 187. +// +// Applies to at least: English PC CD +// Responsible method: rm045::doit +// Fixes bug: #5468 +static const uint16 sq4CdSignatureWalkInFromBelowRoom45[] = { + 0x76, // push0 + SIG_MAGICDWORD, + 0x78, // push1 + 0x38, SIG_UINT16(0x00bd), // pushi 00BDh + 0x38, SIG_ADDTOOFFSET(+2), // pushi [setMotion selector] + 0x39, 0x03, // pushi 3 + 0x51, SIG_ADDTOOFFSET(+1), // class [MoveTo] + 0x36, // push + 0x78, // push1 + 0x76, // push0 + 0x81, 0x00, // lag global[0] + 0x4a, 0x04, // send 04 -> get ego::x + 0x36, // push + 0x38, SIG_UINT16(0x00bc), // pushi 00BCh + SIG_END +}; + +static const uint16 sq4CdPatchWalkInFromBelowRoom45[] = { + PATCH_ADDTOOFFSET(+2), + 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh + PATCH_ADDTOOFFSET(+15), + 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 +}; + + +// For Space Quest 4 CD, Sierra added a pick up animation for Roger, when he picks up the rope. +// +// When the player is detected by the zombie right at the start of the game, while picking up the rope, +// scripts bomb out. This also happens, when using the original interpreter. +// +// This is caused by code, that's supposed to make Roger face the arriving drone. +// We fix it, by checking if ego::cycler is actually set before calling that code. +// +// Applies to at least: English PC CD +// Responsible method: droidShoots::changeState(3) +// Fixes bug: #6076 +static const uint16 sq4CdSignatureGettingShotWhileGettingRope[] = { + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x02fa), // jmp [end] + SIG_MAGICDWORD, + 0x3c, // dup + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x0b, // bnt [state 3 check] + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x02e9), // jmp [end] + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x1e, // bnt [state 4 check] + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls again?? + 0x7a, // push2 + 0x89, 0x00, // lsg global[0] + 0x72, SIG_UINT16(0x0242), // lofsa deathDroid + 0x36, // push + 0x45, 0x0d, 0x04, // call export 13 of script 0 -> set heading of ego to face droid + SIG_END +}; + +static const uint16 sq4CdPatchGettingShotWhileGettingRope[] = { + PATCH_ADDTOOFFSET(+11), + // this makes state 2 only do the 2 cycles wait, controls should always be disabled already at this point + 0x2f, 0xf3, // bt [previous state aTop cycles code] + // Now we check for state 3, this change saves us 11 bytes + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x29, // bnt [state 4 check] + // new state 3 code + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 (disable controls, actually not needed) + 0x38, PATCH_SELECTOR16(cycler), // pushi cycler + 0x76, // push0 + 0x81, 0x00, // lag global[0] + 0x4a, 0x04, // send 04 (get ego::cycler) + 0x30, PATCH_UINT16(10), // bnt [jump over heading call] + PATCH_END +}; + // The scripts in SQ4CD support simultaneous playing of speech and subtitles, // but this was not available as an option. The following two patches enable // this functionality in the game's GUI options dialog. @@ -2439,8 +4020,11 @@ static const uint16 sq4CdPatchTextOptions[] = { 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, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions }, + { true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 }, + { true, 396, "CD: get points for changing back clothes fix",1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes }, + { true, 701, "CD: getting shot, while getting rope", 1, sq4CdSignatureGettingShotWhileGettingRope, sq4CdPatchGettingShotWhileGettingRope }, { true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon }, + { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions }, { true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2471,6 +4055,29 @@ static const uint16 sq1vgaPatchUlenceFlatsTimepodGfxGlitch[] = { PATCH_END }; +// In Ulence Flats, there is a space ship, that you will use at some point. +// Near that space ship are 2 force field generators. +// When you look at the top of those generators, the game will crash. +// This happens also in Sierra SCI. It's caused by a jump, that goes out of bounds. +// We currently do not know if this was caused by a compiler glitch or if it was a developer error. +// Anyway we patch this glitchy code, so that the game won't crash anymore. +// +// Applies to at least: English Floppy +// Responsible method: radar1::doVerb +// Fixes bug: #6816 +static const uint16 sq1vgaSignatureUlenceFlatsGeneratorGlitch[] = { + SIG_MAGICDWORD, 0x1a, // eq? + 0x30, SIG_UINT16(0xcdf4), // bnt absolute 0xf000 + SIG_END +}; + +static const uint16 sq1vgaPatchUlenceFlatsGeneratorGlitch[] = { + PATCH_ADDTOOFFSET(+1), + 0x32, PATCH_UINT16(0x0000), // jmp 0x0000 (waste bytes) + PATCH_END +}; + +// No documentation for this patch (TODO) static const uint16 sq1vgaSignatureEgoShowsCard[] = { SIG_MAGICDWORD, 0x38, SIG_SELECTOR16(timesShownID), // push "timesShownID" @@ -2534,24 +4141,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] @@ -2576,8 +4194,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? @@ -2595,6 +4213,7 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = { // script, description, signature patch static const SciScriptPatcherEntry sq1vgaSignatures[] = { { true, 45, "Ulence Flats: timepod graphic glitch", 1, sq1vgaSignatureUlenceFlatsTimepodGfxGlitch, sq1vgaPatchUlenceFlatsTimepodGfxGlitch }, + { true, 45, "Ulence Flats: force field generator glitch", 1, sq1vgaSignatureUlenceFlatsGeneratorGlitch, sq1vgaPatchUlenceFlatsGeneratorGlitch }, { true, 58, "Sarien armory droid zapping ego first time", 1, sq1vgaSignatureEgoShowsCard, sq1vgaPatchEgoShowsCard }, { true, 704, "spider droid timing issue", 1, sq1vgaSignatureSpiderDroidTiming, sq1vgaPatchSpiderDroidTiming }, SCI_SIGNATUREENTRY_TERMINATOR @@ -2670,6 +4289,7 @@ ScriptPatcher::ScriptPatcher() { _selectorIdTable[selectorNr] = -1; _runtimeTable = NULL; + _isMacSci11 = false; } ScriptPatcher::~ScriptPatcher() { @@ -2678,7 +4298,7 @@ ScriptPatcher::~ScriptPatcher() { } // will actually patch previously found signature area -void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset, const bool isMacSci11) { +void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset) { const uint16 *patchData = patchEntry->patchData; byte orgData[PATCH_VALUELIMIT]; int32 offset = signatureOffset; @@ -2742,7 +4362,7 @@ void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *sc default: byte1 = 0; byte2 = 0; } - if (!isMacSci11) { + if (!_isMacSci11) { scriptData[offset++] = byte1; scriptData[offset++] = byte2; } else { @@ -2769,102 +4389,107 @@ void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *sc } } -// will return -1 if no match was found, otherwise an offset to the start of the signature match -int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize, const bool isMacSci11) { - if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay - return -1; +bool ScriptPatcher::verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const byte *scriptData, const uint32 scriptSize) { + uint16 sigSelector = 0; + + uint16 sigWord = *signatureData; + while (sigWord != SIG_END) { + uint16 sigCommand = sigWord & SIG_COMMANDMASK; + uint16 sigValue = sigWord & SIG_VALUEMASK; + switch (sigCommand) { + case SIG_CODE_ADDTOOFFSET: { + // add value to offset + byteOffset += sigValue; + break; + } + case SIG_CODE_UINT16: + case SIG_CODE_SELECTOR16: { + if ((byteOffset + 1) < scriptSize) { + byte byte1; + byte byte2; - const uint32 magicDWord = runtimeEntry->magicDWord; // is platform-specific BE/LE form, so that the later match will work - const uint32 searchLimit = scriptSize - 3; - uint32 DWordOffset = 0; - // first search for the magic DWORD - while (DWordOffset < searchLimit) { - if (magicDWord == READ_UINT32(scriptData + DWordOffset)) { - // magic DWORD found, check if actual signature matches - uint32 offset = DWordOffset + runtimeEntry->magicOffset; - uint32 byteOffset = offset; - const uint16 *signatureData = patchEntry->signatureData; - uint16 sigSelector = 0; - - uint16 sigWord = *signatureData; - while (sigWord != SIG_END) { - uint16 sigCommand = sigWord & SIG_COMMANDMASK; - uint16 sigValue = sigWord & SIG_VALUEMASK; switch (sigCommand) { - case SIG_CODE_ADDTOOFFSET: { - // add value to offset - byteOffset += sigValue; + case SIG_CODE_UINT16: { + byte1 = sigValue & SIG_BYTEMASK; + signatureData++; sigWord = *signatureData; + if (sigWord & SIG_COMMANDMASK) + error("Script-Patcher: signature inconsistent\nFaulty signature: '%s'", signatureDescription); + byte2 = sigWord & SIG_BYTEMASK; break; } - case SIG_CODE_UINT16: case SIG_CODE_SELECTOR16: { - if ((byteOffset + 1) < scriptSize) { - byte byte1; - byte byte2; - - switch (sigCommand) { - case SIG_CODE_UINT16: { - byte1 = sigValue & SIG_BYTEMASK; - signatureData++; sigWord = *signatureData; - if (sigWord & SIG_COMMANDMASK) - error("Script-Patcher: signature inconsistent\nFaulty patch: '%s'", patchEntry->description); - byte2 = sigWord & SIG_BYTEMASK; - break; - } - case SIG_CODE_SELECTOR16: { - sigSelector = _selectorIdTable[sigValue]; - byte1 = sigSelector & 0xFF; - byte2 = sigSelector >> 8; - break; - } - default: - byte1 = 0; byte2 = 0; - } - if (!isMacSci11) { - if ((scriptData[byteOffset] != byte1) || (scriptData[byteOffset + 1] != byte2)) - sigWord = SIG_MISMATCH; - } else { - // SCI1.1+ on macintosh had uint16s in script in BE-order - if ((scriptData[byteOffset] != byte2) || (scriptData[byteOffset + 1] != byte1)) - sigWord = SIG_MISMATCH; - } - byteOffset += 2; - } else { - sigWord = SIG_MISMATCH; - } + sigSelector = _selectorIdTable[sigValue]; + byte1 = sigSelector & 0xFF; + byte2 = sigSelector >> 8; break; } - case SIG_CODE_SELECTOR8: { - if (byteOffset < scriptSize) { - sigSelector = _selectorIdTable[sigValue]; - if (sigSelector & 0xFF00) - error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", patchEntry->description); - if (scriptData[byteOffset] != (sigSelector & 0xFF)) - sigWord = SIG_MISMATCH; - byteOffset++; - } else { - sigWord = SIG_MISMATCH; // out of bounds - } - break; + default: + byte1 = 0; byte2 = 0; } - case SIG_CODE_BYTE: - if (byteOffset < scriptSize) { - if (scriptData[byteOffset] != sigWord) - sigWord = SIG_MISMATCH; - byteOffset++; - } else { - sigWord = SIG_MISMATCH; // out of bounds - } + if (!_isMacSci11) { + if ((scriptData[byteOffset] != byte1) || (scriptData[byteOffset + 1] != byte2)) + sigWord = SIG_MISMATCH; + } else { + // SCI1.1+ on macintosh had uint16s in script in BE-order + if ((scriptData[byteOffset] != byte2) || (scriptData[byteOffset + 1] != byte1)) + sigWord = SIG_MISMATCH; } + byteOffset += 2; + } else { + sigWord = SIG_MISMATCH; + } + break; + } + case SIG_CODE_SELECTOR8: { + if (byteOffset < scriptSize) { + sigSelector = _selectorIdTable[sigValue]; + if (sigSelector & 0xFF00) + error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty signature: '%s'", signatureDescription); + if (scriptData[byteOffset] != (sigSelector & 0xFF)) + sigWord = SIG_MISMATCH; + byteOffset++; + } else { + sigWord = SIG_MISMATCH; // out of bounds + } + break; + } + case SIG_CODE_BYTE: + if (byteOffset < scriptSize) { + if (scriptData[byteOffset] != sigWord) + sigWord = SIG_MISMATCH; + byteOffset++; + } else { + sigWord = SIG_MISMATCH; // out of bounds + } + } - if (sigWord == SIG_MISMATCH) - break; + if (sigWord == SIG_MISMATCH) + break; - signatureData++; - sigWord = *signatureData; - } + signatureData++; + sigWord = *signatureData; + } - if (sigWord == SIG_END) // signature fully matched? + if (sigWord == SIG_END) // signature fully matched? + return true; + return false; +} + +// will return -1 if no match was found, otherwise an offset to the start of the signature match +int32 ScriptPatcher::findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize) { + if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay + return -1; + + // magicDWord is in platform-specific BE/LE form, so that the later match will work, this was done for performance + const uint32 searchLimit = scriptSize - 3; + uint32 DWordOffset = 0; + // first search for the magic DWORD + while (DWordOffset < searchLimit) { + if (magicDWord == READ_UINT32(scriptData + DWordOffset)) { + // magic DWORD found, check if actual signature matches + uint32 offset = DWordOffset + magicOffset; + + if (verifySignature(offset, signatureData, patchDescription, scriptData, scriptSize)) return offset; } DWordOffset++; @@ -2873,22 +4498,147 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciS return -1; } -// This method calculates the magic DWORD for each entry in the signature table -// and it also initializes the selector table for selectors used in the signatures/patches of the current game -void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable, bool isMacSci11) { - const SciScriptPatcherEntry *curEntry = patchTable; - SciScriptPatcherRuntimeEntry *curRuntimeEntry; +int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) { + return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize); +} + +// Attention: Magic DWord is returned using platform specific byte order. This is done on purpose for performance. +void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) { Selector curSelector = -1; - int step; int magicOffset; byte magicDWord[4]; int magicDWordLeft = 0; - const uint16 *curData; uint16 curWord; uint16 curCommand; uint32 curValue; byte byte1 = 0; byte byte2 = 0; + + memset(magicDWord, 0, sizeof(magicDWord)); + + curWord = *signatureData; + magicOffset = 0; + while (curWord != SIG_END) { + curCommand = curWord & SIG_COMMANDMASK; + curValue = curWord & SIG_VALUEMASK; + switch (curCommand) { + case SIG_MAGICDWORD: { + if (magicDWordIncluded) { + if ((calculatedMagicDWord) || (magicDWordLeft)) + error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", signatureDescription); + magicDWordLeft = 4; + calculatedMagicDWordOffset = magicOffset; + } else { + error("Script-Patcher: Magic-DWORD sequence found in patch data\nFaulty patch: '%s'", signatureDescription); + } + break; + } + case SIG_CODE_ADDTOOFFSET: { + magicOffset -= curValue; + if (magicDWordLeft) + error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", signatureDescription); + break; + } + case SIG_CODE_UINT16: + case SIG_CODE_SELECTOR16: { + // UINT16 or 1 + switch (curCommand) { + case SIG_CODE_UINT16: { + signatureData++; curWord = *signatureData; + if (curWord & SIG_COMMANDMASK) + error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", signatureDescription); + if (!_isMacSci11) { + byte1 = curValue; + byte2 = curWord & SIG_BYTEMASK; + } else { + byte1 = curWord & SIG_BYTEMASK; + byte2 = curValue; + } + break; + } + case SIG_CODE_SELECTOR16: { + curSelector = _selectorIdTable[curValue]; + if (curSelector == -1) { + curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); + _selectorIdTable[curValue] = curSelector; + } + if (!_isMacSci11) { + byte1 = curSelector & 0x00FF; + byte2 = curSelector >> 8; + } else { + byte1 = curSelector >> 8; + byte2 = curSelector & 0x00FF; + } + break; + } + } + magicOffset -= 2; + if (magicDWordLeft) { + // Remember current word for Magic DWORD + magicDWord[4 - magicDWordLeft] = byte1; + magicDWordLeft--; + if (magicDWordLeft) { + magicDWord[4 - magicDWordLeft] = byte2; + magicDWordLeft--; + } + if (!magicDWordLeft) { + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_UINT32(magicDWord); + } + } + break; + } + case SIG_CODE_BYTE: + case SIG_CODE_SELECTOR8: { + if (curCommand == SIG_CODE_SELECTOR8) { + curSelector = _selectorIdTable[curValue]; + if (curSelector == -1) { + curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); + _selectorIdTable[curValue] = curSelector; + if (curSelector != -1) { + if (curSelector & 0xFF00) + error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", signatureDescription); + } + } + curValue = curSelector; + } + magicOffset--; + if (magicDWordLeft) { + // Remember current byte for Magic DWORD + magicDWord[4 - magicDWordLeft] = (byte)curValue; + magicDWordLeft--; + if (!magicDWordLeft) { + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_UINT32(magicDWord); + } + } + break; + } + case PATCH_CODE_GETORIGINALBYTEADJUST: { + signatureData++; // skip over extra uint16 + break; + } + default: + break; + } + signatureData++; + curWord = *signatureData; + } + + if (magicDWordLeft) + error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", signatureDescription); + if (magicDWordIncluded) { + if (!calculatedMagicDWord) { + error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", signatureDescription); + } + } +} + +// This method calculates the magic DWORD for each entry in the signature table +// and it also initializes the selector table for selectors used in the signatures/patches of the current game +void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { + const SciScriptPatcherEntry *curEntry = patchTable; + SciScriptPatcherRuntimeEntry *curRuntimeEntry; int patchEntryCount = 0; // Count entries and allocate runtime data @@ -2902,120 +4652,14 @@ void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable, bool curRuntimeEntry = _runtimeTable; while (curEntry->signatureData) { // process signature - memset(magicDWord, 0, sizeof(magicDWord)); - curRuntimeEntry->active = curEntry->defaultActive; curRuntimeEntry->magicDWord = 0; curRuntimeEntry->magicOffset = 0; - for (step = 0; step < 2; step++) { - switch (step) { - case 0: curData = curEntry->signatureData; break; - case 1: curData = curEntry->patchData; break; - } - - curWord = *curData; - magicOffset = 0; - while (curWord != SIG_END) { - curCommand = curWord & SIG_COMMANDMASK; - curValue = curWord & SIG_VALUEMASK; - switch (curCommand) { - case SIG_MAGICDWORD: { - if (step == 0) { - if ((curRuntimeEntry->magicDWord) || (magicDWordLeft)) - error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", curEntry->description); - magicDWordLeft = 4; - curRuntimeEntry->magicOffset = magicOffset; - } - break; - } - case SIG_CODE_ADDTOOFFSET: { - magicOffset -= curValue; - if (magicDWordLeft) - error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", curEntry->description); - break; - } - case SIG_CODE_UINT16: - case SIG_CODE_SELECTOR16: { - // UINT16 or 1 - switch (curCommand) { - case SIG_CODE_UINT16: { - curData++; curWord = *curData; - if (curWord & SIG_COMMANDMASK) - error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", curEntry->description); - if (!isMacSci11) { - byte1 = curValue; - byte2 = curWord & SIG_BYTEMASK; - } else { - byte1 = curWord & SIG_BYTEMASK; - byte2 = curValue; - } - break; - } - case SIG_CODE_SELECTOR16: { - curSelector = _selectorIdTable[curValue]; - if (curSelector == -1) { - curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); - _selectorIdTable[curValue] = curSelector; - } - if (!isMacSci11) { - byte1 = curSelector & 0x00FF; - byte2 = curSelector >> 8; - } else { - byte1 = curSelector >> 8; - byte2 = curSelector & 0x00FF; - } - break; - } - } - magicOffset -= 2; - if (magicDWordLeft) { - // Remember current word for Magic DWORD - magicDWord[4 - magicDWordLeft] = byte1; - magicDWordLeft--; - if (magicDWordLeft) { - magicDWord[4 - magicDWordLeft] = byte2; - magicDWordLeft--; - } - if (!magicDWordLeft) { - curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord); - } - } - break; - } - case SIG_CODE_BYTE: - case SIG_CODE_SELECTOR8: { - if (curCommand == SIG_CODE_SELECTOR8) { - curSelector = _selectorIdTable[curValue]; - if (curSelector == -1) { - curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); - _selectorIdTable[curValue] = curSelector; - if (curSelector != -1) { - if (curSelector & 0xFF00) - error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", curEntry->description); - } - } - curValue = curSelector; - } - magicOffset--; - if (magicDWordLeft) { - // Remember current byte for Magic DWORD - magicDWord[4 - magicDWordLeft] = (byte)curValue; - magicDWordLeft--; - if (!magicDWordLeft) { - curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord); - } - } - } - } - curData++; - curWord = *curData; - } - } - if (magicDWordLeft) - error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", curEntry->description); - if (!curRuntimeEntry->magicDWord) - error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", curEntry->description); + // We verify the signature data and remember the calculated magic DWord from the signature data + calculateMagicDWordAndVerify(curEntry->description, curEntry->signatureData, true, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset); + // We verify the patch data + calculateMagicDWordAndVerify(curEntry->description, curEntry->patchData, false, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset); curEntry++; curRuntimeEntry++; } @@ -3073,6 +4717,12 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_KQ6: signatureTable = kq6Signatures; break; + case GID_KQ7: + signatureTable = kq7Signatures; + break; + case GID_LAURABOW: + signatureTable = laurabow1Signatures; + break; case GID_LAURABOW2: signatureTable = laurabow2Signatures; break; @@ -3120,7 +4770,7 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 } if (signatureTable) { - bool isMacSci11 = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1); + _isMacSci11 = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1); if (!_runtimeTable) { // Abort, in case selectors are not yet initialized (happens for games w/o selector-dictionary) @@ -3128,7 +4778,7 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 return; // signature table needs to get initialized (Magic DWORD set, selector table set) - initSignature(signatureTable, isMacSci11); + initSignature(signatureTable); // Do additional game-specific initialization switch (gameId) { @@ -3163,11 +4813,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 int32 foundOffset = 0; int16 applyCount = curEntry->applyCount; do { - foundOffset = findSignature(curEntry, curRuntimeEntry, scriptData, scriptSize, isMacSci11); + foundOffset = findSignature(curEntry, curRuntimeEntry, scriptData, scriptSize); if (foundOffset != -1) { // found, so apply the patch debugC(kDebugLevelScriptPatcher, "Script-Patcher: '%s' on script %d offset %d", curEntry->description, scriptNr, foundOffset); - applyPatch(curEntry, scriptData, scriptSize, foundOffset, isMacSci11); + applyPatch(curEntry, scriptData, scriptSize, foundOffset); } applyCount--; } while ((foundOffset != -1) && (applyCount)); |