diff options
author | Vhati | 2019-01-10 01:15:23 -0500 |
---|---|---|
committer | Filippos Karapetis | 2019-02-13 22:15:02 +0200 |
commit | 603115dfca61446439973689b15f713e7de96edf (patch) | |
tree | 23e9de0671a9275dadc86425cf047f077b135d7f /engines/sci | |
parent | a6bcf88cd4a904f4b4898934b7b8becc7ff024c2 (diff) | |
download | scummvm-rg350-603115dfca61446439973689b15f713e7de96edf.tar.gz scummvm-rg350-603115dfca61446439973689b15f713e7de96edf.tar.bz2 scummvm-rg350-603115dfca61446439973689b15f713e7de96edf.zip |
SCI32: Fix QFG4 guild secret passage
Fixes the walkable polygon not changing when it's revealed, bug #9894
Diffstat (limited to 'engines/sci')
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 260 |
1 files changed, 259 insertions, 1 deletions
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index c516508f78..775bf8f925 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -161,9 +161,11 @@ static const char *const selectorNameTable[] = { "plane", // RAMA "state", // RAMA "getSubscriberObj", // RAMA + "approachVerbs", // QFG4 "cue", // QFG4 "heading", // QFG4 "moveSpeed", // QFG4 + "obstacles", // QFG4 "sayMessage", // QFG4 "setLooper", // QFG4 "setSpeed", // QFG4 @@ -253,9 +255,11 @@ enum ScriptPatcherSelectors { SELECTOR_plane, SELECTOR_state, SELECTOR_getSubscriberObj, + SELECTOR_approachVerbs, SELECTOR_cue, SELECTOR_heading, SELECTOR_moveSpeed, + SELECTOR_obstacles, SELECTOR_sayMessage, SELECTOR_setLooper, SELECTOR_setSpeed, @@ -9990,6 +9994,254 @@ static const uint16 qfg4StuckDoorPatch[] = { PATCH_END }; +// In the thieves' guild (room 430), the tunnel is not immediately walkable +// when it is revealed (by moving a barrel and solving a puzzle). Hero must +// re-enter the room to update the polygon. +// +// Curing Chief *will* immediately replace the polygon. However, most players +// will lack the item necessary on the first visit. Meeting Chief is how they +// learn about the item. If they go get it, they'll re-enter the room. +// +// The room's init has a cond block to check plot flags and declare one of 3 +// polygons. The 3rd condition also inits secritExit, an invisible Feature +// that sends hero out of town when walked upon. +// +// Patch 1: Other patches ensure the passage will be walkable the moment it is +// revealed. Chief is standing inside it. We skip the original code that would +// set up the passage as he gets cured. It is redundant now. If hero can reach +// him, the passage is already revealed. We won't let secritExit init twice. +// +// Patch 2: We free bytes in rm340::init() by condensing Feature inits with a +// loop. Stack up their addresses. Pop & send repeatedly. Then we declare a +// subroutine that disposes any existing obstacles, jumps into the cond block +// to declare the 3rd poly, jumps back, passes it to addObstacles(), and inits +// secritExit. +// +// When the cond block's 3rd condition runs, we immediately call our +// subroutine to do everything and end the cond, leaving the original polygon +// declaration intact below the jump. +// +// Patch 3: The passage starts opening at sBarrelMove state 8. We need more +// room than case 8 can offer, so we arrange for *multiple* cases to run +// during state 8 - by omitting the final jump that would short-circuit. +// +// Cases 1-5 have derelict code, once intended to move the barrel back and +// forth, now only left. This is because barrel::doVerb(4) schedules +// sBarrelMove in the absence of flag 254 and sets register=1 if the barrel is +// in the left position already. Case 0 uses the same criteria in deciding to +// skip to state 6. Thus cases 1-5 never see register==1. The barrel never +// moves back, and bytes predicated on register==1 are available. +// +// We reduce case 2 to only the necessary ops and splice in a new case that +// runs during state 8 as a prologue to the original case 8. Our prologue +// calls the subroutine to add the 3rd polygon. This patch has two variants, +// toggled to match the detected edition with enablePatch() below. Aside from +// the call offset, they are identical. +// +// Applies to at least: English CD, English floppy, German floppy +// Responsible method: sChangeThief::changeState() in script 340 +// Fixes bug: #9894 +static const uint16 qfg4GuildWalkSignature1[] = { + 0x38, SIG_SELECTOR16(dispose), // pushi dispose + SIG_ADDTOOFFSET(+20), // ... (dispose and null global[2]'s "obstacles" property) + 0x4a, SIG_UINT16(0x0006), // send 6d + SIG_ADDTOOFFSET(+85), // ... + SIG_ADDTOOFFSET(+238), // ... (secritExit init and addObstacle) + SIG_MAGICDWORD, + 0x4a, SIG_UINT16(0x00a6), // send 166d (polygon init) + 0x36, // push + SIG_ADDTOOFFSET(+5), // ... (global[2] addObstacle: polygon) + SIG_END +}; + +static const uint16 qfg4GuildWalkPatch1[] = { + 0x32, PATCH_UINT16(0x0017), // jmp 23d (skip obstacles disposal) + PATCH_ADDTOOFFSET(+108), + 0x32, PATCH_UINT16(0x00f4), // jmp 244d (skip secritExit and polygon) + PATCH_END +}; + +// Responsible method: rm340::init() in script 340 +static const uint16 qfg4GuildWalkSignature2[] = { + 0x38, SIG_SELECTOR16(init), // pushi init + 0x76, // push0 + 0x38, SIG_SELECTOR16(approachVerbs), // pushi approachVerbs + 0x78, // push1 + 0x39, 0x04, // pushi 4d + 0x72, SIG_ADDTOOFFSET(+2), // lofsa steps1 + 0x4a, SIG_UINT16(0x000a), // send 10d + + SIG_ADDTOOFFSET(+10), // ... (similar inits follow) + 0x72, SIG_ADDTOOFFSET(+2), // lofsa steps2 + SIG_ADDTOOFFSET(+13), // ... + 0x72, SIG_ADDTOOFFSET(+2), // lofsa barrels1 + SIG_ADDTOOFFSET(+13), // ... + 0x72, SIG_ADDTOOFFSET(+2), // lofsa barrels2 + SIG_ADDTOOFFSET(+13), // ... + 0x72, SIG_ADDTOOFFSET(+2), // lofsa crack1 + SIG_ADDTOOFFSET(+13), // ... + 0x72, SIG_ADDTOOFFSET(+2), // lofsa crack2 + SIG_ADDTOOFFSET(+13), // ... + 0x72, SIG_ADDTOOFFSET(+2), // lofsa pillar + SIG_ADDTOOFFSET(+3), // ... + + SIG_ADDTOOFFSET(+26), // ... (global[78]::add() steps1 and steps2) + + SIG_ADDTOOFFSET(+459), // ... (cond block for polygons) + SIG_MAGICDWORD, + 0x32, SIG_UINT16(0x00f7), // jmp 247d [end the cond] (2nd condition done) + + // (else condition) + 0x38, SIG_SELECTOR16(init), // pushi init + 0x76, // push0 + 0x72, SIG_ADDTOOFFSET(+2), // lofsa secritExit + 0x4a, SIG_UINT16(0x0004), // send 4d + SIG_ADDTOOFFSET(+4), // ... (addObstacle and its arg count) + SIG_ADDTOOFFSET(+228), // ... (3rd Polygon type, init, and push) + SIG_ADDTOOFFSET(+5), // ... (end of the polygons cond) + + 0x38, SIG_SELECTOR16(init), // pushi init (super init:) + SIG_END +}; + +static const uint16 qfg4GuildWalkPatch2[] = { + 0x3f, 0x02, // link 02 (set up loop vars, op affects the stack) + 0x74, PATCH_GETORIGINALUINT16(11), // lofss steps1 + 0x74, PATCH_GETORIGINALUINT16(27), // lofss steps2 + 0x74, PATCH_GETORIGINALUINT16(43), // lofss barrels1 + 0x74, PATCH_GETORIGINALUINT16(59), // lofss barrels2 + 0x74, PATCH_GETORIGINALUINT16(75), // lofss crack1 + 0x74, PATCH_GETORIGINALUINT16(91), // lofss crack2 + 0x74, PATCH_GETORIGINALUINT16(107), // lofss pillar + // + 0x35, 0x08, // ldi 8d (decrement and send 7 times, while != 0) + 0xa5, 0x00, // sat temp[0] + // + 0xe5, 0x00, // -at temp[0] + 0x31, 0x13, // bnt 19d [on 0, end the loop] + 0xad, 0x01, // sst temp[1] (pop the next object into a temp var) + 0x38, PATCH_SELECTOR16(init), // pushi init + 0x76, // push0 + 0x38, PATCH_SELECTOR16(approachVerbs), // pushi approachVerbs + 0x78, // push1 + 0x39, 0x04, // pushi 4d + 0x85, 0x01, // lat temp[1] (accumulate the object) + 0x4a, PATCH_UINT16(0x000a), // send 10d + 0x33, 0xe9, // jmp -23d [loop] + + 0x33, 0x33, // jmp 51d [skip subroutine declaration] + 0x38, PATCH_SELECTOR16(obstacles), // pushi obstacles (look up "obstacles", might be null) + 0x76, // push0 + 0x81, 0x02, // lag global[2] + 0x4a, PATCH_UINT16(0x0004), // send 4d + 0x31, 0x11, // bnt 17d [skip disposal and nulling] + 0x38, PATCH_SELECTOR16(dispose), // pushi dispose + 0x76, // push0 + 0x4a, PATCH_UINT16(0x0004), // send 4d ((global[2] obstacles?) dispose:) + // + 0x38, PATCH_SELECTOR16(obstacles), // pushi obstacles (null the "obstacles" property) + 0x78, // push1 + 0x76, // push0 + 0x81, 0x02, // lag global[2] + 0x4a, PATCH_UINT16(0x0006), // send 6d + // + 0x38, PATCH_SELECTOR16(addObstacle), // pushi addObstacle + 0x78, // push1 + 0x32, PATCH_UINT16(0x020f), // jmp 527d [3rd polygon type, init, and push] + // (That will jmp back here) + 0x81, 0x02, // lag global[2] + 0x4a, PATCH_UINT16(0x0006), // send 6d + // + 0x38, PATCH_SELECTOR16(init), // pushi init + 0x76, // push0 + 0x72, PATCH_GETORIGINALUINT16(605), // lofsa secritExit + 0x4a, PATCH_UINT16(0x0004), // send 4d + 0x48, // ret + + 0x33, 0x07, // jmp 7d [skip waste bytes, to (global[78] add: steps1)] + 0x5c, // selfID (waste 1 byte) + PATCH_ADDTOOFFSET(+494), // ... + 0x76, // push0 (0 call args, clobber the old secritExit init) + 0x40, PATCH_UINT16(0xfdd6), PATCH_UINT16(0x0000), // call 0d [-554] (the subroutine does everything) + 0x32, PATCH_UINT16(0x00ee), // jmp 238d [end the cond] + 0x5c, // selfID (waste 1 byte) + PATCH_ADDTOOFFSET(+4), // ... + PATCH_ADDTOOFFSET(+228), // ... (3rd polygon type, init, and push) + 0x32, PATCH_UINT16(0xfd0a), // jmp -758d [back into the subroutine] + 0x35, 0x00, // ldi 0 (erase 2 bytes to keep disasm aligned) + PATCH_END +}; + +// Applies to at least: English CD +// Responsible method: sBarrelMove::changeState(2) in script 340 +static const uint16 qfg4GuildWalkCDSignature3[] = { + SIG_MAGICDWORD, + 0x30, SIG_UINT16(0x0032), // bnt 50d [next case] + 0x35, 0x02, // ldi 2d (case 2 label) + SIG_ADDTOOFFSET(+26), // ... (register branch and derelict say()) + SIG_ADDTOOFFSET(+19), // ... (else, the rest of case 2 is a necessary say()) + 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch] + SIG_END +}; + +static const uint16 qfg4GuildWalkCDPatch3[] = { + 0x31, 0x15, // bnt 21d [next case] + 0x38, PATCH_SELECTOR16(say), // pushi say + 0x39, 0x05, // pushi 5d + 0x39, 0x06, // pushi 6d + 0x39, 0x04, // pushi 4d + 0x39, 0x13, // pushi 19d + 0x76, // push0 + 0x7c, // pushSelf + 0x81, 0x5b, // lag global[91] + 0x4a, PATCH_UINT16(0x000e), // send 14d + 0x32, PATCH_GETORIGINALUINT16ADJUST(51, +30), // jmp ?? [end the switch] + + 0x3c, // dup (case 8 prologue) + 0x35, 0x08, // ldi 8d + 0x1a, // eq? + 0x31, 0x06, // bnt 6d [next case] + 0x76, // push0 (0 call args) + 0x40, PATCH_UINT16(0xf592), PATCH_UINT16(0x0000), // call [-2670], 0d (patch 2's subroutine) + 0x33, 0x10, // jmp 16d [skip waste bytes] + PATCH_END // (don't end the switch, keep testing cases) +}; + +// Applies to at least: English floppy, German floppy +// Responsible method: sBarrelMove::changeState(2) in script 340 +static const uint16 qfg4GuildWalkFloppySignature3[] = { + SIG_MAGICDWORD, + 0x30, SIG_UINT16(0x0032), // bnt 50d [next case] + 0x35, 0x02, // ldi 2d (case 2 label) + SIG_ADDTOOFFSET(+26), // ... (register branch and derelict say()) + SIG_ADDTOOFFSET(+19), // ... (else, the rest of case 2 is a necessary say()) + 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch] + SIG_END +}; + +static const uint16 qfg4GuildWalkFloppyPatch3[] = { + 0x31, 0x15, // bnt 21d [next case] + 0x38, PATCH_SELECTOR16(say), // pushi say + 0x39, 0x05, // pushi 5d + 0x39, 0x06, // pushi 6d + 0x39, 0x04, // pushi 4d + 0x39, 0x13, // pushi 19d + 0x76, // push0 + 0x7c, // pushSelf + 0x81, 0x5b, // lag global[91] + 0x4a, PATCH_UINT16(0x000e), // send 14d + 0x32, PATCH_GETORIGINALUINT16ADJUST(51, +30), // jmp ?? [end the switch] + + 0x3c, // dup (case 8 prologue) + 0x35, 0x08, // ldi 8d + 0x1a, // eq? + 0x31, 0x06, // bnt 6d [next case] + 0x76, // push0 (0 call args) + 0x40, PATCH_UINT16(0xf5a8), PATCH_UINT16(0x0000), // call [-2648], 0d (patch 2's subroutine) + 0x33, 0x10, // jmp 16d [skip waste bytes] + PATCH_END // (don't end the switch, keep testing cases) +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg4Signatures[] = { { true, 0, "prevent autosave from deleting save games", 1, qfg4AutosaveSignature, qfg4AutosavePatch }, @@ -10008,6 +10260,10 @@ static const SciScriptPatcherEntry qfg4Signatures[] = { { true, 270, "fix town gate after a staff dream", 1, qfg4DreamGateSignature, qfg4DreamGatePatch }, { true, 320, "fix pathfinding at the inn", 1, qfg4InnPathfindingSignature, qfg4InnPathfindingPatch }, { true, 320, "fix talking to absent innkeeper", 1, qfg4AbsentInnkeeperSignature, qfg4AbsentInnkeeperPatch }, + { true, 340, "CD/Floppy: fix guild tunnel access (1/3)", 1, qfg4GuildWalkSignature1, qfg4GuildWalkPatch1 }, + { true, 340, "CD/Floppy: fix guild tunnel access (2/3)", 1, qfg4GuildWalkSignature2, qfg4GuildWalkPatch2 }, + { false, 340, "CD: fix guild tunnel access (3/3)", 1, qfg4GuildWalkCDSignature3, qfg4GuildWalkCDPatch3 }, + { false, 340, "Floppy: fix guild tunnel access (3/3)", 1, qfg4GuildWalkFloppySignature3, qfg4GuildWalkFloppyPatch3 }, { true, 440, "fix setLooper calls (1/2)", 1, qfg4SetLooperSignature1, qfg4SetLooperPatch1 }, { true, 475, "fix tarot 3 queen card", 1, qfg4Tarot3QueenSignature, qfg4Tarot3QueenPatch }, { true, 475, "fix tarot 3 death card", 1, qfg4Tarot3DeathSignature, qfg4Tarot3DeathPatch }, @@ -11888,9 +12144,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) { enablePatch(signatureTable, "CD: fix rope during Igor rescue (1/2)"); enablePatch(signatureTable, "CD: fix rope during Igor rescue (2/2)"); - // Similar signatures that patch with a different lofsa address + // Similar signatures that patch with different addresses/offsets + enablePatch(signatureTable, "CD: fix guild tunnel access (3/3)"); enablePatch(signatureTable, "CD: fix crest bookshelf"); } else { + enablePatch(signatureTable, "Floppy: fix guild tunnel access (3/3)"); enablePatch(signatureTable, "Floppy: fix crest bookshelf"); } break; |