/* 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 "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 #include #include #include #include #include #include namespace po = boost::program_options; #define ENGINE(id, description, engineClass) engines[std::string(id)] = description; engineFactory.addEntry(std::string(id)); int main(int argc, char** argv) { try { std::map engines; ObjectFactory 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(), "Engine the script originates from.") ("list,l", "List the supported engines.") ("dump-disassembly,d", po::value()->implicit_value(""), "Dump the disassembly to a file. Leave out filename to output to stdout.") ("dump-graph,g", po::value()->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()->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(), "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(), "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::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()) != engines.end()) { Engine *engine = engineFactory.create(vm["engine"].as()); std::vector variants; engine->getVariants(variants); if (variants.empty()) { std::cout << engines[vm["engine"].as()] << " does not use variants.\n"; } else { std::cout << "Supported variants for " << engines[vm["engine"].as()] << ":\n"; for (std::vector::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()) == engines.end()) { std::cout << "Unknown engine.\n"; return 2; } if (vm.count("no-stack-effect")) { setOutputStackEffect(false); } Engine *engine = engineFactory.create(vm["engine"].as()); engine->_variant = vm["variant"].as(); std::string inputFile = vm["input-file"].as(); // 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().c_str(), of.binary); dynamic_cast(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() != "") { of.open(vm["dump-disassembly"].as().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() != "") { of.open(vm["dump-graph"].as().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 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; }