#include #include #include #include #include #include #include #define ESMTOOL_VERSION 1.1 using namespace std; using namespace ESM; // Create a local alias for brevity namespace bpo = boost::program_options; struct ESMData { std::string author; std::string description; int version; int type; ESMReader::MasterList masters; std::list records; std::map > cellRefs; }; // Based on the legacy struct struct Arguments { unsigned int raw_given; unsigned int quiet_given; unsigned int loadcells_given; std::string mode; std::string encoding; std::string filename; std::string outname; ESMData data; }; bool parseOptions (int argc, char** argv, Arguments &info) { bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file\n clone\t Clones the input file to the output file.\n\nAllowed options"); desc.add_options() ("help,h", "print help message.") ("version,v", "print version information and quit.") ("raw,r", "Show an unformattet list of all records and subrecords.") ("quiet,q", "Supress all record information. Useful for speed tests.") ("loadcells,C", "Browse through contents of all cells.") ( "encoding,e", bpo::value(&(info.encoding))-> default_value("win1252"), "Character encoding used in ESMTool:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") ; std::string finalText = "\nIf no option is given, the default action is to parse all records in the archive\nand display diagnostic information."; // input-file is hidden and used as a positional argument bpo::options_description hidden("Hidden Options"); hidden.add_options() ( "mode,m", bpo::value(), "esmtool mode") ( "input-file,i", bpo::value< vector >(), "input file") ; bpo::positional_options_description p; p.add("mode", 1).add("input-file", 2); // there might be a better way to do this bpo::options_description all; all.add(desc).add(hidden); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(all).positional(p).run(); bpo::variables_map variables; bpo::store(valid_opts, variables); bpo::notify(variables); if (variables.count ("help")) { std::cout << desc << finalText << std::endl; return false; } if (variables.count ("version")) { std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl; return false; } if (!variables.count("mode")) { std::cout << "No mode specified!" << std::endl << std::endl << desc << finalText << std::endl; return false; } info.mode = variables["mode"].as(); if (!(info.mode == "dump" || info.mode == "clone")) { std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"" << std::endl << std::endl << desc << finalText << std::endl; return false; } if ( !variables.count("input-file") ) { std::cout << "\nERROR: missing ES file\n\n"; std::cout << desc << finalText << std::endl; return false; } // handling gracefully the user adding multiple files /* if (variables["input-file"].as< vector >().size() > 1) { std::cout << "\nERROR: more than one ES file specified\n\n"; std::cout << desc << finalText << std::endl; return false; }*/ info.filename = variables["input-file"].as< vector >()[0]; if (variables["input-file"].as< vector >().size() > 1) info.outname = variables["input-file"].as< vector >()[1]; info.raw_given = variables.count ("raw"); info.quiet_given = variables.count ("quiet"); info.loadcells_given = variables.count ("loadcells"); // Font encoding settings info.encoding = variables["encoding"].as(); if (info.encoding == "win1250") { std::cout << "Using Central and Eastern European font encoding." << std::endl; } else if (info.encoding == "win1251") { std::cout << "Using Cyrillic font encoding." << std::endl; } else { if(info.encoding != "win1252") { std::cout << info.encoding << " is not a valid encoding option." << std::endl; info.encoding = "win1252"; } std::cout << "Using default (English) font encoding." << std::endl; } return true; } void printRaw(ESMReader &esm); void loadCell(Cell &cell, ESMReader &esm, Arguments& info); int load(Arguments& info); int clone(Arguments& info); int main(int argc, char**argv) { Arguments info; if(!parseOptions (argc, argv, info)) return 1; if (info.mode == "dump") return load(info); else if (info.mode == "clone") return clone(info); else { cout << "Invalid or no mode specified, dying horribly. Have a nice day." << endl; return 1; } return 0; } void loadCell(Cell &cell, ESMReader &esm, Arguments& info) { bool quiet = (info.quiet_given || info.mode == "clone"); bool save = (info.mode == "clone"); // Skip back to the beginning of the reference list cell.restore(esm); // Loop through all the references CellRef ref; if(!quiet) cout << " References:\n"; while(cell.getNextRef(esm, ref)) { if (save) info.data.cellRefs[&cell].push_back(ref); if(quiet) continue; cout << " Refnum: " << ref.refnum << endl; cout << " ID: '" << ref.refID << "'\n"; cout << " Owner: '" << ref.owner << "'\n"; cout << " INTV: " << ref.intv << " NAM9: " << ref.intv << endl; } } void printRaw(ESMReader &esm) { while(esm.hasMoreRecs()) { NAME n = esm.getRecName(); cout << "Record: " << n.toString() << endl; esm.getRecHeader(); while(esm.hasMoreSubs()) { uint64_t offs = esm.getOffset(); esm.getSubName(); esm.skipHSub(); n = esm.retSubName(); cout << " " << n.toString() << " - " << esm.getSubSize() << " bytes @ 0x" << hex << offs << "\n"; } } } int load(Arguments& info) { ESMReader esm; esm.setEncoding(info.encoding); string filename = info.filename; cout << "\nFile: " << filename << endl; std::list skipped; try { if(info.raw_given && info.mode == "dump") { cout << "RAW file listing:\n"; esm.openRaw(filename); printRaw(esm); return 0; } bool quiet = (info.quiet_given || info.mode == "clone"); bool loadCells = (info.loadcells_given || info.mode == "clone"); bool save = (info.mode == "clone"); esm.open(filename); info.data.author = esm.getAuthor(); info.data.description = esm.getDesc(); info.data.masters = esm.getMasters(); info.data.version = esm.getVer(); info.data.type = esm.getType(); if (!quiet) { cout << "Author: " << esm.getAuthor() << endl << "Description: " << esm.getDesc() << endl << "File format version: " << esm.getFVer() << endl << "Special flag: " << esm.getSpecial() << endl; ESMReader::MasterList m = esm.getMasters(); if (!m.empty()) { cout << "Masters:" << endl; for(unsigned int i=0;isetId(id); if (save) info.data.records.push_back(rec); else delete rec; } } } catch(exception &e) { cout << "\nERROR:\n\n " << e.what() << endl; for (std::list::iterator it = info.data.records.begin(); it != info.data.records.end();) { delete *it; info.data.records.erase(it++); } return 1; } return 0; } #include int clone(Arguments& info) { if (info.outname.empty()) { cout << "You need to specify an output name" << endl; return 1; } if (load(info) != 0) { cout << "Failed to load, aborting." << endl; return 1; } int recordCount = info.data.records.size(); int digitCount = 1; // For a nicer output if (recordCount > 9) ++digitCount; if (recordCount > 99) ++digitCount; if (recordCount > 999) ++digitCount; if (recordCount > 9999) ++digitCount; if (recordCount > 99999) ++digitCount; if (recordCount > 999999) ++digitCount; cout << "Loaded " << recordCount << " records:" << endl << endl; std::map records; for (std::list::iterator it = info.data.records.begin(); it != info.data.records.end(); ++it) { Record* rec = *it; NAME n; n.val = rec->getName(); records[n.toString()]++; } int i = 0; for (std::map::iterator it = records.begin(); it != records.end(); ++it) { std::string n = it->first; float amount = it->second; cout << setw(digitCount) << amount << " " << n << " "; if (++i % 3 == 0) cout << endl; } if (i % 3 != 0) cout << endl; cout << endl << "Saving records to: " << info.outname << "..." << endl; ESMWriter esm; esm.setAuthor(info.data.author); esm.setDescription(info.data.description); esm.setVersion(info.data.version); esm.setType(info.data.type); for (ESMReader::MasterList::iterator it = info.data.masters.begin(); it != info.data.masters.end(); ++it) esm.addMaster(it->name, it->size); std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary); esm.save(save); int saved = 0; for (std::list::iterator it = info.data.records.begin(); it != info.data.records.end() && i > 0; ++it) { Record* rec = *it; NAME n; n.val = rec->getName(); esm.startRecord(n.toString(), 0); std::string id = rec->getId(); esm.writeHNOString("NAME", id); rec->save(esm); if (n.val == REC_CELL && !info.data.cellRefs[rec].empty()) { std::list& refs = info.data.cellRefs[rec]; for (std::list::iterator it = refs.begin(); it != refs.end(); ++it) { it->save(esm); } } esm.endRecord(n.toString()); saved++; int perc = (saved / (float)recordCount)*100; if (perc % 10 == 0) { cerr << "\r" << perc << "%"; } } cout << "\rDone!" << endl; esm.close(); save.close(); return 0; }