/* 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.
 *
 */

#include "sci/sci.h"
#include "sci/engine/script.h"
#include "sci/engine/state.h"
#include "sci/engine/features.h"

#include "common/util.h"

namespace Sci {

#define PATCH_END             0xFFFF
#define PATCH_COMMANDMASK     0xF000
#define PATCH_VALUEMASK       0x0FFF
#define PATCH_ADDTOOFFSET     0xE000
#define PATCH_GETORIGINALBYTE 0xD000
#define PATCH_ADJUSTWORD      0xC000
#define PATCH_ADJUSTWORD_NEG  0xB000
#define PATCH_MAGICDWORD(a, b, c, d) CONSTANT_LE_32(a | (b << 8) | (c << 16) | (d << 24))
#define PATCH_VALUELIMIT      4096

struct SciScriptSignature {
	uint16 scriptNr;
	const char *description;
	int16 applyCount;
	uint32 magicDWord;
	int magicOffset;
	const byte *data;
	const uint16 *patch;
};

#define SCI_SIGNATUREENTRY_TERMINATOR { 0, NULL, 0, 0, 0, NULL, NULL }

// 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 - refer to 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 - call 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 - call 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 - call 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 - call EcoNarrator::init(2, 0, 0, 23, self, 640)
	PATCH_END
};

