Files
scummvm-cursorfix/engines/stark/tools/decompiler.cpp
2026-02-02 04:50:13 +01:00

449 lines
14 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/stark/tools/decompiler.h"
#include "engines/stark/resources/command.h"
#include "engines/stark/tools/abstractsyntaxtree.h"
#include "engines/stark/tools/block.h"
#include "common/debug.h"
namespace Stark {
namespace Tools {
Decompiler::Decompiler(Resources::Script *script) :
_entryPoint(nullptr),
_astHead(nullptr),
_definitionRegistry(nullptr) {
// Convert the script commands to decompiler commands
Common::Array<Resources::Command *> resourceCommands = script->listChildren<Resources::Command>();
for (uint i = 0; i < resourceCommands.size(); i++) {
_commands.push_back(new CFGCommand(resourceCommands[i]));
}
if (_commands.empty()) {
return;
}
if (!checkCommands()) {
return;
}
_entryPoint = findEntryPoint();
linkCommandBranches();
buildBlocks();
analyseControlFlow();
_definitionRegistry = new DefinitionRegistry();
_astHead = buildAST();
verifyAST();
}
void Decompiler::printCommands() const {
for (uint i = 0; i < _commands.size(); i++) {
_commands[i]->printCall();
}
}
void Decompiler::printBlocks() const {
for (uint i = 0; i < _blocks.size(); i++) {
_blocks[i]->print();
debug("- - - -");
}
}
void Decompiler::printDecompiled() {
if (_astHead) {
_definitionRegistry->printAll();
debug(" "); // Empty line
_astHead->print(0, _definitionRegistry);
}
}
Decompiler::~Decompiler() {
for (uint i = 0; i < _commands.size(); i++) {
delete _commands[i];
}
for (uint i = 0; i < _blocks.size(); i++) {
delete _blocks[i];
}
for (uint i = 0; i < _controlStructures.size(); i++) {
delete _controlStructures[i];
}
delete _astHead;
delete _definitionRegistry;
}
void Decompiler::linkCommandBranches() {
for (uint i = 0; i < _commands.size(); i++) {
_commands[i]->linkBranches(_commands);
}
}
CFGCommand *Decompiler::findEntryPoint() {
for (uint i = 0; i < _commands.size(); i++) {
if (_commands[i]->isEntryPoint()) {
return _commands[i];
}
}
error("Unable to find an entry point");
}
void Decompiler::buildBlocks() {
Block *entryPointBlock = new Block();
_blocks.push_back(entryPointBlock);
buildBlocks(entryPointBlock, _entryPoint);
}
void Decompiler::buildBlocks(Block *block, CFGCommand *command) {
CFGCommand *blockCommand = command;
while (blockCommand) {
if (blockCommand->getBlock()) {
block->setFollower(blockCommand->getBlock());
break;
}
if (blockCommand->isBranchTarget() && !block->isEmpty()) {
Block *follower = buildBranchBlocks(blockCommand);
block->setFollower(follower);
break;
}
block->appendCommand(blockCommand);
if (blockCommand->isBranch()) {
Block *falseBranch = buildBranchBlocks(blockCommand->getFalseBranch());
Block *trueBranch = buildBranchBlocks(blockCommand->getTrueBranch());
block->setBranches(trueBranch, falseBranch);
break;
}
blockCommand = blockCommand->getFollower();
}
}
Block *Decompiler::buildBranchBlocks(CFGCommand *command) {
if (command->getBlock()) {
// The command already has a block. No need to go through this path again.
return command->getBlock();
}
Block *branchBlock = new Block();
_blocks.push_back(branchBlock);
buildBlocks(branchBlock, command);
return branchBlock;
}
void Decompiler::analyseControlFlow() {
detectInfiniteLoop();
detectWhile();
detectIf();
}
void Decompiler::detectInfiniteLoop() {
for (uint i = 0; i < _blocks.size(); i++) {
Block *block = _blocks[i];
// Check all paths from the block go back to itself
if (block->getFollower()) {
bool followerConvergesBack = block->getFollower()->checkAllBranchesConverge(block);
if (followerConvergesBack) {
block->setInfiniteLoopStart(true);
}
} else if (block->isCondition()) {
bool trueBranchConvergesBack = block->getTrueBranch()->checkAllBranchesConverge(block);
bool falseBranchConvergesBack = block->getFalseBranch()->checkAllBranchesConverge(block);
if (trueBranchConvergesBack && falseBranchConvergesBack) {
block->setInfiniteLoopStart(true);
}
}
}
}
void Decompiler::detectWhile() {
for (uint i = 0; i < _blocks.size(); i++) {
Block *block = _blocks[i];
if (block->hasControlStructure()) continue;
if (!block->isCondition()) continue;
if (block->isInfiniteLoopStart()) continue; // TODO: This should be handled by checkAllBranchesConverge already
// Check all paths from the body branch go back to the condition
// TODO: If the original had "break" statement, this will not work
bool trueBranchConvergesToCondition = block->getTrueBranch()->checkAllBranchesConverge(block);
bool falseBranchConvergesToCondition = block->getFalseBranch()->checkAllBranchesConverge(block);
if (!trueBranchConvergesToCondition && !falseBranchConvergesToCondition) continue;
if (trueBranchConvergesToCondition && falseBranchConvergesToCondition) {
warning("Both branches of a condition converge back to the condition");
}
ControlStructure *controlStructure = new ControlStructure(ControlStructure::kTypeWhile);
if (trueBranchConvergesToCondition) {
controlStructure->invertedCondition = false;
controlStructure->loopHead = block->getTrueBranch();
controlStructure->next = block->getFalseBranch();
} else {
controlStructure->invertedCondition = true;
controlStructure->loopHead = block->getFalseBranch();
controlStructure->next = block->getTrueBranch();
}
block->setControlStructure(controlStructure);
_controlStructures.push_back(controlStructure);
}
}
void Decompiler::detectIf() {
for (uint i = 0; i < _blocks.size(); i++) {
Block *block = _blocks[i];
if (block->hasControlStructure()) continue;
if (!block->isCondition()) continue;
ControlStructure *controlStructure = new ControlStructure(ControlStructure::kTypeIf);
controlStructure->next = block->getTrueBranch()->findMergePoint(block->getFalseBranch());
if (!controlStructure->next) {
// When one (or both) of the branches return, there is no merge point
controlStructure->invertedCondition = false;
controlStructure->thenHead = block->getTrueBranch();
controlStructure->elseHead = block->getFalseBranch();
} else if (block->getTrueBranch() != controlStructure->next) {
// Use the "true" branch as the "then" block ...
controlStructure->invertedCondition = false;
controlStructure->thenHead = block->getTrueBranch();
controlStructure->elseHead = controlStructure->next != block->getFalseBranch() ? block->getFalseBranch() : nullptr;
} else {
// ... unless the true branch is empty.
// In which case use the false branch and invert the condition.
controlStructure->invertedCondition = true;
controlStructure->thenHead = block->getFalseBranch();
controlStructure->elseHead = nullptr;
}
block->setControlStructure(controlStructure);
_controlStructures.push_back(controlStructure);
}
}
bool Decompiler::checkCommands() {
for (uint i = 0; i < _commands.size(); i++) {
Command *command = _commands[i];
if (!command->hasSubtypeDescription()) {
_error = Common::String::format("Command subtype %d is not described", command->getSubType());
return false;
}
}
return true;
}
Common::String Decompiler::getError() const {
return _error;
}
ASTNode *Decompiler::buildAST() {
Block *entryPoint = _entryPoint->getBlock();
ASTBlock *head = new ASTBlock(nullptr);
buildASTFromBlock(head, entryPoint, nullptr);
return head;
}
void Decompiler::buildASTFromBlock(ASTBlock *parent, Block *block, Block *stopBlock) {
if (block->isInfiniteLoopStart()) {
bool alreadyVisited = Common::find(_visitedInfiniteLoopStarts.begin(), _visitedInfiniteLoopStarts.end(), block)
!= _visitedInfiniteLoopStarts.end();
if (alreadyVisited) {
// Don't add the same loop multiple times in the AST
return;
}
_visitedInfiniteLoopStarts.push_back(block);
ASTLoop *loop = new ASTLoop(parent);
loop->loopBlock = new ASTBlock(loop);
parent->addNode(loop);
// Hijack the parameters to add the current block to the loop body and continue
parent = loop->loopBlock;
stopBlock = block;
}
{
bool alreadyVisited = Common::find(_visitedBlocks.begin(), _visitedBlocks.end(), block) != _visitedBlocks.end();
if (alreadyVisited && !block->allowDuplication()) {
// FIXME: We just return for now when an already visited block is visited again.
// Obviously, this leads to invalid decompiled code, which is caught by the verification step.
// To fix, either handle the cases leading to multiple visits, or generate gotos.
return;
}
}
_visitedBlocks.push_back(block);
Common::Array<CFGCommand *> commands = block->getLinearCommands();
for (uint i = 0; i < commands.size(); i++) {
parent->addNode(new ASTCommand(parent, commands[i], _definitionRegistry));
}
if (block->hasControlStructure()) {
ControlStructure *cfgControlStructure = block->getControlStructure();
ASTNode *astControlStructure;
switch (cfgControlStructure->type) {
case ControlStructure::kTypeIf:
astControlStructure = buildASTConditionFromBlock(parent, block);
break;
case ControlStructure::kTypeWhile:
astControlStructure = buildASTLoopFromBlock(parent, block);
break;
default:
error("Unknown control structure type %d", cfgControlStructure->type);
}
parent->addNode(astControlStructure);
if (cfgControlStructure->next && cfgControlStructure->next != stopBlock) {
buildASTFromBlock(parent, cfgControlStructure->next, stopBlock);
}
} else {
Block *follower = block->getFollower();
if (follower && follower != stopBlock) {
buildASTFromBlock(parent, follower, stopBlock);
}
}
}
ASTCondition *Decompiler::buildASTConditionFromBlock(ASTNode *parent, Block *block) {
ControlStructure *controlStructure = block->getControlStructure();
ASTCondition *condition = new ASTCondition(parent);
condition->condition = new ASTCommand(condition, block->getConditionCommand(), _definitionRegistry);
condition->invertedCondition = controlStructure->invertedCondition;
condition->thenBlock = new ASTBlock(condition);
buildASTFromBlock(condition->thenBlock, controlStructure->thenHead, controlStructure->next);
if (controlStructure->elseHead) {
condition->elseBlock = new ASTBlock(condition);
buildASTFromBlock(condition->elseBlock, controlStructure->elseHead, controlStructure->next);
}
return condition;
}
ASTLoop *Decompiler::buildASTLoopFromBlock(ASTNode *parent, Block *block) {
ControlStructure *controlStructure = block->getControlStructure();
ASTLoop *loop = new ASTLoop(parent);
loop->condition = new ASTCommand(loop, block->getConditionCommand(), _definitionRegistry);
loop->invertedCondition = controlStructure->invertedCondition;
loop->loopBlock = new ASTBlock(loop);
buildASTFromBlock(loop->loopBlock, controlStructure->loopHead, block);
return loop;
}
void Decompiler::verifyAST() {
for (uint i = 0; i < _commands.size(); i++) {
CFGCommand *command = _commands[i];
if (!verifyCommandInAST(command)) {
return;
}
}
}
bool Decompiler::verifyCommandInAST(CFGCommand *cfgCommand) {
Common::Array<const ASTCommand *> list = _astHead->listCommands(cfgCommand->getIndex());
if (list.empty()) {
_error = Common::String::format("Command %d not found in the AST", cfgCommand->getIndex());
return false;
}
if (list.size() > 1 && !cfgCommand->getBlock()->allowDuplication()) {
_error = Common::String::format("Command %d found %d times in the AST", cfgCommand->getIndex(), list.size());
return false;
}
const ASTCommand *astCommand = list[0];
ASTNode *follower = nullptr, *trueBranch = nullptr, *falseBranch = nullptr;
astCommand->findSuccessors(&follower, &trueBranch, &falseBranch);
if (!verifyCommandSuccessorInAST(cfgCommand, cfgCommand->getFollower(), follower, "follower")) {
return false;
}
if (!verifyCommandSuccessorInAST(cfgCommand, cfgCommand->getTrueBranch(), trueBranch, "trueBranch")) {
return false;
}
if (!verifyCommandSuccessorInAST(cfgCommand, cfgCommand->getFalseBranch(), falseBranch, "falseBranch")) {
return false;
}
return true;
}
bool Decompiler::verifyCommandSuccessorInAST(CFGCommand *cfgCommand, CFGCommand *cfgSuccessor, ASTNode *astSuccessor, const char *successorType) {
if (cfgSuccessor) {
if (!astSuccessor) {
_error = Common::String::format("Command %d does not have a %s in the AST",
cfgCommand->getIndex(), successorType);
return false;
}
const ASTCommand *astSuccessorCommand = astSuccessor->getFirstCommand();
if (!astSuccessorCommand) {
_error = Common::String::format("Command %d has an empty %s in the AST",
cfgCommand->getIndex(), successorType);
return false;
}
int16 expectedSuccessorIndex = cfgSuccessor->getIndex();
if (astSuccessorCommand->getIndex() != expectedSuccessorIndex) {
_error = Common::String::format("Command %d has an unexpected %s %d in the AST, should be %d",
cfgCommand->getIndex(), successorType, astSuccessorCommand->getIndex(), expectedSuccessorIndex);
return false;
}
}
return true;
}
} // End of namespace Tools
} // End of namespace Stark