Skip to content

Commit

Permalink
MOHAWK: Riven: Patch an invalid card change when entering Gehn's office
Browse files Browse the repository at this point in the history
Fixes #10118.
  • Loading branch information
bgK committed Aug 11, 2017
1 parent c28d246 commit ee588a8
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 8 deletions.
115 changes: 107 additions & 8 deletions engines/mohawk/riven_card.cpp
Expand Up @@ -130,13 +130,7 @@ void RivenCard::applyPatches(uint16 id) {
forwardEnabled.index
};

// Script data is expected to be in big endian
for (uint i = 0; i < ARRAYSIZE(patchData); i++) {
patchData[i] = TO_BE_16(patchData[i]);
}

Common::MemoryReadStream patchStream((const byte *)(patchData), ARRAYSIZE(patchData) * sizeof(uint16));
RivenScriptPtr patchScript = _vm->_scriptMan->readScript(&patchStream);
RivenScriptPtr patchScript = _vm->_scriptMan->readScriptFromData(patchData, ARRAYSIZE(patchData));

// Append the patch to the existing script
RivenScriptPtr loadScript = getScript(kCardLoadScript);
Expand All @@ -145,6 +139,111 @@ void RivenCard::applyPatches(uint16 id) {
debugC(kRivenDebugPatches, "Applied fix always enabled forward hotspot in card %x", globalId);
}

// In Gehn's office, after having encountered him once before and coming back
// with the trap book, the draw update script of card 1 tries to switch to
// card 2 while still loading card 1. Switching cards is not allowed during
// draw update scripts, resulting in an use after free crash.
//
// Here we backport the fix that has been made in the DVD version to the CD version.
//
// Script before patch:
// == Script 1 ==
// type: CardUpdate
// switch (agehn) {
// case 1:
// switch (atrapbook) {
// case 1:
// obutton = 1;
// transition(16);
// switchCard(2);
// break;
// }
// break;
// case 2:
// activatePLST(5);
// break;
// case 3:
// activatePLST(5);
// break;
// }
//
//
// Script after patch:
// == Script 1 ==
// type: CardUpdate
// switch (agehn) {
// case 1:
// switch (atrapbook) {
// case 1:
// obutton = 1;
// activatePLST(6);
// break;
// }
// break;
// case 2:
// activatePLST(5);
// break;
// case 3:
// activatePLST(5);
// break;
// }
//
// == Script 2 ==
// type: CardEnter
// switch (agehn) {
// case 1:
// switch (atrapbook) {
// case 1:
// transition(16);
// switchCard(2);
// break;
// }
// break;
// }
if (globalId == 0x2E76 && !(_vm->getFeatures() & GF_DVD)) {
uint16 aGehnVariable = _vm->getStack()->getIdFromName(kVariableNames, "agehn");
uint16 aTrapBookVariable = _vm->getStack()->getIdFromName(kVariableNames, "atrapbook");
uint16 patchData[] = {
1, // Command count in script
kRivenCommandSwitch,
2, // Unused
aGehnVariable,
1, // Branches count

1, // agehn == 1 branch
1, // Command count in sub-script
kRivenCommandSwitch,
2, // Unused
aTrapBookVariable,
1, // Branches count

1, // atrapbook == 1 branch
2, // Command count in sub-script
kRivenCommandTransition,
1, // Argument count
kRivenTransitionBlend,
kRivenCommandChangeCard,
1, // Argument count
2 // Card id
};

// Add the new script to the list
RivenTypedScript patchScript;
patchScript.type = kCardEnterScript;
patchScript.script = _vm->_scriptMan->readScriptFromData(patchData, ARRAYSIZE(patchData));
_scripts.push_back(patchScript);

// Add a black picture to the card's list to be able to use it in the second part of the patch
Picture blackPicture;
blackPicture.index = 6;
blackPicture.id = 117;
blackPicture.rect = Common::Rect(608, 392);
_pictureList.push_back(blackPicture);

debugC(kRivenDebugPatches, "Applied invalid card change during screen update (1/2) to card %x", globalId);
// The second part of this patch is in the script patches
}

// Apply script patches
for (uint i = 0; i < _scripts.size(); i++) {
_scripts[i].script->applyCardPatches(_vm, globalId, _scripts[i].type, 0xFFFF);
Expand Down Expand Up @@ -698,7 +797,7 @@ RivenScriptPtr RivenCard::onKeyAction(RivenKeyAction keyAction) {
static const char *upNames [] = { "up", nullptr };
static const char *downNames [] = { "down", nullptr };

static const char **hotspotNames;
const char **hotspotNames = nullptr;
switch (keyAction) {
case kKeyActionMoveForward:
hotspotNames = forwardNames;
Expand Down
33 changes: 33 additions & 0 deletions engines/mohawk/riven_scripts.cpp
Expand Up @@ -179,6 +179,16 @@ RivenScriptPtr RivenScriptManager::createScriptFromData(uint16 commandCount, ...
return readScript(&readStream);
}

RivenScriptPtr RivenScriptManager::readScriptFromData(uint16 *data, uint16 size) {
// Script data is expected to be in big endian
for (uint i = 0; i < size; i++) {
data[i] = TO_BE_16(data[i]);
}

Common::MemoryReadStream patchStream((const byte *)(data), size * sizeof(uint16));
return _vm->_scriptMan->readScript(&patchStream);
}

RivenScriptPtr RivenScriptManager::createScriptWithCommand(RivenCommand *command) {
assert(command);

Expand Down Expand Up @@ -312,6 +322,29 @@ void RivenScript::applyCardPatches(MohawkEngine_Riven *vm, uint32 cardGlobalId,
debugC(kRivenDebugPatches, "Applied missing closing sound patch to card %x", cardGlobalId);
}

// Second part of the patch to fix the invalid card change when entering Gehn's office
// The first part is in the card patches.
if (cardGlobalId == 0x2E76 && scriptType == kCardUpdateScript && !(vm->getFeatures() & GF_DVD)) {
shouldApplyPatches = true;

for (uint i = 0; i < _commands.size(); i++) {
int transitionIndex = -1;
if (_commands[i]->getType() == kRivenCommandTransition) {
transitionIndex = i;
}
if (transitionIndex >= 0) {
_commands.remove_at(transitionIndex + 1);
_commands.remove_at(transitionIndex);

RivenSimpleCommand::ArgumentArray arguments;
arguments.push_back(6);
_commands.push_back(RivenCommandPtr(new RivenSimpleCommand(vm, kRivenCommandActivatePLST, arguments)));
}
}

debugC(kRivenDebugPatches, "Applied invalid card change during screen update (2/2) to card %x", cardGlobalId);
}

if (shouldApplyPatches) {
for (uint i = 0; i < _commands.size(); i++) {
_commands[i]->applyCardPatches(cardGlobalId, scriptType, hotspotId);
Expand Down
8 changes: 8 additions & 0 deletions engines/mohawk/riven_scripts.h
Expand Up @@ -170,6 +170,14 @@ class RivenScriptManager {
/** Read a single script from a stream */
RivenScriptPtr readScript(Common::ReadStream *stream);

/**
* Read a script from an array of uint16
* @param data Script data array. Will be modified.
* @param size Number of uint16 in data
* @return
*/
RivenScriptPtr readScriptFromData(uint16 *data, uint16 size);

/** Create a script from the caller provided arguments containing raw data */
RivenScriptPtr createScriptFromData(uint16 commandCount, ...);

Expand Down

0 comments on commit ee588a8

Please sign in to comment.