Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
/* 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/abstractsyntaxtree.h"
#include "common/debug.h"
namespace Stark {
namespace Tools {
ASTNode::ASTNode(ASTNode *parent) :
_parent(parent) {
}
ASTNode::~ASTNode() {
}
void ASTNode::printWithDepth(uint depth, const Common::String &string) const {
Common::String prefix;
for (uint i = 0; i < depth; i++) {
prefix += "\t";
}
debug("%s%s", prefix.c_str(), string.c_str());
}
void ASTNode::findSuccessors(ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
findSuccessorsIntern(this, follower, trueBranch, falseBranch);
}
ASTBlock::ASTBlock(ASTNode *parent) :
ASTNode(parent) {
}
ASTBlock::~ASTBlock() {
for (uint i = 0; i < _children.size(); i++) {
delete _children[i];
}
}
void ASTBlock::addNode(ASTNode *node) {
_children.push_back(node);
}
void ASTBlock::print(uint depth, DefinitionRegistry *definitions) {
for (uint i = 0; i < _children.size(); i++) {
_children[i]->print(depth, definitions);
}
}
Common::Array<const ASTCommand *> ASTBlock::listCommands(uint16 index) const {
Common::Array<const ASTCommand *> list;
for (uint i = 0; i < _children.size(); i++) {
list.push_back(_children[i]->listCommands(index));
}
return list;
}
void ASTBlock::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
if (node == this) {
if (_parent) {
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
}
return;
}
for (uint i = 0; i < _children.size() - 1; i++) {
if (node == _children[i]) {
*follower = _children[i+1];
return;
}
}
if (node == _children.back()) {
if (_parent) {
_parent->findSuccessorsIntern(this, follower, trueBranch, falseBranch);
}
return;
}
error("Unknown node");
}
const ASTCommand *ASTBlock::getFirstCommand() const {
if (!_children.empty()) {
return _children[0]->getFirstCommand();
} else {
return nullptr;
}
}
ASTCommand::ASTCommand(ASTNode *parent, Command *command, DefinitionRegistry *definitions) :
ASTNode(parent),
Command(command) {
_arguments = command->getEffectiveArguments();
for (uint i = 0; i < _arguments.size(); i++) {
if (_arguments[i].type == Resources::Command::Argument::kTypeResourceReference) {
definitions->registerReference(_arguments[i].referenceValue);
}
}
}
void ASTCommand::print(uint depth, DefinitionRegistry *definitions) {
printWithDepth(depth, callString(definitions));
}
Common::String ASTCommand::callString(DefinitionRegistry *definitions) {
return Common::String::format("%s(%s)", _subTypeDesc->name, describeArguments(definitions).c_str());
}
Common::Array<const ASTCommand *> ASTCommand::listCommands(uint16 index) const {
Common::Array<const ASTCommand *> list;
if (_index == index) {
list.push_back(this);
}
return list;
}
void ASTCommand::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
assert(node == this);
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
}
const ASTCommand *ASTCommand::getFirstCommand() const {
return this;
}
ASTCondition::ASTCondition(ASTNode *parent) :
ASTNode(parent),
condition(nullptr),
invertedCondition(false),
thenBlock(nullptr),
elseBlock(nullptr) {
}
ASTCondition::~ASTCondition() {
delete condition;
delete thenBlock;
delete elseBlock;
}
void ASTCondition::print(uint depth, DefinitionRegistry *definitions) {
Common::String ifHeader = Common::String::format("if (%s%s) {", invertedCondition ? "!" : "",
condition->callString(definitions).c_str());
printWithDepth(depth, ifHeader);
thenBlock->print(depth + 1, definitions);
if (elseBlock) {
printWithDepth(depth, "} else {");
elseBlock->print(depth + 1, definitions);
}
printWithDepth(depth, "}");
}
Common::Array<const ASTCommand *> ASTCondition::listCommands(uint16 index) const {
Common::Array<const ASTCommand *> list;
list.push_back(condition->listCommands(index));
list.push_back(thenBlock->listCommands(index));
if (elseBlock) {
list.push_back(elseBlock->listCommands(index));
}
return list;
}
void ASTCondition::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
if (node == this) {
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
return;
}
if (node == condition) {
ASTNode *nextNode = nullptr;
if (!elseBlock) {
_parent->findSuccessorsIntern(this, &nextNode, nullptr, nullptr);
}
if (!invertedCondition) {
*trueBranch = thenBlock;
*falseBranch = elseBlock ? elseBlock : nextNode;
} else {
*trueBranch = elseBlock ? elseBlock : nextNode;
*falseBranch = thenBlock;
}
return;
}
if (node == thenBlock) {
_parent->findSuccessorsIntern(this, follower, trueBranch, falseBranch);
return;
}
if (node == elseBlock) {
_parent->findSuccessorsIntern(this, follower, trueBranch, falseBranch);
return;
}
error("Unknown node");
}
const ASTCommand *ASTCondition::getFirstCommand() const {
return condition->getFirstCommand();
}
ASTLoop::ASTLoop(ASTNode *parent) :
ASTNode(parent),
condition(nullptr),
invertedCondition(false),
loopBlock(nullptr) {
}
ASTLoop::~ASTLoop() {
delete condition;
delete loopBlock;
}
void ASTLoop::print(uint depth, DefinitionRegistry *definitions) {
Common::String loopHeader;
if (condition) {
loopHeader = Common::String::format("while (%s%s) {", invertedCondition ? "!" : "",
condition->callString(definitions).c_str());
} else {
loopHeader = "loop {";
}
printWithDepth(depth, loopHeader);
loopBlock->print(depth + 1, definitions);
printWithDepth(depth, "}");
}
Common::Array<const ASTCommand *> ASTLoop::listCommands(uint16 index) const {
Common::Array<const ASTCommand *> list;
if (condition) {
list.push_back(condition->listCommands(index));
}
list.push_back(loopBlock->listCommands(index));
return list;
}
void ASTLoop::findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const {
if (node == this) {
_parent->findSuccessorsIntern(node, follower, trueBranch, falseBranch);
return;
}
if (node == condition) {
ASTNode *nextNode = nullptr;
_parent->findSuccessorsIntern(this, &nextNode, nullptr, nullptr);
if (!invertedCondition) {
*trueBranch = loopBlock;
*falseBranch = nextNode;
} else {
*trueBranch = nextNode;
*falseBranch = loopBlock;
}
return;
}
if (node == loopBlock) {
*follower = condition ? (ASTNode *)condition : (ASTNode *)loopBlock;
return;
}
error("Unknown node");
}
const ASTCommand *ASTLoop::getFirstCommand() const {
return condition ? condition->getFirstCommand() : loopBlock->getFirstCommand();
}
} // End of namespace Tools
} // End of namespace Stark

View File

@@ -0,0 +1,146 @@
/* 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/>.
*
*/
#ifndef STARK_TOOLS_ABSTRACT_SYNTAX_TREE_H
#define STARK_TOOLS_ABSTRACT_SYNTAX_TREE_H
#include "common/array.h"
#include "engines/stark/tools/command.h"
namespace Stark {
namespace Tools {
struct ASTCommand;
/**
* Base Abstract Syntax Tree node
*
* The abstract syntax tree directly maps the script source code.
*/
struct ASTNode {
ASTNode(ASTNode *parent);
virtual ~ASTNode();
/** Print the script source code for this node and its children */
virtual void print(uint depth, DefinitionRegistry *definitions) = 0;
/** Recursively list all the commands in the tree with the requested index */
virtual Common::Array<const ASTCommand *> listCommands(uint16 index) const = 0;
/** Find the successors of a node, either the direct follower or the condition branches */
void findSuccessors(ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const;
virtual void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const = 0;
/** Find the first command to be executed when running this job */
virtual const ASTCommand *getFirstCommand() const = 0;
protected:
void printWithDepth(uint depth, const Common::String &string) const;
ASTNode *_parent;
};
/**
* Command AST Node
*
* Commands are leaf AST nodes. They represent an engine command call.
*/
struct ASTCommand : public ASTNode, public Command {
ASTCommand(ASTNode *parent, Command *command, DefinitionRegistry *definitions);
// ASTNode API
void print(uint depth, DefinitionRegistry *definitions) override;
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
const ASTCommand *getFirstCommand() const override;
/** Build a script source code call for this command */
Common::String callString(DefinitionRegistry *definitions);
};
/**
* Block AST Node
*
* A list of consecutive script expressions.
*/
struct ASTBlock : public ASTNode {
ASTBlock(ASTNode *parent);
~ASTBlock() override;
// ASTNode API
void print(uint depth, DefinitionRegistry *definitions) override;
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
const ASTCommand *getFirstCommand() const override;
/** Append a child expression to this block */
void addNode(ASTNode *node);
private:
Common::Array<ASTNode *> _children;
};
/**
* Condition AST Node
*
* An if / then / else branching condition.
*/
struct ASTCondition : public ASTNode {
ASTCondition(ASTNode *parent);
~ASTCondition() override;
// ASTNode API
void print(uint depth, DefinitionRegistry *definitions) override;
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
const ASTCommand *getFirstCommand() const override;
ASTCommand *condition;
bool invertedCondition;
ASTBlock *thenBlock;
ASTBlock *elseBlock;
};
/**
* Loop AST Node
*
* A while loop.
*/
struct ASTLoop : public ASTNode {
ASTLoop(ASTNode *parent);
~ASTLoop() override;
// ASTNode API
void print(uint depth, DefinitionRegistry *definitions) override;
Common::Array<const ASTCommand *> listCommands(uint16 index) const override;
void findSuccessorsIntern(const ASTNode *node, ASTNode **follower, ASTNode **trueBranch, ASTNode **falseBranch) const override;
const ASTCommand *getFirstCommand() const override;
ASTCommand *condition;
bool invertedCondition;
ASTBlock *loopBlock;
};
} // End of namespace Tools
} // End of namespace Stark
#endif // STARK_TOOLS_ABSTRACT_SYNTAX_TREE_H

View File

@@ -0,0 +1,328 @@
/* 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/block.h"
#include "engines/stark/tools/command.h"
#include "common/debug.h"
namespace Stark {
namespace Tools {
Block::Block() :
_follower(nullptr),
_trueBranch(nullptr),
_falseBranch(nullptr),
_controlStructure(nullptr),
_infiniteLoopStart(false) {
}
void Block::appendCommand(CFGCommand *command) {
_commands.push_back(command);
command->setBlock(this);
}
bool Block::isEmpty() const {
return _commands.empty();
}
void Block::print() const {
for (uint i = 0; i < _commands.size(); i++) {
_commands[i]->printCall();
}
if (_controlStructure) {
switch (_controlStructure->type) {
case ControlStructure::kTypeIf:
debug("if%s: %d else: %d next: %d",
_controlStructure->invertedCondition ? " not" : "",
_controlStructure->thenHead->getFirstCommandIndex(),
_controlStructure->elseHead ? _controlStructure->elseHead->getFirstCommandIndex() : -1,
_controlStructure->next ? _controlStructure->next->getFirstCommandIndex() : -1);
break;
case ControlStructure::kTypeWhile:
debug("while%s: %d next: %d",
_controlStructure->invertedCondition ? " not" : "",
_controlStructure->loopHead->getFirstCommandIndex(),
_controlStructure->next->getFirstCommandIndex());
break;
}
}
if (_infiniteLoopStart) {
debug("infinite loop start: %d", getFirstCommandIndex());
}
if (isCondition() && !_controlStructure) {
debug("unrecognized control flow");
}
}
void Block::setBranches(Block *trueBranch, Block *falseBranch) {
_trueBranch = trueBranch;
_falseBranch = falseBranch;
trueBranch->addPredecessor(this);
falseBranch->addPredecessor(this);
}
void Block::setFollower(Block *follower) {
_follower = follower;
follower->addPredecessor(this);
}
void Block::addPredecessor(Block *predecessor) {
_predecessors.push_back(predecessor);
}
bool Block::isCondition() const {
return _trueBranch != nullptr && _falseBranch != nullptr;
}
bool Block::hasPredecessor(Block *predecessor) const {
Common::Array<const Block *> visited;
return hasPredecessorIntern(visited, predecessor);
}
bool Block::hasPredecessorIntern(Common::Array<const Block *> &visited, Block *predecessor) const {
visited.push_back(this);
if (isInfiniteLoopStart()) {
// Don't follow infinite loops
return false;
}
for (uint i = 0; i < _predecessors.size(); i++) {
if (_predecessors[i] == predecessor) {
return true;
}
bool alreadyVisited = Common::find(visited.begin(), visited.end(), _predecessors[i]) != visited.end();
if (!alreadyVisited && _predecessors[i]->hasPredecessorIntern(visited, predecessor)) {
return true;
}
}
return false;
}
bool Block::hasSuccessor(Block *successor) const {
Common::Array<const Block *> visited;
return hasSuccessorIntern(visited, successor);
}
bool Block::hasSuccessorIntern(Common::Array<const Block *> &visited, Block *successor) const {
visited.push_back(this);
if (this == successor) {
return true;
}
bool followerHasSuccessor = hasChildSuccessorIntern(visited, _follower, successor);
bool trueBranchHasSuccessor = hasChildSuccessorIntern(visited, _trueBranch, successor);
bool falseBranchHasSuccessor = hasChildSuccessorIntern(visited, _falseBranch, successor);
return followerHasSuccessor || trueBranchHasSuccessor || falseBranchHasSuccessor;
}
bool Block::hasChildSuccessorIntern(Common::Array<const Block *> &visited, Block *child, Block *successor) const {
if (!child) {
return false;
}
bool alreadyVisited = Common::find(visited.begin(), visited.end(), child) != visited.end();
return !alreadyVisited && child->hasSuccessorIntern(visited, successor);
}
Block *Block::findMergePoint(Block *other) {
// TODO: Find the closest merge point? How to define that notion?
Common::Array<const Block *> visited;
return findMergePointIntern(visited, other);
}
Block *Block::findMergePointIntern(Common::Array<const Block *> &visited, Block *other) {
visited.push_back(this);
if (other == this) {
return this;
}
if (hasPredecessor(other)) {
return this;
}
Block *mergePoint = findChildMergePoint(visited, _follower, other);
if (mergePoint) {
return mergePoint;
}
mergePoint = findChildMergePoint(visited, _trueBranch, other);
if (mergePoint) {
return mergePoint;
}
mergePoint = findChildMergePoint(visited, _falseBranch, other);
if (mergePoint) {
return mergePoint;
}
return nullptr;
}
Block *Block::findChildMergePoint(Common::Array<const Block *> &visited, Block *child, Block *other) const {
if (child) {
bool alreadyVisited = Common::find(visited.begin(), visited.end(), child) != visited.end();
if (!alreadyVisited) {
return child->findMergePointIntern(visited, other);
}
}
return nullptr;
}
bool Block::checkAllBranchesConverge(Block *junction) const {
// Check if there is at least one path to the junction node
if (!hasSuccessor(junction)) {
return false;
}
// Check all the successors go to the junction point or loop infinitely
Common::Array<const Block *> visited;
return checkAllBranchesConvergeIntern(visited, junction);
}
bool Block::checkAllBranchesConvergeIntern(Common::Array<const Block *> &visited, Block *junction) const {
visited.push_back(this);
if (this == junction) {
return true;
}
if (!_follower && !_trueBranch && !_falseBranch) {
return false;
}
if (isInfiniteLoopStart()) {
// Don't follow infinite loops
return false;
}
bool followerConverges = checkChildConvergeIntern(visited, _follower, junction);
bool trueBranchConverges = checkChildConvergeIntern(visited, _trueBranch, junction);
bool falseBranchConverges = checkChildConvergeIntern(visited, _falseBranch, junction);
return followerConverges && trueBranchConverges && falseBranchConverges;
}
bool Block::checkChildConvergeIntern(Common::Array<const Block *> &visited, Block *child, Block *junction) const {
if (child) {
bool alreadyVisited = Common::find(visited.begin(), visited.end(), child) != visited.end();
if (!alreadyVisited) {
return child->checkAllBranchesConvergeIntern(visited, junction);
}
}
return true;
}
Block *Block::getFollower() const {
return _follower;
}
Block *Block::getTrueBranch() const {
return _trueBranch;
}
Block *Block::getFalseBranch() const {
return _falseBranch;
}
uint16 Block::getFirstCommandIndex() const {
return _commands[0]->getIndex();
}
bool Block::hasControlStructure() const {
return _controlStructure != nullptr;
}
void Block::setControlStructure(ControlStructure *controlStructure) {
_controlStructure = controlStructure;
}
ControlStructure *Block::getControlStructure() const {
return _controlStructure;
}
void Block::setInfiniteLoopStart(bool infiniteLoopStart) {
_infiniteLoopStart = infiniteLoopStart;
}
bool Block::isInfiniteLoopStart() const {
return _infiniteLoopStart;
}
Common::Array<CFGCommand *> Block::getLinearCommands() const {
if (!hasControlStructure()) {
return _commands;
} else {
Common::Array<CFGCommand *> commands;
// The last command in the block is the condition.
// Exclude it from the linear commands.
for (uint i = 0; i < _commands.size() - 1; i ++) {
commands.push_back(_commands[i]);
}
return commands;
}
}
CFGCommand *Block::getConditionCommand() const {
if (hasControlStructure()) {
// The condition command is always the last one
return _commands.back();
} else {
return nullptr;
}
}
bool Block::allowDuplication() const {
// Allow simple termination blocks to be duplicated in the decompiled output
bool isScriptEnd = !_follower && !_trueBranch && !_falseBranch;
bool isShort = _commands.size() < 5;
return isScriptEnd && isShort;
}
ControlStructure::ControlStructure(ControlStructureType t) :
type(t),
condition(nullptr),
invertedCondition(false),
loopHead(nullptr),
thenHead(nullptr),
elseHead(nullptr),
next(nullptr) {
}
} // End of namespace Tools
} // End of namespace Stark

140
engines/stark/tools/block.h Normal file
View File

@@ -0,0 +1,140 @@
/* 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/>.
*
*/
#ifndef STARK_TOOLS_BLOCK_H
#define STARK_TOOLS_BLOCK_H
#include "common/array.h"
namespace Stark {
namespace Tools {
class CFGCommand;
struct ControlStructure;
/**
* An aggregate of script commands
*
* Commands in the same group are always executed in sequence.
*
* This class is a node in the disassembly node graph.
*/
class Block {
public:
Block();
/** Add a command at the end of the block, and set it as the command's block */
void appendCommand(CFGCommand *command);
/** Get the commands from the block that are not part of a condition */
Common::Array<CFGCommand *> getLinearCommands() const;
/** Get the command from the block that plays as a condition in a control structure */
CFGCommand *getConditionCommand() const;
/** Does the block contain commands? */
bool isEmpty() const;
/** Can the block branch? */
bool isCondition() const;
/**
* Blocks are linked together in the block graph with these relationships:
* - follower: The natural follower of the block. Used when the block is not a branch, nor an end point.
* - true branch: The next block when the block's condition evaluates to true.
* - false branch: The next block when the block's condition evaluates to false.
* - predecessors: A list of blocks whose execution can lead to this block.
*/
void setBranches(Block *trueBranch, Block *falseBranch);
void setFollower(Block *follower);
void addPredecessor(Block *predecessor);
Block *getTrueBranch() const;
Block *getFalseBranch() const;
Block *getFollower() const;
/**
* Print a list of this block's commands to the debug output
*/
void print() const;
/**
* The high level control structure this block has the main role in
*/
bool hasControlStructure() const;
ControlStructure *getControlStructure() const;
void setControlStructure(ControlStructure *controlStructure);
/** Flag to indicate this block is the first in an unconditional infinite loop */
bool isInfiniteLoopStart() const;
void setInfiniteLoopStart(bool infiniteLoopStart);
/** Can this block appear multiple times in the decompiled output? */
bool allowDuplication() const;
// Graph query methods
bool hasPredecessor(Block *predecessor) const;
bool hasSuccessor(Block *successor) const;
Block *findMergePoint(Block *other);
bool checkAllBranchesConverge(Block *junction) const;
private:
bool hasPredecessorIntern(Common::Array<const Block *> &visited, Block *predecessor) const;
bool hasSuccessorIntern(Common::Array<const Block *> &visited, Block *successor) const;
bool hasChildSuccessorIntern(Common::Array<const Block *> &visited, Block *child, Block *successor) const;
Block *findMergePointIntern(Common::Array<const Block *> &visited, Block *other);
Block *findChildMergePoint(Common::Array<const Block *> &visited, Block *child, Block *other) const;
bool checkAllBranchesConvergeIntern(Common::Array<const Block *> &visited, Block *junction) const;
bool checkChildConvergeIntern(Common::Array<const Block *> &visited, Block *child, Block *junction) const;
uint16 getFirstCommandIndex() const;
Common::Array<CFGCommand *> _commands;
Block *_follower;
Block *_trueBranch;
Block *_falseBranch;
Common::Array<Block *> _predecessors;
ControlStructure *_controlStructure;
bool _infiniteLoopStart;
};
struct ControlStructure {
enum ControlStructureType {
kTypeIf,
kTypeWhile
};
ControlStructureType type;
Block *condition;
bool invertedCondition;
Block *loopHead;
Block *thenHead;
Block *elseHead;
Block *next;
ControlStructure(ControlStructureType t);
};
} // End of namespace Tools
} // End of namespace Stark
#endif // STARK_TOOLS_BLOCK_H

View File

@@ -0,0 +1,376 @@
/* 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/command.h"
#include "common/debug.h"
#include "common/tokenizer.h"
namespace Stark {
namespace Tools {
Command::Command(Command *command) {
_index = command->_index;
_subType = command->_subType;
_subTypeDesc = command->_subTypeDesc;
_arguments = command->_arguments;
}
Command::Command(Resources::Command *resource) {
_index = resource->getIndex();
_subType = (Resources::Command::SubType) resource->getSubType();
_subTypeDesc = searchSubTypeDesc(_subType);
_arguments = resource->getArguments();
}
const Command::SubTypeDesc *Command::searchSubTypeDesc(Resources::Command::SubType subType) {
static const SubTypeDesc typeNames[] = {
{ Resources::Command::kCommandBegin, "begin", kFlowNormal },
{ Resources::Command::kCommandEnd, "end", kFlowEnd },
{ Resources::Command::kScriptCall, "scriptCall", kFlowNormal },
{ Resources::Command::kDialogCall, "dialogCall", kFlowNormal },
{ Resources::Command::kSetInteractiveMode, "setInteractiveMode", kFlowNormal },
{ Resources::Command::kLocationGoTo, "locationGoTo", kFlowEnd },
{ Resources::Command::kWalkTo, "walkTo", kFlowBranch },
{ Resources::Command::kGameLoop, "gameLoop", kFlowNormal },
{ Resources::Command::kScriptPause, "scriptPause", kFlowNormal },
{ Resources::Command::kScriptPauseRandom, "scriptPauseRandom", kFlowNormal },
{ Resources::Command::kScriptPauseSkippable, "scriptPauseSkippable", kFlowNormal },
{ Resources::Command::kScriptAbort, "scriptAbort", kFlowNormal },
{ Resources::Command::kExit2DLocation, "exit2DLocation", kFlowEnd },
{ Resources::Command::kGoto2DLocation, "goto2DLocation", kFlowEnd },
{ Resources::Command::kRumbleScene, "rumbleScene", kFlowNormal },
{ Resources::Command::kFadeScene, "fadeScene", kFlowNormal },
{ Resources::Command::kSwayScene, "swayScene", kFlowNormal },
{ Resources::Command::kLocationGoToNewCD, "locationGoToNewCD", kFlowEnd },
{ Resources::Command::kGameEnd, "gameEnd", kFlowNormal },
{ Resources::Command::kInventoryOpen, "inventoryOpen", kFlowNormal },
{ Resources::Command::kFloatScene, "floatScene", kFlowNormal },
{ Resources::Command::kBookOfSecretsOpen, "bookOfSecretsOpen", kFlowNormal },
{ Resources::Command::kDoNothing, "doNothing", kFlowNormal },
{ Resources::Command::kItem3DPlaceOn, "item3DPlaceOn", kFlowNormal },
{ Resources::Command::kItem3DWalkTo, "item3DWalkTo", kFlowNormal },
{ Resources::Command::kItem3DFollowPath, "item3DFollowPath", kFlowNormal },
{ Resources::Command::kItemLookAt, "itemLookAt", kFlowNormal },
{ Resources::Command::kItem2DFollowPath, "item2DFollowPath", kFlowNormal },
{ Resources::Command::kItemEnable, "itemEnable", kFlowNormal },
{ Resources::Command::kItemSetActivity, "itemSetActivity", kFlowNormal },
{ Resources::Command::kItemSelectInInventory, "itemSelectInInventory", kFlowNormal },
{ Resources::Command::kUseAnimHierarchy, "useAnimHierarchy", kFlowNormal },
{ Resources::Command::kPlayAnimation, "playAnimation", kFlowNormal },
{ Resources::Command::kScriptEnable, "scriptEnable", kFlowNormal },
{ Resources::Command::kShowPlay, "showPlay", kFlowNormal },
{ Resources::Command::kKnowledgeSetBoolean, "knowledgeSetBoolean", kFlowNormal },
{ Resources::Command::kKnowledgeSetInteger, "knowledgeSetInteger", kFlowNormal },
{ Resources::Command::kKnowledgeAddInteger, "knowledgeAddInteger", kFlowNormal },
{ Resources::Command::kEnableFloorField, "enableFloorField", kFlowNormal },
{ Resources::Command::kPlayAnimScriptItem, "playAnimScriptItem", kFlowNormal },
{ Resources::Command::kItemAnimFollowPath, "itemAnimFollowPath", kFlowNormal },
{ Resources::Command::kKnowledgeAssignBool, "knowledgeAssignBool", kFlowNormal },
{ Resources::Command::kKnowledgeAssignInteger, "knowledgeAssignInteger", kFlowNormal },
{ Resources::Command::kLocationScrollTo, "locationScrollTo", kFlowNormal },
{ Resources::Command::kSoundPlay, "soundPlay", kFlowNormal },
{ Resources::Command::kKnowledgeSetIntRandom, "knowledgeSetIntRandom", kFlowNormal },
{ Resources::Command::kKnowledgeSubValue, "knowledgeSubValue", kFlowNormal },
{ Resources::Command::kItemLookDirection, "itemLookDirection", kFlowNormal },
{ Resources::Command::kStopPlayingSound, "stopPlayingSound", kFlowNormal },
{ Resources::Command::kLayerGoTo, "layerGoTo", kFlowNormal },
{ Resources::Command::kLayerEnable, "layerEnable", kFlowNormal },
{ Resources::Command::kLocationScrollSet, "locationScrollSet", kFlowNormal },
{ Resources::Command::kFullMotionVideoPlay, "fullMotionVideoPlay", kFlowNormal },
{ Resources::Command::kAnimSetFrame, "animSetFrame", kFlowNormal },
{ Resources::Command::kKnowledgeAssignNegatedBool, "knowledgeAssignNegatedBool", kFlowNormal },
{ Resources::Command::kDiaryEnableEntry, "diaryEnableEntry", kFlowNormal },
{ Resources::Command::kPATChangeTooltip, "pATChangeTooltip", kFlowNormal },
{ Resources::Command::kSoundChange, "soundChange", kFlowNormal },
{ Resources::Command::kLightSetColor, "lightSetColor", kFlowNormal },
{ Resources::Command::kLightFollowPath, "lightFollowPath", kFlowNormal },
{ Resources::Command::kItem3DRunTo, "item3DRunTo", kFlowNormal },
{ Resources::Command::kItemPlaceDirection, "itemPlaceDirection", kFlowNormal },
{ Resources::Command::kItemRotateDirection, "itemRotateDirection", kFlowNormal },
{ Resources::Command::kActivateTexture, "activateTexture", kFlowNormal },
{ Resources::Command::kActivateMesh, "activateMesh", kFlowNormal },
{ Resources::Command::kItem3DSetWalkTarget, "item3DSetWalkTarget", kFlowNormal },
{ Resources::Command::kSpeakWithoutTalking, "speakWithoutTalking", kFlowNormal },
{ Resources::Command::kIsOnFloorField, "isOnFloorField", kFlowBranch },
{ Resources::Command::kIsItemEnabled, "isItemEnabled", kFlowBranch },
{ Resources::Command::kIsScriptEnabled, "isScriptEnabled", kFlowBranch },
{ Resources::Command::kIsKnowledgeBooleanSet, "isKnowledgeBooleanSet", kFlowBranch },
{ Resources::Command::kIsKnowledgeIntegerInRange, "isKnowledgeIntegerInRange", kFlowBranch },
{ Resources::Command::kIsKnowledgeIntegerAbove, "isKnowledgeIntegerAbove", kFlowBranch },
{ Resources::Command::kIsKnowledgeIntegerEqual, "isKnowledgeIntegerEqual", kFlowBranch },
{ Resources::Command::kIsKnowledgeIntegerLower, "isKnowledgeIntegerLower", kFlowBranch },
{ Resources::Command::kIsScriptActive, "isScriptActive", kFlowBranch },
{ Resources::Command::kIsRandom, "isRandom", kFlowBranch },
{ Resources::Command::kIsAnimScriptItemReached, "isAnimScriptItemReached", kFlowBranch },
{ Resources::Command::kIsItemOnPlace, "isItemOnPlace", kFlowBranch },
{ Resources::Command::kIsAnimPlaying, "isAnimPlaying", kFlowBranch },
{ Resources::Command::kIsItemActivity, "isItemActivity", kFlowBranch },
{ Resources::Command::kIsItemNearPlace, "isItemNearPlace", kFlowBranch },
{ Resources::Command::kIsAnimAtTime, "isAnimAtTime", kFlowBranch },
{ Resources::Command::kIsLocation2D, "isLocation2D", kFlowBranch },
{ Resources::Command::kIsInventoryOpen, "isInventoryOpen", kFlowBranch }
};
for (uint i = 0; i < ARRAYSIZE(typeNames); i++) {
if (typeNames[i].subType == subType) {
return &typeNames[i];
}
}
return nullptr;
}
Command::ArgumentArray Command::getEffectiveArguments() const {
uint effectiveArgumentsStart;
switch (_subTypeDesc->controlFlowType) {
case kFlowEnd:
effectiveArgumentsStart = 0;
break;
case kFlowNormal:
effectiveArgumentsStart = 1;
break;
case kFlowBranch:
effectiveArgumentsStart = 2;
break;
default:
error("Unhandled control flow type '%d'", _subTypeDesc->controlFlowType);
}
ArgumentArray effectiveArguments;
for (uint i = effectiveArgumentsStart; i < _arguments.size(); i++) {
effectiveArguments.push_back(_arguments[i]);
}
return effectiveArguments;
}
Common::String Command::describeArguments(DefinitionRegistry *definitions) const {
Common::String desc;
for (uint i = 0; i < _arguments.size(); i++) {
switch (_arguments[i].type) {
case Resources::Command::Argument::kTypeInteger1:
case Resources::Command::Argument::kTypeInteger2:
desc += Common::String::format("%d", _arguments[i].intValue);
break;
case Resources::Command::Argument::kTypeResourceReference: {
if (definitions) {
desc += definitions->getFromReference(_arguments[i].referenceValue);
} else {
desc += _arguments[i].referenceValue.describe();
}
}
break;
case Resources::Command::Argument::kTypeString:
desc += _arguments[i].stringValue;
break;
default:
error("Unknown argument type %d", _arguments[i].type);
}
if (i != _arguments.size() - 1) {
desc += ", ";
}
}
return desc;
}
void Command::printCall() const {
debug("%d: %s(%s)", _index, _subTypeDesc->name, describeArguments(nullptr).c_str());
}
uint16 Command::getIndex() const {
return _index;
}
bool Command::hasSubtypeDescription() const {
return _subTypeDesc != nullptr;
}
Resources::Command::SubType Command::getSubType() const {
return _subType;
}
CFGCommand::CFGCommand(Resources::Command *resource) :
Command(resource),
_followerIndex(-1),
_trueBranchIndex(-1),
_falseBranchIndex(-1),
_follower(nullptr),
_trueBranch(nullptr),
_falseBranch(nullptr),
_block(nullptr) {
if (_subTypeDesc) {
initBranches();
}
}
void CFGCommand::initBranches() {
switch (_subTypeDesc->controlFlowType) {
case kFlowNormal:
_followerIndex = _arguments[0].intValue;
break;
case kFlowBranch:
if (_arguments[0].intValue == _arguments[1].intValue) {
// Degenerate conditions are handled here so that blocks are not split after them
_followerIndex = _arguments[0].intValue;
} else {
_falseBranchIndex = _arguments[0].intValue;
_trueBranchIndex = _arguments[1].intValue;
}
break;
case kFlowEnd:
// No followers
break;
}
}
bool CFGCommand::isEntryPoint() const {
return _subType == Resources::Command::kCommandBegin;
}
bool CFGCommand::isBranch() const {
return _trueBranchIndex >= 0 && _falseBranchIndex >= 0;
}
bool CFGCommand::isBranchTarget() const {
return _predecessors.size() > 1;
}
Block *CFGCommand::getBlock() const {
return _block;
}
void CFGCommand::setBlock(Block *block) {
_block = block;
}
CFGCommand *CFGCommand::getFollower() const {
return _follower;
}
CFGCommand *CFGCommand::getTrueBranch() const {
return _trueBranch;
}
CFGCommand *CFGCommand::getFalseBranch() const {
return _falseBranch;
}
void CFGCommand::linkBranches(const Common::Array<CFGCommand *> &commands) {
if (_followerIndex >= 0) {
_follower = findCommandWithIndex(commands, _followerIndex);
_follower->_predecessors.push_back(this);
}
if (_falseBranchIndex >= 0) {
_falseBranch = findCommandWithIndex(commands, _falseBranchIndex);
_falseBranch->_predecessors.push_back(this);
}
if (_trueBranchIndex >= 0) {
_trueBranch = findCommandWithIndex(commands, _trueBranchIndex);
_trueBranch->_predecessors.push_back(this);
}
}
CFGCommand *CFGCommand::findCommandWithIndex(const Common::Array<CFGCommand *> &commands, int32 index) {
for (uint i = 0; i < commands.size(); i++) {
CFGCommand *command = commands[i];
if (command->_index == index) {
return command;
}
}
error("Unable to find command with index %d", index);
}
void DefinitionRegistry::registerReference(const ResourceReference &reference) {
if (!reference.canResolve()) {
// The reference uses archives that are not currently loaded
return;
}
Resources::Object *object = reference.resolve<Resources::Object>();
if (!_definitions.contains(object)) {
// TODO: There is no guarantee the definition is unique
_definitions[object] = object->getType().getName() + stringToCamelCase(object->getName());
}
}
Common::String DefinitionRegistry::getFromReference(const ResourceReference &reference) const {
if (!reference.canResolve()) {
// The reference uses archives that are not currently loaded
return reference.describe();
}
Resources::Object *object = reference.resolve<Resources::Object>();
if (_definitions.contains(object)) {
return _definitions.getVal(object);
} else {
return reference.describe();
}
}
Common::String DefinitionRegistry::stringToCamelCase(const Common::String &input) {
Common::String clean = input;
// First replace all non alphanumerical characters with spaces
for (uint i = 0; i < clean.size(); i++) {
if (!Common::isAlnum(clean[i])) {
clean.setChar(' ', i);
}
}
// Then turn the string into camel case
Common::String output;
Common::StringTokenizer tokens = Common::StringTokenizer(clean);
while (!tokens.empty()) {
Common::String token = tokens.nextToken();
char upperFirstLetter = toupper(token[0]);
token.setChar(upperFirstLetter, 0);
output += token;
}
return output;
}
void DefinitionRegistry::printAll() const {
DefinitionMap::const_iterator it = _definitions.begin();
while (it != _definitions.end()) {
ResourceReference reference;
reference.buildFromResource(it->_key);
debug("let %s = %s", it->_value.c_str(), reference.describe().c_str());
it++;
}
}
} // End of namespace Tools
} // End of namespace Stark

View File

@@ -0,0 +1,179 @@
/* 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/>.
*
*/
#ifndef STARK_TOOLS_COMMAND_H
#define STARK_TOOLS_COMMAND_H
#include "common/array.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
#include "engines/stark/resources/command.h"
namespace Stark {
namespace Tools {
class Block;
class DefinitionRegistry;
/**
* A base script command for disassembly use
*
* As opposed to the Command class in the Resources namespace, this class
* is not meant to be executed. It is meant to be used for script disassembly,
* to store analysis results.
*/
class Command {
public:
enum ControlFlowType {
kFlowNormal,
kFlowBranch,
kFlowEnd
};
typedef Common::Array<Resources::Command::Argument> ArgumentArray;
Command(Command *command);
Command(Resources::Command *resource);
/**
* Print a call to this command to the debug output
*/
void printCall() const;
/** This command's resource tree index */
uint16 getIndex() const;
Resources::Command::SubType getSubType() const;
bool hasSubtypeDescription() const;
/** List the command's arguments ignoring the control flow related ones */
ArgumentArray getEffectiveArguments() const;
protected:
struct SubTypeDesc {
Resources::Command::SubType subType;
const char *name;
ControlFlowType controlFlowType;
};
/** Get a description for a command subtype from an internal database */
static const SubTypeDesc *searchSubTypeDesc(Resources::Command::SubType subType);
/** List the arguments values as a coma separated string */
Common::String describeArguments(DefinitionRegistry *definitions) const;
uint16 _index;
Resources::Command::SubType _subType;
const SubTypeDesc *_subTypeDesc;
ArgumentArray _arguments;
};
/**
* A script command with control flow information
*
* This class is a node in the disassembly command control flow graph.
* It is referenced by the blocks in the block control flow graph.
*/
class CFGCommand : public Command {
public:
CFGCommand(Resources::Command *resource);
/** Is this command an entry point for the whole script? */
bool isEntryPoint() const;
/** Can this command influence the control flow? */
bool isBranch() const;
/** Is this command a jump target? */
bool isBranchTarget() const;
/**
* Commands are linked together in the command graph with these relationships:
* - follower: The natural follower of the command. Used when the command is not a branch, nor an end point.
* - true branch: The next command when the command's condition evaluates to true.
* - false branch: The next command when the command's condition evaluates to false.
* - predecessors: A list of commands whose execution can lead to this command.
*/
CFGCommand *getFollower() const;
CFGCommand *getTrueBranch() const;
CFGCommand *getFalseBranch() const;
/**
* Commands are aggregated into blocks
*/
Block *getBlock() const;
void setBlock(Block *block);
/**
* Add the command to the command graph
*
* This sets the graph edges concerning this command.
*/
void linkBranches(const Common::Array<CFGCommand *> &commands);
protected:
/** Set the link indices from the argument values */
void initBranches();
/** Gets the command with the specifed index */
static CFGCommand *findCommandWithIndex(const Common::Array<CFGCommand *> &commands, int32 index);
int32 _followerIndex;
int32 _trueBranchIndex;
int32 _falseBranchIndex;
CFGCommand *_follower;
CFGCommand *_trueBranch;
CFGCommand *_falseBranch;
Common::Array<CFGCommand *> _predecessors;
Block *_block;
};
/**
* Storage for aliases between world resources and names
*/
class DefinitionRegistry {
public:
/**
* Add a definition from a reference
*
* The name is computed from the object's name
*/
void registerReference(const ResourceReference &reference);
/** Get a previously registered definition from a reference */
Common::String getFromReference(const ResourceReference &reference) const;
/** Print all the registered definitions */
void printAll() const;
private:
typedef Common::HashMap<Resources::Object *, Common::String> DefinitionMap;
Common::String stringToCamelCase(const Common::String &input);
DefinitionMap _definitions;
};
} // End of namespace Tools
} // End of namespace Stark
#endif // STARK_TOOLS_COMMAND_H

View File

@@ -0,0 +1,448 @@
/* 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

View File

@@ -0,0 +1,103 @@
/* 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/>.
*
*/
#ifndef STARK_TOOLS_SCRIPT_H
#define STARK_TOOLS_SCRIPT_H
#include "common/str.h"
#include "engines/stark/resources/script.h"
namespace Stark {
namespace Resources {
class Script;
}
namespace Tools {
class Block;
class CFGCommand;
class DefinitionRegistry;
struct ControlStructure;
struct ASTBlock;
struct ASTNode;
struct ASTCondition;
struct ASTLoop;
class Decompiler {
public:
Decompiler(Resources::Script *script);
~Decompiler();
void printCommands() const;
void printBlocks() const;
void printDecompiled();
Common::String getError() const;
private:
// Command control flow graph construction
bool checkCommands();
void linkCommandBranches();
CFGCommand *findEntryPoint();
// Block control flow graph construction
void buildBlocks();
void buildBlocks(Block *block, CFGCommand *command);
Block *buildBranchBlocks(CFGCommand *command);
// Control flow analysis
void analyseControlFlow();
void detectInfiniteLoop();
void detectWhile();
void detectIf();
// AST generation
ASTNode *buildAST();
void buildASTFromBlock(ASTBlock *parent, Block *block, Block *stopBlock);
ASTCondition *buildASTConditionFromBlock(ASTNode *parent, Block *block);
ASTLoop *buildASTLoopFromBlock(ASTNode *parent, Block *block);
// AST verification
void verifyAST();
bool verifyCommandInAST(CFGCommand *cfgCommand);
bool verifyCommandSuccessorInAST(CFGCommand *cfgCommand, CFGCommand *cfgSuccessor, ASTNode *astSuccessor, const char *successorType);
Common::String _error;
Common::Array<CFGCommand *> _commands;
CFGCommand *_entryPoint;
Common::Array<Block *> _blocks;
Common::Array<ControlStructure *> _controlStructures;
ASTNode *_astHead;
Common::Array<Block *> _visitedInfiniteLoopStarts;
Common::Array<Block *> _visitedBlocks;
DefinitionRegistry *_definitionRegistry;
};
} // End of namespace Tools
} // End of namespace Stark
#endif // STARK_TOOLS_SCRIPT_H