mirror of
https://github.com/scummvm/scummvm-tools.git
synced 2026-05-21 05:40:44 +00:00
c17dc85e10
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.
253 lines
8.4 KiB
C++
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;
|
|
}
|