Files
Die4Ever c17dc85e10 DECOMPILER: compiler and Groovie 2 support
New Reassembler base class for compiling assembly back into a binary file. Use like:
./decompile -e groovie -v v2 SCRIPT.GRV.gasm -b SUSCRIPT.GRV
Also support for decompiling/compiling Groovie 2 script files.
2022-10-22 17:09:25 -04:00

253 lines
8.4 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "objectFactory.h"
#include "disassembler.h"
#include "reassembler.h"
#include "engine.h"
#include "instruction.h"
#include "control_flow.h"
#include "groovie/engine.h"
#include "kyra/engine.h"
#include "scummv6/engine.h"
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/graph/graphviz.hpp>
namespace po = boost::program_options;
#define ENGINE(id, description, engineClass) engines[std::string(id)] = description; engineFactory.addEntry<engineClass>(std::string(id));
int main(int argc, char** argv) {
try {
std::map<std::string, std::string> engines;
ObjectFactory<std::string, Engine> engineFactory;
ENGINE("groovie", "Groovie", Groovie::GroovieEngine);
ENGINE("kyra2", "Legend of Kyrandia: Hand of Fate", Kyra::Kyra2Engine);
ENGINE("scummv6", "SCUMM v6", Scumm::v6::Scummv6Engine);
po::options_description visible("Options");
visible.add_options()
("help,h", "Produce this help message.")
("engine,e", po::value<std::string>(), "Engine the script originates from.")
("list,l", "List the supported engines.")
("dump-disassembly,d", po::value<std::string>()->implicit_value(""), "Dump the disassembly to a file. Leave out filename to output to stdout.")
("dump-graph,g", po::value<std::string>()->implicit_value(""), "Output the control flow graph in dot format to a file. Leave out filename to output to stdout.")
("only-disassembly,D", "Stops after disassembly. Implies -d.")
("only-graph,G", "Stops after control flow graph has been generated. Implies -g.")
("show-unreachable,u", "Show the address and contents of unreachable groups in the script.")
("variant,v", po::value<std::string>()->default_value(""), "Tell the engine that the script is from a specific variant. To see a list of variants supported by a specific engine, use the -h option and the -e option together.")
("no-stack-effect,s", "Leave out the stack effect when printing raw instructions.")
("dump-binary,b", po::value<std::string>(), "Compile the assembly to a binary file.");
// TODO: option for only outputting labels for lines that receive jumps
po::options_description args("");
args.add(visible).add_options()
("input-file", po::value<std::string>(), "Input file");
po::positional_options_description fileArg;
fileArg.add("input-file", -1);
po::variables_map vm;
try {
// FIXME: If specified as the last parameter before the input file name, -d currently requires a filename to specified. -d must be specified earlier than that if outputting to stdout.
po::store(po::command_line_parser(argc, argv).options(args).positional(fileArg).run(), vm);
po::notify(vm);
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
if (vm.count("list")) {
std::cout << "Available engines:" << "\n";
std::map<std::string, std::string>::iterator it;
for (it = engines.begin(); it != engines.end(); ++it)
std::cout << (*it).first << " " << (*it).second << "\n";
return 0;
}
if (vm.count("help") || !vm.count("input-file")) {
std::cout << "Usage: " << argv[0] << " [option...] file" << "\n";
std::cout << visible << "\n";
if (vm.count("engine") && engines.find(vm["engine"].as<std::string>()) != engines.end()) {
Engine *engine = engineFactory.create(vm["engine"].as<std::string>());
std::vector<std::string> variants;
engine->getVariants(variants);
if (variants.empty()) {
std::cout << engines[vm["engine"].as<std::string>()] << " does not use variants.\n";
} else {
std::cout << "Supported variants for " << engines[vm["engine"].as<std::string>()] << ":\n";
for (std::vector<std::string>::iterator i = variants.begin(); i != variants.end(); ++i) {
std::cout << " " << *i << "\n";
}
}
delete engine;
std::cout << "\n";
}
std::cout << "Note: If outputting to stdout, -d or -g must NOT be specified immediately before the input file.\n";
return 1;
}
if (!vm.count("engine")) {
std::cout << "Engine must be specified.\n";
return 2;
} else if (engines.find(vm["engine"].as<std::string>()) == engines.end()) {
std::cout << "Unknown engine.\n";
return 2;
}
if (vm.count("no-stack-effect")) {
setOutputStackEffect(false);
}
Engine *engine = engineFactory.create(vm["engine"].as<std::string>());
engine->_variant = vm["variant"].as<std::string>();
std::string inputFile = vm["input-file"].as<std::string>();
// Disassembly
InstVec insts;
Disassembler *disassembler = engine->getDisassembler(insts);
disassembler->open(inputFile.c_str());
if (vm.count("dump-binary")) {
std::ofstream of;
of.open(vm["dump-binary"].as<std::string>().c_str(), of.binary);
dynamic_cast<Reassembler*>(disassembler)->dumpBinary(of);
delete disassembler;
delete engine;
return 0;
}
disassembler->disassemble();
if (vm.count("dump-disassembly")) {
std::streambuf *buf;
std::ofstream of;
if (vm["dump-disassembly"].as<std::string>() != "") {
of.open(vm["dump-disassembly"].as<std::string>().c_str());
buf = of.rdbuf();
} else {
buf = std::cout.rdbuf();
}
std::ostream out(buf);
disassembler->dumpDisassembly(out);
}
if (!engine->supportsCodeFlow() || vm.count("only-disassembly") || insts.empty()) {
if (!vm.count("dump-disassembly")) {
disassembler->dumpDisassembly(std::cout);
}
delete disassembler;
delete engine;
return 0;
}
delete disassembler;
// Control flow analysis
ControlFlow *cf = new ControlFlow(insts, engine);
cf->createGroups();
Graph g = cf->analyze();
if (vm.count("dump-graph")) {
std::streambuf *buf;
std::ofstream of;
if (vm["dump-graph"].as<std::string>() != "") {
of.open(vm["dump-graph"].as<std::string>().c_str());
buf = of.rdbuf();
} else {
buf = std::cout.rdbuf();
}
std::ostream out(buf);
boost::write_graphviz(out, g, boost::make_label_writer(get(boost::vertex_name, g)), boost::makeArrowheadWriter(get(boost::edge_attribute, g)), GraphProperties(engine, g));
}
if (!engine->supportsCodeGen() || vm.count("only-graph")) {
if (!vm.count("dump-graph")) {
boost::write_graphviz(std::cout, g, boost::make_label_writer(get(boost::vertex_name, g)), boost::makeArrowheadWriter(get(boost::edge_attribute, g)), GraphProperties(engine, g));
}
delete cf;
delete engine;
return 0;
}
// Post-processing of CFG
engine->postCFG(insts, g);
// Code generation
CodeGenerator *cg = engine->getCodeGenerator(std::cout);
cg->generate(g);
if (vm.count("show-unreachable")) {
std::vector<GroupPtr> unreachable;
VertexRange vr = boost::vertices(g);
for (VertexIterator v = vr.first; v != vr.second; ++v)
{
GroupPtr gr = boost::get(boost::vertex_name, g, *v);
if (gr->_stackLevel == -1)
unreachable.push_back(gr);
}
if (!unreachable.empty()) {
for (size_t i = 0; i < unreachable.size(); i++) {
if (i == 0) {
if (unreachable.size() == 1)
std::cout << boost::format("\n%d unreachable group detected.\n") % unreachable.size();
else
std::cout << boost::format("\n%d unreachable groups detected.\n") % unreachable.size();
}
std::cout << "Group " << (i + 1) << ":\n";
ConstInstIterator inst = unreachable[i]->_start;
do {
std::cout << *inst;
} while (inst++ != unreachable[i]->_end);
std::cout << "----------\n";
}
}
}
// Free memory
delete cf;
delete cg;
delete engine;
} catch (UnknownOpcodeException &e) {
std::cerr << "ERROR: " << e.what() << "\n";
return 3;
} catch (std::exception &e) {
std::cerr << "ERROR: " << e.what() << "\n";
return 4;
}
return 0;
}