/* ScummVM Tools
*
* ScummVM Tools 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 .
*
*/
#include
#include "decompiler/control_flow.h"
#include "decompiler/disassembler.h"
#include "decompiler/graph.h"
#include "decompiler/codegen.h"
#include "decompiler/scummv6/engine.h"
#include "decompiler/kyra/engine.h"
#include
#define GET(vertex) (boost::get(boost::vertex_name, g, vertex))
#include
#include
// Define an ostream which doesn't output anything to avoid clutter
// Source: http://groups.google.com/group/comp.lang.c++/msg/4a81a74500f9f4d3?hl=en
template >
class basic_nullbuf: public std::basic_streambuf {
typename traits::int_type overflow(typename traits::int_type c)
{
return traits::not_eof(c);
}
};
template >
class basic_onullstream: public std::basic_ostream {
public:
basic_onullstream():
std::basic_ios(&m_sbuf),
std::basic_ostream(&m_sbuf)
{
this->init(&m_sbuf);
}
private:
basic_nullbuf m_sbuf;
};
typedef basic_onullstream onullstream;
std::string removeSpaces(std::string s) {
size_t found;
while ((found = s.find(' '))!=std::string::npos)
s = s.erase(found,1);
return s;
}
typedef std::vector::iterator CodeIterator;
class CodeGenTestSuite : public CxxTest::TestSuite {
public:
void testContinue() {
InstVec insts;
Scumm::v6::Scummv6Engine *engine = new Scumm::v6::Scummv6Engine();
Disassembler *d = engine->getDisassembler(insts);
d->open("decompiler/test/continue-do-while.dmp");
d->disassemble();
delete d;
ControlFlow *c = new ControlFlow(insts, engine);
c->createGroups();
Graph g = c->analyze();
onullstream ns;
CodeGenerator *cg = engine->getCodeGenerator(ns);
cg->generate(g);
VertexIterator v = boost::vertices(g).first;
std::vector output, expected;
expected.push_back("do{");
expected.push_back("if(18 != var321) {");
expected.push_back("continue;");
expected.push_back("}");
expected.push_back("VAR_CHARSET_MASK--;");
expected.push_back("} while (42 == VAR_CHARSET_MASK)");
expected.push_back("stopObjectCodeA();");
GroupPtr gr = GET(*v);
// Find first node
while (gr->_prev != NULL)
gr = gr->_prev;
// Copy out all lines of code
while (gr != NULL) {
for (std::vector::iterator it = gr->_code.begin(); it != gr->_code.end(); ++it)
output.push_back(it->_line);
gr = gr->_next;
}
TS_ASSERT(output.size() == expected.size());
CodeIterator it, it2;
for (it = output.begin(), it2 = expected.begin(); it != output.end() && it2 != expected.end(); ++it, ++it2) {
TS_ASSERT(removeSpaces(*it).compare(removeSpaces(*it2)) == 0);
}
delete cg;
delete c;
delete engine;
}
void testBreak() {
InstVec insts;
Scumm::v6::Scummv6Engine *engine = new Scumm::v6::Scummv6Engine();
Disassembler *d = engine->getDisassembler(insts);
d->open("decompiler/test/break-while.dmp");
d->disassemble();
delete d;
ControlFlow *c = new ControlFlow(insts, engine);
c->createGroups();
Graph g = c->analyze();
onullstream ns;
CodeGenerator *cg = engine->getCodeGenerator(ns);
cg->generate(g);
VertexIterator v = boost::vertices(g).first;
std::vector output, expected;
expected.push_back("while (42 != VAR_CHARSET_MASK) {");
expected.push_back("if (18 != var321) {");
expected.push_back("break;");
expected.push_back("}");
expected.push_back("VAR_CHARSET_MASK--;");
expected.push_back("}");
expected.push_back("stopObjectCodeA();");
GroupPtr gr = GET(*v);
// Find first node
while (gr->_prev != NULL)
gr = gr->_prev;
// Copy out all lines of code
while (gr != NULL) {
for (std::vector::iterator it = gr->_code.begin(); it != gr->_code.end(); ++it)
output.push_back(it->_line);
gr = gr->_next;
}
TS_ASSERT(output.size() == expected.size());
CodeIterator it, it2;
for (it = output.begin(), it2 = expected.begin(); it != output.end() && it2 != expected.end(); ++it, ++it2) {
TS_ASSERT(removeSpaces(*it).compare(removeSpaces(*it2)) == 0);
}
delete cg;
delete c;
delete engine;
}
void testElse() {
InstVec insts;
Scumm::v6::Scummv6Engine *engine = new Scumm::v6::Scummv6Engine();
Disassembler *d = engine->getDisassembler(insts);
d->open("decompiler/test/if-else.dmp");
d->disassemble();
delete d;
ControlFlow *c = new ControlFlow(insts, engine);
c->createGroups();
Graph g = c->analyze();
onullstream ns;
CodeGenerator *cg = engine->getCodeGenerator(ns);
cg->generate(g);
VertexIterator v = boost::vertices(g).first;
std::vector output, expected;
expected.push_back("if (42 != VAR_CHARSET_MASK) {");
expected.push_back("VAR_CHARSET_MASK--;");
expected.push_back("} else {");
expected.push_back("VAR_CHARSET_MASK++;");
expected.push_back("}");
expected.push_back("stopObjectCodeA();");
GroupPtr gr = GET(*v);
// Find first node
while (gr->_prev != NULL)
gr = gr->_prev;
// Copy out all lines of code
while (gr != NULL) {
for (std::vector::iterator it = gr->_code.begin(); it != gr->_code.end(); ++it)
output.push_back(it->_line);
gr = gr->_next;
}
TS_ASSERT(output.size() == expected.size());
CodeIterator it, it2;
for (it = output.begin(), it2 = expected.begin(); it != output.end() && it2 != expected.end(); ++it, ++it2) {
TS_ASSERT(removeSpaces(*it).compare(removeSpaces(*it2)) == 0);
}
delete cg;
delete c;
delete engine;
}
// This test requires script-30 and script-48.dmp from Sam & Max: Hit The Road.
// 6e48faca13e1f6df9341567608962744 *script-30.dmp
// afd7dc5d377894b3b9d0504927adf1b1 *script-48.dmp
void testCoalescing() {
InstVec insts;
Scumm::v6::Scummv6Engine *engine = new Scumm::v6::Scummv6Engine();
Disassembler *d = engine->getDisassembler(insts);
d->open("decompiler/test/script-30.dmp");
d->disassemble();
delete d;
ControlFlow *c = new ControlFlow(insts, engine);
c->createGroups();
Graph g = c->analyze();
onullstream ns;
CodeGenerator *cg = engine->getCodeGenerator(ns);
cg->generate(g);
VertexIterator v = boost::vertices(g).first;
GroupPtr gr = GET(*v);
// Find first node
while (gr->_prev != NULL)
gr = gr->_prev;
// Find vertex to test
while ((*gr->_start)->_address != 0x91)
gr = gr->_next;
TS_ASSERT(gr->_code.size() == 2);
TS_ASSERT(removeSpaces(gr->_code[0]._line).compare("}else{") == 0);
TS_ASSERT(removeSpaces(gr->_code[1]._line).substr(0, 2).compare("if") == 0);
delete cg;
delete c;
delete engine;
insts.clear();
engine = new Scumm::v6::Scummv6Engine();
d = engine->getDisassembler(insts);
d->open("decompiler/test/script-48.dmp");
d->disassemble();
delete d;
c = new ControlFlow(insts, engine);
c->createGroups();
g = c->analyze();
cg = engine->getCodeGenerator(ns);
cg->generate(g);
v = boost::vertices(g).first;
gr = GET(*v);
// Find first node
while (gr->_prev != NULL)
gr = gr->_prev;
// Find vertex to test
while ((*gr->_start)->_address != 0x191)
gr = gr->_next;
TS_ASSERT(gr->_code.size() == 1);
TS_ASSERT(removeSpaces(gr->_code[0]._line).substr(0, 7).compare("}elseif") == 0);
delete cg;
delete c;
delete engine;
}
// This test requires _START04.EMC from the CD demo of
// Legend of Kyrandia: Hand of Fate, found in MISC_EMC.PAK.
// Extract using extract_kyra from the scummvm-tools-cli bundle.
// ba2821ac6da96394ce0af75a3cbe48eb *_START04.EMC
void testKyra2Start04CodeGen() {
InstVec insts;
Kyra::Kyra2Engine *engine = new Kyra::Kyra2Engine();
Disassembler *d = engine->getDisassembler(insts);
d->open("decompiler/test/_START04.EMC");
d->disassemble();
delete d;
ControlFlow *c = new ControlFlow(insts, engine);
c->createGroups();
Graph g = c->analyze();
engine->postCFG(insts, g);
onullstream ns;
CodeGenerator *cg = engine->getCodeGenerator(ns);
cg->generate(g);
VertexIterator v = boost::vertices(g).first;
std::vector output, expected;
expected.push_back("auto_sub0x278(param1, param2, param3, param4) {");
expected.push_back("if (var1 > param1 && var1 < param3 && var2 > param2 && var2 < param4) {");
expected.push_back("retval = o1_queryGameFlag(3);");
expected.push_back("if (retval) {");
expected.push_back("retval = o2_drawBox(param1, param2, param3, param4, 199);");
expected.push_back("}");
expected.push_back("retval = 1;");
expected.push_back("return;");
expected.push_back("}");
expected.push_back("retval = o1_queryGameFlag(3);");
expected.push_back("if (retval) {");
expected.push_back("retval = o2_drawBox(param1, param2, param3, param4, 132);");
expected.push_back("}");
expected.push_back("retval = 0;");
expected.push_back("return;");
expected.push_back("}");
GroupPtr gr = GET(*v);
// Find first node
while (gr->_prev != NULL)
gr = gr->_prev;
// Find right starting node
while ((*gr->_start)->_address != 0x278)
gr = gr->_next;
// Copy out all lines of code from function
while ((*gr->_start)->_address <= 0x2DC) {
for (std::vector::iterator it = gr->_code.begin(); it != gr->_code.end(); ++it) {
if (it->_line.compare("") != 0)
output.push_back(it->_line);
}
gr = gr->_next;
}
TS_ASSERT(output.size() == expected.size());
CodeIterator it, it2;
for (it = output.begin(), it2 = expected.begin(); it != output.end() && it2 != expected.end(); ++it, ++it2) {
TS_ASSERT(removeSpaces(*it).compare(removeSpaces(*it2)) == 0);
}
delete cg;
delete c;
delete engine;
}
};