//    script, description,                                      magic DWORD,                                 adjust
const SciScriptSignature ecoquest1Signatures[] = {
	{    660, "CD: bad messagebox and freeze",               1, PATCH_MAGICDWORD(0x38, 0x22, 0x01, 0x78),   -17, ecoquest1SignatureStayAndHelp, ecoquest1PatchStayAndHelp },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// doMyThing::changeState (2) is supposed to remove the initial text on the
//  ecorder. This is done by reusing temp-space, that was filled on state 1.
//  this worked in sierra sci just by accident. In our sci, the temp space
//  is resetted every time, which means the previous text isn't available
//  anymore. We have to patch the code because of that - bug #3035386
const byte ecoquest2SignatureEcorder[] = {
	35,
	0x31, 0x22,        // bnt [next state]
	0x39, 0x0a,        // pushi 0a
	0x5b, 0x04, 0x1e,  // lea temp[1e]
	0x36,              // push
	0x39, 0x64,        // pushi 64
	0x39, 0x7d,        // pushi 7d
	0x39, 0x32,        // pushi 32
	0x39, 0x66,        // pushi 66
	0x39, 0x17,        // pushi 17
	0x39, 0x69,        // pushi 69
	0x38, 0x31, 0x26,  // pushi 2631
	0x39, 0x6a,        // pushi 6a
	0x39, 0x64,        // pushi 64
	0x43, 0x1b, 0x14,  // call kDisplay
	0x35, 0x0a,        // ldi 0a
	0x65, 0x20,        // aTop ticks
	0x33,              // jmp [end]
	+1, 5,             // [skip 1 byte]
	0x3c,              // dup
	0x35, 0x03,        // ldi 03
	0x1a,              // eq?
	0x31,              // bnt [end]
	0
};

const uint16 ecoquest2PatchEcorder[] = {
	0x2f, 0x02,        // bt [to pushi 07]
	0x3a,              // toss
	0x48,              // ret
	0x38, 0x07, 0x00,  // pushi 07 (parameter count) (waste 1 byte)
	0x39, 0x0b,        // push (FillBoxAny)
	0x39, 0x1d,        // pushi 29d
	0x39, 0x73,        // pushi 115d
	0x39, 0x5e,        // pushi 94d
	0x38, 0xd7, 0x00,  // pushi 215d
	0x78,              // push1 (visual screen)
	0x38, 0x17, 0x00,  // pushi 17 (color) (waste 1 byte)
	0x43, 0x6c, 0x0e,  // call kGraph
	0x38, 0x05, 0x00,  // pushi 05 (parameter count) (waste 1 byte)
	0x39, 0x0c,        // pushi 12d (UpdateBox)
	0x39, 0x1d,        // pushi 29d
	0x39, 0x73,        // pushi 115d
	0x39, 0x5e,        // pushi 94d
	0x38, 0xd7, 0x00,  // pushi 215d
	0x43, 0x6c, 0x0a,  // call kGraph
	PATCH_END
};

// ===========================================================================
// Same patch as above for the ecorder introduction. Fixes bug #3092115.
// Two workarounds are needed for this patch in workarounds.cpp (when calling
// kGraphFillBoxAny and kGraphUpdateBox), as there isn't enough space to patch
// the function otherwise.
const byte ecoquest2SignatureEcorderTutorial[] = {
	36,
	0x30, 0x23, 0x00,  // bnt [next state]
	0x39, 0x0a,        // pushi 0a
	0x5b, 0x04, 0x1f,  // lea temp[1f]
	0x36,              // push
	0x39, 0x64,        // pushi 64
	0x39, 0x7d,        // pushi 7d
	0x39, 0x32,        // pushi 32
	0x39, 0x66,        // pushi 66
	0x39, 0x17,        // pushi 17
	0x39, 0x69,        // pushi 69
	0x38, 0x31, 0x26,  // pushi 2631
	0x39, 0x6a,        // pushi 6a
	0x39, 0x64,        // pushi 64
	0x43, 0x1b, 0x14,  // call kDisplay
	0x35, 0x1e,        // ldi 1e
	0x65, 0x20,        // aTop ticks
	0x32,              // jmp [end]
	// 2 extra bytes, jmp offset
	0
};

const uint16 ecoquest2PatchEcorderTutorial[] = {
	0x31, 0x23,        // bnt [next state] (save 1 byte)
	// The parameter count below should be 7, but we're out of bytes
	// to patch! A workaround has been added because of this
	0x78,              // push1 (parameter count)
	//0x39, 0x07,        // pushi 07 (parameter count)
	0x39, 0x0b,        // push (FillBoxAny)
	0x39, 0x1d,        // pushi 29d
	0x39, 0x73,        // pushi 115d
	0x39, 0x5e,        // pushi 94d
	0x38, 0xd7, 0x00,  // pushi 215d
	0x78,              // push1 (visual screen)
	0x39, 0x17,        // pushi 17 (color)
	0x43, 0x6c, 0x0e,  // call kGraph
	// The parameter count below should be 5, but we're out of bytes
	// to patch! A workaround has been added because of this
	0x78,              // push1 (parameter count)
	//0x39, 0x05,        // pushi 05 (parameter count)
	0x39, 0x0c,        // pushi 12d (UpdateBox)
	0x39, 0x1d,        // pushi 29d
	0x39, 0x73,        // pushi 115d
	0x39, 0x5e,        // pushi 94d
	0x38, 0xd7, 0x00,  // pushi 215d
	0x43, 0x6c, 0x0a,  // call kGraph
	// We are out of bytes to patch at this point,
	// so we skip 494 (0x1EE) bytes to reuse this code:
	// ldi 1e
	// aTop 20
	// jmp 030e (jump to end)
	0x32, 0xee, 0x01,  // skip 494 (0x1EE) bytes
	PATCH_END
};

//    script, description,                                      magic DWORD,                                 adjust
const SciScriptSignature ecoquest2Signatures[] = {
	{     50, "initial text not removed on ecorder",         1, PATCH_MAGICDWORD(0x39, 0x64, 0x39, 0x7d),    -8, ecoquest2SignatureEcorder, ecoquest2PatchEcorder },
	{    333, "initial text not removed on ecorder tutorial",1, PATCH_MAGICDWORD(0x39, 0x64, 0x39, 0x7d),    -9, ecoquest2SignatureEcorderTutorial, ecoquest2PatchEcorderTutorial },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// 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 #3038870.
const byte fanmadeSignatureInfiniteLoop[] = {
	13,
	0x38, 0x4c, 0x00,  // pushi 004c
	0x39, 0x00,        // pushi 00
	0x87, 0x01,        // lap 01
	0x4b, 0x04,        // send 04
	0x18,              // not
	0x30, 0x2f, 0x00,  // bnt 002f  [06a5]	--> jmp ffbc  [0664] --> BUG! infinite loop
	0
};

const uint16 fanmadePatchInfiniteLoop[] = {
	PATCH_ADDTOOFFSET | +10,
	0x30, 0x32, 0x00,  // bnt 0032  [06a8] --> pushi 004c
	PATCH_END
};

//    script, description,                                      magic DWORD,                                 adjust
const SciScriptSignature fanmadeSignatures[] = {
	{    999, "infinite loop on typo",                       1, PATCH_MAGICDWORD(0x18, 0x30, 0x2f, 0x00),    -9, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
//  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
//   sound disposal. This somewhat worked in sierra sci, because the sample
//   was already playing in the sound driver. In our case we would also stop
//   the sample from playing, so we patch it out
//   The "score" code is already buggy and sets volume to 0 when playing
const byte freddypharkasSignatureScoreDisposal[] = {
	10,
	0x67, 0x32,       // pTos 32 (selector theAudCount)
	0x78,             // push1
	0x39, 0x0d,       // pushi 0d
	0x43, 0x75, 0x02, // call kDoAudio
	0x1c,             // ne?
	0x31,             // bnt (-> to skip disposal)
	0
};

const uint16 freddypharkasPatchScoreDisposal[] = {
	0x34, 0x00, 0x00, // ldi 0000
	0x34, 0x00, 0x00, // ldi 0000
	0x34, 0x00, 0x00, // ldi 0000
	PATCH_END
};

//  script 235 of freddy pharkas rm235::init and sEnterFrom500::changeState
//   disable icon 7+8 of iconbar (CD only). When picking up the canister after
//   placing it down, the scripts will disable all the other icons. This results
//   in IconBar::disable doing endless loops even in sierra sci, because there
//   is no enabled icon left. We remove disabling of icon 8 (which is help),
//   this fixes the issue.
const byte freddypharkasSignatureCanisterHang[] = {
	12,
	0x38, 0xf1, 0x00, // pushi f1 (selector disable)
	0x7a,             // push2
	0x39, 0x07,       // pushi 07
	0x39, 0x08,       // pushi 08
	0x81, 0x45,       // lag 45
	0x4a, 0x08,       // send 08 - call IconBar::disable(7, 8)
	0
};

const uint16 freddypharkasPatchCanisterHang[] = {
	PATCH_ADDTOOFFSET | +3,
	0x78,             // push1
	PATCH_ADDTOOFFSET | +2,
	0x33, 0x00,       // ldi 00 (waste 2 bytes)
	PATCH_ADDTOOFFSET | +3,
	0x06,             // send 06 - call IconBar::disable(7)
	PATCH_END
};

//  script 215 of freddy pharkas lowerLadder::doit and highLadder::doit actually
//   process keyboard-presses when the ladder is on the screen in that room.
//   They strangely also call kGetEvent. Because the main User::doit also calls
//   kGetEvent, it's pure luck, where the event will hit. It's the same issue
//   as in QfG1VGA and if you turn dos-box to max cycles, and click around for
//   ego, sometimes clicks also won't get registered. Strangely it's not nearly
//   as bad as in our sci, but these differences may be caused by timing.
//   We just reuse the active event, thus removing the duplicate kGetEvent call.
const byte freddypharkasSignatureLadderEvent[] = {
	21,
	0x39, 0x6d,       // pushi 6d (selector new)
	0x76,             // push0
	0x38, 0xf5, 0x00, // pushi f5 (selector curEvent)
	0x76,             // push0
	0x81, 0x50,       // lag global[50]
	0x4a, 0x04,       // send 04 - read User::curEvent
	0x4a, 0x04,       // send 04 - call curEvent::new
	0xa5, 0x00,       // sat temp[0]
	0x38, 0x94, 0x00, // pushi 94 (selector localize)
	0x76,             // push0
	0x4a, 0x04,       // send 04 - call curEvent::localize
	0
};

const uint16 freddypharkasPatchLadderEvent[] = {
	0x34, 0x00, 0x00, // ldi 0000 (waste 3 bytes, overwrites first 2 pushes)
	PATCH_ADDTOOFFSET | +8,
	0xa5, 0x00,       // sat temp[0] (waste 2 bytes, overwrites 2nd send)
	PATCH_ADDTOOFFSET | +2,
	0x34, 0x00, 0x00, // ldi 0000
	0x34, 0x00, 0x00, // ldi 0000 (waste 6 bytes, overwrites last 3 opcodes)
	PATCH_END
};

// In the Macintosh version of Freddy Pharkas, kRespondsTo is broken for
// property selectors. They hacked the script to work around the issue,
// so we revert the script back to using the values of the DOS script.
const byte freddypharkasSignatureMacInventory[] = {
	10,
	0x39, 0x23,       // pushi 23
	0x39, 0x74,       // pushi 74
	0x78,             // push1
	0x38, 0x01, 0x74, // pushi 0174
	0x85, 0x15,       // lat 15
	0
};

const uint16 freddypharkasPatchMacInventory[] = {
	0x39, 0x02,       // pushi 02 (now matches the DOS version)
	0x39, 0x74,       // pushi 74
	0x78,             // push1
	0x38, 0x01, 0x74, // pushi 0174
	0x85, 0x15,       // lat 15
	0x4a, 0x06,       // send 06
	0x31, 0x08,       // bnt 08
	0x38, 0x01, 0x74, // pushi 0174
	0x76,             // push0
	0x85, 0x15,       // lat 15
	0x4a, 0x04,       // send 04
	0x02,             // add
	0xa5, 0x12,       // sat 12
	0x39, 0x04,       // pushi 04 (now matches the DOS version)
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature freddypharkasSignatures[] = {
	{      0, "CD: score early disposal",                    1, PATCH_MAGICDWORD(0x39, 0x0d, 0x43, 0x75),    -3, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal },
	{     15, "Mac: broken inventory",                       1, PATCH_MAGICDWORD(0x39, 0x23, 0x39, 0x74),     0, freddypharkasSignatureMacInventory,  freddypharkasPatchMacInventory },
	{    235, "CD: canister pickup hang",                    3, PATCH_MAGICDWORD(0x39, 0x07, 0x39, 0x08),    -4, freddypharkasSignatureCanisterHang,  freddypharkasPatchCanisterHang },
	{    320, "ladder event issue",                          2, PATCH_MAGICDWORD(0x6d, 0x76, 0x38, 0xf5),    -1, freddypharkasSignatureLadderEvent,   freddypharkasPatchLadderEvent },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// 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
};

// Floppy version: Interrogation::dispose() compares an object reference
// (stored in the view selector) with a number, leading to a crash (this kind
// of comparison was not used in SCI32). The view selector is used to store
// both a view number (in some cases), and a view reference (in other cases).
// In the floppy version, the checks are in the wrong order, so there is a
// comparison between a number an an object. In the CD version, the checks are
// in the correct order, thus the comparison is correct, thus we use the code
// from the CD version in the floppy one.
const byte gk1SignatureInterrogationBug[] = {
	43,
	0x65, 0x4c,        // aTop 4c
	0x67, 0x50,        // pTos 50
	0x34, 0x10, 0x27,  // ldi 2710
	0x1e,              // gt?
	0x31, 0x08,        // bnt 08  [05a0]
	0x67, 0x50,        // pTos 50
	0x34, 0x10, 0x27,  // ldi 2710
	0x04,              // sub
	0x65, 0x50,        // aTop 50
	0x63, 0x50,        // pToa 50
	0x31, 0x15,        // bnt 15  [05b9]
	0x39, 0x0e,        // pushi 0e
	0x76,              // push0
	0x4a, 0x04, 0x00,  // send 0004
	0xa5, 0x00,        // sat 00
	0x38, 0x93, 0x00,  // pushi 0093
	0x76,              // push0
	0x63, 0x50,        // pToa 50
	0x4a, 0x04, 0x00,  // send 0004
	0x85, 0x00,        // lat 00
	0x65, 0x50,        // aTop 50
	0
};

const uint16 gk1PatchInterrogationBug[] = {
	0x65, 0x4c,        // aTop 4c
	0x63, 0x50,        // pToa 50
	0x31, 0x15,        // bnt 15  [05b9]
	0x39, 0x0e,        // pushi 0e
	0x76,              // push0
	0x4a, 0x04, 0x00,  // send 0004
	0xa5, 0x00,        // sat 00
	0x38, 0x93, 0x00,  // pushi 0093
	0x76,              // push0
	0x63, 0x50,        // pToa 50
	0x4a, 0x04, 0x00,  // send 0004
	0x85, 0x00,        // lat 00
	0x65, 0x50,        // aTop 50
	0x67, 0x50,        // pTos 50
	0x34, 0x10, 0x27,  // ldi 2710
	0x1e,              // gt?
	0x31, 0x08,        // bnt 08  [05b9]
	0x67, 0x50,        // pTos 50
	0x34, 0x10, 0x27,  // ldi 2710
	0x04,              // sub
	0x65, 0x50,        // aTop 50
	PATCH_END
};

//    script, description,                                      magic DWORD,                                 adjust
const SciScriptSignature gk1Signatures[] = {
	{     51, "interrogation bug",                           1, PATCH_MAGICDWORD(0x65, 0x4c, 0x67, 0x50),     0, gk1SignatureInterrogationBug, gk1PatchInterrogationBug },
	{    212, "day 5 phone freeze",                          1, PATCH_MAGICDWORD(0x35, 0x03, 0x65, 0x1a),     0, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
	{    230, "day 6 police beignet timer issue",            1, PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65),   -16, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
	{    230, "day 6 police sleep timer issue",              1, PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65),    -5, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// 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 - read 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 - read 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 - read 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
};

// This is a heap patch, and it modifies the properties of an object, instead
// of patching script code.
//
// The witchCage object in script 200 is broken and claims to have 12
// variables instead of the 8 it should have because it is a Cage.
// Additionally its top,left,bottom,right properties are set to 0 rather
// than the right values. We fix the object by setting the right values.
// If they are all zero, this causes an impossible position check in
// witch::cantBeHere and an infinite loop when entering room 22 (bug #3034714).
//
// This bug is accidentally not triggered in SSCI because the invalid number
// of variables effectively hides witchCage::doit, causing this position check
// to be bypassed entirely.
// See also the warning+comment in Object::initBaseObject
const byte kq5SignatureWitchCageInit[] = {
	16,
	0x00, 0x00,	// top
	0x00, 0x00,	// left
	0x00, 0x00,	// bottom
	0x00, 0x00,	// right
	0x00, 0x00,	// extra property #1
	0x7a, 0x00,	// extra property #2
	0xc8, 0x00,	// extra property #3
	0xa3, 0x00,	// extra property #4
	0
};

const uint16 kq5PatchWitchCageInit[] = {
	0x00, 0x00,	// top
	0x7a, 0x00,	// left
	0xc8, 0x00,	// bottom
	0xa3, 0x00,	// right
	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
// tracks do not contain these signals.
// The original kq5 interpreter used global 400 to distinguish between
// Windows (1) and DOS (0) versions.
// We replace the 4 relevant checks for global 400 by a fixed true when
// we use these GM tracks.
//
// Instead, we could have set global 400, but this has the possibly unwanted
// side effects of switching to black&white cursors (which also needs complex
// changes to GameFeatures::detectsetCursorType() ) and breaking savegame
// compatibilty between the DOS and Windows CD versions of KQ5.
// TODO: Investigate these side effects more closely.
const byte kq5SignatureWinGMSignals[] = {
	9,
	0x80, 0x90, 0x01, // lag 0x190
	0x18,             // not
	0x30, 0x1b, 0x00, // bnt +0x001B
	0x89, 0x57,       // lsg 0x57
	0
};

const uint16 kq5PatchWinGMSignals[] = {
	0x34, 0x01, 0x00, // ldi 0x0001
	PATCH_END
};

//    script, description,                                      magic DWORD,                                 adjust
const SciScriptSignature kq5Signatures[] = {
	{      0, "CD: harpy volume change",                     1, PATCH_MAGICDWORD(0x80, 0x91, 0x01, 0x18),     0, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
	{    200, "CD: witch cage init",                         1, PATCH_MAGICDWORD(0x7a, 0x00, 0xc8, 0x00),   -10, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
	SCI_SIGNATUREENTRY_TERMINATOR
};

const SciScriptSignature kq5WinGMSignatures[] = {
	{      0, "CD: harpy volume change",                     1, PATCH_MAGICDWORD(0x80, 0x91, 0x01, 0x18),     0, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
	{    200, "CD: witch cage init",                         1, PATCH_MAGICDWORD(0x7a, 0x00, 0xc8, 0x00),   -10, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
	{    124, "Win: GM Music signal checks",                 4, PATCH_MAGICDWORD(0x80, 0x90, 0x01, 0x18),     0, kq5SignatureWinGMSignals, kq5PatchWinGMSignals },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// When giving the milk bottle to one of the babies in the garden in KQ6 (room
// 480), script 481 starts a looping baby cry sound. However, that particular
// script also has an overriden check method (cryMusic::check). This method
// explicitly restarts the sound, even if it's set to be looped, thus the same
// sound is played twice, squelching all other sounds. We just rip the
// unnecessary cryMusic::check method out, thereby stopping the sound from
// constantly restarting (since it's being looped anyway), thus the normal
// game speech can work while the baby cry sound is heard. Fixes bug #3034579.
const byte kq6SignatureDuplicateBabyCry[] = {
	10,
	0x83, 0x00,         // lal 00
    0x31, 0x1e,         // bnt 1e  [07f4]
    0x78,               // push1
    0x39, 0x04,         // pushi 04
    0x43, 0x75, 0x02,   // callk DoAudio[75] 02
	0
};

const uint16 kq6PatchDuplicateBabyCry[] = {
	0x48,              // ret
	PATCH_END
};

//    script, description,                                      magic DWORD,                                 adjust
const SciScriptSignature kq6Signatures[] = {
	{    481, "duplicate baby cry",                          1, PATCH_MAGICDWORD(0x83, 0x00, 0x31, 0x1e),     0, kq6SignatureDuplicateBabyCry, kq6PatchDuplicateBabyCry },
	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),
// and the function that is used to show each letter is called twice. The
// second time that the function is called, the second parameter passed to
// the function is undefined, thus kStrCat() that is called inside the function
// reads a random pointer and crashes. We patch all of the 5 function calls
// (one for each letter typed from "R", "O", "B", "I", "N") so that they are
// the same as the English version. Fixes bug #3048054.
const byte longbowSignatureShowHandCode[] = {
	3,
	0x78,                   // push1
	0x78,                   // push1
	0x72,                   // lofsa
	+2, 2,                  // skip 2 bytes, offset of lofsa (the letter typed)
	0x36,                   // push
	0x40,                   // call
	+2, 3,                  // skip 2 bytes, offset of call
	0x02,                   // perform the call above with 2 parameters
	0x36,                   // push
	0x40,                   // call
	+2, 8,                  // skip 2 bytes, offset of call
	0x02,                   // perform the call above with 2 parameters
	0x38, 0x1c, 0x01,       // pushi 011c (setMotion)
	0x39, 0x04,             // pushi 04 (x)
	0x51, 0x1e,             // class MoveTo
	0
};

const uint16 longbowPatchShowHandCode[] = {
	0x39, 0x01,             // pushi 1 (combine the two push1's in one, like in the English version)
	PATCH_ADDTOOFFSET | +3, // leave the lofsa call untouched
	// The following will remove the duplicate call
	0x32, 0x02, 0x00,       // jmp 02 - skip 2 bytes (the remainder of the first call)
	0x48,                   // ret (dummy, should never be reached)
	0x48,                   // ret (dummy, should never be reached)
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature longbowSignatures[] = {
	{    210, "hand code crash",                             5, PATCH_MAGICDWORD(0x02, 0x38, 0x1c, 0x01),   -14, longbowSignatureShowHandCode, longbowPatchShowHandCode },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// 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",              1, PATCH_MAGICDWORD(0x3e, 0x33, 0x01, 0x35),     0, larry6SignatureDeathDialog, larry6PatchDeathDialog },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// 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 - read 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",                1, PATCH_MAGICDWORD(0x36, 0x81, 0x0b, 0x1c),    -2, laurabow2SignaturePaintingClosing, laurabow2PatchPaintingClosing },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// Mother Goose SCI1/SCI1.1
// 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.
const byte mothergoose256SignatureReplay[] = {
	6,
	0x36,             // push
	0x35, 0x20,       // ldi 20
	0x04,             // sub
	0xa1, 0xb3,       // sag global[b3]
	0
};

const uint16 mothergoose256PatchReplay[] = {
	0x34, 0x00, 0x00, // ldi 0000 (dummy)
	0x34, 0x00, 0x00, // ldi 0000 (dummy)
	PATCH_END
};

// when saving, it also checks if the savegame ID is below 13.
//  we change this to check if below 113 instead
const byte mothergoose256SignatureSaveLimit[] = {
	5,
	0x89, 0xb3,       // lsg global[b3]
	0x35, 0x0d,       // ldi 0d
	0x20,             // ge?
	0
};

const uint16 mothergoose256PatchSaveLimit[] = {
	PATCH_ADDTOOFFSET | +2,
	0x35, 0x0d + SAVEGAMEID_OFFICIALRANGE_START, // ldi 113d
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature mothergoose256Signatures[] = {
	{      0, "replay save issue",                           1, PATCH_MAGICDWORD(0x20, 0x04, 0xa1, 0xb3),    -2, mothergoose256SignatureReplay,    mothergoose256PatchReplay },
	{      0, "save limit dialog (SCI1.1)",                  1, PATCH_MAGICDWORD(0xb3, 0x35, 0x0d, 0x20),    -1, mothergoose256SignatureSaveLimit, mothergoose256PatchSaveLimit },
	{    994, "save limit dialog (SCI1)",                    1, PATCH_MAGICDWORD(0xb3, 0x35, 0x0d, 0x20),    -1, mothergoose256SignatureSaveLimit, mothergoose256PatchSaveLimit },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
//  script 215 of qfg1vga pointBox::doit actually processes button-presses
//   during fighting with monsters. It strangely also calls kGetEvent. Because
//   the main User::doit also calls kGetEvent it's pure luck, where the event
//   will hit. It's the same issue as in freddy pharkas and if you turn dos-box
//   to max cycles, sometimes clicks also won't get registered. Strangely it's
//   not nearly as bad as in our sci, but these differences may be caused by
//   timing.
//   We just reuse the active event, thus removing the duplicate kGetEvent call.
const byte qfg1vgaSignatureFightEvents[] = {
	25,
	0x39, 0x6d,       // pushi 6d (selector new)
	0x76,             // push0
	0x51, 0x07,       // class Event
	0x4a, 0x04,       // send 04 - call Event::new
	0xa5, 0x00,       // sat temp[0]
	0x78,             // push1
	0x76,             // push0
	0x4a, 0x04,       // send 04 - read Event::x
	0xa5, 0x03,       // sat temp[3]
	0x76,             // push0 (selector y)
	0x76,             // push0
	0x85, 0x00,       // lat temp[0]
	0x4a, 0x04,       // send 04 - read Event::y
	0x36,             // push
	0x35, 0x0a,       // ldi 0a
	0x04,             // sub (poor mans localization) ;-)
	0
};

const uint16 qfg1vgaPatchFightEvents[] = {
	0x38, 0x5a, 0x01, // pushi 15a (selector curEvent)
	0x76,             // push0
	0x81, 0x50,       // lag global[50]
	0x4a, 0x04,       // send 04 - read User::curEvent -> needs one byte more than previous code
	0xa5, 0x00,       // sat temp[0]
	0x78,             // push1
	0x76,             // push0
	0x4a, 0x04,       // send 04 - read Event::x
	0xa5, 0x03,       // sat temp[3]
	0x76,             // push0 (selector y)
	0x76,             // push0
	0x85, 0x00,       // lat temp[0]
	0x4a, 0x04,       // send 04 - read Event::y
	0x39, 0x00,       // pushi 00
	0x02,             // add (waste 3 bytes) - we don't need localization, User::doit has already done it
	PATCH_END
};

// Script 814 of QFG1VGA is responsible for showing dialogs. However, the death
// screen message shown when the hero dies in room 64 (ghost room) is too large
// (254 chars long). Since the window header and main text are both stored in
// temp space, this is an issue, as the scripts read the window header, then the
// window text, which erases the window header text because of its length. To
// fix that, we allocate more temp space and move the pointer used for the
// window header a little bit, wherever it's used in script 814.
// Fixes bug #3568431.

// Patch 1: Increase temp space
const byte qfg1vgaSignatureTempSpace[] = {
	4,
	0x3f, 0xba,       // link 0xba
	0x87, 0x00,       // lap 0
	0
};

const uint16 qfg1vgaPatchTempSpace[] = {
	0x3f, 0xca,       // link 0xca
	PATCH_END
};

// Patch 2: Move the pointer used for the window header a little bit
const byte qfg1vgaSignatureDialogHeader[] = {
	4,
	0x5b, 0x04, 0x80,  // lea temp[0x80]
	0x36,              // push
	0
};

const uint16 qfg1vgaPatchDialogHeader[] = {
	0x5b, 0x04, 0x90,  // lea temp[0x90]
	PATCH_END
};

// When clicking on the crusher in room 331, Ego approaches him to talk to him,
// an action that is handled by moveToCrusher::changeState in script 331. The
// scripts set Ego to move close to the crusher, but when Ego is running instead
// of walking, the target coordinates specified by script 331 are never reached,
// as Ego is making larger steps, and never reaches the required spot. This is an
// edge case that can occur when Ego is set to run. Normally, when clicking on
// the crusher, ego is supposed to move close to position 79, 165. We change it
// to 85, 165, which is not an edge case thus the freeze is avoided.
// Fixes bug #3585189.
const byte qfg1vgaSignatureMoveToCrusher[] = {
	9,
	0x51, 0x1f,       // class Motion
	0x36,             // push
	0x39, 0x4f,       // pushi 4f (79 - x)
	0x38, 0xa5, 0x00, // pushi 00a5 (165 - y)
	0x7c,             // pushSelf
	0
};

const uint16 qfg1vgaPatchMoveToCrusher[] = {
	PATCH_ADDTOOFFSET | +3,
	0x39, 0x55,       // pushi 55 (85 - x)
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature qfg1vgaSignatures[] = {
	{    215, "fight event issue",                           1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07),    -1, qfg1vgaSignatureFightEvents,       qfg1vgaPatchFightEvents },
	{    216, "weapon master event issue",                   1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07),    -1, qfg1vgaSignatureFightEvents,       qfg1vgaPatchFightEvents },
	{    814, "window text temp space",                      1, PATCH_MAGICDWORD(0x3f, 0xba, 0x87, 0x00),     0, qfg1vgaSignatureTempSpace,         qfg1vgaPatchTempSpace },
	{    814, "dialog header offset",                        3, PATCH_MAGICDWORD(0x5b, 0x04, 0x80, 0x36),     0, qfg1vgaSignatureDialogHeader,      qfg1vgaPatchDialogHeader },
	{    331, "moving to crusher",                           1, PATCH_MAGICDWORD(0x51, 0x1f, 0x36, 0x39),     0, qfg1vgaSignatureMoveToCrusher,     qfg1vgaPatchMoveToCrusher },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// 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
// probably done because it would be easier to refresh the list whenever the
// user inserted a new floppy disk, or changed directory. The problem is that
// the script has a bug, and it invalidates the text of the entries in the
// list. This has a high probability of breaking, as the user could change the
// list very quickly, or the garbage collector could kick in and remove the
// deleted entries. We don't allow the user to change the directory, thus the
// contents of the file list are constant, so we can avoid the constant file
// and text entry refreshes whenever a button is pressed, and prevent possible
// crashes because of these constant quick object reallocations. Fixes bug
// #3037996.
const byte qfg2SignatureImportDialog[] = {
	16,
	0x63, 0x20,       // pToa text
	0x30, 0x0b, 0x00, // bnt [next state]
	0x7a,             // push2
	0x39, 0x03,       // pushi 03
	0x36,             // push
	0x43, 0x72, 0x04, // callk Memory 4
	0x35, 0x00,       // ldi 00
	0x65, 0x20,       // aTop text
	0
};

const uint16 qfg2PatchImportDialog[] = {
	PATCH_ADDTOOFFSET | +5,
	0x48,             // ret
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature qfg2Signatures[] = {
	{    944, "import dialog continuous calls",                 1, PATCH_MAGICDWORD(0x20, 0x30, 0x0b, 0x00),  -1, qfg2SignatureImportDialog, qfg2PatchImportDialog },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
// Patch for the import screen in QFG3, same as the one for QFG2 above
const byte qfg3SignatureImportDialog[] = {
	15,
	0x63, 0x2a,       // pToa text
	0x31, 0x0b,       // bnt [next state]
	0x7a,             // push2
	0x39, 0x03,       // pushi 03
	0x36,             // push
	0x43, 0x72, 0x04, // callk Memory 4
	0x35, 0x00,       // ldi 00
	0x65, 0x2a,       // aTop text
	0
};

const uint16 qfg3PatchImportDialog[] = {
	PATCH_ADDTOOFFSET | +4,
	0x48,             // ret
	PATCH_END
};



// ===========================================================================
// Patch for the Woo dialog option in Uhura's conversation. Bug #3040722
// Problem: The Woo dialog option (0xffb5) is negative, and therefore
// treated as an option opening a submenu. This leads to uhuraTell::doChild
// being called, which calls hero::solvePuzzle and then proceeds with
// Teller::doChild to open the submenu. However, there is no actual submenu
// defined for option -75 since -75 does not show up in uhuraTell::keys.
// This will cause Teller::doChild to run out of bounds while scanning through
// uhuraTell::keys.
// Strategy: there is another conversation option in uhuraTell::doChild calling
// hero::solvePuzzle (0xfffc) which does a ret afterwards without going to
// Teller::doChild. We jump to this call of hero::solvePuzzle to get that same
// behaviour.

const byte qfg3SignatureWooDialog[] = {
	30,
	0x67, 0x12,       // pTos 12 (query)
	0x35, 0xb6,       // ldi b6
	0x1a,             // eq?
	0x2f, 0x05,       // bt 05
	0x67, 0x12,       // pTos 12 (query)
	0x35, 0x9b,       // ldi 9b
	0x1a,             // eq?
	0x31, 0x0c,       // bnt 0c
	0x38, 0x97, 0x02, // pushi 0297
	0x7a,             // push2
	0x38, 0x0c, 0x01, // pushi 010c
	0x7a,             // push2
	0x81, 0x00,       // lag 00
	0x4a, 0x08,       // send 08
	0x67, 0x12,       // pTos 12 (query)
	0x35, 0xb5,       // ldi b5
	0
};

const uint16 qfg3PatchWooDialog[] = {
	PATCH_ADDTOOFFSET | +0x29,
	0x33, 0x11, // jmp to 0x6a2, the call to hero::solvePuzzle for 0xFFFC
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature qfg3Signatures[] = {
	{    944, "import dialog continuous calls",                 1, PATCH_MAGICDWORD(0x2a, 0x31, 0x0b, 0x7a),  -1, qfg3SignatureImportDialog,         qfg3PatchImportDialog },
	{    440, "dialog crash when asking about Woo",             1, PATCH_MAGICDWORD(0x67, 0x12, 0x35, 0xb5),  -26, qfg3SignatureWooDialog,         qfg3PatchWooDialog },
	SCI_SIGNATUREENTRY_TERMINATOR
};

// ===========================================================================
//  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 (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
};

// Similar to the above, for the German version (bug #3110215)
const byte sq4FloppySignatureEndlessFlightGerman[] = {
	8,
	0x39, 0x04,       // pushi 04 (selector x)
	0x78,             // push1
	0x67, 0x08,       // pTos 08 (property x)
	0x63, 0x4c,       // pToa 4c (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
};

// 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.
// Patch 1: iconTextSwitch::show, called when the text options button is shown.
// This is patched to add the "Both" text resource (i.e. we end up with
// "Speech", "Text" and "Both")
const byte sq4CdSignatureTextOptionsButton[] = {
	11,
	0x35, 0x01,      // ldi 0x01
	0xa1, 0x53,      // sag 0x53
	0x39, 0x03,      // pushi 0x03
	0x78,            // push1
	0x39, 0x09,      // pushi 0x09
	0x54, 0x06,      // self 0x06
	0
};

const uint16 sq4CdPatchTextOptionsButton[] = {
	PATCH_ADDTOOFFSET | +7,
	0x39, 0x0b,      // pushi 0x0b
	PATCH_END
};

// Patch 2: Adjust a check in babbleIcon::init, which handles the babble icon
// (e.g. the two guys from Andromeda) shown when dying/quitting.
// Fixes bug #3538418.
const byte sq4CdSignatureBabbleIcon[] = {
	7,
	0x89, 0x5a,      // lsg 5a
	0x35, 0x02,      // ldi 02
	0x1a,            // eq?
	0x31, 0x26,      // bnt 26  [02a7]
	0
};

const uint16 sq4CdPatchBabbleIcon[] = {
	0x89, 0x5a,      // lsg 5a
	0x35, 0x01,      // ldi 01
	0x1a,            // eq?
	0x2f, 0x26,      // bt 26  [02a7]
	PATCH_END
};

// Patch 3: Add the ability to toggle among the three available options,
// when the text options button is clicked: "Speech", "Text" and "Both".
// Refer to the patch above for additional details.
// iconTextSwitch::doit (called when the text options button is clicked)
const byte sq4CdSignatureTextOptions[] = {
	32,
	0x89, 0x5a,       // lsg 0x5a (load global 90 to stack)
	0x3c,             // dup
	0x35, 0x01,       // ldi 0x01
	0x1a,             // eq? (global 90 == 1)
	0x31, 0x06,       // bnt 0x06 (0x0691)
	0x35, 0x02,       // ldi 0x02
	0xa1, 0x5a,       // sag 0x5a (save acc to global 90)
	0x33, 0x0a,       // jmp 0x0a (0x69b)
	0x3c,             // dup
	0x35, 0x02,       // ldi 0x02
	0x1a,             // eq? (global 90 == 2)
	0x31, 0x04,       // bnt 0x04 (0x069b)
	0x35, 0x01,       // ldi 0x01
	0xa1, 0x5a,       // sag 0x5a (save acc to global 90)
	0x3a,             // toss
	0x38, 0xd9, 0x00, // pushi 0x00d9
	0x76,             // push0
	0x54, 0x04,       // self 0x04
	0x48,             // ret
	0
};

const uint16 sq4CdPatchTextOptions[] = {
	0x89, 0x5a,       // lsg 0x5a (load global 90 to stack)
	0x3c,             // dup
	0x35, 0x03,       // ldi 0x03 (acc = 3)
	0x1a,             // eq? (global 90 == 3)
	0x2f, 0x07,       // bt 0x07
	0x89, 0x5a,       // lsg 0x5a (load global 90 to stack again)
	0x35, 0x01,       // ldi 0x01 (acc = 1)
	0x02,             // add: acc = global 90 (on stack) + 1 (previous acc value)
	0x33, 0x02,       // jmp 0x02
	0x35, 0x01,       // ldi 0x01 (reset acc to 1)
	0xa1, 0x5a,       // sag 0x5a (save acc to global 90)
	0x33, 0x03,       // jmp 0x03 (jump over the wasted bytes below)
	0x34, 0x00, 0x00, // ldi 0x0000 (waste 3 bytes)
	0x3a,             // toss
	// (the rest of the code is the same)
	PATCH_END
};

//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature sq4Signatures[] = {
	{    298, "Floppy: endless flight",                      1, PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x44),    -3,       sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight },
	{    298, "Floppy (German): endless flight",             1, PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x4c),    -3, sq4FloppySignatureEndlessFlightGerman, sq4FloppyPatchEndlessFlight },
	{    818, "CD: Speech and subtitles option",             1, PATCH_MAGICDWORD(0x89, 0x5a, 0x3c, 0x35),     0,             sq4CdSignatureTextOptions,       sq4CdPatchTextOptions },
	{      0, "CD: Babble icon speech and subtitles fix",    1, PATCH_MAGICDWORD(0x89, 0x5a, 0x35, 0x02),     0,              sq4CdSignatureBabbleIcon,        sq4CdPatchBabbleIcon },
	{    818, "CD: Speech and subtitles option button",      1, PATCH_MAGICDWORD(0x35, 0x01, 0xa1, 0x53),     0,       sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton },
	SCI_SIGNATUREENTRY_TERMINATOR
};

const byte sq1vgaSignatureEgoShowsCard[] = {
	25,
	0x38, 0x46, 0x02, // push 0x246 (set up send frame to set timesShownID)
	0x78,             // push1
	0x38, 0x46, 0x02, // push 0x246 (set up send frame to get timesShownID)
	0x76,             // push0
	0x51, 0x7c,       // class DeltaurRegion
	0x4a, 0x04,       // send 0x04 (get timesShownID)
	0x36,             // push
	0x35, 0x01,       // ldi 1
	0x02,             // add
	0x36,             // push
	0x51, 0x7c,       // class DeltaurRegion
	0x4a, 0x06,       // send 0x06 (set timesShownID)
	0x36,             // push      (wrong, acc clobbered by class, above)
	0x35, 0x03,       // ldi 0x03
	0x22,             // lt?
	0};

// Note that this script patch is merely a reordering of the
// instructions in the original script.
const uint16 sq1vgaPatchEgoShowsCard[] = {
	0x38, 0x46, 0x02, // push 0x246 (set up send frame to get timesShownID)
	0x76,             // push0
	0x51, 0x7c,       // class DeltaurRegion
	0x4a, 0x04,       // send 0x04 (get timesShownID)
	0x36,             // push
	0x35, 0x01,       // ldi 1
	0x02,             // add
	0x36,             // push (this push corresponds to the wrong one above)
	0x38, 0x46, 0x02, // push 0x246 (set up send frame to set timesShownID)
	0x78,             // push1
	0x36,             // push
	0x51, 0x7c,       // class DeltaurRegion
	0x4a, 0x06,       // send 0x06 (set timesShownID)
	0x35, 0x03,       // ldi 0x03
	0x22,             // lt?
	PATCH_END};


//    script, description,                                      magic DWORD,                                  adjust
const SciScriptSignature sq1vgaSignatures[] = {
	{   58, "Sarien armory droid zapping ego first time", 1, PATCH_MAGICDWORD( 0x72, 0x88, 0x15, 0x36 ), -70,
		sq1vgaSignatureEgoShowsCard, sq1vgaPatchEgoShowsCard },

	SCI_SIGNATUREENTRY_TERMINATOR};

// will actually patch previously found signature area
void Script::applyPatch(const uint16 *patch, byte *scriptData, const uint32 scriptSize, int32 signatureOffset) {
	byte orgData[PATCH_VALUELIMIT];
	int32 offset = signatureOffset;
	uint16 patchWord = *patch;

	// Copy over original bytes from script
	uint32 orgDataSize = scriptSize - offset;
	if (orgDataSize > PATCH_VALUELIMIT)
		orgDataSize = PATCH_VALUELIMIT;
	memcpy(&orgData, &scriptData[offset], orgDataSize);

	while (patchWord != PATCH_END) {
		uint16 patchValue = patchWord & PATCH_VALUEMASK;
		switch (patchWord & PATCH_COMMANDMASK) {
		case PATCH_ADDTOOFFSET:
			// add value to offset
			offset += patchValue & ~PATCH_ADDTOOFFSET;
			break;
		case PATCH_GETORIGINALBYTE:
			// get original byte from script
			if (patchValue >= orgDataSize)
				error("patching: can not get requested original byte from script");
			scriptData[offset] = orgData[patchValue];
			offset++;
			break;
		case PATCH_ADJUSTWORD: {
			// Adjust word right before current position
			byte *adjustPtr = &scriptData[offset - 2];
			uint16 adjustWord = READ_LE_UINT16(adjustPtr);
			adjustWord += patchValue;
			WRITE_LE_UINT16(adjustPtr, adjustWord);
			break;
		}
		case PATCH_ADJUSTWORD_NEG: {
			// Adjust word right before current position (negative way)
			byte *adjustPtr = &scriptData[offset - 2];
			uint16 adjustWord = READ_LE_UINT16(adjustPtr);
			adjustWord -= patchValue;
			WRITE_LE_UINT16(adjustPtr, adjustWord);
			break;
		}
		default:
			scriptData[offset] = patchValue & 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 == READ_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;
	switch (g_sci->getGameId()) {
	case GID_ECOQUEST:
		signatureTable = ecoquest1Signatures;
		break;
	case GID_ECOQUEST2:
		signatureTable = ecoquest2Signatures;
		break;
	case GID_FANMADE:
		signatureTable = fanmadeSignatures;
		break;
	case GID_FREDDYPHARKAS:
		signatureTable = freddypharkasSignatures;
		break;
	case GID_GK1:
		signatureTable = gk1Signatures;
		break;
	case GID_KQ5:
		// See the explanation in the kq5SignatureWinGMSignals comment
		if (g_sci->_features->useAltWinGMSound())
			signatureTable = kq5WinGMSignatures;
		else
			signatureTable = kq5Signatures;
		break;
	case GID_KQ6:
		signatureTable = kq6Signatures;
		break;
	case GID_LAURABOW2:
		signatureTable = laurabow2Signatures;
		break;
	case GID_LONGBOW:
		signatureTable = longbowSignatures;
		break;
	case GID_LSL6:
		signatureTable = larry6Signatures;
		break;
	case GID_MOTHERGOOSE256:
		signatureTable = mothergoose256Signatures;
		break;
	case GID_QFG1VGA:
		signatureTable = qfg1vgaSignatures;
		break;
	case GID_QFG2:
		signatureTable = qfg2Signatures;
		break;
	case GID_QFG3:
		signatureTable = qfg3Signatures;
		break;
	case GID_SQ1:
		signatureTable = sq1vgaSignatures;
		break;
	case GID_SQ4:
		signatureTable = sq4Signatures;
		break;
	default:
		break;
	}

	if (signatureTable) {
		while (signatureTable->data) {
			if (scriptNr == signatureTable->scriptNr) {
				int32 foundOffset = 0;
				int16 applyCount = signatureTable->applyCount;
				do {
					foundOffset = findSignature(signatureTable, scriptData, scriptSize);
					if (foundOffset != -1) {
						// found, so apply the patch
						debugC(kDebugLevelScripts, "matched and patched %s on script %d offset %d", signatureTable->description, scriptNr, foundOffset);
						applyPatch(signatureTable->patch, scriptData, scriptSize, foundOffset);
					}
					applyCount--;
				} while ((foundOffset != -1) && (applyCount));
			}
			signatureTable++;
		}
	}
}

} // End of namespace Sci
