#include #include #include #include #include #include #include #define BSATOOL_VERSION 1.1 // Create local aliases for brevity namespace bpo = boost::program_options; namespace bfs = boost::filesystem; struct Arguments { std::string mode; std::string filename; std::string extractfile; std::string outdir; bool longformat; bool fullpath; }; void replaceAll(std::string& str, const std::string& needle, const std::string& substitute) { size_t pos = str.find(needle); while(pos != std::string::npos) { str.replace(pos, needle.size(), substitute); pos = str.find(needle); } } bool parseOptions (int argc, char** argv, Arguments &info) { bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n" "Usages:\n" " bsatool list [-l] archivefile\n" " List the files presents in the input archive.\n\n" " bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n" " Extract a file from the input archive.\n\n" " bsatool extractall archivefile [output_directory]\n" " Extract all files from the input archive.\n\n" "Allowed options"); desc.add_options() ("help,h", "print help message.") ("version,v", "print version information and quit.") ("long,l", "Include extra information in archive listing.") ("full-path,f", "Create directory hierarchy on file extraction " "(always true for extractall).") ; // input-file is hidden and used as a positional argument bpo::options_description hidden("Hidden Options"); hidden.add_options() ( "mode,m", bpo::value(), "bsatool mode") ( "input-file,i", bpo::value< std::vector >(), "input file") ; bpo::positional_options_description p; p.add("mode", 1).add("input-file", 3); // there might be a better way to do this bpo::options_description all; all.add(desc).add(hidden); bpo::variables_map variables; try { bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(all).positional(p).run(); bpo::store(valid_opts, variables); } catch(std::exception &e) { std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } bpo::notify(variables); if (variables.count ("help")) { std::cout << desc << std::endl; return false; } if (variables.count ("version")) { std::cout << "BSATool version " << BSATOOL_VERSION << std::endl; return false; } if (!variables.count("mode")) { std::cout << "ERROR: no mode specified!\n\n" << desc << std::endl; return false; } info.mode = variables["mode"].as(); if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall")) { std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << std::endl; return false; } if (!variables.count("input-file")) { std::cout << "\nERROR: missing BSA archive\n\n" << desc << std::endl; return false; } info.filename = variables["input-file"].as< std::vector >()[0]; // Default output to the working directory info.outdir = "."; if (info.mode == "extract") { if (variables["input-file"].as< std::vector >().size() < 2) { std::cout << "\nERROR: file to extract unspecified\n\n" << desc << std::endl; return false; } if (variables["input-file"].as< std::vector >().size() > 1) info.extractfile = variables["input-file"].as< std::vector >()[1]; if (variables["input-file"].as< std::vector >().size() > 2) info.outdir = variables["input-file"].as< std::vector >()[2]; } else if (variables["input-file"].as< std::vector >().size() > 1) info.outdir = variables["input-file"].as< std::vector >()[1]; info.longformat = variables.count("long") != 0; info.fullpath = variables.count("full-path") != 0; return true; } int list(Bsa::BSAFile& bsa, Arguments& info); int extract(Bsa::BSAFile& bsa, Arguments& info); int extractAll(Bsa::BSAFile& bsa, Arguments& info); int main(int argc, char** argv) { try { Arguments info; if(!parseOptions (argc, argv, info)) return 1; // Open file Bsa::BSAFile bsa; bsa.open(info.filename); if (info.mode == "list") return list(bsa, info); else if (info.mode == "extract") return extract(bsa, info); else if (info.mode == "extractall") return extractAll(bsa, info); else { std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; return 1; } } catch (std::exception& e) { std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; return 2; } } int list(Bsa::BSAFile& bsa, Arguments& info) { // List all files const Bsa::BSAFile::FileList &files = bsa.getList(); for(unsigned int i=0; irdbuf(); out.close(); return 0; } int extractAll(Bsa::BSAFile& bsa, Arguments& info) { // Get the list of files present in the archive Bsa::BSAFile::FileList list = bsa.getList(); // Iter on the list for(Bsa::BSAFile::FileList::iterator it = list.begin(); it != list.end(); ++it) { const char* archivePath = it->name; std::string extractPath (archivePath); replaceAll(extractPath, "\\", "/"); // Get the target path (the path the file will be extracted to) bfs::path target (info.outdir); target /= extractPath; // Create the directory hierarchy bfs::create_directories(target.parent_path()); bfs::file_status s = bfs::status(target.parent_path()); if (!bfs::is_directory(s)) { std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl; return 3; } // Get a stream for the file to extract // (inefficient because getFile iter on the list again) Files::IStreamPtr data = bsa.getFile(archivePath); bfs::ofstream out(target, std::ios::binary); // Write the file to disk std::cout << "Extracting " << target << std::endl; out << data->rdbuf(); out.close(); } return 0; }