diff options
author | sluicebox | 2019-06-03 18:21:36 -0700 |
---|---|---|
committer | sluicebox | 2019-06-09 15:35:57 -0700 |
commit | 56ea963cea3cdf04c44d3d6f545df664f4a27e8d (patch) | |
tree | 2286aa7b32f52de2185b7a9206b49c1f5824880a /engines/sci/engine | |
parent | 9326f2f31d4cd33131c8c384694369f4b27ed600 (diff) | |
download | scummvm-rg350-56ea963cea3cdf04c44d3d6f545df664f4a27e8d.tar.gz scummvm-rg350-56ea963cea3cdf04c44d3d6f545df664f4a27e8d.tar.bz2 scummvm-rg350-56ea963cea3cdf04c44d3d6f545df664f4a27e8d.zip |
SCI: Create message workaround system
Adds a new workaround system for known broken messages and their
corresponding audio and sync resources. This replaces all special
cases in c++ and several script patches with data structures and
generic handling.
Common message bugs:
- Wrong tuple requested by game script
- Wrong tuple in message resource
- Wrong message text that exists in another record
- Missing message text
- Audio or sync resource with different tuple than message
Diffstat (limited to 'engines/sci/engine')
-rw-r--r-- | engines/sci/engine/message.cpp | 237 | ||||
-rw-r--r-- | engines/sci/engine/message.h | 1 | ||||
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 155 | ||||
-rw-r--r-- | engines/sci/engine/workarounds.cpp | 200 | ||||
-rw-r--r-- | engines/sci/engine/workarounds.h | 43 |
5 files changed, 293 insertions, 343 deletions
diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index 88aa73541a..0688b1ad12 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -25,6 +25,7 @@ #include "sci/engine/kernel.h" #include "sci/engine/seg_manager.h" #include "sci/engine/state.h" +#include "sci/engine/workarounds.h" #include "sci/util.h" namespace Sci { @@ -183,10 +184,17 @@ public: #endif bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &record) { - Resource *res = g_sci->getResMan()->findResource(ResourceId(kResourceTypeMessage, stack.getModule()), false); + // find a workaround for the requested message and use the prescribed module + int module = stack.getModule(); + MessageTuple &tuple = stack.top(); + SciMessageWorkaroundSolution workaround = findMessageWorkaround(module, tuple.noun, tuple.verb, tuple.cond, tuple.seq); + if (workaround.type != MSG_WORKAROUND_NONE) { + module = workaround.module; + } + Resource *res = g_sci->getResMan()->findResource(ResourceId(kResourceTypeMessage, module), false); if (!res) { - warning("Failed to open message resource %d", stack.getModule()); + warning("Failed to open message resource %d", module); return false; } @@ -224,198 +232,51 @@ bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &re return false; } - while (1) { - MessageTuple &t = stack.top(); - - // Fix known incorrect message tuples - // TODO: Add a more generic mechanism, like the one we have for - // script workarounds, for cases with incorrect sync resources, - // like the ones below. - if (g_sci->getGameId() == GID_QFG1VGA && stack.getModule() == 322 && - t.noun == 14 && t.verb == 1 && t.cond == 19 && t.seq == 1) { - // Talking to Kaspar the shopkeeper - bug #3604944 - t.verb = 2; - } - - if (g_sci->getGameId() == GID_PQ1 && stack.getModule() == 38 && - t.noun == 10 && t.verb == 4 && t.cond == 8 && t.seq == 1) { - // Using the hand icon on Keith in the Blue Room - bug #3605654 - t.cond = 9; - } - - if (g_sci->getGameId() == GID_PQ1 && stack.getModule() == 38 && - t.noun == 10 && t.verb == 1 && t.cond == 0 && t.seq == 1) { - // Using the eye icon on Keith in the Blue Room - bug #3605654 - t.cond = 13; - } - - if (g_sci->getGameId() == GID_QFG4 && stack.getModule() == 16 && - t.noun == 49 && t.verb == 1 && t.cond == 0 && t.seq == 2) { - // Examining the statue inventory item from the monastery - bug #10770 - // The description says "squid-like monster", yet the icon is - // clearly an insect. It turned Chief into "an enormous beetle". We - // change the phrase to "monstrous insect". - // - // Note: The German string contains accented characters. - // 0x84 "a with diaeresis" - // 0x94 "o with diaeresis" - // - // Values were pulled from SCI Companion's raw message data. They - // are escaped that way here, as encoded bytes. - record.tuple = t; - record.refTuple = MessageTuple(); - record.talker = 99; - if (g_sci->getSciLanguage() == K_LANG_GERMAN) { - record.string = "Die groteske Skulptur eines schrecklichen, monstr\x94sen insekts ist sorgf\x84ltig in die Einkaufstasche eingewickelt."; - record.length = 112; - } else { - record.string = "Carefully wrapped in a shopping bag is the grotesque sculpture of a horrible, monstrous insect."; - record.length = 95; - } - delete reader; - return true; - } - - if (g_sci->getGameId() == GID_QFG4 && stack.getModule() == 579 && - t.noun == 0 && t.verb == 0 && t.cond == 0 && t.seq == 1) { - // Talking with the Leshy and telling him about "bush in goo" - bug #10137 - t.verb = 1; - } - - if (g_sci->getGameId() == GID_QFG4 && g_sci->isCD() && stack.getModule() == 520 && - t.noun == 2 && t.verb == 59 && t.cond == 0) { - // The CD edition mangled the Rusalka flowers dialogue. - bug #10849 - // In the floppy edition, there are 3 lines, the first from - // the narrator, then two from Rusalka. The CD edition omits - // narration and only has the 3rd text, with the 2nd audio! The - // 3rd audio is orphaned but available. - // - // We only restore Rusalka's lines, providing the correct text - // for seq:1 to match the audio. We respond to seq:2 requests - // with Rusalka's last text. The orphaned audio (seq:3) has its - // tuple adjusted to seq:2 in resource_audio.cpp. - if (t.seq == 1) { - record.tuple = t; - record.refTuple = MessageTuple(); - record.talker = 28; - record.string = "Thank you for the beautiful flowers. No one has been so nice to me since I can remember."; - record.length = 89; - delete reader; - return true; - } else if (t.seq == 2) { - // The CD edition ships with this text at seq:1. - // Look it up instead of hardcoding. - t.seq = 1; - if (!reader->findRecord(t, record)) { - delete reader; - return false; - } - t.seq = 2; // Prevent an endless 2=1 -> 2=1 -> 2=1... loop. - record.tuple.seq = 2; // Make the record seq:2 to get the seq:2 audio. + // apply the message workaround + if (workaround.type == MSG_WORKAROUND_REMAP) { + // remap the request to a different message record. + // this alters the stack, nextMessage() will return the next + // record in the sequence following the returned record. + stack.setModule(module); + tuple.noun = workaround.noun; + tuple.verb = workaround.verb; + tuple.cond = workaround.cond; + tuple.seq = workaround.seq; + } else if (workaround.type == MSG_WORKAROUND_FAKE) { + // return a fake message record hard-coded in the workaround. + // this leaves the stack unchanged. + record.tuple = stack.top(); + record.refTuple = MessageTuple(); + record.string = workaround.text; + record.length = strlen(workaround.text); + record.talker = workaround.talker; + delete reader; + return true; + } else if (workaround.type == MSG_WORKAROUND_EXTRACT) { + // extract and return text from a different message record. + // use the talker provided by the workaround since the correct value + // could be in either, or neither, of the records. + // this leaves the stack unchanged. + MessageTuple textTuple(workaround.noun, workaround.verb, workaround.cond, workaround.seq); + MessageRecord textRecord; + if (reader->findRecord(textTuple, textRecord)) { + uint32 textLength = (workaround.substringLength == 0) ? textRecord.length : workaround.substringLength; + if (workaround.substringIndex + textLength <= textRecord.length) { + record.tuple = stack.top(); record.refTuple = MessageTuple(); + record.string = textRecord.string + workaround.substringIndex; + record.length = textLength; + record.talker = workaround.talker; delete reader; return true; } } + } - if (g_sci->getGameId() == GID_LAURABOW2 && !g_sci->isCD() && stack.getModule() == 1885 && - t.noun == 1 && t.verb == 6 && t.cond == 16 && t.seq == 4 && - (g_sci->getEngineState()->currentRoomNumber() == 350 || - g_sci->getEngineState()->currentRoomNumber() == 360 || - g_sci->getEngineState()->currentRoomNumber() == 370)) { - // Asking Yvette about Tut in act 2 party - bug #10723 - // Skip the last two lines of dialogue about a murder that hasn't occurred yet. - // Sierra fixed this in cd version by creating a copy of this message without those lines. - // Room-specific as the message is used again later where it should display in full. - t.seq += 2; - } - - // Fill in known missing message tuples - if (g_sci->getGameId() == GID_SQ4 && stack.getModule() == 16 && - t.noun == 7 && t.verb == 0 && t.cond == 3 && t.seq == 1) { - // This fixes the error message shown when speech and subtitles are - // enabled simultaneously in SQ4 - the (very) long dialog when Roger - // is talking with the aliens is missing - bug #3538416. - record.tuple = t; - record.refTuple = MessageTuple(); - record.talker = 7; // Roger - // The missing text is just too big to fit in one speech bubble, and - // if it's added here manually and drawn on screen, it's painted over - // the entrance in the back where the Sequel Police enters, so it - // looks very ugly. Perhaps this is why this particular text is missing, - // as the text shown in this screen is very short (one-liners). - // Just output an empty string here instead of showing an error. - record.string = ""; - record.length = 0; - delete reader; - return true; - } - - // FPFP CD has several message sequences where audio and text were left - // out of sync. This is probably because this version didn't formally - // support text mode. Most of these texts just say "Dummy Msg". All the - // real text is there but it's either concatenated or in a different - // tuple. We extract and return the correct text. Fixes #10964 - if (g_sci->getGameId() == GID_FREDDYPHARKAS && g_sci->isCD() && - g_sci->getLanguage() == Common::EN_ANY && !g_sci->isDemo()) { - byte textSeq = 0; - uint32 substringIndex = 0; - uint32 substringLength = 0; - - // lever brothers' introduction - if (stack.getModule() == 220 && t.noun == 24 && t.verb == 0 && t.cond == 0) { - switch (t.seq) { - case 1: textSeq = 1; substringIndex = 0; substringLength = 25; break; - case 2: textSeq = 1; substringIndex = 26; substringLength = 20; break; - case 3: textSeq = 1; substringIndex = 47; substringLength = 58; break; - case 4: textSeq = 1; substringIndex = 106; substringLength = 34; break; - case 5: textSeq = 1; substringIndex = 141; substringLength = 27; break; - case 6: textSeq = 1; substringIndex = 169; substringLength = 29; break; - case 7: textSeq = 1; substringIndex = 199; substringLength = 52; break; - case 8: textSeq = 1; substringIndex = 252; substringLength = 37; break; - } - } - - // kenny's introduction - if (stack.getModule() == 220 && t.noun == 30 && t.verb == 0 && t.cond == 0) { - switch (t.seq) { - case 3: textSeq = 3; substringIndex = 0; substringLength = 14; break; - case 4: textSeq = 3; substringIndex = 15; substringLength = 245; break; - case 5: textSeq = 4; break; - } - } - - // helen swatting flies - if (stack.getModule() == 660 && t.noun == 35 && t.verb == 0 && t.cond == 0) { - switch (t.seq) { - case 1: textSeq = 1; substringIndex = 0; substringLength = 42; break; - case 2: textSeq = 1; substringIndex = 43; substringLength = 93; break; - case 3: textSeq = 1; substringIndex = 137; substringLength = 72; break; - case 4: textSeq = 2; break; - case 5: textSeq = 1; substringIndex = 210; substringLength = 57; break; - case 6: textSeq = 3; break; - } - } - - // find the original message record and the record that contains the - // correct text, then use the correct substring. the original must - // be used to preserve its correct talker and tuple values. - if (textSeq != 0 && reader->findRecord(t, record)) { - MessageTuple textTuple(t.noun, t.verb, t.cond, textSeq); - MessageRecord textRecord; - if (reader->findRecord(textTuple, textRecord)) { - uint32 textLength = (substringLength == 0) ? textRecord.length : substringLength; - if (substringIndex + textLength <= textRecord.length) { - record.string = textRecord.string + substringIndex; - record.length = textLength; - delete reader; - return true; - } - } - } - } + while (1) { + tuple = stack.top(); - if (!reader->findRecord(t, record)) { + if (!reader->findRecord(tuple, record)) { // Tuple not found if (recurse && (stack.size() > 1)) { stack.pop(); @@ -430,7 +291,7 @@ bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &re MessageTuple &ref = record.refTuple; if (ref.noun || ref.verb || ref.cond) { - t.seq++; + tuple.seq++; stack.push(ref); continue; } diff --git a/engines/sci/engine/message.h b/engines/sci/engine/message.h index 8a1c548fa9..2777cd5897 100644 --- a/engines/sci/engine/message.h +++ b/engines/sci/engine/message.h @@ -56,6 +56,7 @@ public: } int getModule() const { return _module; } + void setModule(int module) { _module = module; } private: int _module; diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 3e8cb7d783..6574eb4961 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -2091,28 +2091,6 @@ static const uint16 gk1Day5MoselyVevePointsPatch[] = { PATCH_END }; -// GK1 english pc floppy has a missing message when showing certain items to -// Magentia in room 290 such as the flashlight. This triggers an error message. -// We fix this by passing GkMessager:say the correct cond as later versions do. -// -// Applies to: English PC Floppy 1.0 -// Responsible method: magentia:doVerb -// Fixes bug: #10782 -static const uint16 gk1ShowMagentiaItemSignature[] = { - SIG_MAGICDWORD, - 0x76, // push0 - 0x39, 0x23, // pushi 23 [ invalid message cond ] - 0x76, // push0 - 0x81, 0x5b, // lag global[5b] [ GkMessager ] - SIG_END -}; - -static const uint16 gk1ShowMagentiaItemPatch[] = { - PATCH_ADDTOOFFSET(+1), - 0x39, 0x00, // pushi 0 [ "Does this mean anything to you?" ] - PATCH_END -}; - // The day 5 snake attack has speed, audio, and graphics problems. // These occur in all versions and also in Sierra's interpreter. // @@ -2459,85 +2437,6 @@ static const uint16 gk1GranRoomInitPatch[] = { PATCH_END }; -// Using the money on the fortune teller Lorelei is scripted to respond with -// a different message depending on whether she's sitting or dancing. This -// feature manages to have three bugs which all occur in Sierra's interpreter. -// -// Bug 1: The script transposes the sitting and dancing responses. -// We reverse the test so that the right messages are attempted. -// -// Bug 2: The script passes the wrong message tuple when Lorelei is sitting -// and so a missing message error occurs. We pass the right tuple. -// -// Bug 3: The audio36 resource for message 420 2 32 0 1 has the wrong tuple and -// so no audio plays when using the money on Lorelei while dancing. -// This is a CD resource bug which we fix in the audio loader. -// -// Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply -// Responsible method: lorelei:doVerb(32) -// Fixes bug: #10819 -static const uint16 gk1LoreleiMoneySignature[] = { - 0x30, SIG_UINT16(0x000d), // bnt 000d [ lorelei is sitting ] - SIG_ADDTOOFFSET(+19), - SIG_MAGICDWORD, - 0x7a, // push2 [ noun ] - 0x8f, 0x01, // lsp param[1] [ verb (32d) ] - 0x39, 0x03, // pushi 03 [ cond ] - SIG_END -}; - -static const uint16 gk1LoreleiMoneyPatch[] = { - 0x2e, PATCH_UINT16(0x000d), // bt 000d [ lorelei is dancing ] - PATCH_ADDTOOFFSET(+22), - 0x39, 0x02, // pushi 02 [ correct cond ] - PATCH_END -}; - -// Using "Operate" on the fortune teller Lorelei's right chair causes a -// missing message error when she's standing in english pc floppy. -// We fix the message tuple as Sierra did in later versions. -// -// Applies to: English PC Floppy 1.0 -// Responsible method: chair2:doVerb(8) -// Fixes bug: #10820 -static const uint16 gk1OperateLoreleiChairSignature[] = { - // we have to reach far ahead of the doVerb method for a unique byte - // sequence to base a signature off of. chair2:doVerb has no unique bytes - // and is surrounded by doVerb methods which are duplicates of others. - SIG_MAGICDWORD, - 0x72, SIG_UINT16(0x023a), // lofsa sPickupVeil - 0x36, // push - SIG_ADDTOOFFSET(+913), - 0x39, 0x03, // pushi 03 [ cond ] - 0x81, 0x5b, // lag global[5b] [ gkMessager ] - SIG_END -}; - -static const uint16 gk1OperateLoreleiChairPatch[] = { - PATCH_ADDTOOFFSET(+917), - 0x39, 0x07, // pushi 07 [ correct cond ] - PATCH_END -}; - -// Using the photocopy of the veve on the artist causes a missing message error -// after giving the sketch from the lake and then the original veve file. -// This is due to using the wrong verb in the message tuple, which we fix. -// -// Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply -// Responsible method: artist:doVerb(24) -// Fixes bug: #10818 -static const uint16 gk1ArtistVeveCopySignature[] = { - SIG_MAGICDWORD, - 0x39, 0x30, // pushi 30 [ verb: original veve file ] - 0x39, 0x1f, // pushi 1f [ cond ] - SIG_END -}; - -static const uint16 gk1ArtistVeveCopyPatch[] = { - 0x39, 0x18, // pushi 18 [ verb: veve photocopy ] - PATCH_END -}; - // After phoning Wolfgang on day 7, Gabriel is placed beyond room 220's obstacle // boundary and can walk through walls and behind the room. This also occurs in // the original. The script inconsistently uses accessible and inaccessible @@ -2577,12 +2476,8 @@ static const SciScriptPatcherEntry gk1Signatures[] = { { true, 260, "fix day 5 snake attack (1/2)", 1, gk1Day5SnakeAttackSignature1, gk1Day5SnakeAttackPatch1 }, { true, 260, "fix day 5 snake attack (2/2)", 1, gk1Day5SnakeAttackSignature2, gk1Day5SnakeAttackPatch2 }, { true, 280, "fix pathfinding in Madame Cazanoux's house", 1, gk1CazanouxPathfindingSignature, gk1CazanouxPathfindingPatch }, - { true, 290, "fix magentia missing message", 1, gk1ShowMagentiaItemSignature, gk1ShowMagentiaItemPatch }, { true, 380, "fix Gran's room obstacles and ego flicker", 1, gk1GranRoomInitSignature, gk1GranRoomInitPatch }, { true, 410, "fix day 2 binoculars lockup", 1, gk1Day2BinocularsLockupSignature, gk1Day2BinocularsLockupPatch }, - { true, 410, "fix artist veve photocopy missing message", 1, gk1ArtistVeveCopySignature, gk1ArtistVeveCopyPatch }, - { true, 420, "fix lorelei chair missing message", 1, gk1OperateLoreleiChairSignature, gk1OperateLoreleiChairPatch }, - { true, 420, "fix lorelei money messages", 1, gk1LoreleiMoneySignature, gk1LoreleiMoneyPatch }, { true, 710, "fix day 9 vine swing speech playing", 1, gk1Day9VineSwingSignature, gk1Day9VineSwingPatch }, { true, 710, "fix day 9 mummy animation (floppy)", 1, gk1MummyAnimateFloppySignature, gk1MummyAnimateFloppyPatch }, { true, 710, "fix day 9 mummy animation (cd)", 1, gk1MummyAnimateCDSignature, gk1MummyAnimateCDPatch }, @@ -5824,33 +5719,6 @@ static const uint16 laurabow2PatchFixAct4Initialization[] = { PATCH_END }; -// LB2 Floppy 1.0 attempts to show a non-existent message when using the -// carbon paper on the desk lamp in room 550. -// -// deskLamp:<noname300>(39), which is really doVerb, attempts to show a message -// for its own noun (5) instead of the expected noun (45) when the lamp is off. -// This results in "<Messager> 550: 5, 39, 6, 1 not found". -// -// We fix this the way Sierra did in version 1.1 by passing the correct noun. -// -// Applies to: English floppy 1.000 -// Responsible method: deskLamp:<noname300>(39), which is really doVerb -// Fixes bug: #10706 -static const uint16 laurabow2SignatureMissingDeskLampMessage[] = { - SIG_MAGICDWORD, - 0x33, 0x1a, // jmp 1a - 0x38, SIG_UINT16(0x0127), // pushi 127h [ say, hardcoded as we only patch one floppy version ] - 0x39, 0x03, // pushi 3 - 0x67, 0x1a, // pTos 1a [ deskLamp noun (5) ] - SIG_END -}; - -static const uint16 laurabow2PatchMissingDeskLampMessage[] = { - PATCH_ADDTOOFFSET(+7), - 0x39, 0x2d, // pushi 45d [ correct message noun ] - PATCH_END -}; - // The armor exhibit rooms (440 and 448) have event handlers that fail to handle // all events, preventing messages from being displayed. // @@ -6196,7 +6064,6 @@ static const SciScriptPatcherEntry laurabow2Signatures[] = { { true, 550, "CD/Floppy: fix back rub east entrance lockup", 1, laurabow2SignatureFixBackRubEastEntranceLockup, laurabow2PatchFixBackRubEastEntranceLockup }, { true, 550, "CD/Floppy: fix disappearing desk items", 1, laurabow2SignatureFixDisappearingDeskItems, laurabow2PatchFixDisappearingDeskItems }, { true, 26, "Floppy: fix act 4 initialization", 1, laurabow2SignatureFixAct4Initialization, laurabow2PatchFixAct4Initialization }, - { true, 550, "Floppy: missing desk lamp message", 1, laurabow2SignatureMissingDeskLampMessage, laurabow2PatchMissingDeskLampMessage }, { true, 440, "CD/Floppy: handle armor room events", 1, laurabow2SignatureHandleArmorRoomEvents, laurabow2PatchHandleArmorRoomEvents }, { true, 448, "CD/Floppy: handle armor hall room events", 1, laurabow2SignatureHandleArmorRoomEvents, laurabow2PatchHandleArmorRoomEvents }, { true, 600, "Floppy: fix bugs with meat", 1, laurabow2FloppySignatureFixBugsWithMeat, laurabow2FloppyPatchFixBugsWithMeat }, @@ -11873,27 +11740,6 @@ static const uint16 sq4PatchEgaIntroDelay[] = { PATCH_END }; -// Talking in room 520 results in a missing message due to passing the wrong -// modNum to the narrator. -// -// Applies to: English PC CD -// Responsible method: rm520:doVerb(2) -// Fixes bug #10915 -static const uint16 sq4CdSignatureMazeTalkMessage[] = { - 0x38, SIG_SELECTOR16(modNum), // pushi modNum - SIG_MAGICDWORD, - 0x78, // push1 - 0x38, SIG_UINT16(0x01fe), // pushi 01fe [ incorrect modNum: 510 ] - 0x38, SIG_SELECTOR16(say), // pushi say - SIG_END, -}; - -static const uint16 sq4CdPatchMazeTalkMessage[] = { - PATCH_ADDTOOFFSET(+4), - 0x38, PATCH_UINT16(0x01f4), // pushi 01f4 [ correct modNum: 500 ] - PATCH_END -}; - // Talking to the red shopper in the mall has a 5% chance of a funny message but // this script is broken in the CD version. After the first time the wrong // message tuple is attempted by the narrator as its modNum value is cleared. @@ -12613,7 +12459,6 @@ static const SciScriptPatcherEntry sq4Signatures[] = { { true, 406, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast }, { true, 410, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast }, { true, 411, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast }, - { true, 520, "CD: maze talk message fix", 1, sq4CdSignatureMazeTalkMessage, sq4CdPatchMazeTalkMessage }, { true, 545, "CD: vohaul pocketpal text+speech fix", 1, sq4CdSignatureVohaulPocketPalTextSpeech, sq4CdPatchVohaulPocketPalTextSpeech }, { true, 610, "CD: biker bar door fix", 1, sq4CdSignatureBikerBarDoor, sq4CdPatchBikerBarDoor }, { true, 610, "CD: biker hands-on fix", 3, sq4CdSignatureBikerHandsOn, sq4CdPatchBikerHandsOn }, diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 1fd99e4a9f..a9c9302414 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -28,6 +28,7 @@ #include "sci/engine/workarounds.h" #define SCI_WORKAROUNDENTRY_TERMINATOR { (SciGameId)0, -1, -1, 0, NULL, NULL, NULL, 0, 0, { WORKAROUND_NONE, 0 } } +#define SCI_MESSAGEWORKAROUNDENTRY_TERMINATOR { (SciGameId)0, (SciMedia)0, (kLanguage)0, -1, -1, 0, 0, 0, 0, { MSG_WORKAROUND_NONE, -1, 0, 0, 0, 0, 0, 0, 0, NULL } } namespace Sci { @@ -1135,4 +1136,203 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun return noneFound; } +// Workarounds for known broken messages +// +// The most common message bug is a script passing the wrong tuple or a message +// having the wrong tuple. This results in a "missing message" message. These +// requests just need to be remapped to the right message. In some cases the +// message text is incorrect, or missing, in which case it can be extracted +// from another record or faked with a hard-coded response. +// +// Workarounds can be optionally scoped to media (floppy vs cd), language, and +// room number. If a message is remapped, this will also remap any audio36 and +// sync36 resources with the same tuple, unless those resources have their own +// remapping workarounds due to their tuples being out of sync with their +// already broken messages, which we've seen twice. +// +// The one kind of broken message that we don't handle yet is when the audio is +// completely missing from the game. QFG4 has most of those. In speech-only +// mode this means no audio or text and in dual mode the text disappears +// quickly as most games use audio length for the message delay. That logic is +// in each game's Messager/Narrator/Talker scripts. +static const SciMessageWorkaroundEntry messageWorkarounds[] = { + // game media language room mod n v c s workaround-type mod n v c s tlk idx len text + // FPFP CD has several message sequences where audio and text were left out of sync - bug #10964 + // Some of the texts just say "Dummy Msg" and the real values are concatenated in the first record. + // Lever Brothers' intro + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 1, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 0, 25, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 2, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 26, 20, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 3, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 47, 58, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 4, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 106, 34, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 5, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 141, 27, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 6, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 169, 29, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 7, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 199, 52, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 24, 0, 0, 8, { MSG_WORKAROUND_EXTRACT, 220, 24, 0, 0, 1, 99, 252, 37, NULL } }, + // Kenny's intro + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 30, 0, 0, 3, { MSG_WORKAROUND_EXTRACT, 220, 30, 0, 0, 3, 99, 0, 14, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 30, 0, 0, 4, { MSG_WORKAROUND_EXTRACT, 220, 30, 0, 0, 3, 99, 15, 245, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 220, 30, 0, 0, 5, { MSG_WORKAROUND_EXTRACT, 220, 30, 0, 0, 4, 99, 0, 0, NULL } }, + // Helen swatting flies + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 660, 35, 0, 0, 1, { MSG_WORKAROUND_EXTRACT, 660, 35, 0, 0, 1, 53, 0, 42, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 660, 35, 0, 0, 2, { MSG_WORKAROUND_EXTRACT, 660, 35, 0, 0, 1, 53, 43, 93, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 660, 35, 0, 0, 3, { MSG_WORKAROUND_EXTRACT, 660, 35, 0, 0, 1, 53, 137, 72, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 660, 35, 0, 0, 4, { MSG_WORKAROUND_EXTRACT, 660, 35, 0, 0, 2, 53, 0, 0, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 660, 35, 0, 0, 5, { MSG_WORKAROUND_EXTRACT, 660, 35, 0, 0, 1, 53, 210, 57, NULL } }, + { GID_FREDDYPHARKAS, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 660, 35, 0, 0, 6, { MSG_WORKAROUND_EXTRACT, 660, 35, 0, 0, 3, 12, 0, 0, NULL } }, + // Missing message when clicking flashlight and other items on Magentia in room 290, floppy 1.0 - bug #10782 + { GID_GK1, SCI_MEDIA_FLOPPY, K_LANG_NONE, -1, 290, 8, 0, 35, 1, { MSG_WORKAROUND_REMAP, 290, 8, 0, 0, 1, 0, 0, 0, NULL } }, + // Missing message when clicking photocopy of the veve on the artist after giving sketch and original veve in room 410 - bug #10818 + { GID_GK1, SCI_MEDIA_ALL, K_LANG_NONE, -1, 410, 3, 48, 31, 1, { MSG_WORKAROUND_REMAP, 410, 3, 24, 31, 1, 0, 0, 0, NULL } }, + // Missing message when clicking operate on Loreli's right chair in room 420, floppy 1.0 - bug #10820 + { GID_GK1, SCI_MEDIA_FLOPPY, K_LANG_NONE, -1, 420, 4, 8, 3, 1, { MSG_WORKAROUND_REMAP, 420, 4, 8, 7, 1, 0, 0, 0, NULL } }, + // Clicking money on Loreli when sitting or dancing in room 420 - bug #10819 + // The script transposes sitting vs dancing responses, passes an invalid cond for one of them, and the + // audio36 for the the other has the wrong tuple, which we fix in the audio36 workarounds. + { GID_GK1, SCI_MEDIA_ALL, K_LANG_NONE, -1, 420, 2, 32, 3, 1, { MSG_WORKAROUND_REMAP, 420, 2, 32, 0, 1, 0, 0, 0, NULL } }, + { GID_GK1, SCI_MEDIA_ALL, K_LANG_NONE, -1, 420, 2, 32, 0, 1, { MSG_WORKAROUND_REMAP, 420, 2, 32, 2, 1, 0, 0, 0, NULL } }, + // Asking Yvette about Tut in act 2 party in floppy version - bug #10723 + // The last two sequences in this five part message reveal a murder that hasn't occurred yet. + // We skip these as to not spoil the plot, but only in the act 2 rooms, as the message is used + // in later acts where all five parts are appropriate. Sierra fixed this in the CD version by + // creating a new three-sequence message for act 2 to accomplish the same thing. + { GID_LAURABOW2, SCI_MEDIA_FLOPPY, K_LANG_NONE, 350, 1885, 1, 6, 16, 4, { MSG_WORKAROUND_REMAP, 1885, 1, 6, 16, 6, 0, 0, 0, NULL } }, + { GID_LAURABOW2, SCI_MEDIA_FLOPPY, K_LANG_NONE, 360, 1885, 1, 6, 16, 4, { MSG_WORKAROUND_REMAP, 1885, 1, 6, 16, 6, 0, 0, 0, NULL } }, + { GID_LAURABOW2, SCI_MEDIA_FLOPPY, K_LANG_NONE, 370, 1885, 1, 6, 16, 4, { MSG_WORKAROUND_REMAP, 1885, 1, 6, 16, 6, 0, 0, 0, NULL } }, + // Missing message when clicking carbon paper on desk lamp in room 550, floppy 1.0 - bug #10706 + { GID_LAURABOW2, SCI_MEDIA_FLOPPY, K_LANG_NONE, -1, 550, 5, 39, 6, 1, { MSG_WORKAROUND_REMAP, 550, 45, 39, 6, 1, 0, 0, 0, NULL } }, + // Using the hand icon on Keith in the Blue Room (missing message) - bug #6253 + { GID_PQ1, SCI_MEDIA_ALL, K_LANG_NONE, -1, 38, 10, 4, 8, 1, { MSG_WORKAROUND_REMAP, 38, 10, 4, 9, 1, 0, 0, 0, NULL } }, + // Using the eye icon on Keith in the Blue Room (no message and wrong talker) - bug #6253 + { GID_PQ1, SCI_MEDIA_ALL, K_LANG_NONE, -1, 38, 10, 1, 0, 1, { MSG_WORKAROUND_EXTRACT, 38, 10, 1, 13, 1, 99, 0, 0, NULL } }, + // Talking to Kaspar the shopkeeper - bug #6250 + { GID_QFG1VGA, SCI_MEDIA_ALL, K_LANG_NONE, -1, 322, 14, 1, 19, 1, { MSG_WORKAROUND_REMAP, 322, 14, 2, 19, 1, 0, 0, 0, NULL } }, + // Talking with the Leshy and telling him about "bush in goo" - bug #10137 + { GID_QFG4, SCI_MEDIA_ALL, K_LANG_NONE, -1, 579, 0, 0, 0, 1, { MSG_WORKAROUND_REMAP, 579, 0, 1, 0, 1, 0, 0, 0, NULL } }, + // Examining the statue inventory item from the monastery - bug #10770 + // The description says "squid-like monster", yet the icon is + // clearly an insect. It turned Chief into "an enormous beetle". We + // change the phrase to "monstrous insect". This message is text-only. + // Note: The German string contains accented characters. + // 0x84 "a with diaeresis" + // 0x94 "o with diaeresis" + { GID_QFG4, SCI_MEDIA_ALL, K_LANG_ENGLISH, -1, 16, 49, 1, 0, 2, { MSG_WORKAROUND_FAKE, 16, 49, 1, 0, 2, 99, 0, 0, "Carefully wrapped in a shopping bag is the grotesque sculpture of a horrible, monstrous insect." } }, + { GID_QFG4, SCI_MEDIA_ALL, K_LANG_GERMAN, -1, 16, 49, 1, 0, 2, { MSG_WORKAROUND_FAKE, 16, 49, 1, 0, 2, 99, 0, 0, "Die groteske Skulptur eines schrecklichen, monstr\x94sen insekts ist sorgf\x84ltig in die Einkaufstasche eingewickelt." } }, + // The CD edition mangled the Rusalka flowers dialogue. - bug #10849 + // In the floppy edition, there are 3 lines, the first from + // the narrator, then two from Rusalka. The CD edition omits + // narration and only has the 3rd text, with the 2nd audio! The + // 3rd audio is orphaned but available. + // We only restore Rusalka's lines, providing the correct text + // for seq:1 to match the audio. We respond to seq:2 requests + // with Rusalka's last text. The orphaned audio (seq:3) has its + // tuple remapped to seq:2 in an audio workaround below. + { GID_QFG4, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 520, 2, 59, 0, 1, { MSG_WORKAROUND_FAKE, 520, 2, 59, 0, 1, 28, 0, 0, "Thank you for the beautiful flowers. No one has been so nice to me since I can remember." } }, + { GID_QFG4, SCI_MEDIA_CD, K_LANG_ENGLISH, -1, 520, 2, 59, 0, 2, { MSG_WORKAROUND_EXTRACT, 520, 2, 59, 0, 1, 28, 0, 0, NULL } }, + // This fixes the error message shown when speech and subtitles are + // enabled simultaneously in SQ4 - the (very) long dialog when Roger + // is talking with the aliens is missing - bug #6067. + // The missing text is just too big to fit in one speech bubble, and + // if it's added here manually and drawn on screen, it's painted over + // the entrance in the back where the Sequel Police enters, so it + // looks very ugly. Perhaps this is why this particular text is missing, + // as the text shown in this screen is very short (one-liners). + // Just output an empty string here instead of showing an error. + { GID_SQ4, SCI_MEDIA_CD, K_LANG_NONE, -1, 16, 7, 0, 3, 1, { MSG_WORKAROUND_FAKE, 16, 7, 0, 3, 1, 7, 0, 0, "" } }, + // Missing message when clicking talk in room 520 - bug #10915 + { GID_SQ4, SCI_MEDIA_CD, K_LANG_NONE, -1, 510, 99, 0, 3, 1, { MSG_WORKAROUND_REMAP, 500, 99, 0, 3, 1, 0, 0, 0, NULL } }, + SCI_MESSAGEWORKAROUNDENTRY_TERMINATOR +}; + +// Audio36 workarounds are for when an audio36 resource's tuple is out of sync +// with its corresponding message, in which case it can be remapped. Any sync36 +// resource with the same tuple will also be remapped. Remapping is the only +// workaround type available to this table. If a tuple is remapped both here +// and in message workarounds, this remapping is used. +static const SciMessageWorkaroundEntry audio36Workarounds[] = { + // game media language room mod n v c s workaround-type mod n v c s tlk idx len text + // Clicking money on Lorelei when dancing - bug #10819 (see message workarounds above) + { GID_GK1, SCI_MEDIA_CD, K_LANG_NONE, -1, 420, 2, 32, 0, 1, { MSG_WORKAROUND_REMAP, 420, 2, 32, 3, 1, 0, 0, 0, NULL } }, + // Clicking Look on floor grate in room 510 - bug #10848 + { GID_QFG4, SCI_MEDIA_CD, K_LANG_NONE, -1, 510, 23, 1, 0, 1, { MSG_WORKAROUND_REMAP, 510, 199, 1, 0, 1, 0, 0, 0, NULL } }, + // Clicking flowers on Rusalka - bug #10849 (see message workarounds above) + { GID_QFG4, SCI_MEDIA_CD, K_LANG_NONE, -1, 520, 2, 59, 0, 2, { MSG_WORKAROUND_REMAP, 520, 2, 59, 0, 3, 0, 0, 0, NULL } }, + SCI_MESSAGEWORKAROUNDENTRY_TERMINATOR +}; + +// Sync36 workarounds are for when a sync36 resource's tuple is out of sync with +// its corresponding message, in which case it can be remapped. This hasn't +// been encountered yet, but remapping solves a similar LB2 sync bug. +static const SciMessageWorkaroundEntry sync36Workarounds[] = { + // game media language room mod n v c s workaround-type mod n v c s tlk idx len text + // Asking yvette about tut in act 2 is missing a sync resource but a duplicate message has a sync resource - bug #9956 + { GID_LAURABOW2, SCI_MEDIA_CD, K_LANG_NONE, -1, 1885, 1, 6, 30, 2, { MSG_WORKAROUND_REMAP, 1885, 1, 6, 10, 2, 0, 0, 0, NULL } }, + SCI_MESSAGEWORKAROUNDENTRY_TERMINATOR +}; + +static SciMessageWorkaroundSolution findMessageWorkaround(int module, byte noun, byte verb, byte cond, byte seq, const SciMessageWorkaroundEntry *workaroundList) { + const SciMessageWorkaroundEntry *workaround = workaroundList; + while (workaround->solution.type != MSG_WORKAROUND_NONE) { + if (workaround->gameId == g_sci->getGameId() && + (workaround->media == SCI_MEDIA_ALL || + (workaround->media == SCI_MEDIA_FLOPPY && !g_sci->isCD()) || + (workaround->media == SCI_MEDIA_CD && g_sci->isCD())) && + (workaround->language == K_LANG_NONE || + workaround->language == g_sci->getSciLanguage()) && + (workaround->roomNumber == -1 || + workaround->roomNumber == g_sci->getEngineState()->currentRoomNumber()) && + workaround->module == module && + workaround->noun == noun && + workaround->verb == verb && + workaround->cond == cond && + workaround->seq == seq) { + break; + } + workaround++; + } + return workaround->solution; +} + +SciMessageWorkaroundSolution findMessageWorkaround(int module, byte noun, byte verb, byte cond, byte seq) { + return findMessageWorkaround(module, noun, verb, cond, seq, messageWorkarounds); +} + +ResourceId remapAudio36ResourceId(const ResourceId &resourceId) { + int module = resourceId.getNumber(); + byte noun = resourceId.getTuple() >> 24; + byte verb = (resourceId.getTuple() >> 16) & 0xff; + byte cond = (resourceId.getTuple() >> 8) & 0xff; + byte seq = resourceId.getTuple() & 0xff; + + SciMessageWorkaroundSolution workaround = findMessageWorkaround(module, noun, verb, cond, seq, audio36Workarounds); + if (workaround.type != MSG_WORKAROUND_REMAP) { + workaround = findMessageWorkaround(module, noun, verb, cond, seq, messageWorkarounds); + } + + if (workaround.type == MSG_WORKAROUND_REMAP) { + return ResourceId(resourceId.getType(), workaround.module, workaround.noun, workaround.verb, workaround.cond, workaround.seq); + } + return resourceId; +} + +ResourceId remapSync36ResourceId(const ResourceId &resourceId) { + int module = resourceId.getNumber(); + byte noun = resourceId.getTuple() >> 24; + byte verb = (resourceId.getTuple() >> 16) & 0xff; + byte cond = (resourceId.getTuple() >> 8) & 0xff; + byte seq = resourceId.getTuple() & 0xff; + + SciMessageWorkaroundSolution workaround = findMessageWorkaround(module, noun, verb, cond, seq, sync36Workarounds); + if (workaround.type != MSG_WORKAROUND_REMAP) { + workaround = findMessageWorkaround(module, noun, verb, cond, seq, audio36Workarounds); + } + if (workaround.type != MSG_WORKAROUND_REMAP) { + workaround = findMessageWorkaround(module, noun, verb, cond, seq, messageWorkarounds); + } + + if (workaround.type == MSG_WORKAROUND_REMAP) { + return ResourceId(resourceId.getType(), workaround.module, workaround.noun, workaround.verb, workaround.cond, workaround.seq); + } + return resourceId; +} + } // End of namespace Sci diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 284936b4a5..17ea81fdda 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -113,6 +113,49 @@ extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[]; extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin); +enum SciMessageWorkaroundType { + MSG_WORKAROUND_NONE, // only used by terminator or when no workaround was found + MSG_WORKAROUND_REMAP, // use a different tuple instead + MSG_WORKAROUND_FAKE, // use a hard-coded response + MSG_WORKAROUND_EXTRACT // use text from a different record, optionally a substring +}; + +enum SciMedia { + SCI_MEDIA_ALL, + SCI_MEDIA_FLOPPY, + SCI_MEDIA_CD +}; + +struct SciMessageWorkaroundSolution { + SciMessageWorkaroundType type; + int module; + byte noun; + byte verb; + byte cond; + byte seq; + byte talker; + uint32 substringIndex; + uint32 substringLength; + const char *text; +}; + +struct SciMessageWorkaroundEntry { + SciGameId gameId; + SciMedia media; + kLanguage language; + int roomNumber; + int module; + byte noun; + byte verb; + byte cond; + byte seq; + SciMessageWorkaroundSolution solution; +}; + +extern SciMessageWorkaroundSolution findMessageWorkaround(int module, byte noun, byte verb, byte cond, byte seq); +extern ResourceId remapAudio36ResourceId(const ResourceId &resourceId); +extern ResourceId remapSync36ResourceId(const ResourceId &resourceId); + } // End of namespace Sci #endif // SCI_ENGINE_WORKAROUNDS_H |