diff options
Diffstat (limited to 'engines/sci/engine/script_patches.cpp')
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp new file mode 100644 index 0000000000..77818dd138 --- /dev/null +++ b/engines/sci/engine/script_patches.cpp @@ -0,0 +1,565 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/sci.h" +#include "sci/engine/script.h" + +#include "common/util.h" + +namespace Sci { + +#define PATCH_END 0xFFFF +#define PATCH_ADDTOOFFSET 0x8000 +#define PATCH_GETORIGINALBYTE 0x4000 +#define PATCH_MAGICDWORD(a, b, c, d) CONSTANT_LE_32(a | (b << 8) | (c << 16) | (d << 24)) + +struct SciScriptSignature { + uint16 scriptNr; + const char *description; + uint32 magicDWord; + int magicOffset; + const byte *data; + const uint16 *patch; +}; + +// signatures are built like this: +// - first a counter of the bytes that follow +// - then the actual bytes that need to get matched +// - then another counter of bytes (0 for EOS) +// - if not EOS, an adjust offset and the actual bytes +// - rinse and repeat + +// =========================================================================== +// stayAndHelp::changeState (0) is called when ego swims to the left or right +// boundaries of room 660. Normally a textbox is supposed to get on screen +// but the call is wrong, so not only do we get an error message the script +// is also hanging because the cue won't get sent out +// This also happens in sierra sci - ffs. bug #3038387 +const byte ecoquest1SignatureStayAndHelp[] = { + 40, + 0x3f, 0x01, // link 01 + 0x87, 0x01, // lap param[1] + 0x65, 0x14, // aTop state + 0x36, // push + 0x3c, // dup + 0x35, 0x00, // ldi 00 + 0x1a, // eq? + 0x31, 0x1c, // bnt [next state] + 0x76, // push0 + 0x45, 0x01, 0x00, // callb export1 from script 0 (switching control off) + 0x38, 0x22, 0x01, // pushi 0122 + 0x78, // push1 + 0x76, // push0 + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, // send 06 - ego::setMotion(0) + 0x39, 0x6e, // pushi 6e (selector init) + 0x39, 0x04, // pushi 04 + 0x76, // push0 + 0x76, // push0 + 0x39, 0x17, // pushi 17 + 0x7c, // pushSelf + 0x51, 0x82, // class EcoNarrator + 0x4a, 0x0c, // send 0c - EcoNarrator::init(0, 0, 23, self) (BADLY BROKEN!) + 0x33, // jmp [end] + 0 +}; + +const uint16 ecoquest1PatchStayAndHelp[] = { + 0x87, 0x01, // lap param[1] + 0x65, 0x14, // aTop state + 0x36, // push + 0x2f, 0x22, // bt [next state] (this optimization saves 6 bytes) + 0x39, 0x00, // pushi 0 (wasting 1 byte here) + 0x45, 0x01, 0x00, // callb export1 from script 0 (switching control off) + 0x38, 0x22, 0x01, // pushi 0122 + 0x78, // push1 + 0x76, // push0 + 0x81, 0x00, // lag global[0] + 0x4a, 0x06, // send 06 - ego::setMotion(0) + 0x39, 0x6e, // pushi 6e (selector init) + 0x39, 0x06, // pushi 06 + 0x39, 0x02, // pushi 02 (additional 2 bytes) + 0x76, // push0 + 0x76, // push0 + 0x39, 0x17, // pushi 17 + 0x7c, // pushSelf + 0x38, 0x80, 0x02, // pushi 280 (additional 3 bytes) + 0x51, 0x82, // class EcoNarrator + 0x4a, 0x10, // send 10 - EcoNarrator::init(2, 0, 0, 23, self, 640) + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature ecoquest1Signatures[] = { + { 660, "CD: bad messagebox and freeze", PATCH_MAGICDWORD(0x38, 0x22, 0x01, 0x78), -17, ecoquest1SignatureStayAndHelp, ecoquest1PatchStayAndHelp }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220. +// this is not enough time to get to the door, so we patch that to 23 seconds +const byte gk1SignatureDay6PoliceBeignet[] = { + 4, + 0x35, 0x04, // ldi 04 + 0x1a, // eq? + 0x30, // bnt [next state check] + +2, 5, // [skip 2 bytes, offset of bnt] + 0x38, 0x93, 0x00, // pushi 93 (selector dispose) + 0x76, // push0 + 0x72, // lofsa deskSarg + +2, 9, // [skip 2 bytes, offset of lofsa] + 0x4a, 0x04, 0x00, // send 04 + 0x34, 0xdc, 0x00, // ldi 220 + 0x65, 0x1a, // aTop cycles + 0x32, // jmp [end] + 0 +}; + +const uint16 gk1PatchDay6PoliceBeignet[] = { + PATCH_ADDTOOFFSET | +16, + 0x34, 0x17, 0x00, // ldi 23 + 0x65, 0x1c, // aTop seconds + PATCH_END +}; + +// sargSleeping::changeState (8) is called when the cop falls asleep and sets cycles to 220. +// this is not enough time to get to the door, so we patch it to 42 seconds +const byte gk1SignatureDay6PoliceSleep[] = { + 4, + 0x35, 0x08, // ldi 08 + 0x1a, // eq? + 0x31, // bnt [next state check] + +1, 6, // [skip 1 byte, offset of bnt] + 0x34, 0xdc, 0x00, // ldi 220 + 0x65, 0x1a, // aTop cycles + 0x32, // jmp [end] + 0 +}; + +const uint16 gk1PatchDay6PoliceSleep[] = { + PATCH_ADDTOOFFSET | +5, + 0x34, 0x2a, 0x00, // ldi 42 + 0x65, 0x1c, // aTop seconds + PATCH_END +}; + +// startOfDay5::changeState (20h) - when gabriel goes to the phone the script will hang +const byte gk1SignatureDay5PhoneFreeze[] = { + 5, + 0x35, 0x03, // ldi 03 + 0x65, 0x1a, // aTop cycles + 0x32, // jmp [end] + +2, 3, // [skip 2 bytes, offset of jmp] + 0x3c, // dup + 0x35, 0x21, // ldi 21 + 0 +}; + +const uint16 gk1PatchDay5PhoneFreeze[] = { + 0x35, 0x06, // ldi 06 + 0x65, 0x20, // aTop ticks + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature gk1Signatures[] = { + { 212, "day 5 phone freeze", PATCH_MAGICDWORD(0x35, 0x03, 0x65, 0x1a), 0, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, + { 230, "day 6 police beignet timer issue", PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -16, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, + { 230, "day 6 police sleep timer issue", PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -5, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// =========================================================================== +// this here gets called on entry and when going out of game windows +// uEvt::port will not get changed after kDisposeWindow but a bit later, so +// we would get an invalid port handle to a kSetPort call. We just patch in +// resetting of the port selector. We destroy the stop/fade code in there, +// it seems it isn't used at all in the game. +const byte hoyle4SignaturePortFix[] = { + 28, + 0x39, 0x09, // pushi 09 + 0x89, 0x0b, // lsg 0b + 0x39, 0x64, // pushi 64 + 0x38, 0xc8, 0x00, // pushi 00c8 + 0x38, 0x2c, 0x01, // pushi 012c + 0x38, 0x90, 0x01, // pushi 0190 + 0x38, 0xf4, 0x01, // pushi 01f4 + 0x38, 0x58, 0x02, // pushi 0258 + 0x38, 0xbc, 0x02, // pushi 02bc + 0x38, 0x20, 0x03, // pushi 0320 + 0x46, // calle [xxxx] [xxxx] [xx] + +5, 43, // [skip 5 bytes] + 0x30, 0x27, 0x00, // bnt 0027 -> end of routine + 0x87, 0x00, // lap 00 + 0x30, 0x19, 0x00, // bnt 0019 -> fade out + 0x87, 0x01, // lap 01 + 0x30, 0x14, 0x00, // bnt 0014 -> fade out + 0x38, 0xa7, 0x00, // pushi 00a7 + 0x76, // push0 + 0x80, 0x29, 0x01, // lag 0129 + 0x4a, 0x04, // send 04 (song::stop) + 0x39, 0x27, // pushi 27 + 0x78, // push1 + 0x8f, 0x01, // lsp 01 + 0x51, 0x54, // class 54 + 0x4a, 0x06, // send 06 (PlaySong::play) + 0x33, 0x09, // jmp 09 -> end of routine + 0x38, 0xaa, 0x00, // pushi 00aa + 0x76, // push0 + 0x80, 0x29, 0x01, // lag 0129 + 0x4a, 0x04, // send 04 + 0x48, // ret + 0 +}; + +const uint16 hoyle4PatchPortFix[] = { + PATCH_ADDTOOFFSET | +33, + 0x38, 0x31, 0x01, // pushi 0131 (selector curEvent) + 0x76, // push0 + 0x80, 0x50, 0x00, // lag 0050 (global var 80h, "User") + 0x4a, 0x04, // send 04 (read User::curEvent) + + 0x38, 0x93, 0x00, // pushi 0093 (selector port) + 0x78, // push1 + 0x76, // push0 + 0x4a, 0x06, // send 06 (write 0 to that object::port) + 0x48, // ret + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature hoyle4Signatures[] = { + { 0, "port fix when disposing windows", PATCH_MAGICDWORD(0x64, 0x38, 0xC8, 0x00), -5, hoyle4SignaturePortFix, hoyle4PatchPortFix }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// =========================================================================== +// at least during harpy scene export 29 of script 0 is called in kq5cd and +// has an issue for those calls, where temp 3 won't get inititialized, but +// is later used to set master volume. This issue makes sierra sci set +// the volume to max. We fix the export, so volume won't get modified in +// those cases. +const byte kq5SignatureCdHarpyVolume[] = { + 34, + 0x80, 0x91, 0x01, // lag global[191h] + 0x18, // not + 0x30, 0x2c, 0x00, // bnt [jump further] (jumping, if global 191h is 1) + 0x35, 0x01, // ldi 01 + 0xa0, 0x91, 0x01, // sag global[191h] (setting global 191h to 1) + 0x38, 0x7b, 0x01, // pushi 017b + 0x76, // push0 + 0x81, 0x01, // lag global[1] + 0x4a, 0x04, // send 04 (getting KQ5::masterVolume) + 0xa5, 0x03, // sat temp[3] (store volume in temp 3) + 0x38, 0x7b, 0x01, // pushi 017b + 0x76, // push0 + 0x81, 0x01, // lag global[1] + 0x4a, 0x04, // send 04 (getting KQ5::masterVolume) + 0x36, // push + 0x35, 0x04, // ldi 04 + 0x20, // ge? (followed by bnt) + 0 +}; + +const uint16 kq5PatchCdHarpyVolume[] = { + 0x38, 0x2f, 0x02, // pushi 022f (selector theVol) (3 new bytes) + 0x76, // push0 (1 new byte) + 0x51, 0x88, // class SpeakTimer (2 new bytes) + 0x4a, 0x04, // send 04 (2 new bytes) -> read SpeakTimer::theVol + 0xa5, 0x03, // sat temp[3] (2 new bytes) -> write to temp 3 + 0x80, 0x91, 0x01, // lag global[191h] + // saving 1 byte due optimization + 0x2e, 0x23, 0x00, // bt [jump further] (jumping, if global 191h is 1) + 0x35, 0x01, // ldi 01 + 0xa0, 0x91, 0x01, // sag global[191h] (setting global 191h to 1) + 0x38, 0x7b, 0x01, // pushi 017b + 0x76, // push0 + 0x81, 0x01, // lag global[1] + 0x4a, 0x04, // send 04 (getting KQ5::masterVolume) + 0xa5, 0x03, // sat temp[3] (store volume in temp 3) + // saving 8 bytes due removing of duplicate code + 0x39, 0x04, // pushi 04 (saving 1 byte due swapping) + 0x22, // lt? (because we switched values) + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature kq5Signatures[] = { + { 0, "CD: harpy volume change", PATCH_MAGICDWORD(0x80, 0x91, 0x01, 0x18), 0, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// =========================================================================== +// this is called on every death dialog. Problem is at least the german +// version of lsl6 gets title text that is far too long for the +// available temp space resulting in temp space corruption +// This patch moves the title text around, so this overflow +// doesn't happen anymore. We would otherwise get a crash +// calling for invalid views (this happens of course also +// in sierra sci) +const byte larry6SignatureDeathDialog[] = { + 7, + 0x3e, 0x33, 0x01, // link 0133 (offset 0x20) + 0x35, 0xff, // ldi ff + 0xa3, 0x00, // sal 00 + +255, 0, + +255, 0, + +170, 12, // [skip 680 bytes] + 0x8f, 0x01, // lsp 01 (offset 0x2cf) + 0x7a, // push2 + 0x5a, 0x04, 0x00, 0x0e, 0x01, // lea 0004 010e + 0x36, // push + 0x43, 0x7c, 0x0e, // kMessage[7c] 0e + +90, 10, // [skip 90 bytes] + 0x38, 0xd6, 0x00, // pushi 00d6 (offset 0x335) + 0x78, // push1 + 0x5a, 0x04, 0x00, 0x0e, 0x01, // lea 0004 010e + 0x36, // push + +76, 11, // [skip 76 bytes] + 0x38, 0xcd, 0x00, // pushi 00cd (offset 0x38b) + 0x39, 0x03, // pushi 03 + 0x5a, 0x04, 0x00, 0x0e, 0x01, // lea 0004 010e + 0x36, + 0 +}; + +const uint16 larry6PatchDeathDialog[] = { + 0x3e, 0x00, 0x02, // link 0200 + PATCH_ADDTOOFFSET | +687, + 0x5a, 0x04, 0x00, 0x40, 0x01, // lea 0004 0140 + PATCH_ADDTOOFFSET | +98, + 0x5a, 0x04, 0x00, 0x40, 0x01, // lea 0004 0140 + PATCH_ADDTOOFFSET | +82, + 0x5a, 0x04, 0x00, 0x40, 0x01, // lea 0004 0140 + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature larry6Signatures[] = { + { 82, "death dialog memory corruption", PATCH_MAGICDWORD(0x3e, 0x33, 0x01, 0x35), 0, larry6SignatureDeathDialog, larry6PatchDeathDialog }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// =========================================================================== +// rm560::doit was supposed to close the painting, when heimlich enters the +// room. The code is buggy, so it actually closes the painting, when heimlich +// is not in the room. We fix that. +const byte laurabow2SignaturePaintingClosing[] = { + 17, + 0x4a, 0x04, // send 04 (gets aHeimlich::room) + 0x36, // push + 0x81, 0x0b, // lag global[11d] -> current room + 0x1c, // ne? + 0x31, 0x0e, // bnt [don't close] + 0x35, 0x00, // ldi 00 + 0xa3, 0x00, // sal local[0] + 0x38, 0x92, 0x00, // pushi 0092 + 0x78, // push1 + 0x72, // lofsa sDumpSafe + 0 +}; + +const uint16 laurabow2PatchPaintingClosing[] = { + PATCH_ADDTOOFFSET | +6, + 0x2f, 0x0e, // bt [don't close] + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature laurabow2Signatures[] = { + { 560, "painting closing immediately", PATCH_MAGICDWORD(0x36, 0x81, 0x0b, 0x1c), -2, laurabow2SignaturePaintingClosing, laurabow2PatchPaintingClosing }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// =========================================================================== +// script 298 of sq4/floppy has an issue. object "nest" uses another property +// which isn't included in property count. We return 0 in that case, the game +// adds it to nest::x. The problem is that the script also checks if x exceeds +// we never reach that of course, so the pterodactyl-flight will go endlessly +// we could either calculate property count differently somehow fixing this +// but I think just patching it out is cleaner (ffs. bug #3037938) +const byte sq4FloppySignatureEndlessFlight[] = { + 8, + 0x39, 0x04, // pushi 04 (selector x) + 0x78, // push1 + 0x67, 0x08, // pTos 08 (property x) + 0x63, 0x44, // pToa 44 (invalid property) + 0x02, // add + 0 +}; + +const uint16 sq4FloppyPatchEndlessFlight[] = { + PATCH_ADDTOOFFSET | +5, + 0x35, 0x03, // ldi 03 (which would be the content of the property) + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature sq4Signatures[] = { + { 298, "Floppy: endless flight", PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x44), -3, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight }, + { 0, NULL, 0, 0, NULL, NULL } +}; + +// =========================================================================== +// It seems to scripts warp ego outside the screen somehow (or maybe kDoBresen?) +// ego::mover is set to 0 and rm119::doit will crash in that case. This here +// fixes part of the problem and actually checks ego::mover to be 0 and skips +// TODO: this should get further investigated by waltervn and maybe properly +// patched. For now ego will shortly disappear and reappear a bit after +// this isn't good, but sierra sci also "crashed" (endless looped) so this +// is at least better than the original code +const byte sq5SignatureScrubbing[] = { + 19, + 0x18, // not + 0x31, 0x37, // bnt 37 + 0x78, // push1 (selector x) + 0x76, // push0 + 0x39, 0x38, // pushi 38 (selector mover) + 0x76, // push0 + 0x81, 0x00, // lag 00 + 0x4a, 0x04, // send 04 (read ego::mover) + 0x4a, 0x04, // send 04 (read ego::mover::x) + 0x36, // push + 0x34, 0xa0, 0x00, // ldi 00a0 + 0x1c, // ne? + 0 +}; + +const uint16 sq5PatchScrubbing[] = { + 0x18, // not + 0x31, 0x37, // bnt 37 +// 0x2f, 0x38, // bt 37 (would save another byte, isn't needed + 0x39, 0x38, // pushi 38 (selector mover) + 0x76, // push0 + 0x81, 0x00, // lag 00 + 0x4a, 0x04, // send 04 (read ego::mover) + 0x31, 0x2e, // bnt 2e (jump if ego::mover is 0) + 0x78, // push1 (selector x) + 0x76, // push0 + 0x4a, 0x04, // send 04 (read ego::mover::x) + 0x39, 0xa0, // pushi a0 (saving 2 bytes) + 0x1c, // ne? + PATCH_END +}; + +// script, description, magic DWORD, adjust +const SciScriptSignature sq5Signatures[] = { + { 119, "scrubbing send crash", PATCH_MAGICDWORD(0x18, 0x31, 0x37, 0x78), 0, sq5SignatureScrubbing, sq5PatchScrubbing }, + { 0, NULL, 0, 0, NULL, NULL } +}; + + +// will actually patch previously found signature area +void Script::applyPatch(const uint16 *patch, byte *scriptData, const uint32 scriptSize, int32 signatureOffset) { + int32 offset = signatureOffset; + uint16 patchWord = *patch; + + while (patchWord != PATCH_END) { + if (patchWord & PATCH_ADDTOOFFSET) { + offset += patchWord & ~PATCH_ADDTOOFFSET; + } else if (patchWord & PATCH_GETORIGINALBYTE) { + // TODO: implement this + } else { + scriptData[offset] = patchWord & 0xFF; + offset++; + } + patch++; + patchWord = *patch; + } +} + +// will return -1 if no match was found, otherwise an offset to the start of the signature match +int32 Script::findSignature(const SciScriptSignature *signature, const byte *scriptData, const uint32 scriptSize) { + if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay + return -1; + + const uint32 magicDWord = signature->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 == *(const uint32 *)(scriptData + DWordOffset)) { + // magic DWORD found, check if actual signature matches + uint32 offset = DWordOffset + signature->magicOffset; + uint32 byteOffset = offset; + const byte *signatureData = signature->data; + byte matchAdjust = 1; + while (matchAdjust) { + byte matchBytesCount = *signatureData++; + if ((byteOffset + matchBytesCount) > scriptSize) // Out-Of-Bounds? + break; + if (memcmp(signatureData, &scriptData[byteOffset], matchBytesCount)) // Byte-Mismatch? + break; + // those bytes matched, adjust offsets accordingly + signatureData += matchBytesCount; + byteOffset += matchBytesCount; + // get offset... + matchAdjust = *signatureData++; + byteOffset += matchAdjust; + } + if (!matchAdjust) // all matches worked? + return offset; + } + DWordOffset++; + } + // nothing found + return -1; +} + +void Script::matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uint32 scriptSize) { + const SciScriptSignature *signatureTable = NULL; + if (g_sci->getGameId() == GID_ECOQUEST) + signatureTable = ecoquest1Signatures; + if (g_sci->getGameId() == GID_GK1) + signatureTable = gk1Signatures; +// hoyle4 now works due workaround inside GfxPorts +// if (g_sci->getGameId() == GID_HOYLE4) +// signatureTable = hoyle4Signatures; + if (g_sci->getGameId() == GID_KQ5) + signatureTable = kq5Signatures; + if (g_sci->getGameId() == GID_LAURABOW2) + signatureTable = laurabow2Signatures; + if (g_sci->getGameId() == GID_LSL6) + signatureTable = larry6Signatures; + if (g_sci->getGameId() == GID_SQ4) + signatureTable = sq4Signatures; + if (g_sci->getGameId() == GID_SQ5) + signatureTable = sq5Signatures; + + if (signatureTable) { + while (signatureTable->data) { + if (scriptNr == signatureTable->scriptNr) { + int32 foundOffset = findSignature(signatureTable, scriptData, scriptSize); + if (foundOffset != -1) { + // found, so apply the patch + warning("matched and patched %s on script %d offset %d", signatureTable->description, scriptNr, foundOffset); + applyPatch(signatureTable->patch, scriptData, scriptSize, foundOffset); + } + } + signatureTable++; + } + } +} + +} // End of namespace Sci |